/xbmc/network/UPnP.cpp

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