2 * Copyright (C) 2011-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
22 #include "SlingboxFile.h"
23 #include "filesystem/File.h"
24 #include "lib/SlingboxLib/SlingboxLib.h"
25 #include "profiles/ProfilesManager.h"
26 #include "utils/log.h"
27 #include "utils/XMLUtils.h"
28 #include "utils/StringUtils.h"
31 using namespace XFILE;
34 CSlingboxFile::CSlingboxFile()
36 // Create the Slingbox object
37 m_pSlingbox = new CSlingbox();
40 CSlingboxFile::~CSlingboxFile()
42 // Destroy the Slingbox object
46 bool CSlingboxFile::Open(const CURL& url)
48 // Setup the IP/hostname and port (setup default port if none specified)
51 uiPort = (unsigned int)url.GetPort();
54 m_pSlingbox->SetAddress(url.GetHostName(), uiPort);
56 // Prepare to connect to the Slingbox
58 if (StringUtils::EqualsNoCase(url.GetUserName(), "administrator"))
60 else if (StringUtils::EqualsNoCase(url.GetUserName(), "viewer"))
64 CLog::Log(LOGERROR, "%s - Invalid or no username specified for Slingbox: %s",
65 __FUNCTION__, url.GetHostName().c_str());
69 // Connect to the Slingbox
70 if (m_pSlingbox->Connect(bAdmin, url.GetPassWord()))
72 CLog::Log(LOGDEBUG, "%s - Successfully connected to Slingbox: %s",
73 __FUNCTION__, url.GetHostName().c_str());
77 CLog::Log(LOGERROR, "%s - Error connecting to Slingbox: %s",
78 __FUNCTION__, url.GetHostName().c_str());
82 // Initialize the stream
83 if (m_pSlingbox->InitializeStream())
85 CLog::Log(LOGDEBUG, "%s - Successfully initialized stream on Slingbox: %s",
86 __FUNCTION__, url.GetHostName().c_str());
90 CLog::Log(LOGERROR, "%s - Error initializing stream on Slingbox: %s",
91 __FUNCTION__, url.GetHostName().c_str());
96 if (url.GetFileNameWithoutPath() != "")
98 if (m_pSlingbox->SetInput(atoi(url.GetFileNameWithoutPath())))
99 CLog::Log(LOGDEBUG, "%s - Successfully requested change to input %i on Slingbox: %s",
100 __FUNCTION__, atoi(url.GetFileNameWithoutPath()), url.GetHostName().c_str());
102 CLog::Log(LOGERROR, "%s - Error requesting change to input %i on Slingbox: %s",
103 __FUNCTION__, atoi(url.GetFileNameWithoutPath()), url.GetHostName().c_str());
106 // Load the video settings
107 LoadSettings(url.GetHostName());
109 // Setup video options
110 if (m_pSlingbox->StreamSettings((CSlingbox::Resolution)m_sSlingboxSettings.iVideoResolution,
111 m_sSlingboxSettings.iVideoBitrate, m_sSlingboxSettings.iVideoFramerate,
112 m_sSlingboxSettings.iVideoSmoothing, m_sSlingboxSettings.iAudioBitrate,
113 m_sSlingboxSettings.iIFrameInterval))
115 CLog::Log(LOGDEBUG, "%s - Successfully set stream options (resolution: %ix%i; "
116 "video bitrate: %i kbit/s; fps: %i; smoothing: %i%%; audio bitrate %i kbit/s; "
117 "I frame interval: %i) on Slingbox: %s", __FUNCTION__,
118 m_sSlingboxSettings.iVideoWidth, m_sSlingboxSettings.iVideoHeight,
119 m_sSlingboxSettings.iVideoBitrate, m_sSlingboxSettings.iVideoFramerate,
120 m_sSlingboxSettings.iVideoSmoothing, m_sSlingboxSettings.iAudioBitrate,
121 m_sSlingboxSettings.iIFrameInterval, url.GetHostName().c_str());
125 CLog::Log(LOGERROR, "%s - Error setting stream options on Slingbox: %s",
126 __FUNCTION__, url.GetHostName().c_str());
130 if (m_pSlingbox->StartStream())
132 CLog::Log(LOGDEBUG, "%s - Successfully started stream on Slingbox: %s",
133 __FUNCTION__, url.GetHostName().c_str());
137 CLog::Log(LOGERROR, "%s - Error starting stream on Slingbox: %s",
138 __FUNCTION__, url.GetHostName().c_str());
142 // Check for correct input
143 if (url.GetFileNameWithoutPath() != "")
145 if (m_pSlingbox->GetInput() == -1)
146 CLog::Log(LOGDEBUG, "%s - Unable to confirm change to input %i on Slingbox: %s",
147 __FUNCTION__, atoi(url.GetFileNameWithoutPath()), url.GetHostName().c_str());
148 else if (m_pSlingbox->GetInput() == atoi(url.GetFileNameWithoutPath()))
149 CLog::Log(LOGDEBUG, "%s - Comfirmed change to input %i on Slingbox: %s",
150 __FUNCTION__, atoi(url.GetFileNameWithoutPath()), url.GetHostName().c_str());
152 CLog::Log(LOGERROR, "%s - Error changing to input %i on Slingbox: %s",
153 __FUNCTION__, atoi(url.GetFileNameWithoutPath()), url.GetHostName().c_str());
159 unsigned int CSlingboxFile::Read(void * pBuffer, int64_t iSize)
161 // Read the data and check for any errors
162 int iRead = m_pSlingbox->ReadStream(pBuffer, (unsigned int)iSize);
165 CLog::Log(LOGERROR, "%s - Error reading stream from Slingbox: %s", __FUNCTION__,
166 m_sSlingboxSettings.strHostname.c_str());
173 void CSlingboxFile::Close()
176 if (m_pSlingbox->StopStream())
177 CLog::Log(LOGDEBUG, "%s - Successfully stopped stream on Slingbox: %s", __FUNCTION__,
178 m_sSlingboxSettings.strHostname.c_str());
180 CLog::Log(LOGERROR, "%s - Error stopping stream on Slingbox: %s", __FUNCTION__,
181 m_sSlingboxSettings.strHostname.c_str());
183 // Disconnect from the Slingbox
184 if (m_pSlingbox->Disconnect())
185 CLog::Log(LOGDEBUG, "%s - Successfully disconnected from Slingbox: %s", __FUNCTION__,
186 m_sSlingboxSettings.strHostname.c_str());
188 CLog::Log(LOGERROR, "%s - Error disconnecting from Slingbox: %s", __FUNCTION__,
189 m_sSlingboxSettings.strHostname.c_str());
192 bool CSlingboxFile::SkipNext()
194 return m_pSlingbox->IsConnected();
197 bool CSlingboxFile::NextChannel(bool bPreview /* = false */)
200 bool bSuccess = true;
201 int iPrevChannel = m_pSlingbox->GetChannel();
204 if (m_pSlingbox->StopStream())
206 CLog::Log(LOGDEBUG, "%s - Successfully stopped stream before channel change request on "
207 "Slingbox: %s", __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
211 CLog::Log(LOGERROR, "%s - Error stopping stream before channel change request on "
212 "Slingbox: %s", __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
216 // Figure out which method to use
217 if (m_sSlingboxSettings.uiCodeChannelUp == 0)
219 // Change the channel
220 if (m_pSlingbox->ChannelUp())
222 CLog::Log(LOGDEBUG, "%s - Successfully requested channel change on Slingbox: %s",
223 __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
225 if (m_pSlingbox->GetChannel() == -1)
227 CLog::Log(LOGDEBUG, "%s - Unable to confirm channel change on Slingbox: %s",
228 __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
230 else if (m_pSlingbox->GetChannel() != iPrevChannel)
232 CLog::Log(LOGDEBUG, "%s - Confirmed change to channel %i on Slingbox: %s",
233 __FUNCTION__, m_pSlingbox->GetChannel(), m_sSlingboxSettings.strHostname.c_str());
237 CLog::Log(LOGERROR, "%s - Error changing channel on Slingbox: %s",
238 __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
244 CLog::Log(LOGERROR, "%s - Error requesting channel change on Slingbox: %s",
245 __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
251 // Change the channel using IR command
252 if (m_pSlingbox->SendIRCommand(m_sSlingboxSettings.uiCodeChannelUp))
254 CLog::Log(LOGDEBUG, "%s - Successfully sent IR command (code: 0x%.2X) from "
255 "Slingbox: %s", __FUNCTION__, m_sSlingboxSettings.uiCodeChannelUp,
256 m_sSlingboxSettings.strHostname.c_str());
260 CLog::Log(LOGERROR, "%s - Error sending IR command (code: 0x%.2X) from "
261 "Slingbox: %s", __FUNCTION__, m_sSlingboxSettings.uiCodeChannelUp,
262 m_sSlingboxSettings.strHostname.c_str());
267 // Start the stream again
268 if (m_pSlingbox->StartStream())
270 CLog::Log(LOGDEBUG, "%s - Successfully started stream after channel change request on "
271 "Slingbox: %s", __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
275 CLog::Log(LOGERROR, "%s - Error starting stream after channel change request on "
276 "Slingbox: %s", __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
283 bool CSlingboxFile::PrevChannel(bool bPreview /* = false */)
286 bool bSuccess = true;
287 int iPrevChannel = m_pSlingbox->GetChannel();
290 if (m_pSlingbox->StopStream())
292 CLog::Log(LOGDEBUG, "%s - Successfully stopped stream before channel change request on "
293 "Slingbox: %s", __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
297 CLog::Log(LOGERROR, "%s - Error stopping stream before channel change request on "
298 "Slingbox: %s", __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
302 // Figure out which method to use
303 if (m_sSlingboxSettings.uiCodeChannelDown == 0)
305 // Change the channel
306 if (m_pSlingbox->ChannelDown())
308 CLog::Log(LOGDEBUG, "%s - Successfully requested channel change on Slingbox: %s",
309 __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
311 if (m_pSlingbox->GetChannel() == -1)
313 CLog::Log(LOGDEBUG, "%s - Unable to confirm channel change on Slingbox: %s",
314 __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
316 else if (m_pSlingbox->GetChannel() != iPrevChannel)
318 CLog::Log(LOGDEBUG, "%s - Confirmed change to channel %i on Slingbox: %s",
319 __FUNCTION__, m_pSlingbox->GetChannel(), m_sSlingboxSettings.strHostname.c_str());
323 CLog::Log(LOGERROR, "%s - Error changing channel on Slingbox: %s",
324 __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
330 CLog::Log(LOGERROR, "%s - Error requesting channel change on Slingbox: %s",
331 __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
337 // Change the channel using IR command
338 if (m_pSlingbox->SendIRCommand(m_sSlingboxSettings.uiCodeChannelDown))
340 CLog::Log(LOGDEBUG, "%s - Successfully sent IR command (code: 0x%.2X) from "
341 "Slingbox: %s", __FUNCTION__, m_sSlingboxSettings.uiCodeChannelDown,
342 m_sSlingboxSettings.strHostname.c_str());
346 CLog::Log(LOGERROR, "%s - Error sending IR command (code: 0x%.2X) from "
347 "Slingbox: %s", __FUNCTION__, m_sSlingboxSettings.uiCodeChannelDown,
348 m_sSlingboxSettings.strHostname.c_str());
353 // Start the stream again
354 if (m_pSlingbox->StartStream())
356 CLog::Log(LOGDEBUG, "%s - Successfully started stream after channel change request on "
357 "Slingbox: %s", __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
361 CLog::Log(LOGERROR, "%s - Error starting Slingbox stream after channel change request on "
362 "Slingbox: %s", __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
369 bool CSlingboxFile::SelectChannel(unsigned int uiChannel)
371 // Check if a channel change is required
372 if (m_pSlingbox->GetChannel() == (int)uiChannel)
376 bool bSuccess = true;
379 if (m_pSlingbox->StopStream())
381 CLog::Log(LOGDEBUG, "%s - Successfully stopped stream before channel change request on "
382 "Slingbox: %s", __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
386 CLog::Log(LOGERROR, "%s - Error stopping stream before channel change request on "
387 "Slingbox: %s", __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
391 // Figure out which method to use
392 unsigned int uiButtonsWithCode = 0;
393 for (unsigned int i = 0; i < 10; i++)
395 if (m_sSlingboxSettings.uiCodeNumber[i] != 0)
398 if (uiButtonsWithCode == 0)
400 // Change the channel
401 if (m_pSlingbox->SetChannel(uiChannel))
403 CLog::Log(LOGDEBUG, "%s - Successfully requested change to channel %i on Slingbox: %s",
404 __FUNCTION__, uiChannel, m_sSlingboxSettings.strHostname.c_str());
406 if (m_pSlingbox->GetChannel() == -1)
408 CLog::Log(LOGDEBUG, "%s - Unable to confirm change to channel %i on Slingbox: %s",
409 __FUNCTION__, uiChannel, m_sSlingboxSettings.strHostname.c_str());
411 else if (m_pSlingbox->GetChannel() == (int)uiChannel)
413 CLog::Log(LOGDEBUG, "%s - Confirmed change to channel %i on Slingbox: %s",
414 __FUNCTION__, uiChannel, m_sSlingboxSettings.strHostname.c_str());
418 CLog::Log(LOGERROR, "%s - Error changing to channel %i on Slingbox: %s",
419 __FUNCTION__, uiChannel, m_sSlingboxSettings.strHostname.c_str());
425 CLog::Log(LOGERROR, "%s - Error requesting change to channel %i on Slingbox: %s",
426 __FUNCTION__, uiChannel, m_sSlingboxSettings.strHostname.c_str());
430 else if (uiButtonsWithCode == 10)
433 CStdString strDigits = StringUtils::Format("%u", uiChannel);
434 size_t uiNumberOfDigits = strDigits.size();
436 // Change the channel using IR commands
437 for (size_t i = 0; i < uiNumberOfDigits; i++)
439 if (m_pSlingbox->SendIRCommand(m_sSlingboxSettings.uiCodeNumber[strDigits[i] - '0']))
441 CLog::Log(LOGDEBUG, "%s - Successfully sent IR command (code: 0x%.2X) from "
442 "Slingbox: %s", __FUNCTION__, m_sSlingboxSettings.uiCodeNumber[strDigits[i] - '0'],
443 m_sSlingboxSettings.strHostname.c_str());
447 CLog::Log(LOGDEBUG, "%s - Error sending IR command (code: 0x%.2X) from "
448 "Slingbox: %s", __FUNCTION__, m_sSlingboxSettings.uiCodeNumber[strDigits[i] - '0'],
449 m_sSlingboxSettings.strHostname.c_str());
456 CLog::Log(LOGERROR, "%s - Error requesting change to channel %i on Slingbox due to one or more "
457 "missing button codes from advancedsettings.xml for Slingbox: %s", __FUNCTION__, uiChannel,
458 m_sSlingboxSettings.strHostname.c_str());
462 // Start the stream again
463 if (m_pSlingbox->StartStream())
465 CLog::Log(LOGDEBUG, "%s - Successfully started stream after channel change request on "
466 "Slingbox: %s", __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
470 CLog::Log(LOGERROR, "%s - Error starting stream after channel change request on "
471 "Slingbox: %s", __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
478 void CSlingboxFile::LoadSettings(const CStdString& strHostname)
480 // Load default settings
481 m_sSlingboxSettings.strHostname = strHostname;
482 m_sSlingboxSettings.iVideoWidth = 320;
483 m_sSlingboxSettings.iVideoHeight = 240;
484 m_sSlingboxSettings.iVideoResolution = (int)CSlingbox::RESOLUTION320X240;
485 m_sSlingboxSettings.iVideoBitrate = 704;
486 m_sSlingboxSettings.iVideoFramerate = 30;
487 m_sSlingboxSettings.iVideoSmoothing = 50;
488 m_sSlingboxSettings.iAudioBitrate = 64;
489 m_sSlingboxSettings.iIFrameInterval = 10;
490 m_sSlingboxSettings.uiCodeChannelUp = 0;
491 m_sSlingboxSettings.uiCodeChannelDown = 0;
492 for (unsigned int i = 0; i < 10; i++)
493 m_sSlingboxSettings.uiCodeNumber[i] = 0;
495 // Check if a SlingboxSettings.xml file exists
496 CStdString slingboxXMLFile = CProfilesManager::Get().GetUserDataItem("SlingboxSettings.xml");
497 if (!CFile::Exists(slingboxXMLFile))
499 CLog::Log(LOGNOTICE, "No SlingboxSettings.xml file (%s) found - using default settings",
500 slingboxXMLFile.c_str());
505 CXBMCTinyXML slingboxXML;
506 if (!slingboxXML.LoadFile(slingboxXMLFile))
508 CLog::Log(LOGERROR, "%s - Error loading %s - line %d\n%s", __FUNCTION__,
509 slingboxXMLFile.c_str(), slingboxXML.ErrorRow(), slingboxXML.ErrorDesc());
513 // Check to make sure layout is correct
514 TiXmlElement * pRootElement = slingboxXML.RootElement();
515 if (!pRootElement || strcmpi(pRootElement->Value(), "slingboxsettings") != 0)
517 CLog::Log(LOGERROR, "%s - Error loading %s - no <slingboxsettings> node found",
518 __FUNCTION__, slingboxXMLFile.c_str());
523 CLog::Log(LOGNOTICE, "Loaded SlingboxSettings.xml from %s", slingboxXMLFile.c_str());
525 // Search for the first settings that specify no hostname or match our hostname
526 TiXmlElement *pElement;
527 for (pElement = pRootElement->FirstChildElement("slingbox"); pElement;
528 pElement = pElement->NextSiblingElement("slingbox"))
530 if (pElement->Attribute("hostname") == NULL ||
531 StringUtils::EqualsNoCase(m_sSlingboxSettings.strHostname, pElement->Attribute("hostname")))
533 // Load setting values
534 XMLUtils::GetInt(pElement, "width", m_sSlingboxSettings.iVideoWidth, 0, 640);
535 XMLUtils::GetInt(pElement, "height", m_sSlingboxSettings.iVideoHeight, 0, 480);
536 XMLUtils::GetInt(pElement, "videobitrate", m_sSlingboxSettings.iVideoBitrate, 50, 8000);
537 XMLUtils::GetInt(pElement, "framerate", m_sSlingboxSettings.iVideoFramerate, 1, 30);
538 XMLUtils::GetInt(pElement, "smoothing", m_sSlingboxSettings.iVideoSmoothing, 0, 100);
539 XMLUtils::GetInt(pElement, "audiobitrate", m_sSlingboxSettings.iAudioBitrate, 16, 96);
540 XMLUtils::GetInt(pElement, "iframeinterval", m_sSlingboxSettings.iIFrameInterval, 1, 30);
542 // Load any button code values
543 TiXmlElement * pCodes = pElement->FirstChildElement("buttons");
546 XMLUtils::GetHex(pCodes, "channelup", m_sSlingboxSettings.uiCodeChannelUp);
547 XMLUtils::GetHex(pCodes, "channeldown", m_sSlingboxSettings.uiCodeChannelDown);
548 XMLUtils::GetHex(pCodes, "zero", m_sSlingboxSettings.uiCodeNumber[0]);
549 XMLUtils::GetHex(pCodes, "one", m_sSlingboxSettings.uiCodeNumber[1]);
550 XMLUtils::GetHex(pCodes, "two", m_sSlingboxSettings.uiCodeNumber[2]);
551 XMLUtils::GetHex(pCodes, "three", m_sSlingboxSettings.uiCodeNumber[3]);
552 XMLUtils::GetHex(pCodes, "four", m_sSlingboxSettings.uiCodeNumber[4]);
553 XMLUtils::GetHex(pCodes, "five", m_sSlingboxSettings.uiCodeNumber[5]);
554 XMLUtils::GetHex(pCodes, "six", m_sSlingboxSettings.uiCodeNumber[6]);
555 XMLUtils::GetHex(pCodes, "seven", m_sSlingboxSettings.uiCodeNumber[7]);
556 XMLUtils::GetHex(pCodes, "eight", m_sSlingboxSettings.uiCodeNumber[8]);
557 XMLUtils::GetHex(pCodes, "nine", m_sSlingboxSettings.uiCodeNumber[9]);
564 // Prepare our resolution enum mapping array
567 unsigned int uiWidth;
568 unsigned int uiHeight;
569 CSlingbox::Resolution eEnum;
570 } m_resolutionMap[11] = {
571 {0, 0, CSlingbox::NOVIDEO},
572 {128, 96, CSlingbox::RESOLUTION128X96},
573 {160, 120, CSlingbox::RESOLUTION160X120},
574 {176, 120, CSlingbox::RESOLUTION176X120},
575 {224, 176, CSlingbox::RESOLUTION224X176},
576 {256, 192, CSlingbox::RESOLUTION256X192},
577 {320, 240, CSlingbox::RESOLUTION320X240},
578 {352, 240, CSlingbox::RESOLUTION352X240},
579 {320, 480, CSlingbox::RESOLUTION320X480},
580 {640, 240, CSlingbox::RESOLUTION640X240},
581 {640, 480, CSlingbox::RESOLUTION640X480}
584 // See if the specified resolution matches something in our mapping array and
585 // setup things accordingly
586 for (unsigned int i = 0; i < 11; i++)
588 if (m_sSlingboxSettings.iVideoWidth == (int)m_resolutionMap[i].uiWidth &&
589 m_sSlingboxSettings.iVideoHeight == (int)m_resolutionMap[i].uiHeight)
591 m_sSlingboxSettings.iVideoResolution = (int)m_resolutionMap[i].eEnum;
596 // If it didn't match anything setup safe defaults
597 CLog::Log(LOGERROR, "%s - Defaulting to 320x240 resolution due to invalid "
598 "resolution specified in SlingboxSettings.xml for Slingbox: %s",
599 __FUNCTION__, m_sSlingboxSettings.strHostname.c_str());
600 m_sSlingboxSettings.iVideoWidth = 320;
601 m_sSlingboxSettings.iVideoHeight = 240;
602 m_sSlingboxSettings.iVideoResolution = (int)CSlingbox::RESOLUTION320X240;