/xbmc/network/UPnP.cpp

https://github.com/nemphys/xbmc · C++ · 2449 lines · 1760 code · 312 blank · 377 comment · 405 complexity · e2302b5d026b5e2ade2556e301c20a1e MD5 · raw file

  1. /*
  2. * UPnP Support for XBMC
  3. * Copyright (c) 2006 c0diq (Sylvain Rebaud)
  4. * Portions Copyright (c) by the authors of libPlatinum
  5. *
  6. * http://www.plutinosoft.com/blog/category/platinum/
  7. *
  8. * This program is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 2 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, write to the Free Software
  20. * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  21. */
  22. #include "threads/SystemClock.h"
  23. #include "UPnP.h"
  24. #include "utils/URIUtils.h"
  25. #include "Application.h"
  26. #include "ApplicationMessenger.h"
  27. #include "Network.h"
  28. #include "utils/log.h"
  29. #include "filesystem/MusicDatabaseDirectory.h"
  30. #include "filesystem/VideoDatabaseDirectory.h"
  31. #include "music/MusicDatabase.h"
  32. #include "video/VideoDatabase.h"
  33. #include "filesystem/VideoDatabaseDirectory/DirectoryNode.h"
  34. #include "filesystem/VideoDatabaseDirectory/QueryParams.h"
  35. #include "filesystem/File.h"
  36. #include "NptStrings.h"
  37. #include "Platinum.h"
  38. #include "PltMediaConnect.h"
  39. #include "PltMediaRenderer.h"
  40. #include "PltSyncMediaBrowser.h"
  41. #include "PltDidl.h"
  42. #include "NptNetwork.h"
  43. #include "NptConsole.h"
  44. #include "music/tags/MusicInfoTag.h"
  45. #include "pictures/PictureInfoTag.h"
  46. #include "pictures/GUIWindowSlideShow.h"
  47. #include "filesystem/Directory.h"
  48. #include "URL.h"
  49. #include "settings/GUISettings.h"
  50. #include "GUIUserMessages.h"
  51. #include "settings/Settings.h"
  52. #include "settings/AdvancedSettings.h"
  53. #include "utils/StringUtils.h"
  54. #include "FileItem.h"
  55. #include "guilib/GUIWindowManager.h"
  56. #include "GUIInfoManager.h"
  57. #include "utils/TimeUtils.h"
  58. #include "utils/md5.h"
  59. #include "guilib/Key.h"
  60. #include "ThumbLoader.h"
  61. #include "Util.h"
  62. using namespace std;
  63. using namespace MUSIC_INFO;
  64. using namespace XFILE;
  65. NPT_SET_LOCAL_LOGGER("xbmc.upnp")
  66. #define UPNP_DEFAULT_MAX_RETURNED_ITEMS 200
  67. #define UPNP_DEFAULT_MIN_RETURNED_ITEMS 30
  68. /*
  69. # Play speed
  70. # 1 normal
  71. # 0 invalid
  72. DLNA_ORG_PS = 'DLNA.ORG_PS'
  73. DLNA_ORG_PS_VAL = '1'
  74. # Convertion Indicator
  75. # 1 transcoded
  76. # 0 not transcoded
  77. DLNA_ORG_CI = 'DLNA.ORG_CI'
  78. DLNA_ORG_CI_VAL = '0'
  79. # Operations
  80. # 00 not time seek range, not range
  81. # 01 range supported
  82. # 10 time seek range supported
  83. # 11 both supported
  84. DLNA_ORG_OP = 'DLNA.ORG_OP'
  85. DLNA_ORG_OP_VAL = '01'
  86. # Flags
  87. # senderPaced 80000000 31
  88. # lsopTimeBasedSeekSupported 40000000 30
  89. # lsopByteBasedSeekSupported 20000000 29
  90. # playcontainerSupported 10000000 28
  91. # s0IncreasingSupported 08000000 27
  92. # sNIncreasingSupported 04000000 26
  93. # rtspPauseSupported 02000000 25
  94. # streamingTransferModeSupported 01000000 24
  95. # interactiveTransferModeSupported 00800000 23
  96. # backgroundTransferModeSupported 00400000 22
  97. # connectionStallingSupported 00200000 21
  98. # dlnaVersion15Supported 00100000 20
  99. DLNA_ORG_FLAGS = 'DLNA.ORG_FLAGS'
  100. DLNA_ORG_FLAGS_VAL = '01500000000000000000000000000000'
  101. */
  102. /*----------------------------------------------------------------------
  103. | static
  104. +---------------------------------------------------------------------*/
  105. CUPnP* CUPnP::upnp = NULL;
  106. // change to false for XBMC_PC if you want real UPnP functionality
  107. // otherwise keep to true for xbox as it doesn't support multicast
  108. // don't change unless you know what you're doing!
  109. bool CUPnP::broadcast = true;
  110. namespace
  111. {
  112. static const NPT_String JoinString(const NPT_List<NPT_String>& array, const NPT_String& delimiter)
  113. {
  114. NPT_String result;
  115. for(NPT_List<NPT_String>::Iterator it = array.GetFirstItem(); it; it++ )
  116. result += delimiter + (*it);
  117. if(result.IsEmpty())
  118. return "";
  119. else
  120. return result.SubString(delimiter.GetLength());
  121. }
  122. enum EClientQuirks
  123. {
  124. ECLIENTQUIRKS_NONE = 0x0
  125. /* Client requires folder's to be marked as storageFolers as verndor type (360)*/
  126. , ECLIENTQUIRKS_ONLYSTORAGEFOLDER = 0x01
  127. /* Client can't handle subtypes for videoItems (360) */
  128. , ECLIENTQUIRKS_BASICVIDEOCLASS = 0x02
  129. /* Client requires album to be set to [Unknown Series] to show title (WMP) */
  130. , ECLIENTQUIRKS_UNKNOWNSERIES = 0x04
  131. };
  132. static EClientQuirks GetClientQuirks(const PLT_HttpRequestContext* context)
  133. {
  134. if(context == NULL)
  135. return ECLIENTQUIRKS_NONE;
  136. unsigned int quirks = 0;
  137. const NPT_String* user_agent = context->GetRequest().GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_USER_AGENT);
  138. const NPT_String* server = context->GetRequest().GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_SERVER);
  139. if (user_agent) {
  140. if (user_agent->Find("XBox", 0, true) >= 0 ||
  141. user_agent->Find("Xenon", 0, true) >= 0)
  142. quirks |= ECLIENTQUIRKS_ONLYSTORAGEFOLDER | ECLIENTQUIRKS_BASICVIDEOCLASS;
  143. if (user_agent->Find("Windows-Media-Player", 0, true) >= 0)
  144. quirks |= ECLIENTQUIRKS_UNKNOWNSERIES;
  145. }
  146. if (server) {
  147. if (server->Find("Xbox", 0, true) >= 0)
  148. quirks |= ECLIENTQUIRKS_ONLYSTORAGEFOLDER | ECLIENTQUIRKS_BASICVIDEOCLASS;
  149. }
  150. return (EClientQuirks)quirks;
  151. }
  152. }
  153. /*----------------------------------------------------------------------
  154. | NPT_Console::Output
  155. +---------------------------------------------------------------------*/
  156. void
  157. NPT_Console::Output(const char* message)
  158. {
  159. CLog::Log(LOGDEBUG, "%s", message);
  160. }
  161. /*----------------------------------------------------------------------
  162. | CDeviceHostReferenceHolder class
  163. +---------------------------------------------------------------------*/
  164. class CDeviceHostReferenceHolder
  165. {
  166. public:
  167. PLT_DeviceHostReference m_Device;
  168. };
  169. /*----------------------------------------------------------------------
  170. | CCtrlPointReferenceHolder class
  171. +---------------------------------------------------------------------*/
  172. class CCtrlPointReferenceHolder
  173. {
  174. public:
  175. PLT_CtrlPointReference m_CtrlPoint;
  176. };
  177. /*----------------------------------------------------------------------
  178. | CUPnPCleaner class
  179. +---------------------------------------------------------------------*/
  180. class CUPnPCleaner : public NPT_Thread
  181. {
  182. public:
  183. CUPnPCleaner(CUPnP* upnp) : NPT_Thread(true), m_UPnP(upnp) {}
  184. void Run() {
  185. delete m_UPnP;
  186. }
  187. CUPnP* m_UPnP;
  188. };
  189. /*----------------------------------------------------------------------
  190. | CUPnP::CUPnP
  191. +---------------------------------------------------------------------*/
  192. class CUPnPServer : public PLT_MediaConnect
  193. {
  194. public:
  195. CUPnPServer(const char* friendly_name, const char* uuid = NULL, int port = 0) :
  196. PLT_MediaConnect("", friendly_name, false, uuid, port) {
  197. // hack: override path to make sure it's empty
  198. // urls will contain full paths to local files
  199. m_Path = "";
  200. }
  201. // PLT_MediaServer methods
  202. virtual NPT_Result OnBrowseMetadata(PLT_ActionReference& action,
  203. const char* object_id,
  204. const char* filter,
  205. NPT_UInt32 starting_index,
  206. NPT_UInt32 requested_count,
  207. const NPT_List<NPT_String>& sort_criteria,
  208. const PLT_HttpRequestContext& context);
  209. virtual NPT_Result OnBrowseDirectChildren(PLT_ActionReference& action,
  210. const char* object_id,
  211. const char* filter,
  212. NPT_UInt32 starting_index,
  213. NPT_UInt32 requested_count,
  214. const NPT_List<NPT_String>& sort_criteria,
  215. const PLT_HttpRequestContext& context);
  216. virtual NPT_Result OnSearchContainer(PLT_ActionReference& action,
  217. const char* container_id,
  218. const char* search_criteria,
  219. const char* filter,
  220. NPT_UInt32 starting_index,
  221. NPT_UInt32 requested_count,
  222. const NPT_List<NPT_String>& sort_criteria,
  223. const PLT_HttpRequestContext& context);
  224. // PLT_FileMediaServer methods
  225. virtual NPT_Result ServeFile(NPT_HttpRequest& request,
  226. const NPT_HttpRequestContext& context,
  227. NPT_HttpResponse& response,
  228. const NPT_String& file_path);
  229. // class methods
  230. static NPT_Result PopulateObjectFromTag(CMusicInfoTag& tag,
  231. PLT_MediaObject& object,
  232. NPT_String* file_path,
  233. PLT_MediaItemResource* resource,
  234. EClientQuirks quirks);
  235. static NPT_Result PopulateObjectFromTag(CVideoInfoTag& tag,
  236. PLT_MediaObject& object,
  237. NPT_String* file_path,
  238. PLT_MediaItemResource* resource,
  239. EClientQuirks quirks);
  240. static PLT_MediaObject* BuildObject(const CFileItem& item,
  241. NPT_String& file_path,
  242. bool with_count,
  243. const PLT_HttpRequestContext* context = NULL,
  244. CUPnPServer* upnp_server = NULL);
  245. NPT_String BuildSafeResourceUri(const char* host,
  246. const char* file_path);
  247. void AddSafeResourceUri(PLT_MediaObject* object, NPT_List<NPT_IpAddress> ips, const char* file_path, const NPT_String& info)
  248. {
  249. PLT_MediaItemResource res;
  250. for(NPT_List<NPT_IpAddress>::Iterator ip = ips.GetFirstItem(); ip; ++ip) {
  251. res.m_ProtocolInfo = PLT_ProtocolInfo(info);
  252. res.m_Uri = BuildSafeResourceUri((*ip).ToString(), file_path);
  253. object->m_Resources.Add(res);
  254. }
  255. }
  256. static const char* GetMimeTypeFromExtension(const char* extension, const PLT_HttpRequestContext* context = NULL);
  257. static NPT_String GetMimeType(const CFileItem& item, const PLT_HttpRequestContext* context = NULL);
  258. static NPT_String GetMimeType(const char* filename, const PLT_HttpRequestContext* context = NULL);
  259. static const CStdString& CorrectAllItemsSortHack(const CStdString &item);
  260. private:
  261. PLT_MediaObject* Build(CFileItemPtr item,
  262. bool with_count,
  263. const PLT_HttpRequestContext& context,
  264. const char* parent_id = NULL);
  265. NPT_Result BuildResponse(PLT_ActionReference& action,
  266. CFileItemList& items,
  267. const char* filter,
  268. NPT_UInt32 starting_index,
  269. NPT_UInt32 requested_count,
  270. const NPT_List<NPT_String>& sort_criteria,
  271. const PLT_HttpRequestContext& context,
  272. const char* parent_id /* = NULL */);
  273. // class methods
  274. static NPT_String GetParentFolder(NPT_String file_path) {
  275. int index = file_path.ReverseFind("\\");
  276. if (index == -1) return "";
  277. return file_path.Left(index);
  278. }
  279. static const NPT_String GetProtocolInfo(const CFileItem& item,
  280. const char* protocol,
  281. const PLT_HttpRequestContext* context = NULL);
  282. NPT_Mutex m_FileMutex;
  283. NPT_Map<NPT_String, NPT_String> m_FileMap;
  284. public:
  285. // class members
  286. static NPT_UInt32 m_MaxReturnedItems;
  287. };
  288. NPT_UInt32 CUPnPServer::m_MaxReturnedItems = 0;
  289. /*----------------------------------------------------------------------
  290. | CUPnPServer::BuildSafeResourceUri
  291. +---------------------------------------------------------------------*/
  292. NPT_String CUPnPServer::BuildSafeResourceUri(const char* host,
  293. const char* file_path)
  294. {
  295. CStdString md5;
  296. XBMC::XBMC_MD5 md5state;
  297. md5state.append(file_path);
  298. md5state.getDigest(md5);
  299. md5 += "/" + URIUtils::GetFileName(file_path);
  300. { NPT_AutoLock lock(m_FileMutex);
  301. NPT_CHECK(m_FileMap.Put(md5.c_str(), file_path));
  302. }
  303. return PLT_FileMediaServer::BuildSafeResourceUri(m_FileBaseUri, host, md5);
  304. }
  305. /*----------------------------------------------------------------------
  306. | CUPnPServer::GetMimeType
  307. +---------------------------------------------------------------------*/
  308. NPT_String
  309. CUPnPServer::GetMimeType(const char* filename,
  310. const PLT_HttpRequestContext* context /* = NULL */)
  311. {
  312. NPT_String ext = URIUtils::GetExtension(filename).c_str();
  313. ext.TrimLeft('.');
  314. ext = ext.ToLowercase();
  315. return PLT_MediaObject::GetMimeTypeFromExtension(ext, context);
  316. }
  317. /*----------------------------------------------------------------------
  318. | CUPnPServer::GetMimeType
  319. +---------------------------------------------------------------------*/
  320. NPT_String
  321. CUPnPServer::GetMimeType(const CFileItem& item,
  322. const PLT_HttpRequestContext* context /* = NULL */)
  323. {
  324. CStdString path = item.GetPath();
  325. if (item.HasVideoInfoTag() && !item.GetVideoInfoTag()->GetPath().IsEmpty()) {
  326. path = item.GetVideoInfoTag()->GetPath();
  327. } else if (item.HasMusicInfoTag() && !item.GetMusicInfoTag()->GetURL().IsEmpty()) {
  328. path = item.GetMusicInfoTag()->GetURL();
  329. }
  330. if(path.Left(8).Equals("stack://"))
  331. return "audio/x-mpegurl";
  332. NPT_String ext = URIUtils::GetExtension(path).c_str();
  333. ext.TrimLeft('.');
  334. ext = ext.ToLowercase();
  335. NPT_String mime;
  336. /* We always use Platinum mime type first
  337. as it is defined to map extension to DLNA compliant mime type
  338. or custom according to context (who asked for it) */
  339. if (!ext.IsEmpty()) {
  340. mime = PLT_MediaObject::GetMimeTypeFromExtension(ext, context);
  341. if (mime == "application/octet-stream") mime = "";
  342. }
  343. /* if Platinum couldn't map it, default to XBMC mapping */
  344. if (mime.IsEmpty()) {
  345. NPT_String mime = item.GetMimeType().c_str();
  346. if (mime == "application/octet-stream") mime = "";
  347. }
  348. /* fallback to generic mime type if not found */
  349. if (mime.IsEmpty()) {
  350. if (item.IsVideo() || item.IsVideoDb() )
  351. mime = "video/" + ext;
  352. else if (item.IsAudio() || item.IsMusicDb() )
  353. mime = "audio/" + ext;
  354. else if (item.IsPicture() )
  355. mime = "image/" + ext;
  356. }
  357. /* nothing we can figure out */
  358. if (mime.IsEmpty()) {
  359. mime = "application/octet-stream";
  360. }
  361. return mime;
  362. }
  363. /*----------------------------------------------------------------------
  364. | CUPnPServer::GetProtocolInfo
  365. +---------------------------------------------------------------------*/
  366. const NPT_String
  367. CUPnPServer::GetProtocolInfo(const CFileItem& item,
  368. const char* protocol,
  369. const PLT_HttpRequestContext* context /* = NULL */)
  370. {
  371. NPT_String proto = protocol;
  372. /* fixup the protocol just in case nothing was passed */
  373. if (proto.IsEmpty()) {
  374. proto = item.GetAsUrl().GetProtocol();
  375. }
  376. /*
  377. map protocol to right prefix and use xbmc-get for
  378. unsupported UPnP protocols for other xbmc clients
  379. TODO: add rtsp ?
  380. */
  381. if (proto == "http") {
  382. proto = "http-get";
  383. } else {
  384. proto = "xbmc-get";
  385. }
  386. /* we need a valid extension to retrieve the mimetype for the protocol info */
  387. NPT_String mime = GetMimeType(item, context);
  388. proto += ":*:" + mime + ":" + PLT_MediaObject::GetDlnaExtension(mime, context);
  389. return proto;
  390. }
  391. /*----------------------------------------------------------------------
  392. | CUPnPServer::PopulateObjectFromTag
  393. +---------------------------------------------------------------------*/
  394. NPT_Result
  395. CUPnPServer::PopulateObjectFromTag(CMusicInfoTag& tag,
  396. PLT_MediaObject& object,
  397. NPT_String* file_path, /* = NULL */
  398. PLT_MediaItemResource* resource, /* = NULL */
  399. EClientQuirks quirks)
  400. {
  401. if (!tag.GetURL().IsEmpty() && file_path)
  402. *file_path = tag.GetURL();
  403. std::vector<std::string> genres = tag.GetGenre();
  404. for (unsigned int index = 0; index < genres.size(); index++)
  405. object.m_Affiliation.genre.Add(genres.at(index).c_str());
  406. object.m_Title = tag.GetTitle();
  407. object.m_Affiliation.album = tag.GetAlbum();
  408. for (unsigned int index = 0; index < tag.GetArtist().size(); index++)
  409. {
  410. object.m_People.artists.Add(tag.GetArtist().at(index).c_str());
  411. object.m_People.artists.Add(tag.GetArtist().at(index).c_str(), "Performer");
  412. }
  413. object.m_People.artists.Add(StringUtils::Join(!tag.GetAlbumArtist().empty() ? tag.GetAlbumArtist() : tag.GetArtist(), g_advancedSettings.m_musicItemSeparator).c_str(), "AlbumArtist");
  414. if(tag.GetAlbumArtist().empty())
  415. object.m_Creator = StringUtils::Join(tag.GetArtist(), g_advancedSettings.m_musicItemSeparator);
  416. else
  417. object.m_Creator = StringUtils::Join(tag.GetAlbumArtist(), g_advancedSettings.m_musicItemSeparator);
  418. object.m_MiscInfo.original_track_number = tag.GetTrackNumber();
  419. if(tag.GetDatabaseId() >= 0) {
  420. object.m_ReferenceID = NPT_String::Format("musicdb://4/%i%s", tag.GetDatabaseId(), URIUtils::GetExtension(tag.GetURL()).c_str());
  421. }
  422. if (object.m_ReferenceID == object.m_ObjectID)
  423. object.m_ReferenceID = "";
  424. if (resource) resource->m_Duration = tag.GetDuration();
  425. return NPT_SUCCESS;
  426. }
  427. /*----------------------------------------------------------------------
  428. | CUPnPServer::PopulateObjectFromTag
  429. +---------------------------------------------------------------------*/
  430. NPT_Result
  431. CUPnPServer::PopulateObjectFromTag(CVideoInfoTag& tag,
  432. PLT_MediaObject& object,
  433. NPT_String* file_path, /* = NULL */
  434. PLT_MediaItemResource* resource, /* = NULL */
  435. EClientQuirks quirks)
  436. {
  437. // some usefull buffers
  438. CStdStringArray strings;
  439. if (!tag.m_strFileNameAndPath.IsEmpty() && file_path)
  440. *file_path = tag.m_strFileNameAndPath;
  441. if (tag.m_iDbId != -1 ) {
  442. if (!tag.m_artist.empty()) {
  443. object.m_ObjectClass.type = "object.item.videoItem.musicVideoClip";
  444. object.m_Creator = StringUtils::Join(tag.m_artist, g_advancedSettings.m_videoItemSeparator);
  445. object.m_Title = tag.m_strTitle;
  446. object.m_ReferenceID = NPT_String::Format("videodb://3/2/%i", tag.m_iDbId);
  447. } else if (!tag.m_strShowTitle.IsEmpty()) {
  448. object.m_ObjectClass.type = "object.item.videoItem.videoBroadcast";
  449. object.m_Recorded.program_title = "S" + ("0" + NPT_String::FromInteger(tag.m_iSeason)).Right(2);
  450. object.m_Recorded.program_title += "E" + ("0" + NPT_String::FromInteger(tag.m_iEpisode)).Right(2);
  451. object.m_Recorded.program_title += " : " + tag.m_strTitle;
  452. object.m_Recorded.series_title = tag.m_strShowTitle;
  453. object.m_Recorded.episode_number = tag.m_iSeason * 100 + tag.m_iEpisode;
  454. object.m_Title = object.m_Recorded.series_title + " - " + object.m_Recorded.program_title;
  455. object.m_Date = tag.m_firstAired.GetAsLocalizedDate();
  456. if(tag.m_iSeason != -1)
  457. object.m_ReferenceID = NPT_String::Format("videodb://2/0/%i", tag.m_iDbId);
  458. } else {
  459. object.m_ObjectClass.type = "object.item.videoItem.movie";
  460. object.m_Title = tag.m_strTitle;
  461. object.m_Date = NPT_String::FromInteger(tag.m_iYear) + "-01-01";
  462. object.m_ReferenceID = NPT_String::Format("videodb://1/2/%i", tag.m_iDbId);
  463. }
  464. }
  465. if(quirks & ECLIENTQUIRKS_BASICVIDEOCLASS)
  466. object.m_ObjectClass.type = "object.item.videoItem";
  467. if(object.m_ReferenceID == object.m_ObjectID)
  468. object.m_ReferenceID = "";
  469. for (unsigned int index = 0; index < tag.m_genre.size(); index++)
  470. object.m_Affiliation.genre.Add(tag.m_genre.at(index).c_str());
  471. for(CVideoInfoTag::iCast it = tag.m_cast.begin();it != tag.m_cast.end();it++) {
  472. object.m_People.actors.Add(it->strName.c_str(), it->strRole.c_str());
  473. }
  474. object.m_People.director = StringUtils::Join(tag.m_director, g_advancedSettings.m_videoItemSeparator);
  475. for (unsigned int index = 0; index < tag.m_writingCredits.size(); index++)
  476. object.m_People.authors.Add(tag.m_writingCredits[index].c_str());
  477. object.m_Description.description = tag.m_strTagLine;
  478. object.m_Description.long_description = tag.m_strPlot;
  479. if (resource) resource->m_Duration = tag.m_streamDetails.GetVideoDuration();
  480. if (resource) resource->m_Resolution = NPT_String::FromInteger(tag.m_streamDetails.GetVideoWidth()) + "x" + NPT_String::FromInteger(tag.m_streamDetails.GetVideoHeight());
  481. return NPT_SUCCESS;
  482. }
  483. /*----------------------------------------------------------------------
  484. | CUPnPServer::CorrectAllItemsSortHack
  485. +---------------------------------------------------------------------*/
  486. const CStdString&
  487. CUPnPServer::CorrectAllItemsSortHack(const CStdString &item)
  488. {
  489. // This is required as in order for the "* All Albums" etc. items to sort
  490. // correctly, they must have fake artist/album etc. information generated.
  491. // This looks nasty if we attempt to render it to the GUI, thus this (further)
  492. // workaround
  493. if ((item.size() == 1 && item[0] == 0x01) || (item.size() > 1 && ((unsigned char) item[1]) == 0xff))
  494. return StringUtils::EmptyString;
  495. return item;
  496. }
  497. /*----------------------------------------------------------------------
  498. | CUPnPServer::BuildObject
  499. +---------------------------------------------------------------------*/
  500. PLT_MediaObject*
  501. CUPnPServer::BuildObject(const CFileItem& item,
  502. NPT_String& file_path,
  503. bool with_count,
  504. const PLT_HttpRequestContext* context /* = NULL */,
  505. CUPnPServer* upnp_server /* = NULL */)
  506. {
  507. PLT_MediaItemResource resource;
  508. PLT_MediaObject* object = NULL;
  509. CLog::Log(LOGDEBUG, "Building didl for object '%s'", (const char*)item.GetPath());
  510. EClientQuirks quirks = GetClientQuirks(context);
  511. // get list of ip addresses
  512. NPT_List<NPT_IpAddress> ips;
  513. NPT_CHECK_LABEL(PLT_UPnPMessageHelper::GetIPAddresses(ips), failure);
  514. // if we're passed an interface where we received the request from
  515. // move the ip to the top
  516. if (context && context->GetLocalAddress().GetIpAddress().ToString() != "0.0.0.0") {
  517. ips.Remove(context->GetLocalAddress().GetIpAddress());
  518. ips.Insert(ips.GetFirstItem(), context->GetLocalAddress().GetIpAddress());
  519. }
  520. if (!item.m_bIsFolder) {
  521. object = new PLT_MediaItem();
  522. object->m_ObjectID = item.GetPath();
  523. /* Setup object type */
  524. if (item.IsMusicDb() || item.IsAudio()) {
  525. object->m_ObjectClass.type = "object.item.audioItem.musicTrack";
  526. if (item.HasMusicInfoTag()) {
  527. CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
  528. PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks);
  529. }
  530. } else if (item.IsVideoDb() || item.IsVideo()) {
  531. object->m_ObjectClass.type = "object.item.videoItem";
  532. if(quirks & ECLIENTQUIRKS_UNKNOWNSERIES)
  533. object->m_Affiliation.album = "[Unknown Series]";
  534. if (item.HasVideoInfoTag()) {
  535. CVideoInfoTag *tag = (CVideoInfoTag*)item.GetVideoInfoTag();
  536. PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks);
  537. }
  538. } else if (item.IsPicture()) {
  539. object->m_ObjectClass.type = "object.item.imageItem.photo";
  540. } else {
  541. object->m_ObjectClass.type = "object.item";
  542. }
  543. // duration of zero is invalid
  544. if (resource.m_Duration == 0) resource.m_Duration = -1;
  545. // Set the resource file size
  546. resource.m_Size = item.m_dwSize;
  547. if (resource.m_Size == 0) {
  548. struct __stat64 info;
  549. if(CFile::Stat((const char*)file_path, &info) == 0 && info.st_size >= 0)
  550. resource.m_Size = info.st_size;
  551. }
  552. if(resource.m_Size == 0)
  553. resource.m_Size = (NPT_LargeSize)-1;
  554. // set date
  555. if (object->m_Date.IsEmpty() && item.m_dateTime.IsValid()) {
  556. object->m_Date = item.m_dateTime.GetAsLocalizedDate();
  557. }
  558. if (upnp_server) {
  559. upnp_server->AddSafeResourceUri(object, ips, file_path, GetProtocolInfo(item, "http", context));
  560. }
  561. // if the item is remote, add a direct link to the item
  562. if (URIUtils::IsRemote((const char*)file_path)) {
  563. resource.m_ProtocolInfo = PLT_ProtocolInfo(CUPnPServer::GetProtocolInfo(item, item.GetAsUrl().GetProtocol(), context));
  564. resource.m_Uri = file_path;
  565. // if the direct link can be served directly using http, then push it in front
  566. // otherwise keep the xbmc-get resource last and let a compatible client look for it
  567. if (resource.m_ProtocolInfo.ToString().StartsWith("xbmc", true)) {
  568. object->m_Resources.Add(resource);
  569. } else {
  570. object->m_Resources.Insert(object->m_Resources.GetFirstItem(), resource);
  571. }
  572. }
  573. // Some upnp clients expect all audio items to have parent root id 4
  574. #ifdef WMP_ID_MAPPING
  575. object->m_ParentID = "4";
  576. #endif
  577. } else {
  578. PLT_MediaContainer* container = new PLT_MediaContainer;
  579. object = container;
  580. /* Assign a title and id for this container */
  581. container->m_ObjectID = item.GetPath();
  582. container->m_ObjectClass.type = "object.container";
  583. container->m_ChildrenCount = -1;
  584. CStdStringArray strings;
  585. /* this might be overkill, but hey */
  586. if (item.IsMusicDb()) {
  587. MUSICDATABASEDIRECTORY::NODE_TYPE node = CMusicDatabaseDirectory::GetDirectoryType(item.GetPath());
  588. switch(node) {
  589. case MUSICDATABASEDIRECTORY::NODE_TYPE_ARTIST: {
  590. container->m_ObjectClass.type += ".person.musicArtist";
  591. CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
  592. if (tag) {
  593. container->m_People.artists.Add(
  594. CorrectAllItemsSortHack(StringUtils::Join(tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "Performer");
  595. container->m_People.artists.Add(
  596. CorrectAllItemsSortHack(StringUtils::Join(!tag->GetAlbumArtist().empty() ? tag->GetAlbumArtist() : tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "AlbumArtist");
  597. }
  598. #ifdef WMP_ID_MAPPING
  599. // Some upnp clients expect all artists to have parent root id 107
  600. container->m_ParentID = "107";
  601. #endif
  602. }
  603. break;
  604. case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM:
  605. case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_COMPILATIONS:
  606. case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_RECENTLY_ADDED:
  607. case MUSICDATABASEDIRECTORY::NODE_TYPE_YEAR_ALBUM: {
  608. container->m_ObjectClass.type += ".album.musicAlbum";
  609. // for Sonos to be happy
  610. CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
  611. if (tag) {
  612. container->m_People.artists.Add(
  613. CorrectAllItemsSortHack(StringUtils::Join(tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "Performer");
  614. container->m_People.artists.Add(
  615. CorrectAllItemsSortHack(StringUtils::Join(!tag->GetAlbumArtist().empty() ? tag->GetAlbumArtist() : tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)).c_str(), "AlbumArtist");
  616. container->m_Affiliation.album = CorrectAllItemsSortHack(tag->GetAlbum()).c_str();
  617. }
  618. #ifdef WMP_ID_MAPPING
  619. // Some upnp clients expect all albums to have parent root id 7
  620. container->m_ParentID = "7";
  621. #endif
  622. }
  623. break;
  624. case MUSICDATABASEDIRECTORY::NODE_TYPE_GENRE:
  625. container->m_ObjectClass.type += ".genre.musicGenre";
  626. break;
  627. default:
  628. break;
  629. }
  630. } else if (item.IsVideoDb()) {
  631. VIDEODATABASEDIRECTORY::NODE_TYPE node = CVideoDatabaseDirectory::GetDirectoryType(item.GetPath());
  632. CVideoInfoTag &tag = *(CVideoInfoTag*)item.GetVideoInfoTag();
  633. switch(node) {
  634. case VIDEODATABASEDIRECTORY::NODE_TYPE_GENRE:
  635. container->m_ObjectClass.type += ".genre.movieGenre";
  636. break;
  637. case VIDEODATABASEDIRECTORY::NODE_TYPE_ACTOR:
  638. container->m_ObjectClass.type += ".person.videoArtist";
  639. container->m_Creator = StringUtils::Join(tag.m_artist, g_advancedSettings.m_videoItemSeparator);
  640. container->m_Title = tag.m_strTitle;
  641. break;
  642. case VIDEODATABASEDIRECTORY::NODE_TYPE_TITLE_TVSHOWS:
  643. container->m_ObjectClass.type += ".album.videoAlbum";
  644. container->m_Recorded.program_title = "S" + ("0" + NPT_String::FromInteger(tag.m_iSeason)).Right(2);
  645. container->m_Recorded.program_title += "E" + ("0" + NPT_String::FromInteger(tag.m_iEpisode)).Right(2);
  646. container->m_Recorded.program_title += " : " + tag.m_strTitle;
  647. container->m_Recorded.series_title = tag.m_strShowTitle;
  648. container->m_Recorded.episode_number = tag.m_iSeason * 100 + tag.m_iEpisode;
  649. container->m_Title = container->m_Recorded.series_title + " - " + container->m_Recorded.program_title;
  650. container->m_Title = tag.m_strTitle;
  651. if(!tag.m_firstAired.IsValid() && tag.m_iYear)
  652. container->m_Date = NPT_String::FromInteger(tag.m_iYear) + "-01-01";
  653. else
  654. container->m_Date = tag.m_firstAired.GetAsLocalizedDate();
  655. for (unsigned int index = 0; index < tag.m_genre.size(); index++)
  656. container->m_Affiliation.genre.Add(tag.m_genre.at(index).c_str());
  657. for(CVideoInfoTag::iCast it = tag.m_cast.begin();it != tag.m_cast.end();it++) {
  658. container->m_People.actors.Add(it->strName.c_str(), it->strRole.c_str());
  659. }
  660. container->m_People.director = StringUtils::Join(tag.m_director, g_advancedSettings.m_videoItemSeparator);;
  661. for (unsigned int index = 0; index < tag.m_writingCredits.size(); index++)
  662. container->m_People.authors.Add(tag.m_writingCredits[index].c_str());
  663. container->m_Description.description = tag.m_strTagLine;
  664. container->m_Description.long_description = tag.m_strPlot;
  665. break;
  666. default:
  667. container->m_ObjectClass.type += ".storageFolder";
  668. break;
  669. }
  670. } else if (item.IsPlayList()) {
  671. container->m_ObjectClass.type += ".playlistContainer";
  672. }
  673. if(quirks & ECLIENTQUIRKS_ONLYSTORAGEFOLDER) {
  674. container->m_ObjectClass.type = "object.container.storageFolder";
  675. }
  676. /* Get the number of children for this container */
  677. if (with_count && upnp_server) {
  678. if (object->m_ObjectID.StartsWith("virtualpath://")) {
  679. NPT_Cardinal count = 0;
  680. NPT_CHECK_LABEL(NPT_File::GetCount(file_path, count), failure);
  681. container->m_ChildrenCount = count;
  682. } else {
  683. /* this should be a standard path */
  684. // TODO - get file count of this directory
  685. }
  686. }
  687. }
  688. // set a title for the object
  689. if (object->m_Title.IsEmpty()) {
  690. if (!item.GetLabel().IsEmpty()) {
  691. CStdString title = item.GetLabel();
  692. if (item.IsPlayList() || !item.m_bIsFolder) URIUtils::RemoveExtension(title);
  693. object->m_Title = title;
  694. } else {
  695. CStdString title, volumeNumber;
  696. CUtil::GetVolumeFromFileName(item.GetPath(), title, volumeNumber);
  697. if (!item.m_bIsFolder) URIUtils::RemoveExtension(title);
  698. object->m_Title = title;
  699. }
  700. }
  701. // set a thumbnail if we have one
  702. if (item.HasThumbnail() && upnp_server) {
  703. object->m_ExtraInfo.album_art_uri = upnp_server->BuildSafeResourceUri(
  704. (*ips.GetFirstItem()).ToString(),
  705. item.GetThumbnailImage());
  706. // Set DLNA profileID by extension, defaulting to JPEG.
  707. NPT_String ext = URIUtils::GetExtension(item.GetThumbnailImage()).c_str();
  708. if (strcmp(ext, ".png") == 0) {
  709. object->m_ExtraInfo.album_art_uri_dlna_profile = "PNG_TN";
  710. } else {
  711. object->m_ExtraInfo.album_art_uri_dlna_profile = "JPEG_TN";
  712. }
  713. }
  714. if (upnp_server) {
  715. if (item.HasProperty("fanart_image")) {
  716. upnp_server->AddSafeResourceUri(object, ips, item.GetProperty("fanart_image").asString().c_str(), "xbmc.org:*:fanart:*");
  717. }
  718. }
  719. return object;
  720. failure:
  721. delete object;
  722. return NULL;
  723. }
  724. /*----------------------------------------------------------------------
  725. | CUPnPServer::Build
  726. +---------------------------------------------------------------------*/
  727. PLT_MediaObject*
  728. CUPnPServer::Build(CFileItemPtr item,
  729. bool with_count,
  730. const PLT_HttpRequestContext& context,
  731. const char* parent_id /* = NULL */)
  732. {
  733. PLT_MediaObject* object = NULL;
  734. NPT_String path = item->GetPath().c_str();
  735. //HACK: temporary disabling count as it thrashes HDD
  736. with_count = false;
  737. CLog::Log(LOGDEBUG, "Preparing upnp object for item '%s'", (const char*)path);
  738. if (path == "virtualpath://upnproot") {
  739. path.TrimRight("/");
  740. if (path.StartsWith("virtualpath://")) {
  741. object = new PLT_MediaContainer;
  742. object->m_Title = item->GetLabel();
  743. object->m_ObjectClass.type = "object.container";
  744. object->m_ObjectID = path;
  745. // root
  746. object->m_ObjectID = "0";
  747. object->m_ParentID = "-1";
  748. // root has 5 children
  749. if (with_count) {
  750. ((PLT_MediaContainer*)object)->m_ChildrenCount = 5;
  751. }
  752. } else {
  753. goto failure;
  754. }
  755. } else {
  756. // db path handling
  757. NPT_String file_path, share_name;
  758. file_path = item->GetPath();
  759. share_name = "";
  760. if (path.StartsWith("musicdb://")) {
  761. if (path == "musicdb://" ) {
  762. item->SetLabel("Music Library");
  763. item->SetLabelPreformated(true);
  764. } else {
  765. if (!item->HasMusicInfoTag() || !item->GetMusicInfoTag()->Loaded() )
  766. item->LoadMusicTag();
  767. if (!item->HasThumbnail() )
  768. item->SetThumbnailImage(CThumbLoader::GetCachedImage(*item, "thumb"));
  769. if (item->GetLabel().IsEmpty()) {
  770. /* if no label try to grab it from node type */
  771. CStdString label;
  772. if (CMusicDatabaseDirectory::GetLabel((const char*)path, label)) {
  773. item->SetLabel(label);
  774. item->SetLabelPreformated(true);
  775. }
  776. }
  777. }
  778. } else if (file_path.StartsWith("videodb://")) {
  779. if (path == "videodb://" ) {
  780. item->SetLabel("Video Library");
  781. item->SetLabelPreformated(true);
  782. } else {
  783. if (!item->HasVideoInfoTag()) {
  784. XFILE::VIDEODATABASEDIRECTORY::CQueryParams params;
  785. XFILE::VIDEODATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo((const char*)path, params);
  786. CVideoDatabase db;
  787. if (!db.Open() ) return NULL;
  788. if (params.GetMovieId() >= 0 )
  789. db.GetMovieInfo((const char*)path, *item->GetVideoInfoTag(), params.GetMovieId());
  790. else if (params.GetEpisodeId() >= 0 )
  791. db.GetEpisodeInfo((const char*)path, *item->GetVideoInfoTag(), params.GetEpisodeId());
  792. else if (params.GetTvShowId() >= 0 )
  793. db.GetTvShowInfo((const char*)path, *item->GetVideoInfoTag(), params.GetTvShowId());
  794. }
  795. // try to grab title from tag
  796. if (item->HasVideoInfoTag() && !item->GetVideoInfoTag()->m_strTitle.IsEmpty()) {
  797. item->SetLabel(item->GetVideoInfoTag()->m_strTitle);
  798. item->SetLabelPreformated(true);
  799. }
  800. // try to grab it from the folder
  801. if (item->GetLabel().IsEmpty()) {
  802. CStdString label;
  803. if (CVideoDatabaseDirectory::GetLabel((const char*)path, label)) {
  804. item->SetLabel(label);
  805. item->SetLabelPreformated(true);
  806. }
  807. }
  808. if (!item->HasThumbnail() )
  809. item->SetThumbnailImage(CThumbLoader::GetCachedImage(*item, "thumb"));
  810. }
  811. }
  812. // not a virtual path directory, new system
  813. object = BuildObject(*item.get(), file_path, with_count, &context, this);
  814. // set parent id if passed, otherwise it should have been determined
  815. if (object && parent_id) {
  816. object->m_ParentID = parent_id;
  817. }
  818. }
  819. if (object) {
  820. // remap Root virtualpath://upnproot/ to id "0"
  821. if (object->m_ObjectID == "virtualpath://upnproot/")
  822. object->m_ObjectID = "0";
  823. // remap Parent Root virtualpath://upnproot/ to id "0"
  824. if (object->m_ParentID == "virtualpath://upnproot/")
  825. object->m_ParentID = "0";
  826. }
  827. return object;
  828. failure:
  829. delete object;
  830. return NULL;
  831. }
  832. /*----------------------------------------------------------------------
  833. | TranslateWMPObjectId
  834. +---------------------------------------------------------------------*/
  835. static NPT_String TranslateWMPObjectId(NPT_String id)
  836. {
  837. if (id == "0") {
  838. id = "virtualpath://upnproot/";
  839. } else if (id == "15") {
  840. // Xbox 360 asking for videos
  841. id = "videodb://";
  842. } else if (id == "16") {
  843. // Xbox 360 asking for photos
  844. } else if (id == "107") {
  845. // Sonos uses 107 for artists root container id
  846. id = "musicdb://2/";
  847. } else if (id == "7") {
  848. // Sonos uses 7 for albums root container id
  849. id = "musicdb://3/";
  850. } else if (id == "4") {
  851. // Sonos uses 4 for tracks root container id
  852. id = "musicdb://4/";
  853. }
  854. CLog::Log(LOGDEBUG, "UPnP Translated id to '%s'", (const char*)id);
  855. return id;
  856. }
  857. /*----------------------------------------------------------------------
  858. | CUPnPServer::OnBrowseMetadata
  859. +---------------------------------------------------------------------*/
  860. NPT_Result
  861. CUPnPServer::OnBrowseMetadata(PLT_ActionReference& action,
  862. const char* object_id,
  863. const char* filter,
  864. NPT_UInt32 starting_index,
  865. NPT_UInt32 requested_count,
  866. const NPT_List<NPT_String>& sort_criteria,
  867. const PLT_HttpRequestContext& context)
  868. {
  869. NPT_COMPILER_UNUSED(sort_criteria);
  870. NPT_COMPILER_UNUSED(requested_count);
  871. NPT_COMPILER_UNUSED(starting_index);
  872. NPT_String didl;
  873. NPT_Reference<PLT_MediaObject> object;
  874. NPT_String id = TranslateWMPObjectId(object_id);
  875. vector<CStdString> paths;
  876. CFileItemPtr item;
  877. CLog::Log(LOGINFO, "Received UPnP Browse Metadata request for object '%s'", (const char*)object_id);
  878. if (id.StartsWith("virtualpath://")) {
  879. id.TrimRight("/");
  880. if (id == "virtualpath://upnproot") {
  881. id += "/";
  882. item.reset(new CFileItem((const char*)id, true));
  883. item->SetLabel("Root");
  884. item->SetLabelPreformated(true);
  885. object = Build(item, true, context);
  886. } else {
  887. return NPT_FAILURE;
  888. }
  889. } else {
  890. // determine if it's a container by calling CDirectory::Exists
  891. item.reset(new CFileItem((const char*)id, CDirectory::Exists((const char*)id)));
  892. // determine parent id for shared paths only
  893. // otherwise let db find out
  894. CStdString parent;
  895. if (!URIUtils::GetParentPath((const char*)id, parent)) parent = "0";
  896. //#ifdef WMP_ID_MAPPING
  897. // if (!id.StartsWith("musicdb://") && !id.StartsWith("videodb://")) {
  898. // parent = "";
  899. // }
  900. //#endif
  901. object = Build(item, true, context, parent.empty()?NULL:parent.c_str());
  902. }
  903. if (object.IsNull()) {
  904. /* error */
  905. NPT_LOG_WARNING_1("CUPnPServer::OnBrowseMetadata - Object null (%s)", object_id);
  906. action->SetError(701, "No Such Object.");
  907. return NPT_FAILURE;
  908. }
  909. NPT_String tmp;
  910. NPT_CHECK(PLT_Didl::ToDidl(*object.AsPointer(), filter, tmp));
  911. /* add didl header and footer */
  912. didl = didl_header + tmp + didl_footer;
  913. NPT_CHECK(action->SetArgumentValue("Result", didl));
  914. NPT_CHECK(action->SetArgumentValue("NumberReturned", "1"));
  915. NPT_CHECK(action->SetArgumentValue("TotalMatches", "1"));
  916. // update ID may be wrong here, it should be the one of the container?
  917. NPT_CHECK(action->SetArgumentValue("UpdateId", "0"));
  918. // TODO: We need to keep track of the overall SystemUpdateID of the CDS
  919. return NPT_SUCCESS;
  920. }
  921. /*----------------------------------------------------------------------
  922. | CUPnPServer::OnBrowseDirectChildren
  923. +---------------------------------------------------------------------*/
  924. NPT_Result
  925. CUPnPServer::OnBrowseDirectChildren(PLT_ActionReference& action,
  926. const char* object_id,
  927. const char* filter,
  928. NPT_UInt32 starting_index,
  929. NPT_UInt32 requested_count,
  930. const NPT_List<NPT_String>& sort_criteria,
  931. const PLT_HttpRequestContext& context)
  932. {
  933. CFileItemList items;
  934. NPT_String parent_id = TranslateWMPObjectId(object_id);
  935. CLog::Log(LOGINFO, "Received UPnP Browse DirectChildren request for object '%s'", (const char*)object_id);
  936. items.SetPath(CStdString(parent_id));
  937. if (!items.Load()) {
  938. // cache anything that takes more than a second to retrieve
  939. unsigned int time = XbmcThreads::SystemClockMillis();
  940. if (parent_id.StartsWith("virtualpath://upnproot")) {
  941. CFileItemPtr item;
  942. // music library
  943. item.reset(new CFileItem("musicdb://", true));
  944. item->SetLabel("Music Library");
  945. item->SetLabelPreformated(true);
  946. items.Add(item);
  947. // video library
  948. item.reset(new CFileItem("videodb://", true));
  949. item->SetLabel("Video Library");
  950. item->SetLabelPreformated(true);
  951. items.Add(item);
  952. } else {
  953. CDirectory::GetDirectory((const char*)parent_id, items);
  954. }
  955. if (items.CacheToDiscAlways() || (items.CacheToDiscIfSlow() && (XbmcThreads::SystemClockMillis() - time) > 1000 )) {
  956. items.Save();
  957. }
  958. }
  959. // Always sort by label
  960. items.Sort(SORT_METHOD_LABEL, SortOrderAscending);
  961. // Don't pass parent_id if action is Search not BrowseDirectChildren, as
  962. // we want the engine to determine the best parent id, not necessarily the one
  963. // passed
  964. NPT_String action_name = action->GetActionDesc().GetName();
  965. return BuildResponse(
  966. action,
  967. items,
  968. filter,
  969. starting_index,
  970. requested_count,
  971. sort_criteria,
  972. context,
  973. (action_name.Compare("Search", true)==0)?NULL:parent_id.GetChars());
  974. }
  975. /*----------------------------------------------------------------------
  976. | CUPnPServer::BuildResponse
  977. +---------------------------------------------------------------------*/
  978. NPT_Result
  979. CUPnPServer::BuildResponse(PLT_ActionReference& action,
  980. CFileItemList& items,
  981. const char* filter,
  982. NPT_UInt32 starting_index,
  983. NPT_UInt32 requested_count,
  984. const NPT_List<NPT_String>& sort_criteria,
  985. const PLT_HttpRequestContext& context,
  986. const char* parent_id /* = NULL */)
  987. {
  988. NPT_COMPILER_UNUSED(sort_criteria);
  989. CLog::Log(LOGDEBUG, "Building UPnP response with filter '%s', starting @ %d with %d requested",
  990. (const char*)filter,
  991. starting_index,
  992. requested_count);
  993. // won't return more than UPNP_MAX_RETURNED_ITEMS items at a time to keep things smooth
  994. // 0 requested means as many as possible
  995. NPT_UInt32 max_count = (requested_count == 0)?m_MaxReturnedItems:min((unsigned long)requested_count, (unsigned long)m_MaxReturnedItems);
  996. NPT_UInt32 stop_index = min((unsigned long)(starting_index + max_count), (unsigned long)items.Size()); // don't return more than we can
  997. NPT_Cardinal count = 0;
  998. NPT_String didl = didl_header;
  999. PLT_MediaObjectReference object;
  1000. for (unsigned long i=starting_index; i<stop_index; ++i) {
  1001. object = Build(items[i], true, context, parent_id);
  1002. if (object.IsNull()) {
  1003. continue;
  1004. }
  1005. NPT_String tmp;
  1006. NPT_CHECK(PLT_Didl::ToDidl(*object.AsPointer(), filter, tmp));
  1007. // Neptunes string growing is dead slow for small additions
  1008. if (didl.GetCapacity() < tmp.GetLength() + didl.GetLength()) {
  1009. didl.Reserve((tmp.GetLength() + didl.GetLength())*2);
  1010. }
  1011. didl += tmp;
  1012. ++count;
  1013. }
  1014. didl += didl_footer;
  1015. CLog::Log(LOGDEBUG, "Returning UPnP response with %d items out of %d total matches",
  1016. count,
  1017. items.Size());
  1018. NPT_CHECK(action->SetArgumentValue("Result", didl));
  1019. NPT_CHECK(action->SetArgumentValue("NumberReturned", NPT_String::FromInteger(count)));
  1020. NPT_CHECK(action->SetArgumentValue("TotalMatches", NPT_String::FromInteger(items.Size())));
  1021. NPT_CHECK(action->SetArgumentValue("UpdateId", "0"));
  1022. return NPT_SUCCESS;
  1023. }
  1024. /*----------------------------------------------------------------------
  1025. | FindSubCriteria
  1026. +---------------------------------------------------------------------*/
  1027. static
  1028. NPT_String
  1029. FindSubCriteria(NPT_String criteria, const char* name)
  1030. {
  1031. NPT_String result;
  1032. int search = criteria.Find(name);
  1033. if (search >= 0) {
  1034. criteria = criteria.Right(criteria.GetLength() - search - NPT_StringLength(name));
  1035. criteria.TrimLeft(" ");
  1036. if (criteria.GetLength()>0 && criteria[0] == '=') {
  1037. criteria.TrimLeft("= ");
  1038. if (criteria.GetLength()>0 && criteria[0] == '\"') {
  1039. search = criteria.Find("\"", 1);
  1040. if (search > 0) result = criteria.SubString(1, search-1);
  1041. }
  1042. }
  1043. }
  1044. return result;
  1045. }
  1046. /*----------------------------------------------------------------------
  1047. | CUPnPServer::OnSearchContainer
  1048. +---------------------------------------------------------------------*/
  1049. NPT_Result
  1050. CUPnPServer::OnSearchContainer(PLT_ActionReference& action,
  1051. const char* object_id,
  1052. const char* search_criteria,
  1053. const char* filter,
  1054. NPT_UInt32 starting_index,
  1055. NPT_UInt32 requested_count,
  1056. const NPT_List<NPT_String>& sort_criteria,
  1057. const PLT_HttpRequestContext& context)
  1058. {
  1059. CLog::Log(LOGDEBUG, "Received Search request for object '%s' with search '%s'",
  1060. (const char*)object_id,
  1061. (const char*)search_criteria);
  1062. NPT_String id = object_id;
  1063. if (id.StartsWith("musicdb://")) {
  1064. // we browse for all tracks given a genre, artist or album
  1065. if (NPT_String(search_criteria).Find("object.item.audioItem") >= 0) {
  1066. if (!id.EndsWith("/")) id += "/";
  1067. NPT_Cardinal count = id.SubString(10).Split("/").GetItemCount();
  1068. // remove extra empty node count
  1069. count = count?count-1:0;
  1070. // genre
  1071. if (id.StartsWith("musicdb://1/")) {
  1072. // all tracks of all genres
  1073. if (count == 1)
  1074. id += "-1/-1/-1/";
  1075. // all tracks of a specific genre
  1076. else if (count == 2)
  1077. id += "-1/-1/";
  1078. // all tracks of a specific genre of a specfic artist
  1079. else if (count == 3)
  1080. id += "-1/";
  1081. } else if (id.StartsWith("musicdb://2/")) {
  1082. // all tracks by all artists
  1083. if (count == 1)
  1084. id += "-1/-1/";
  1085. // all tracks of a specific artist
  1086. else if (count == 2)
  1087. id += "-1/";
  1088. } else if (id.StartsWith("musicdb://3/")) {
  1089. // all albums ?
  1090. if (count == 1) id += "-1/";
  1091. }
  1092. }
  1093. return OnBrowseDirectChildren(action, id, filter, starting_index, requested_count, sort_criteria, context);
  1094. } else if (NPT_String(search_criteria).Find("object.item.audioItem") >= 0) {
  1095. // look for artist, album & genre filters
  1096. NPT_String genre = FindSubCriteria(search_criteria, "upnp:genre");
  1097. NPT_String album = FindSubCriteria(search_criteria, "upnp:album");
  1098. NPT_String artist = FindSubCriteria(search_criteria, "upnp:artist");
  1099. // sonos looks for microsoft specific stuff
  1100. artist = artist.GetLength()?artist:FindSubCriteria(search_criteria, "microsoft:artistPerformer");
  1101. artist = artist.GetLength()?artist:FindSubCriteria(search_criteria, "microsoft:artistAlbumArtist");
  1102. artist = artist.GetLength()?artist:FindSubCriteria(search_criteria, "microsoft:authorComposer");
  1103. CMusicDatabase database;
  1104. database.Open();
  1105. if (genre.GetLength() > 0) {
  1106. // all tracks by genre filtered by artist and/or album
  1107. CStdString strPath;
  1108. strPath.Format("musicdb://1/%ld/%ld/%ld/",
  1109. database.GetGenreByName((const char*)genre),
  1110. database.GetArtistByName((const char*)artist), // will return -1 if no artist
  1111. database.GetAlbumByName((const char*)album)); // will return -1 if no album
  1112. return OnBrowseDirectChildren(action, strPath.c_str(), filter, starting_index, requested_count, sort_criteria, context);
  1113. } else if (artist.GetLength() > 0) {
  1114. // all tracks by artist name filtered by album if passed
  1115. CStdString strPath;
  1116. strPath.Format("musicdb://2/%ld/%ld/",
  1117. database.GetArtistByName((const char*)artist),
  1118. database.GetAlbumByName((const char*)album)); // will return -1 if no album
  1119. return OnBrowseDirectChildren(action, strPath.c_str(), filter, starting_index, requested_count, sort_criteria, context);
  1120. } else if (album.GetLength() > 0) {
  1121. // all tracks by album name
  1122. CStdString strPath;
  1123. strPath.Format("musicdb://3/%ld/",
  1124. database.GetAlbumByName((const char*)album));
  1125. return OnBrowseDirectChildren(action, strPath.c_str(), filter, starting_index, requested_count, sort_criteria, context);
  1126. }
  1127. // browse all songs
  1128. return OnBrowseDirectChildren(action, "musicdb://4/", filter, starting_index, requested_count, sort_criteria, context);
  1129. } else if (NPT_String(search_criteria).Find("object.container.album.musicAlbum") >= 0) {
  1130. // sonos filters by genre
  1131. NPT_String genre = FindSubCriteria(search_criteria, "upnp:genre");
  1132. // 360 hack: artist/albums using search
  1133. NPT_String artist = FindSubCriteria(search_criteria, "upnp:artist");
  1134. // sonos looks for microsoft specific stuff
  1135. artist = artist.GetLength()?artist:FindSubCriteria(search_criteria, "microsoft:artistPerformer");
  1136. artist = artist.GetLength()?artist:FindSubCriteria(search_criteria, "microsoft:artistAlbumArtist");
  1137. artist = artist.GetLength()?artist:FindSubCriteria(search_criteria, "microsoft:authorComposer");
  1138. CMusicDatabase database;
  1139. database.Open();
  1140. if (genre.GetLength() > 0) {
  1141. CStdString strPath;
  1142. strPath.Format("musicdb://1/%ld/%ld/",
  1143. database.GetGenreByName((const char*)genre),
  1144. database.GetArtistByName((const char*)artist)); // no artist should return -1
  1145. return OnBrowseDirectChildren(action, strPath.c_str(), filter, starting_index, requested_count, sort_criteria, context);
  1146. } else if (artist.GetLength() > 0) {
  1147. CStdString strPath;
  1148. strPath.Format("musicdb://2/%ld/",
  1149. database.GetArtistByName((const char*)artist));
  1150. return OnBrowseDirectChildren(action, strPath.c_str(), filter, starting_index, requested_count, sort_criteria, context);
  1151. }
  1152. // all albums
  1153. return OnBrowseDirectChildren(action, "musicdb://3/", filter, starting_index, requested_count, sort_criteria, context);
  1154. } else if (NPT_String(search_criteria).Find("object.container.person.musicArtist") >= 0) {
  1155. // Sonos filters by genre
  1156. NPT_String genre = FindSubCriteria(search_criteria, "upnp:genre");
  1157. if (genre.GetLength() > 0) {
  1158. CMusicDatabase database;
  1159. database.Open();
  1160. CStdString strPath;
  1161. strPath.Format("musicdb://1/%ld/", database.GetGenreByName((const char*)genre));
  1162. return OnBrowseDirectChildren(action, strPath.c_str(), filter, starting_index, requested_count, sort_criteria, context);
  1163. }
  1164. return OnBrowseDirectChildren(action, "musicdb://2/", filter, starting_index, requested_count, sort_criteria, context);
  1165. } else if (NPT_String(search_criteria).Find("object.container.genre.musicGenre") >= 0) {
  1166. return OnBrowseDirectChildren(action, "musicdb://1/", filter, starting_index, requested_count, sort_criteria, context);
  1167. } else if (NPT_String(search_criteria).Find("object.container.playlistContainer") >= 0) {
  1168. return OnBrowseDirectChildren(action, "special://musicplaylists/", filter, starting_index, requested_count, sort_criteria, context);
  1169. } else if (NPT_String(search_criteria).Find("object.item.videoItem") >= 0) {
  1170. CFileItemList items, itemsall;
  1171. CVideoDatabase database;
  1172. if (!database.Open()) {
  1173. action->SetError(800, "Internal Error");
  1174. return NPT_SUCCESS;
  1175. }
  1176. if (!database.GetMoviesNav("videodb://1/2/", items)) {
  1177. action->SetError(800, "Internal Error");
  1178. return NPT_SUCCESS;
  1179. }
  1180. itemsall.Append(items);
  1181. items.Clear();
  1182. // TODO - set proper base url for this
  1183. if (!database.GetEpisodesByWhere("videodb://2/0/", "", items, false)) {
  1184. action->SetError(800, "Internal Error");
  1185. return NPT_SUCCESS;
  1186. }
  1187. itemsall.Append(items);
  1188. items.Clear();
  1189. return BuildResponse(action, itemsall, filter, starting_index, requested_count, sort_criteria, context, NULL);
  1190. } else if (NPT_String(search_criteria).Find("object.item.imageItem") >= 0) {
  1191. CFileItemList items;
  1192. return BuildResponse(action, items, filter, starting_index, requested_count, sort_criteria, context, NULL);;
  1193. }
  1194. return NPT_FAILURE;
  1195. }
  1196. /*----------------------------------------------------------------------
  1197. | CUPnPServer::ServeFile
  1198. +---------------------------------------------------------------------*/
  1199. NPT_Result
  1200. CUPnPServer::ServeFile(NPT_HttpRequest& request,
  1201. const NPT_HttpRequestContext& context,
  1202. NPT_HttpResponse& response,
  1203. const NPT_String& md5)
  1204. {
  1205. // Translate hash to filename
  1206. NPT_String file_path(md5), *file_path2;
  1207. { NPT_AutoLock lock(m_FileMutex);
  1208. if(NPT_SUCCEEDED(m_FileMap.Get(md5, file_path2))) {
  1209. file_path = *file_path2;
  1210. CLog::Log(LOGDEBUG, "Received request to serve '%s' = '%s'", (const char*)md5, (const char*)file_path);
  1211. } else {
  1212. CLog::Log(LOGDEBUG, "Received request to serve unknown md5 '%s'", (const char*)md5);
  1213. response.SetStatus(404, "File Not Found");
  1214. return NPT_SUCCESS;
  1215. }
  1216. }
  1217. // File requested
  1218. NPT_String path = m_FileBaseUri.GetPath();
  1219. if (path.Compare(request.GetUrl().GetPath().Left(path.GetLength()), true) == 0 &&
  1220. file_path.Left(8).Compare("stack://", true) == 0) {
  1221. NPT_List<NPT_String> files = file_path.SubString(8).Split(" , ");
  1222. if (files.GetItemCount() == 0) {
  1223. response.SetStatus(404, "File Not Found");
  1224. return NPT_SUCCESS;
  1225. }
  1226. NPT_String output;
  1227. output.Reserve(file_path.GetLength()*2);
  1228. output += "#EXTM3U\r\n";
  1229. NPT_List<NPT_String>::Iterator url = files.GetFirstItem();
  1230. for (;url;url++) {
  1231. output += "#EXTINF:-1," + URIUtils::GetFileName((const char*)*url);
  1232. output += "\r\n";
  1233. output += BuildSafeResourceUri(
  1234. context.GetLocalAddress().GetIpAddress().ToString(),
  1235. *url);
  1236. output += "\r\n";
  1237. }
  1238. PLT_HttpHelper::SetContentType(response, "audio/x-mpegurl");
  1239. PLT_HttpHelper::SetBody(response, (const char*)output, output.GetLength());
  1240. response.GetHeaders().SetHeader("Content-Disposition", "inline; filename=\"stack.m3u\"");
  1241. return NPT_SUCCESS;
  1242. }
  1243. if(URIUtils::IsURL((const char*)file_path))
  1244. {
  1245. CStdString disp = "inline; filename=\"" + URIUtils::GetFileName((const char*)file_path) + "\"";
  1246. response.GetHeaders().SetHeader("Content-Disposition", disp.c_str());
  1247. }
  1248. return PLT_MediaConnect::ServeFile(request,
  1249. context,
  1250. response,
  1251. file_path);
  1252. }
  1253. /*----------------------------------------------------------------------
  1254. | CUPnPRenderer
  1255. +---------------------------------------------------------------------*/
  1256. class CUPnPRenderer : public PLT_MediaRenderer
  1257. {
  1258. public:
  1259. CUPnPRenderer(const char* friendly_name,
  1260. bool show_ip = false,
  1261. const char* uuid = NULL,
  1262. unsigned int port = 0);
  1263. void UpdateState();
  1264. // Http server handler
  1265. virtual NPT_Result ProcessHttpRequest(NPT_HttpRequest& request,
  1266. const NPT_HttpRequestContext& context,
  1267. NPT_HttpResponse& response);
  1268. // AVTransport methods
  1269. virtual NPT_Result OnNext(PLT_ActionReference& action);
  1270. virtual NPT_Result OnPause(PLT_ActionReference& action);
  1271. virtual NPT_Result OnPlay(PLT_ActionReference& action);
  1272. virtual NPT_Result OnPrevious(PLT_ActionReference& action);
  1273. virtual NPT_Result OnStop(PLT_ActionReference& action);
  1274. virtual NPT_Result OnSeek(PLT_ActionReference& action);
  1275. virtual NPT_Result OnSetAVTransportURI(PLT_ActionReference& action);
  1276. // RenderingControl methods
  1277. virtual NPT_Result OnSetVolume(PLT_ActionReference& action);
  1278. virtual NPT_Result OnSetMute(PLT_ActionReference& action);
  1279. private:
  1280. NPT_Result SetupServices(PLT_DeviceData& data);
  1281. NPT_Result GetMetadata(NPT_String& meta);
  1282. NPT_Result PlayMedia(const char* uri,
  1283. const char* metadata = NULL,
  1284. PLT_Action* action = NULL);
  1285. NPT_Mutex m_state;
  1286. };
  1287. /*----------------------------------------------------------------------
  1288. | CUPnPRenderer::CUPnPRenderer
  1289. +---------------------------------------------------------------------*/
  1290. CUPnPRenderer::CUPnPRenderer(const char* friendly_name,
  1291. bool show_ip /* = false */,
  1292. const char* uuid /* = NULL */,
  1293. unsigned int port /* = 0 */) :
  1294. PLT_MediaRenderer(friendly_name,
  1295. show_ip,
  1296. uuid,
  1297. port)
  1298. {
  1299. }
  1300. /*----------------------------------------------------------------------
  1301. | CUPnPRenderer::SetupServices
  1302. +---------------------------------------------------------------------*/
  1303. NPT_Result
  1304. CUPnPRenderer::SetupServices(PLT_DeviceData& data)
  1305. {
  1306. NPT_CHECK(PLT_MediaRenderer::SetupServices(data));
  1307. // update what we can play
  1308. PLT_Service* service = NULL;
  1309. NPT_CHECK_FATAL(FindServiceByType("urn:schemas-upnp-org:service:ConnectionManager:1", service));
  1310. service->SetStateVariable("SinkProtocolInfo"
  1311. ,"http-get:*:*:*"
  1312. ",xbmc-get:*:*:*"
  1313. ",http-get:*:audio/mpegurl:*"
  1314. ",http-get:*:audio/mpeg:*"
  1315. ",http-get:*:audio/mpeg3:*"
  1316. ",http-get:*:audio/mp3:*"
  1317. ",http-get:*:audio/mp4:*"
  1318. ",http-get:*:audio/basic:*"
  1319. ",http-get:*:audio/midi:*"
  1320. ",http-get:*:audio/ulaw:*"
  1321. ",http-get:*:audio/ogg:*"
  1322. ",http-get:*:audio/DVI4:*"
  1323. ",http-get:*:audio/G722:*"
  1324. ",http-get:*:audio/G723:*"
  1325. ",http-get:*:audio/G726-16:*"
  1326. ",http-get:*:audio/G726-24:*"
  1327. ",http-get:*:audio/G726-32:*"
  1328. ",http-get:*:audio/G726-40:*"
  1329. ",http-get:*:audio/G728:*"
  1330. ",http-get:*:audio/G729:*"
  1331. ",http-get:*:audio/G729D:*"
  1332. ",http-get:*:audio/G729E:*"
  1333. ",http-get:*:audio/GSM:*"
  1334. ",http-get:*:audio/GSM-EFR:*"
  1335. ",http-get:*:audio/L8:*"
  1336. ",http-get:*:audio/L16:*"
  1337. ",http-get:*:audio/LPC:*"
  1338. ",http-get:*:audio/MPA:*"
  1339. ",http-get:*:audio/PCMA:*"
  1340. ",http-get:*:audio/PCMU:*"
  1341. ",http-get:*:audio/QCELP:*"
  1342. ",http-get:*:audio/RED:*"
  1343. ",http-get:*:audio/VDVI:*"
  1344. ",http-get:*:audio/ac3:*"
  1345. ",http-get:*:audio/vorbis:*"
  1346. ",http-get:*:audio/speex:*"
  1347. ",http-get:*:audio/x-aiff:*"
  1348. ",http-get:*:audio/x-pn-realaudio:*"
  1349. ",http-get:*:audio/x-realaudio:*"
  1350. ",http-get:*:audio/x-wav:*"
  1351. ",http-get:*:audio/x-ms-wma:*"
  1352. ",http-get:*:audio/x-mpegurl:*"
  1353. ",http-get:*:application/x-shockwave-flash:*"
  1354. ",http-get:*:application/ogg:*"
  1355. ",http-get:*:application/sdp:*"
  1356. ",http-get:*:image/gif:*"
  1357. ",http-get:*:image/jpeg:*"
  1358. ",http-get:*:image/ief:*"
  1359. ",http-get:*:image/png:*"
  1360. ",http-get:*:image/tiff:*"
  1361. ",http-get:*:video/avi:*"
  1362. ",http-get:*:video/mpeg:*"
  1363. ",http-get:*:video/fli:*"
  1364. ",http-get:*:video/flv:*"
  1365. ",http-get:*:video/quicktime:*"
  1366. ",http-get:*:video/vnd.vivo:*"
  1367. ",http-get:*:video/vc1:*"
  1368. ",http-get:*:video/ogg:*"
  1369. ",http-get:*:video/mp4:*"
  1370. ",http-get:*:video/BT656:*"
  1371. ",http-get:*:video/CelB:*"
  1372. ",http-get:*:video/JPEG:*"
  1373. ",http-get:*:video/H261:*"
  1374. ",http-get:*:video/H263:*"
  1375. ",http-get:*:video/H263-1998:*"
  1376. ",http-get:*:video/H263-2000:*"
  1377. ",http-get:*:video/MPV:*"
  1378. ",http-get:*:video/MP2T:*"
  1379. ",http-get:*:video/MP1S:*"
  1380. ",http-get:*:video/MP2P:*"
  1381. ",http-get:*:video/BMPEG:*"
  1382. ",http-get:*:video/x-ms-wmv:*"
  1383. ",http-get:*:video/x-ms-avi:*"
  1384. ",http-get:*:video/x-flv:*"
  1385. ",http-get:*:video/x-fli:*"
  1386. ",http-get:*:video/x-ms-asf:*"
  1387. ",http-get:*:video/x-ms-asx:*"
  1388. ",http-get:*:video/x-ms-wmx:*"
  1389. ",http-get:*:video/x-ms-wvx:*"
  1390. ",http-get:*:video/x-msvideo:*"
  1391. );
  1392. return NPT_SUCCESS;
  1393. }
  1394. /*----------------------------------------------------------------------
  1395. | CUPnPRenderer::ProcessHttpRequest
  1396. +---------------------------------------------------------------------*/
  1397. NPT_Result
  1398. CUPnPRenderer::ProcessHttpRequest(NPT_HttpRequest& request,
  1399. const NPT_HttpRequestContext& context,
  1400. NPT_HttpResponse& response)
  1401. {
  1402. // get the address of who sent us some data back
  1403. NPT_String ip_address = context.GetRemoteAddress().GetIpAddress().ToString();
  1404. NPT_String method = request.GetMethod();
  1405. NPT_String protocol = request.GetProtocol();
  1406. NPT_HttpUrl url = request.GetUrl();
  1407. if (url.GetPath() == "/thumb.jpg") {
  1408. NPT_HttpUrlQuery query(url.GetQuery());
  1409. NPT_String filepath = query.GetField("path");
  1410. if (!filepath.IsEmpty()) {
  1411. NPT_HttpEntity* entity = response.GetEntity();
  1412. if (entity == NULL) return NPT_ERROR_INVALID_STATE;
  1413. // check the method
  1414. if (request.GetMethod() != NPT_HTTP_METHOD_GET &&
  1415. request.GetMethod() != NPT_HTTP_METHOD_HEAD) {
  1416. response.SetStatus(405, "Method Not Allowed");
  1417. return NPT_SUCCESS;
  1418. }
  1419. // ensure that the request's path is a valid thumb path
  1420. if (URIUtils::IsRemote(filepath.GetChars()) ||
  1421. !filepath.StartsWith(g_settings.GetUserDataFolder())) {
  1422. response.SetStatus(404, "Not Found");
  1423. return NPT_SUCCESS;
  1424. }
  1425. // prevent hackers from accessing files outside of our root
  1426. if ((filepath.Find("/..") >= 0) || (filepath.Find("\\..") >=0)) {
  1427. return NPT_FAILURE;
  1428. }
  1429. // open the file
  1430. NPT_File file(filepath);
  1431. NPT_Result result = file.Open(NPT_FILE_OPEN_MODE_READ);
  1432. if (NPT_FAILED(result)) {
  1433. response.SetStatus(404, "Not Found");
  1434. return NPT_SUCCESS;
  1435. }
  1436. NPT_InputStreamReference stream;
  1437. file.GetInputStream(stream);
  1438. entity->SetContentType(CUPnPServer::GetMimeType(filepath));
  1439. entity->SetInputStream(stream, true);
  1440. return NPT_SUCCESS;
  1441. }
  1442. }
  1443. return PLT_MediaRenderer::ProcessHttpRequest(request, context, response);
  1444. }
  1445. /*----------------------------------------------------------------------
  1446. | CUPnPRenderer::UpdateState
  1447. +---------------------------------------------------------------------*/
  1448. void
  1449. CUPnPRenderer::UpdateState()
  1450. {
  1451. NPT_AutoLock lock(m_state);
  1452. PLT_Service *avt, *rct;
  1453. if (NPT_FAILED(FindServiceByType("urn:schemas-upnp-org:service:AVTransport:1", avt)))
  1454. return;
  1455. if (NPT_FAILED(FindServiceByType("urn:schemas-upnp-org:service:RenderingControl:1", rct)))
  1456. return;
  1457. /* don't update state while transitioning */
  1458. NPT_String state;
  1459. avt->GetStateVariableValue("TransportState", state);
  1460. if(state == "TRANSITIONING")
  1461. return;
  1462. CStdString buffer;
  1463. int volume;
  1464. if (g_settings.m_bMute) {
  1465. rct->SetStateVariable("Mute", "1");
  1466. } else {
  1467. rct->SetStateVariable("Mute", "0");
  1468. }
  1469. volume = g_application.GetVolume();
  1470. buffer.Format("%d", volume);
  1471. rct->SetStateVariable("Volume", buffer.c_str());
  1472. buffer.Format("%d", 256 * (volume * 60 - 60) / 100);
  1473. rct->SetStateVariable("VolumeDb", buffer.c_str());
  1474. if (g_application.IsPlaying() || g_application.IsPaused()) {
  1475. if (g_application.IsPaused()) {
  1476. avt->SetStateVariable("TransportState", "PAUSED_PLAYBACK");
  1477. } else {
  1478. avt->SetStateVariable("TransportState", "PLAYING");
  1479. }
  1480. avt->SetStateVariable("TransportStatus", "OK");
  1481. avt->SetStateVariable("TransportPlaySpeed", (const char*)NPT_String::FromInteger(g_application.GetPlaySpeed()));
  1482. avt->SetStateVariable("NumberOfTracks", "1");
  1483. avt->SetStateVariable("CurrentTrack", "1");
  1484. buffer = g_infoManager.GetCurrentPlayTime(TIME_FORMAT_HH_MM_SS);
  1485. avt->SetStateVariable("RelativeTimePosition", buffer.c_str());
  1486. avt->SetStateVariable("AbsoluteTimePosition", buffer.c_str());
  1487. buffer = g_infoManager.GetDuration(TIME_FORMAT_HH_MM_SS);
  1488. if (buffer.length() > 0) {
  1489. avt->SetStateVariable("CurrentTrackDuration", buffer.c_str());
  1490. avt->SetStateVariable("CurrentMediaDuration", buffer.c_str());
  1491. } else {
  1492. avt->SetStateVariable("CurrentTrackDuration", "00:00:00");
  1493. avt->SetStateVariable("CurrentMediaDuration", "00:00:00");
  1494. }
  1495. avt->SetStateVariable("AVTransportURI", g_application.CurrentFile().c_str());
  1496. avt->SetStateVariable("CurrentTrackURI", g_application.CurrentFile().c_str());
  1497. NPT_String metadata;
  1498. avt->GetStateVariableValue("AVTransportURIMetaData", metadata);
  1499. // try to recreate the didl dynamically if not set
  1500. if (metadata.IsEmpty()) {
  1501. GetMetadata(metadata);
  1502. }
  1503. avt->SetStateVariable("CurrentTrackMetadata", metadata);
  1504. avt->SetStateVariable("AVTransportURIMetaData", metadata);
  1505. } else if (g_windowManager.GetActiveWindow() == WINDOW_SLIDESHOW) {
  1506. avt->SetStateVariable("TransportState", "PLAYING");
  1507. avt->SetStateVariable("AVTransportURI" , g_infoManager.GetPictureLabel(SLIDE_FILE_PATH));
  1508. avt->SetStateVariable("CurrentTrackURI", g_infoManager.GetPictureLabel(SLIDE_FILE_PATH));
  1509. avt->SetStateVariable("TransportPlaySpeed", "1");
  1510. CGUIWindowSlideShow *slideshow = (CGUIWindowSlideShow *)g_windowManager.GetWindow(WINDOW_SLIDESHOW);
  1511. if (slideshow)
  1512. {
  1513. CStdString index;
  1514. index.Format("%d", slideshow->NumSlides());
  1515. avt->SetStateVariable("NumberOfTracks", index.c_str());
  1516. index.Format("%d", slideshow->CurrentSlide());
  1517. avt->SetStateVariable("CurrentTrack", index.c_str());
  1518. }
  1519. avt->SetStateVariable("CurrentTrackMetadata", "");
  1520. avt->SetStateVariable("AVTransportURIMetaData", "");
  1521. } else {
  1522. avt->SetStateVariable("TransportState", "STOPPED");
  1523. avt->SetStateVariable("TransportPlaySpeed", "1");
  1524. avt->SetStateVariable("NumberOfTracks", "0");
  1525. avt->SetStateVariable("CurrentTrack", "0");
  1526. avt->SetStateVariable("RelativeTimePosition", "00:00:00");
  1527. avt->SetStateVariable("AbsoluteTimePosition", "00:00:00");
  1528. avt->SetStateVariable("CurrentTrackDuration", "00:00:00");
  1529. avt->SetStateVariable("CurrentMediaDuration", "00:00:00");
  1530. }
  1531. }
  1532. /*----------------------------------------------------------------------
  1533. | CUPnPRenderer::GetMetadata
  1534. +---------------------------------------------------------------------*/
  1535. NPT_Result
  1536. CUPnPRenderer::GetMetadata(NPT_String& meta)
  1537. {
  1538. NPT_Result res = NPT_FAILURE;
  1539. const CFileItem &item = g_application.CurrentFileItem();
  1540. NPT_String file_path;
  1541. PLT_MediaObject* object = CUPnPServer::BuildObject(item, file_path, false);
  1542. if (object) {
  1543. // fetch the path to the thumbnail
  1544. CStdString thumb = g_infoManager.GetImage(MUSICPLAYER_COVER, -1); //TODO: Only audio for now
  1545. NPT_String ip;
  1546. if (g_application.getNetwork().GetFirstConnectedInterface()) {
  1547. ip = g_application.getNetwork().GetFirstConnectedInterface()->GetCurrentIPAddress().c_str();
  1548. }
  1549. // build url, use the internal device http server to serv the image
  1550. NPT_HttpUrlQuery query;
  1551. query.AddField("path", thumb.c_str());
  1552. object->m_ExtraInfo.album_art_uri = NPT_HttpUrl(
  1553. ip,
  1554. m_URLDescription.GetPort(),
  1555. "/thumb.jpg",
  1556. query.ToString()).ToString();
  1557. res = PLT_Didl::ToDidl(*object, "*", meta);
  1558. delete object;
  1559. }
  1560. return res;
  1561. }
  1562. /*----------------------------------------------------------------------
  1563. | CUPnPRenderer::OnNext
  1564. +---------------------------------------------------------------------*/
  1565. NPT_Result
  1566. CUPnPRenderer::OnNext(PLT_ActionReference& action)
  1567. {
  1568. if (g_windowManager.GetActiveWindow() == WINDOW_SLIDESHOW) {
  1569. CAction action(ACTION_NEXT_PICTURE);
  1570. CApplicationMessenger::Get().SendAction(action, WINDOW_SLIDESHOW);
  1571. } else {
  1572. CApplicationMessenger::Get().PlayListPlayerNext();
  1573. }
  1574. return NPT_SUCCESS;
  1575. }
  1576. /*----------------------------------------------------------------------
  1577. | CUPnPRenderer::OnPause
  1578. +---------------------------------------------------------------------*/
  1579. NPT_Result
  1580. CUPnPRenderer::OnPause(PLT_ActionReference& action)
  1581. {
  1582. if (g_windowManager.GetActiveWindow() == WINDOW_SLIDESHOW) {
  1583. CAction action(ACTION_PAUSE);
  1584. CApplicationMessenger::Get().SendAction(action, WINDOW_SLIDESHOW);
  1585. } else if (!g_application.IsPaused())
  1586. CApplicationMessenger::Get().MediaPause();
  1587. return NPT_SUCCESS;
  1588. }
  1589. /*----------------------------------------------------------------------
  1590. | CUPnPRenderer::OnPlay
  1591. +---------------------------------------------------------------------*/
  1592. NPT_Result
  1593. CUPnPRenderer::OnPlay(PLT_ActionReference& action)
  1594. {
  1595. if (g_windowManager.GetActiveWindow() == WINDOW_SLIDESHOW) {
  1596. return NPT_SUCCESS;
  1597. } else if (g_application.IsPaused()) {
  1598. CApplicationMessenger::Get().MediaPause();
  1599. } else if (!g_application.IsPlaying()) {
  1600. NPT_String uri, meta;
  1601. PLT_Service* service;
  1602. // look for value set previously by SetAVTransportURI
  1603. NPT_CHECK_SEVERE(FindServiceByType("urn:schemas-upnp-org:service:AVTransport:1", service));
  1604. NPT_CHECK_SEVERE(service->GetStateVariableValue("AVTransportURI", uri));
  1605. NPT_CHECK_SEVERE(service->GetStateVariableValue("AVTransportURIMetaData", meta));
  1606. // if not set, use the current file being played
  1607. PlayMedia(uri, meta);
  1608. }
  1609. return NPT_SUCCESS;
  1610. }
  1611. /*----------------------------------------------------------------------
  1612. | CUPnPRenderer::OnPrevious
  1613. +---------------------------------------------------------------------*/
  1614. NPT_Result
  1615. CUPnPRenderer::OnPrevious(PLT_ActionReference& action)
  1616. {
  1617. if (g_windowManager.GetActiveWindow() == WINDOW_SLIDESHOW) {
  1618. CAction action(ACTION_PREV_PICTURE);
  1619. CApplicationMessenger::Get().SendAction(action, WINDOW_SLIDESHOW);
  1620. } else {
  1621. CApplicationMessenger::Get().PlayListPlayerPrevious();
  1622. }
  1623. return NPT_SUCCESS;
  1624. }
  1625. /*----------------------------------------------------------------------
  1626. | CUPnPRenderer::OnStop
  1627. +---------------------------------------------------------------------*/
  1628. NPT_Result
  1629. CUPnPRenderer::OnStop(PLT_ActionReference& action)
  1630. {
  1631. if (g_windowManager.GetActiveWindow() == WINDOW_SLIDESHOW) {
  1632. CAction action(ACTION_STOP);
  1633. CApplicationMessenger::Get().SendAction(action, WINDOW_SLIDESHOW);
  1634. } else {
  1635. CApplicationMessenger::Get().MediaStop();
  1636. }
  1637. return NPT_SUCCESS;
  1638. }
  1639. /*----------------------------------------------------------------------
  1640. | CUPnPRenderer::OnSetAVTransportURI
  1641. +---------------------------------------------------------------------*/
  1642. NPT_Result
  1643. CUPnPRenderer::OnSetAVTransportURI(PLT_ActionReference& action)
  1644. {
  1645. NPT_String uri, meta;
  1646. PLT_Service* service;
  1647. NPT_CHECK_SEVERE(FindServiceByType("urn:schemas-upnp-org:service:AVTransport:1", service));
  1648. NPT_CHECK_SEVERE(action->GetArgumentValue("CurrentURI", uri));
  1649. NPT_CHECK_SEVERE(action->GetArgumentValue("CurrentURIMetaData", meta));
  1650. // if not playing already, just keep around uri & metadata
  1651. // and wait for play command
  1652. if (!g_application.IsPlaying() && g_windowManager.GetActiveWindow() != WINDOW_SLIDESHOW) {
  1653. service->SetStateVariable("TransportState", "STOPPED");
  1654. service->SetStateVariable("TransportStatus", "OK");
  1655. service->SetStateVariable("TransportPlaySpeed", "1");
  1656. service->SetStateVariable("AVTransportURI", uri);
  1657. service->SetStateVariable("AVTransportURIMetaData", meta);
  1658. NPT_CHECK_SEVERE(action->SetArgumentsOutFromStateVariable());
  1659. return NPT_SUCCESS;
  1660. }
  1661. return PlayMedia(uri, meta, action.AsPointer());
  1662. }
  1663. /*----------------------------------------------------------------------
  1664. | CUPnPRenderer::PlayMedia
  1665. +---------------------------------------------------------------------*/
  1666. NPT_Result
  1667. CUPnPRenderer::PlayMedia(const char* uri, const char* meta, PLT_Action* action)
  1668. {
  1669. bool bImageFile = false;
  1670. PLT_Service* service;
  1671. NPT_CHECK_SEVERE(FindServiceByType("urn:schemas-upnp-org:service:AVTransport:1", service));
  1672. { NPT_AutoLock lock(m_state);
  1673. service->SetStateVariable("TransportState", "TRANSITIONING");
  1674. service->SetStateVariable("TransportStatus", "OK");
  1675. }
  1676. PLT_MediaObjectListReference list;
  1677. PLT_MediaObject* object = NULL;
  1678. if (meta && NPT_SUCCEEDED(PLT_Didl::FromDidl(meta, list))) {
  1679. list->Get(0, object);
  1680. }
  1681. if (object) {
  1682. CFileItem item(uri, false);
  1683. PLT_MediaItemResource* res = object->m_Resources.GetFirstItem();
  1684. for(NPT_Cardinal i = 0; i < object->m_Resources.GetItemCount(); i++) {
  1685. if(object->m_Resources[i].m_Uri == uri) {
  1686. res = &object->m_Resources[i];
  1687. break;
  1688. }
  1689. }
  1690. for(NPT_Cardinal i = 0; i < object->m_Resources.GetItemCount(); i++) {
  1691. if(object->m_Resources[i].m_ProtocolInfo.ToString().StartsWith("xbmc-get:")) {
  1692. res = &object->m_Resources[i];
  1693. item.SetPath(CStdString(res->m_Uri));
  1694. break;
  1695. }
  1696. }
  1697. if (res && res->m_ProtocolInfo.IsValid()) {
  1698. item.SetMimeType((const char*)res->m_ProtocolInfo.GetContentType());
  1699. }
  1700. item.m_dateTime.SetFromDateString((const char*)object->m_Date);
  1701. item.m_strTitle = (const char*)object->m_Title;
  1702. item.SetLabel((const char*)object->m_Title);
  1703. item.SetLabelPreformated(true);
  1704. item.SetThumbnailImage((const char*)object->m_ExtraInfo.album_art_uri);
  1705. if (object->m_ObjectClass.type.StartsWith("object.item.audioItem")) {
  1706. if(NPT_SUCCEEDED(CUPnP::PopulateTagFromObject(*item.GetMusicInfoTag(), *object, res)))
  1707. item.SetLabelPreformated(false);
  1708. } else if (object->m_ObjectClass.type.StartsWith("object.item.videoItem")) {
  1709. if(NPT_SUCCEEDED(CUPnP::PopulateTagFromObject(*item.GetVideoInfoTag(), *object, res)))
  1710. item.SetLabelPreformated(false);
  1711. } else if (object->m_ObjectClass.type.StartsWith("object.item.imageItem")) {
  1712. bImageFile = true;
  1713. }
  1714. bImageFile?CApplicationMessenger::Get().PictureShow(item.GetPath())
  1715. :CApplicationMessenger::Get().MediaPlay(item);
  1716. } else {
  1717. bImageFile = NPT_String(PLT_MediaObject::GetUPnPClass(uri)).StartsWith("object.item.imageItem", true);
  1718. bImageFile?CApplicationMessenger::Get().PictureShow((const char*)uri)
  1719. :CApplicationMessenger::Get().MediaPlay((const char*)uri);
  1720. }
  1721. if (g_application.IsPlaying() || g_windowManager.GetActiveWindow() == WINDOW_SLIDESHOW) {
  1722. NPT_AutoLock lock(m_state);
  1723. service->SetStateVariable("TransportState", "PLAYING");
  1724. service->SetStateVariable("TransportStatus", "OK");
  1725. service->SetStateVariable("AVTransportURI", uri);
  1726. service->SetStateVariable("AVTransportURIMetaData", meta);
  1727. } else {
  1728. NPT_AutoLock lock(m_state);
  1729. service->SetStateVariable("TransportState", "STOPPED");
  1730. service->SetStateVariable("TransportStatus", "ERROR_OCCURRED");
  1731. }
  1732. if (action) {
  1733. NPT_CHECK_SEVERE(action->SetArgumentsOutFromStateVariable());
  1734. }
  1735. return NPT_SUCCESS;
  1736. }
  1737. /*----------------------------------------------------------------------
  1738. | CUPnPRenderer::OnSetVolume
  1739. +---------------------------------------------------------------------*/
  1740. NPT_Result
  1741. CUPnPRenderer::OnSetVolume(PLT_ActionReference& action)
  1742. {
  1743. NPT_String volume;
  1744. NPT_CHECK_SEVERE(action->GetArgumentValue("DesiredVolume", volume));
  1745. g_application.SetVolume((float)strtod((const char*)volume, NULL));
  1746. return NPT_SUCCESS;
  1747. }
  1748. /*----------------------------------------------------------------------
  1749. | CUPnPRenderer::OnSetMute
  1750. +---------------------------------------------------------------------*/
  1751. NPT_Result
  1752. CUPnPRenderer::OnSetMute(PLT_ActionReference& action)
  1753. {
  1754. NPT_String mute;
  1755. NPT_CHECK_SEVERE(action->GetArgumentValue("DesiredMute",mute));
  1756. if((mute == "1") ^ g_settings.m_bMute)
  1757. g_application.ToggleMute();
  1758. return NPT_SUCCESS;
  1759. }
  1760. /*----------------------------------------------------------------------
  1761. | CUPnPRenderer::OnSeek
  1762. +---------------------------------------------------------------------*/
  1763. NPT_Result
  1764. CUPnPRenderer::OnSeek(PLT_ActionReference& action)
  1765. {
  1766. if (!g_application.IsPlaying()) return NPT_ERROR_INVALID_STATE;
  1767. NPT_String unit, target;
  1768. NPT_CHECK_SEVERE(action->GetArgumentValue("Unit", unit));
  1769. NPT_CHECK_SEVERE(action->GetArgumentValue("Target", target));
  1770. if (!unit.Compare("REL_TIME")) {
  1771. // converts target to seconds
  1772. NPT_UInt32 seconds;
  1773. NPT_CHECK_SEVERE(PLT_Didl::ParseTimeStamp(target, seconds));
  1774. g_application.SeekTime(seconds);
  1775. }
  1776. return NPT_SUCCESS;
  1777. }
  1778. /*----------------------------------------------------------------------
  1779. | CRendererReferenceHolder class
  1780. +---------------------------------------------------------------------*/
  1781. class CRendererReferenceHolder
  1782. {
  1783. public:
  1784. PLT_DeviceHostReference m_Device;
  1785. };
  1786. /*----------------------------------------------------------------------
  1787. | CMediaBrowser class
  1788. +---------------------------------------------------------------------*/
  1789. class CMediaBrowser : public PLT_SyncMediaBrowser,
  1790. public PLT_MediaContainerChangesListener
  1791. {
  1792. public:
  1793. CMediaBrowser(PLT_CtrlPointReference& ctrlPoint)
  1794. : PLT_SyncMediaBrowser(ctrlPoint, true)
  1795. {
  1796. SetContainerListener(this);
  1797. }
  1798. // PLT_MediaBrowser methods
  1799. virtual bool OnMSAdded(PLT_DeviceDataReference& device)
  1800. {
  1801. CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH);
  1802. message.SetStringParam("upnp://");
  1803. g_windowManager.SendThreadMessage(message);
  1804. return PLT_SyncMediaBrowser::OnMSAdded(device);
  1805. }
  1806. virtual void OnMSRemoved(PLT_DeviceDataReference& device)
  1807. {
  1808. PLT_SyncMediaBrowser::OnMSRemoved(device);
  1809. CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH);
  1810. message.SetStringParam("upnp://");
  1811. g_windowManager.SendThreadMessage(message);
  1812. PLT_SyncMediaBrowser::OnMSRemoved(device);
  1813. }
  1814. // PLT_MediaContainerChangesListener methods
  1815. virtual void OnContainerChanged(PLT_DeviceDataReference& device,
  1816. const char* item_id,
  1817. const char* update_id)
  1818. {
  1819. NPT_String path = "upnp://"+device->GetUUID()+"/";
  1820. if (!NPT_StringsEqual(item_id, "0")) {
  1821. CStdString id = item_id;
  1822. CURL::Encode(id);
  1823. path += id.c_str();
  1824. path += "/";
  1825. }
  1826. CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH);
  1827. message.SetStringParam(path.GetChars());
  1828. g_windowManager.SendThreadMessage(message);
  1829. }
  1830. };
  1831. /*----------------------------------------------------------------------
  1832. | CUPnP::CUPnP
  1833. +---------------------------------------------------------------------*/
  1834. CUPnP::CUPnP() :
  1835. m_MediaBrowser(NULL),
  1836. m_ServerHolder(new CDeviceHostReferenceHolder()),
  1837. m_RendererHolder(new CRendererReferenceHolder()),
  1838. m_CtrlPointHolder(new CCtrlPointReferenceHolder())
  1839. {
  1840. broadcast = false;
  1841. // initialize upnp in broadcast listening mode for xbmc
  1842. m_UPnP = new PLT_UPnP(1900, !broadcast);
  1843. // keep main IP around
  1844. if (g_application.getNetwork().GetFirstConnectedInterface()) {
  1845. m_IP = g_application.getNetwork().GetFirstConnectedInterface()->GetCurrentIPAddress().c_str();
  1846. }
  1847. NPT_List<NPT_IpAddress> list;
  1848. if (NPT_SUCCEEDED(PLT_UPnPMessageHelper::GetIPAddresses(list))) {
  1849. m_IP = (*(list.GetFirstItem())).ToString();
  1850. }
  1851. // start upnp monitoring
  1852. m_UPnP->Start();
  1853. }
  1854. /*----------------------------------------------------------------------
  1855. | CUPnP::~CUPnP
  1856. +---------------------------------------------------------------------*/
  1857. CUPnP::~CUPnP()
  1858. {
  1859. m_UPnP->Stop();
  1860. StopClient();
  1861. StopServer();
  1862. delete m_UPnP;
  1863. delete m_ServerHolder;
  1864. delete m_RendererHolder;
  1865. delete m_CtrlPointHolder;
  1866. }
  1867. /*----------------------------------------------------------------------
  1868. | CUPnP::GetInstance
  1869. +---------------------------------------------------------------------*/
  1870. CUPnP*
  1871. CUPnP::GetInstance()
  1872. {
  1873. if (!upnp) {
  1874. upnp = new CUPnP();
  1875. }
  1876. return upnp;
  1877. }
  1878. /*----------------------------------------------------------------------
  1879. | CUPnP::ReleaseInstance
  1880. +---------------------------------------------------------------------*/
  1881. void
  1882. CUPnP::ReleaseInstance(bool bWait)
  1883. {
  1884. if (upnp) {
  1885. CUPnP* _upnp = upnp;
  1886. upnp = NULL;
  1887. if (bWait) {
  1888. delete _upnp;
  1889. } else {
  1890. // since it takes a while to clean up
  1891. // starts a detached thread to do this
  1892. CUPnPCleaner* cleaner = new CUPnPCleaner(_upnp);
  1893. cleaner->Start();
  1894. }
  1895. }
  1896. }
  1897. /*----------------------------------------------------------------------
  1898. | CUPnP::StartClient
  1899. +---------------------------------------------------------------------*/
  1900. void
  1901. CUPnP::StartClient()
  1902. {
  1903. if (!m_CtrlPointHolder->m_CtrlPoint.IsNull()) return;
  1904. // create controlpoint, pass NULL to avoid sending a multicast search
  1905. m_CtrlPointHolder->m_CtrlPoint = new PLT_CtrlPoint(broadcast?NULL:"upnp:rootdevice");
  1906. // ignore our own server
  1907. if (!m_ServerHolder->m_Device.IsNull()) {
  1908. m_CtrlPointHolder->m_CtrlPoint->IgnoreUUID(m_ServerHolder->m_Device->GetUUID());
  1909. }
  1910. // start it
  1911. m_UPnP->AddCtrlPoint(m_CtrlPointHolder->m_CtrlPoint);
  1912. // start browser
  1913. m_MediaBrowser = new CMediaBrowser(m_CtrlPointHolder->m_CtrlPoint);
  1914. }
  1915. /*----------------------------------------------------------------------
  1916. | CUPnP::StopClient
  1917. +---------------------------------------------------------------------*/
  1918. void
  1919. CUPnP::StopClient()
  1920. {
  1921. if (m_CtrlPointHolder->m_CtrlPoint.IsNull()) return;
  1922. m_UPnP->RemoveCtrlPoint(m_CtrlPointHolder->m_CtrlPoint);
  1923. m_CtrlPointHolder->m_CtrlPoint = NULL;
  1924. delete m_MediaBrowser;
  1925. m_MediaBrowser = NULL;
  1926. }
  1927. /*----------------------------------------------------------------------
  1928. | CUPnP::CreateServer
  1929. +---------------------------------------------------------------------*/
  1930. CUPnPServer*
  1931. CUPnP::CreateServer(int port /* = 0 */)
  1932. {
  1933. CUPnPServer* device =
  1934. new CUPnPServer(g_infoManager.GetLabel(SYSTEM_FRIENDLY_NAME),
  1935. g_settings.m_UPnPUUIDServer.length()?g_settings.m_UPnPUUIDServer.c_str():NULL,
  1936. port);
  1937. // trying to set optional upnp values for XP UPnP UI Icons to detect us
  1938. // but it doesn't work anyways as it requires multicast for XP to detect us
  1939. device->m_PresentationURL =
  1940. NPT_HttpUrl(m_IP,
  1941. atoi(g_guiSettings.GetString("services.webserverport")),
  1942. "/").ToString();
  1943. device->m_ModelName = "XBMC Media Center";
  1944. device->m_ModelNumber = "1.0";
  1945. device->m_ModelDescription = "XBMC Media Center - Media Server";
  1946. device->m_ModelURL = "http://www.xbmc.org/";
  1947. device->m_Manufacturer = "Team XBMC";
  1948. device->m_ManufacturerURL = "http://www.xbmc.org/";
  1949. return device;
  1950. }
  1951. /*----------------------------------------------------------------------
  1952. | CUPnP::StartServer
  1953. +---------------------------------------------------------------------*/
  1954. void
  1955. CUPnP::StartServer()
  1956. {
  1957. if (!m_ServerHolder->m_Device.IsNull()) return;
  1958. // load upnpserver.xml so that g_settings.m_vecUPnPMusiCMediaSources, etc.. are loaded
  1959. CStdString filename;
  1960. URIUtils::AddFileToFolder(g_settings.GetUserDataFolder(), "upnpserver.xml", filename);
  1961. g_settings.LoadUPnPXml(filename);
  1962. // create the server with a XBox compatible friendlyname and UUID from upnpserver.xml if found
  1963. m_ServerHolder->m_Device = CreateServer(g_settings.m_UPnPPortServer);
  1964. // tell controller to ignore ourselves from list of upnp servers
  1965. if (!m_CtrlPointHolder->m_CtrlPoint.IsNull()) {
  1966. m_CtrlPointHolder->m_CtrlPoint->IgnoreUUID(m_ServerHolder->m_Device->GetUUID());
  1967. }
  1968. // start server
  1969. NPT_Result res = m_UPnP->AddDevice(m_ServerHolder->m_Device);
  1970. if (NPT_FAILED(res)) {
  1971. // if the upnp device port was not 0, it could have failed because
  1972. // of port being in used, so restart with a random port
  1973. if (g_settings.m_UPnPPortServer > 0) m_ServerHolder->m_Device = CreateServer(0);
  1974. // tell controller to ignore ourselves from list of upnp servers
  1975. if (!m_CtrlPointHolder->m_CtrlPoint.IsNull()) {
  1976. m_CtrlPointHolder->m_CtrlPoint->IgnoreUUID(m_ServerHolder->m_Device->GetUUID());
  1977. }
  1978. res = m_UPnP->AddDevice(m_ServerHolder->m_Device);
  1979. }
  1980. // save port but don't overwrite saved settings if port was random
  1981. if (NPT_SUCCEEDED(res)) {
  1982. if (g_settings.m_UPnPPortServer == 0) {
  1983. g_settings.m_UPnPPortServer = m_ServerHolder->m_Device->GetPort();
  1984. }
  1985. CUPnPServer::m_MaxReturnedItems = UPNP_DEFAULT_MAX_RETURNED_ITEMS;
  1986. if (g_settings.m_UPnPMaxReturnedItems > 0) {
  1987. // must be > UPNP_DEFAULT_MIN_RETURNED_ITEMS
  1988. CUPnPServer::m_MaxReturnedItems = max(UPNP_DEFAULT_MIN_RETURNED_ITEMS, g_settings.m_UPnPMaxReturnedItems);
  1989. }
  1990. g_settings.m_UPnPMaxReturnedItems = CUPnPServer::m_MaxReturnedItems;
  1991. }
  1992. // save UUID
  1993. g_settings.m_UPnPUUIDServer = m_ServerHolder->m_Device->GetUUID();
  1994. g_settings.SaveUPnPXml(filename);
  1995. }
  1996. /*----------------------------------------------------------------------
  1997. | CUPnP::StopServer
  1998. +---------------------------------------------------------------------*/
  1999. void
  2000. CUPnP::StopServer()
  2001. {
  2002. if (m_ServerHolder->m_Device.IsNull()) return;
  2003. m_UPnP->RemoveDevice(m_ServerHolder->m_Device);
  2004. m_ServerHolder->m_Device = NULL;
  2005. }
  2006. /*----------------------------------------------------------------------
  2007. | CUPnP::CreateRenderer
  2008. +---------------------------------------------------------------------*/
  2009. CUPnPRenderer*
  2010. CUPnP::CreateRenderer(int port /* = 0 */)
  2011. {
  2012. CUPnPRenderer* device =
  2013. new CUPnPRenderer(g_infoManager.GetLabel(SYSTEM_FRIENDLY_NAME).c_str(),
  2014. false,
  2015. (g_settings.m_UPnPUUIDRenderer.length() ? g_settings.m_UPnPUUIDRenderer.c_str() : NULL),
  2016. port);
  2017. device->m_PresentationURL =
  2018. NPT_HttpUrl(m_IP,
  2019. atoi(g_guiSettings.GetString("services.webserverport")),
  2020. "/").ToString();
  2021. device->m_ModelName = "XBMC";
  2022. device->m_ModelNumber = "2.0";
  2023. device->m_ModelDescription = "XBMC Media Center - Media Renderer";
  2024. device->m_ModelURL = "http://www.xbmc.org/";
  2025. device->m_Manufacturer = "Team XBMC";
  2026. device->m_ManufacturerURL = "http://www.xbmc.org/";
  2027. return device;
  2028. }
  2029. /*----------------------------------------------------------------------
  2030. | CUPnP::StartRenderer
  2031. +---------------------------------------------------------------------*/
  2032. void CUPnP::StartRenderer()
  2033. {
  2034. if (!m_RendererHolder->m_Device.IsNull()) return;
  2035. CStdString filename;
  2036. URIUtils::AddFileToFolder(g_settings.GetUserDataFolder(), "upnpserver.xml", filename);
  2037. g_settings.LoadUPnPXml(filename);
  2038. m_RendererHolder->m_Device = CreateRenderer(g_settings.m_UPnPPortRenderer);
  2039. // tell controller to ignore ourselves from list of upnp servers
  2040. if (!m_CtrlPointHolder->m_CtrlPoint.IsNull()) {
  2041. m_CtrlPointHolder->m_CtrlPoint->IgnoreUUID(m_RendererHolder->m_Device->GetUUID());
  2042. }
  2043. NPT_Result res = m_UPnP->AddDevice(m_RendererHolder->m_Device);
  2044. // failed most likely because port is in use, try again with random port now
  2045. if (NPT_FAILED(res) && g_settings.m_UPnPPortRenderer != 0) {
  2046. m_RendererHolder->m_Device = CreateRenderer(0);
  2047. // tell controller to ignore ourselves from list of upnp servers
  2048. if (!m_CtrlPointHolder->m_CtrlPoint.IsNull()) {
  2049. m_CtrlPointHolder->m_CtrlPoint->IgnoreUUID(m_RendererHolder->m_Device->GetUUID());
  2050. }
  2051. res = m_UPnP->AddDevice(m_RendererHolder->m_Device);
  2052. }
  2053. // save port but don't overwrite saved settings if random
  2054. if (NPT_SUCCEEDED(res) && g_settings.m_UPnPPortRenderer == 0) {
  2055. g_settings.m_UPnPPortRenderer = m_RendererHolder->m_Device->GetPort();
  2056. }
  2057. // save UUID
  2058. g_settings.m_UPnPUUIDRenderer = m_RendererHolder->m_Device->GetUUID();
  2059. g_settings.SaveUPnPXml(filename);
  2060. }
  2061. /*----------------------------------------------------------------------
  2062. | CUPnP::StopRenderer
  2063. +---------------------------------------------------------------------*/
  2064. void CUPnP::StopRenderer()
  2065. {
  2066. if (m_RendererHolder->m_Device.IsNull()) return;
  2067. m_UPnP->RemoveDevice(m_RendererHolder->m_Device);
  2068. m_RendererHolder->m_Device = NULL;
  2069. }
  2070. /*----------------------------------------------------------------------
  2071. | CUPnP::UpdateState
  2072. +---------------------------------------------------------------------*/
  2073. void CUPnP::UpdateState()
  2074. {
  2075. if (!m_RendererHolder->m_Device.IsNull())
  2076. ((CUPnPRenderer*)m_RendererHolder->m_Device.AsPointer())->UpdateState();
  2077. }
  2078. int CUPnP::PopulateTagFromObject(CMusicInfoTag& tag,
  2079. PLT_MediaObject& object,
  2080. PLT_MediaItemResource* resource /* = NULL */)
  2081. {
  2082. tag.SetTitle((const char*)object.m_Title);
  2083. tag.SetArtist((const char*)object.m_Creator);
  2084. for(PLT_PersonRoles::Iterator it = object.m_People.artists.GetFirstItem(); it; it++) {
  2085. if (it->role == "") tag.SetArtist((const char*)it->name);
  2086. else if(it->role == "Performer") tag.SetArtist((const char*)it->name);
  2087. else if(it->role == "AlbumArtist") tag.SetAlbumArtist((const char*)it->name);
  2088. }
  2089. tag.SetTrackNumber(object.m_MiscInfo.original_track_number);
  2090. tag.SetGenre((const char*)JoinString(object.m_Affiliation.genre, " / "));
  2091. tag.SetAlbum((const char*)object.m_Affiliation.album);
  2092. if(resource)
  2093. tag.SetDuration(resource->m_Duration);
  2094. tag.SetLoaded();
  2095. return NPT_SUCCESS;
  2096. }
  2097. int CUPnP::PopulateTagFromObject(CVideoInfoTag& tag,
  2098. PLT_MediaObject& object,
  2099. PLT_MediaItemResource* resource /* = NULL */)
  2100. {
  2101. CDateTime date;
  2102. date.SetFromDateString((const char*)object.m_Date);
  2103. if(!object.m_Recorded.program_title.IsEmpty())
  2104. {
  2105. int episode;
  2106. int season;
  2107. int title = object.m_Recorded.program_title.Find(" : ");
  2108. if(sscanf(object.m_Recorded.program_title, "S%2dE%2d", &season, &episode) == 2 && title >= 0) {
  2109. tag.m_strTitle = object.m_Recorded.program_title.SubString(title + 3);
  2110. tag.m_iEpisode = episode;
  2111. tag.m_iSeason = season;
  2112. } else {
  2113. tag.m_strTitle = object.m_Recorded.program_title;
  2114. tag.m_iSeason = object.m_Recorded.episode_number / 100;
  2115. tag.m_iEpisode = object.m_Recorded.episode_number % 100;
  2116. }
  2117. tag.m_firstAired = date;
  2118. }
  2119. else
  2120. {
  2121. tag.m_strTitle = object.m_Title;
  2122. tag.m_premiered = date;
  2123. }
  2124. tag.m_iYear = date.GetYear();
  2125. for (unsigned int index = 0; index < object.m_Affiliation.genre.GetItemCount(); index++)
  2126. tag.m_genre.push_back(object.m_Affiliation.genre.GetItem(index)->GetChars());
  2127. tag.m_director = StringUtils::Split((CStdString)object.m_People.director, g_advancedSettings.m_videoItemSeparator);
  2128. tag.m_strTagLine = object.m_Description.description;
  2129. tag.m_strPlot = object.m_Description.long_description;
  2130. tag.m_strShowTitle = object.m_Recorded.series_title;
  2131. if(resource)
  2132. tag.m_strRuntime.Format("%d",resource->m_Duration);
  2133. return NPT_SUCCESS;
  2134. }