added: much better improved UPnP Renderer support. Now provides didl for currently...
[xbmc:xbmc-antiquated.git] / xbmc / UPnP.cpp
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
23
24 #include "stdafx.h"
25 #include "Util.h"
26 #include "Application.h"
27 #include "XBMChttp.h"
28
29 #include "utils/Network.h"
30 #include "UPnP.h"
31 #include "FileSystem/UPnPVirtualPathDirectory.h"
32 #include "FileSystem/MusicDatabaseDirectory.h"
33 #include "FileSystem/VideoDatabaseDirectory.h"
34 #include "MusicDatabase.h"
35 #include "VideoDatabase.h"
36 #include "FileSystem/VideoDatabaseDirectory/DirectoryNode.h"
37 #include "FileSystem/VideoDatabaseDirectory/QueryParams.h"
38 #include "Platinum.h"
39 #include "PltMediaConnect.h"
40 #include "PltMediaRenderer.h"
41 #include "PltSyncMediaBrowser.h"
42 #include "PltDidl.h"
43 #include "NptNetwork.h"
44 #include "NptConsole.h"
45 #include "MusicInfoTag.h"
46 #include "FileSystem/Directory.h"
47 #include "URL.h"
48 #include "Settings.h"
49 #include "FileItem.h"
50 #include "GUIWindowManager.h"
51 #include "GUIInfoManager.h"
52
53 using namespace std;
54 using namespace MUSIC_INFO;
55 using namespace DIRECTORY;
56
57 extern CGUIInfoManager g_infoManager;
58
59 NPT_SET_LOCAL_LOGGER("xbmc.upnp")
60
61 typedef struct {
62   const char* extension;
63   const char* mimetype;
64 } mimetype_extension_struct;
65
66 static const mimetype_extension_struct mimetype_extension_map[] = {
67     {"mp3",  "audio/mpeg"},
68     {"m4a",  "audio/mp4"},
69     {"wma",  "audio/x-ms-wma"},
70     {"wav",  "audio/x-wav"},
71     {"wmv",  "video/x-ms-wmv"},
72     {"asf",  "video/x-ms-asf"},
73     {"mpg",  "video/mpeg"},
74     {"divx", "video/avi"},
75     {"xvid", "video/avi"},
76     {"jpg",  "image/jpeg"},
77     {"tif",  "image/tiff"},
78     {NULL, NULL}
79 };
80
81 /*
82 # Play speed
83 #    1 normal
84 #    0 invalid
85 DLNA_ORG_PS = 'DLNA.ORG_PS'
86 DLNA_ORG_PS_VAL = '1'
87
88 # Convertion Indicator
89 #    1 transcoded
90 #    0 not transcoded
91 DLNA_ORG_CI = 'DLNA.ORG_CI'
92 DLNA_ORG_CI_VAL = '0'
93
94 # Operations
95 #    00 not time seek range, not range 
96 #    01 range supported
97 #    10 time seek range supported
98 #    11 both supported
99 DLNA_ORG_OP = 'DLNA.ORG_OP'
100 DLNA_ORG_OP_VAL = '01'
101
102 # Flags
103 #    senderPaced                      80000000  31
104 #    lsopTimeBasedSeekSupported       40000000  30
105 #    lsopByteBasedSeekSupported       20000000  29
106 #    playcontainerSupported           10000000  28
107 #    s0IncreasingSupported            08000000  27  
108 #    sNIncreasingSupported            04000000  26  
109 #    rtspPauseSupported               02000000  25  
110 #    streamingTransferModeSupported   01000000  24  
111 #    interactiveTransferModeSupported 00800000  23  
112 #    backgroundTransferModeSupported  00400000  22  
113 #    connectionStallingSupported      00200000  21  
114 #    dlnaVersion15Supported           00100000  20  
115 DLNA_ORG_FLAGS = 'DLNA.ORG_FLAGS'
116 DLNA_ORG_FLAGS_VAL = '01500000000000000000000000000000'
117 */
118
119 /*----------------------------------------------------------------------
120 |   static
121 +---------------------------------------------------------------------*/
122 CUPnP* CUPnP::upnp = NULL;
123 // change to false for XBMC_PC if you want real UPnP functionality
124 // otherwise keep to true for xbmc as it doesn't support multicast
125 // don't change unless you know what you're doing!
126 bool CUPnP::broadcast = true; 
127
128 /*----------------------------------------------------------------------
129 |   NPT_Console::Output
130 +---------------------------------------------------------------------*/
131 void 
132 NPT_Console::Output(const char* message)
133 {
134     CLog::Log(LOGDEBUG, "%s", message);
135 }
136
137 /*----------------------------------------------------------------------
138 |   CDeviceHostReferenceHolder class
139 +---------------------------------------------------------------------*/
140 class CDeviceHostReferenceHolder
141 {
142 public:
143     PLT_DeviceHostReference m_Device;
144 };
145
146 /*----------------------------------------------------------------------
147 |   CCtrlPointReferenceHolder class
148 +---------------------------------------------------------------------*/
149 class CCtrlPointReferenceHolder
150 {
151 public:
152     PLT_CtrlPointReference m_CtrlPoint;
153 };
154
155 /*----------------------------------------------------------------------
156 |   CUPnPCleaner class
157 +---------------------------------------------------------------------*/
158 class CUPnPCleaner : public NPT_Thread
159 {
160 public:
161     CUPnPCleaner(CUPnP* upnp) : NPT_Thread(true), m_UPnP(upnp) {}
162     void Run() {
163         delete m_UPnP;
164     }
165
166     CUPnP* m_UPnP;
167 };
168
169 /*----------------------------------------------------------------------
170 |   CUPnP::CUPnP
171 +---------------------------------------------------------------------*/
172 class CUPnPServer : public PLT_MediaConnect
173 {
174 public:
175     CUPnPServer(const char* friendly_name, const char* uuid = NULL) : 
176         PLT_MediaConnect("", friendly_name, true, uuid, 0, 81) { // force a lower port other than 80 for 360 to be happy
177         // hack: override path to make sure it's empty
178         // urls will contain full paths to local files
179         m_Path = "";
180         m_DirDelimiter = "\\";
181     }
182
183     // PLT_MediaServer methods
184     virtual NPT_Result OnBrowseMetadata(PLT_ActionReference&          action, 
185                                         const char*                   object_id, 
186                                         const NPT_HttpRequestContext& context);
187
188     virtual NPT_Result OnBrowseDirectChildren(PLT_ActionReference&          action, 
189                                               const char*                           object_id, 
190                                               const NPT_HttpRequestContext& context);
191
192     virtual NPT_Result OnSearch(PLT_ActionReference&          action, 
193                                 const NPT_String&             object_id, 
194                                 const NPT_String&             searchCriteria,
195                                 const NPT_HttpRequestContext& context);
196                                 
197     // PLT_FileMediaServer methods
198     virtual NPT_Result ServeFile(NPT_HttpRequest&              request, 
199                                  const NPT_HttpRequestContext& context,
200                                  NPT_HttpResponse&             response,
201                                  NPT_String                    uri_path,
202                                  NPT_String                    file_path);
203
204     // class methods
205     static NPT_Result PopulateObjectFromTag(CMusicInfoTag&         tag,
206                                             PLT_MediaObject&       object, 
207                                             NPT_String*            file_path = NULL,
208                                             PLT_MediaItemResource* resource = NULL);
209     static NPT_Result PopulateObjectFromTag(CVideoInfoTag&         tag,
210                                             PLT_MediaObject&       object, 
211                                             NPT_String*            file_path = NULL,
212                                             PLT_MediaItemResource* resource = NULL);     
213                     
214     static PLT_MediaObject* BuildObject(const CFileItem&              item,
215                                         NPT_String&                   file_path,
216                                         bool                          with_count,
217                                         const NPT_HttpRequestContext& context,
218                                         CUPnPServer*                  upnp_server = NULL);
219
220 private:
221     PLT_MediaObject* Build(CFileItemPtr                  item, 
222                            bool                          with_count, 
223                            const NPT_HttpRequestContext& context,
224                            const char*                   parent_id = NULL);
225     NPT_Result       BuildResponse(PLT_ActionReference&          action,
226                                    CFileItemList&                items,
227                                    const NPT_HttpRequestContext& context,
228                                    const char*                   parent_id);
229                            
230     static NPT_String GetParentFolder(NPT_String file_path) {       
231         int index = file_path.ReverseFind("\\");
232         if (index == -1) return "";
233
234         return file_path.Left(index);
235     }
236     static NPT_String BuildHttpUri(const NPT_HttpUrl& base_uri, const char* host, const char* file_path);
237     static NPT_String GetProtocolInfo(const CFileItem& item, const NPT_String& protocol);
238 };
239
240 /*----------------------------------------------------------------------
241 |   CUPnPServer::GetProtocolInfo
242 +---------------------------------------------------------------------*/
243 NPT_String
244 CUPnPServer::GetProtocolInfo(const CFileItem& item, const NPT_String& protocol)
245 {
246     NPT_String proto = protocol;
247     /* fixup the protocol */
248     if (proto.IsEmpty()) {
249         proto = item.GetAsUrl().GetProtocol();
250         if (proto == "http") {
251             proto = "http-get";
252         }
253     }
254     NPT_String ext = CUtil::GetExtension(item.m_strPath).c_str();
255     if (item.HasVideoInfoTag() && !item.GetVideoInfoTag()->m_strFileNameAndPath.IsEmpty()) {
256         ext = CUtil::GetExtension(item.GetVideoInfoTag()->m_strFileNameAndPath);
257     } else if (item.HasMusicInfoTag() && !item.GetMusicInfoTag()->GetURL().IsEmpty()) {
258         ext = CUtil::GetExtension(item.GetMusicInfoTag()->GetURL());
259     }
260     ext.TrimLeft('.');
261     ext = ext.ToLowercase();
262
263     /* we need a valid extension to retrieve the mimetype for the protocol info */
264     NPT_String content = item.GetContentType().c_str();
265     if (content == "application/octet-stream")
266         content = "";
267
268     if (content.IsEmpty()) {
269         content == "application/octet-stream";
270         const mimetype_extension_struct* mapping = mimetype_extension_map;
271         while (mapping->extension) {
272             if (ext == mapping->extension) {
273                 content = mapping->mimetype;
274                 break;
275             }
276             mapping++;
277         }
278     }
279
280     /* fallback to generic content type if not found */
281     if (content.IsEmpty()) {      
282         if (item.IsVideo() || item.IsVideoDb() )
283             content = "video/" + ext;
284         else if (item.IsAudio() || item.IsMusicDb() )
285             content = "audio/" + ext;
286         else if (item.IsPicture() )
287             content = "image/" + ext;
288     }
289     
290     /* nothing we can figure out */
291     if (content.IsEmpty()) {
292         content = "application/octet-stream";
293     }
294
295     /* setup dlna strings, wish i knew what all of they mean */
296     NPT_String extra = "DLNA.ORG_OP=01";
297     if (content == "audio/mpeg" )
298         extra.Insert("DLNA.ORG_PN=MP3;");
299     else if (content == "image/jpeg" )
300         extra.Insert("DLNA.ORG_PN=JPEG_LRG;");
301     else if (content == "image/png" )
302         extra.Insert("DLNA.ORG_PN=PNG_LRG;");
303     else if (content == "image/bmp" )
304         extra.Insert("DLNA.ORG_PN=BMP_LRG;");
305     else if (content == "image/tiff" )
306         extra.Insert("DLNA.ORG_PN=TIFF_LRG;");
307     else if (content == "image/gif" )
308         extra.Insert("DLNA.ORG_PN=GIF_LRG;");
309     else if (content == "image/jp2" )
310         extra.Insert("DLNA.ORG_PN=JPEG_LRG;");
311     else if (content == "video/avi" )
312         extra.Insert("DLNA.ORG_PN=AVI;");
313
314     NPT_String info = proto + ":*:" + content + ":" + extra;
315     return info;
316 }
317
318 /*----------------------------------------------------------------------
319 |   Substitute
320 +---------------------------------------------------------------------*/
321 static NPT_String
322 Substitute(const char* in, char ch, const char* str)
323 {
324     NPT_String out;
325
326     // check args
327     if (str == NULL) return out;
328
329     // reserve at least the size of the current uri
330     out.Reserve(NPT_StringLength(in));
331
332     while (unsigned char c = *in++) {
333         if (c == ch) {
334             out.Append(str);
335         } else {
336             out += c;
337         }
338     }
339
340     return out;
341 }
342
343 /*----------------------------------------------------------------------
344 |   CUPnPServer::PopulateObjectFromTag
345 +---------------------------------------------------------------------*/
346 NPT_Result
347 CUPnPServer::PopulateObjectFromTag(CMusicInfoTag&         tag,
348                                    PLT_MediaObject&       object, 
349                                    NPT_String*            file_path, /* = NULL */
350                                    PLT_MediaItemResource* resource   /* = NULL */)
351 {
352     // some usefull buffers
353     CStdStringArray strings;
354     
355     if (!tag.GetURL().IsEmpty() && file_path)
356       *file_path = tag.GetURL();
357
358     StringUtils::SplitString(tag.GetGenre(), " / ", strings);
359     for(CStdStringArray::iterator it = strings.begin(); it != strings.end(); it++) {
360         object.m_Affiliation.genre.Add((*it).c_str());
361     }
362
363     object.m_Affiliation.album = tag.GetAlbum();
364     object.m_People.artists.Add(tag.GetArtist().c_str());
365     object.m_People.artists.Add(tag.GetAlbumArtist().c_str());
366     object.m_Creator = tag.GetArtist();
367     object.m_MiscInfo.original_track_number = tag.GetTrackNumber();
368     if (resource) resource->m_Duration = tag.GetDuration();   
369     
370     return NPT_SUCCESS;
371 }
372
373 /*----------------------------------------------------------------------
374 |   CUPnPServer::PopulateObjectFromTag
375 +---------------------------------------------------------------------*/
376 NPT_Result
377 CUPnPServer::PopulateObjectFromTag(CVideoInfoTag&         tag,
378                                    PLT_MediaObject&       object, 
379                                    NPT_String*            file_path, /* = NULL */
380                                    PLT_MediaItemResource* resource   /* = NULL */)
381 {
382     // some usefull buffers
383     CStdStringArray strings;
384     
385     if (!tag.m_strFileNameAndPath.IsEmpty() && file_path)
386       *file_path = tag.m_strFileNameAndPath;
387
388     if (tag.m_iDbId != -1 ) {
389         if (tag.m_strShowTitle.IsEmpty()) {
390           object.m_ObjectClass.type = "object.item.videoItem"; // XBox 360 wants object.item.videoItem instead of object.item.videoItem.movie, is WMP happy?
391           object.m_Affiliation.album = "[Unknown Series]"; // required to make WMP to show title
392           object.m_Title = tag.m_strTitle;                                             
393         } else {
394           object.m_ObjectClass.type = "object.item.videoItem.videoBroadcast";
395           object.m_Affiliation.album = tag.m_strShowTitle;
396           object.m_Title = tag.m_strShowTitle + " - ";
397           object.m_Title += "S" + ("0" + NPT_String::FromInteger(tag.m_iSeason)).Right(2);
398           object.m_Title += "E" + ("0" + NPT_String::FromInteger(tag.m_iEpisode)).Right(2);
399           object.m_Title += " : " + tag.m_strTitle;
400         }
401     }
402
403     StringUtils::SplitString(tag.m_strGenre, " / ", strings);                
404     for(CStdStringArray::iterator it = strings.begin(); it != strings.end(); it++) {
405         object.m_Affiliation.genre.Add((*it).c_str());
406     }
407
408     for(CVideoInfoTag::iCast it = tag.m_cast.begin();it != tag.m_cast.end();it++) {
409         object.m_People.actors.Add(it->strName.c_str(), it->strRole.c_str());
410     }
411     object.m_People.director = tag.m_strDirector;
412
413     object.m_Description.description = tag.m_strTagLine;
414     object.m_Description.long_description = tag.m_strPlot;
415     if (resource) resource->m_Duration = StringUtils::TimeStringToSeconds(tag.m_strRuntime.c_str());
416     
417     return NPT_SUCCESS;
418 }
419
420 /*----------------------------------------------------------------------
421 |   CUPnPServer::BuildHttpUri
422 +---------------------------------------------------------------------*/
423 NPT_String
424 CUPnPServer::BuildHttpUri(const NPT_HttpUrl& base_uri, const char* host, const char* file_path)
425 {
426     NPT_HttpUrl uri = base_uri;
427     NPT_HttpUrlQuery query(uri.GetQuery());
428     NPT_String result;
429
430     query.AddField("path", file_path);
431     if (host) uri.SetHost(host);
432     uri.SetQuery(query.ToString());
433     // 360 hack: force inclusion of port 80
434     result = uri.ToStringWithDefaultPort(0);
435     // 360 hack: it removes the query, so we make it look like a path
436     // and we replace + with urlencoded value of space
437     result = Substitute(result, '?', "%3F");
438     result = Substitute(result, '+', "%20");
439     return result;
440 }
441                          
442 /*----------------------------------------------------------------------
443 |   CUPnPServer::BuildObject
444 +---------------------------------------------------------------------*/
445 PLT_MediaObject*
446 CUPnPServer::BuildObject(const CFileItem&              item,
447                          NPT_String&                   file_path,
448                          bool                          with_count,
449                          const NPT_HttpRequestContext& context,
450                          CUPnPServer*                  upnp_server /* = NULL */)
451 {
452     PLT_MediaItemResource resource;
453     PLT_MediaObject*      object = NULL;
454
455     // get list of ip addresses
456     NPT_List<NPT_String> ips;
457     NPT_CHECK_LABEL(PLT_UPnPMessageHelper::GetIPAddresses(ips), failure);
458
459     // if we're passed an interface where we received the request from
460     // move the ip to the top
461     if (context.GetLocalAddress().GetIpAddress().ToString() != "0.0.0.0") {
462         ips.Remove(context.GetLocalAddress().GetIpAddress().ToString());
463         ips.Insert(ips.GetFirstItem(), context.GetLocalAddress().GetIpAddress().ToString());
464     }
465
466     if (!item.m_bIsFolder) {
467         object = new PLT_MediaItem();
468         object->m_ObjectID = item.m_strPath;
469
470         /* Setup object type */
471         if (item.IsMusicDb() || item.IsAudio()) {
472             object->m_ObjectClass.type = "object.item.audioItem.musicTrack";
473           
474             if (item.HasMusicInfoTag()) {
475                 CMusicInfoTag *tag = (CMusicInfoTag*)item.GetMusicInfoTag();
476                 PopulateObjectFromTag(*tag, *object, &file_path, &resource);
477             }
478         } else if (item.IsVideoDb() || item.IsVideo()) {
479             object->m_ObjectClass.type = "object.item.videoItem";
480             object->m_Affiliation.album = "[Unknown Series]"; // required to make WMP to show title
481
482             if (item.HasVideoInfoTag()) {
483                 CVideoInfoTag *tag = (CVideoInfoTag*)item.GetVideoInfoTag();
484                 PopulateObjectFromTag(*tag, *object, &file_path, &resource);
485             }
486         } else if (item.IsPicture()) {
487             object->m_ObjectClass.type = "object.item.imageItem.photo";
488         } else {
489             object->m_ObjectClass.type = "object.item";
490         }
491         
492         // duration of zero is invalid
493         if (resource.m_Duration == 0) resource.m_Duration = -1;
494
495         // Set the resource file size
496         resource.m_Size = (NPT_Size)item.m_dwSize;
497
498         // if the item is remote, add a direct link to the item
499         if (CUtil::IsRemote((const char*)file_path)) {
500             resource.m_ProtocolInfo = CUPnPServer::GetProtocolInfo(item, "http-get"); // assumes http-get always
501             resource.m_Uri = file_path;
502             object->m_Resources.Add(resource);
503         } else if (upnp_server) {
504             // iterate through ip addresses and build list of resources
505             // throught http file server
506             NPT_List<NPT_String>::Iterator ip = ips.GetFirstItem();
507             while (ip) {
508                 resource.m_ProtocolInfo = GetProtocolInfo(item, "http-get");
509                 resource.m_Uri = BuildHttpUri(upnp_server->m_FileBaseUri, *ip, file_path);
510                 object->m_Resources.Add(resource);
511                 ++ip;
512             }
513         }
514     } else {
515         PLT_MediaContainer* container = new PLT_MediaContainer;
516         object = container;
517
518         /* Assign a title and id for this container */
519         container->m_ObjectID = item.m_strPath;
520         container->m_ObjectClass.type = "object.container";
521         container->m_ChildrenCount = -1;
522
523         /* this might be overkill, but hey */
524         if (item.IsMusicDb()) {
525             MUSICDATABASEDIRECTORY::NODE_TYPE node = CMusicDatabaseDirectory::GetDirectoryType(item.m_strPath);
526             switch(node) {
527                 case MUSICDATABASEDIRECTORY::NODE_TYPE_ARTIST:
528                   container->m_ObjectClass.type += ".person.musicArtist";
529                   break;
530                 case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM:
531                 case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_COMPILATIONS:
532                 case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_RECENTLY_ADDED:
533                 case MUSICDATABASEDIRECTORY::NODE_TYPE_YEAR_ALBUM:
534                   container->m_ObjectClass.type += ".album.musicAlbum";
535                   break;
536                 case MUSICDATABASEDIRECTORY::NODE_TYPE_GENRE:
537                   container->m_ObjectClass.type += ".genre.musicGenre";
538                   break;
539                 default:
540                   break;
541             }
542         } else if (item.IsVideoDb()) {
543             VIDEODATABASEDIRECTORY::NODE_TYPE node = CVideoDatabaseDirectory::GetDirectoryType(item.m_strPath);
544             switch(node) {
545                 case VIDEODATABASEDIRECTORY::NODE_TYPE_GENRE:
546                   container->m_ObjectClass.type += ".genre.movieGenre";
547                   break;
548                 case VIDEODATABASEDIRECTORY::NODE_TYPE_MOVIES_OVERVIEW:
549                   container->m_ObjectClass.type += ".storageFolder";
550                   break;
551                 default:
552                   break;
553             }
554         } else if (item.IsPlayList()) {
555             container->m_ObjectClass.type += ".playlistContainer";
556         }
557
558         /* Get the number of children for this container */
559         if (with_count && upnp_server) {
560             if (object->m_ObjectID.StartsWith("virtualpath://")) {
561                 NPT_Cardinal count = 0;
562                 NPT_CHECK_LABEL(upnp_server->GetEntryCount(file_path, count), failure);
563                 container->m_ChildrenCount = count;
564             } else {
565                 /* this should be a standard path */
566                 // TODO - get file count of this directory
567             }
568         }        
569     }
570     
571     // set a title for the object
572     if (object->m_Title.IsEmpty()) {
573         if (!item.GetLabel().IsEmpty()) {
574             CStdString title = item.GetLabel();
575             if (item.IsPlayList()) CUtil::RemoveExtension(title);
576             object->m_Title = title;
577         } else {
578             object->m_Title = CUtil::GetTitleFromPath(item.m_strPath, item.m_bIsFolder);
579         }
580     }
581     // set a thumbnail if we have one
582     if (item.HasThumbnail() && upnp_server) {
583         object->m_ExtraInfo.album_art_uri = BuildHttpUri(upnp_server->m_FileBaseUri, *ips.GetFirstItem(), item.GetThumbnailImage());
584     }
585
586     return object;
587
588 failure:
589     if (object) delete object;
590     return NULL;
591 }
592
593 /*----------------------------------------------------------------------
594 |   CUPnPServer::Build
595 +---------------------------------------------------------------------*/
596 PLT_MediaObject* 
597 CUPnPServer::Build(CFileItemPtr                  item, 
598                    bool                          with_count, 
599                    const NPT_HttpRequestContext& context,
600                    const char*                   parent_id /* = NULL */)
601 {
602     PLT_MediaObject* object = NULL;
603     NPT_String       path = item->m_strPath.c_str();
604     NPT_String       share_name;
605     NPT_String       file_path;
606
607     //HACK: temporary disabling count as it thrashes HDD
608     with_count = false;
609
610     if (!CUPnPVirtualPathDirectory::SplitPath(path, share_name, file_path))
611     {
612       file_path = item->m_strPath;
613       share_name = "";
614
615       if (path.StartsWith("musicdb://")) {
616           CStdString label;
617           if (path == "musicdb://" ) {              
618               item->SetLabel("Music Library");
619               item->SetLabelPreformated(true);
620           } else {
621               if (!item->HasMusicInfoTag() || !item->GetMusicInfoTag()->Loaded() )
622                   item->LoadMusicTag();
623
624               if (!item->HasThumbnail() )
625                   item->SetCachedMusicThumb();
626
627               if (item->GetLabel().IsEmpty()) {
628                   /* if no label try to grab it from node type */
629                   if (CMusicDatabaseDirectory::GetLabel((const char*)path, label)) {
630                       item->SetLabel(label);
631                       item->SetLabelPreformated(true);
632                   }
633               }
634           }
635       } else if (file_path.StartsWith("videodb://")) {
636           CStdString label;
637           if (path == "videodb://" ) {
638               item->SetLabel("Video Library");
639               item->SetLabelPreformated(true);
640           } else {
641               if (!item->HasVideoInfoTag()) {
642                   DIRECTORY::VIDEODATABASEDIRECTORY::CQueryParams params;
643                   DIRECTORY::VIDEODATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo((const char*)path, params);
644
645                   CVideoDatabase db;
646                   if (!db.Open() )
647                       return NULL;
648
649                   if (params.GetMovieId() >= 0 )
650                       db.GetMovieInfo((const char*)path, *item->GetVideoInfoTag(), params.GetMovieId());
651                   else if (params.GetEpisodeId() >= 0 )
652                       db.GetEpisodeInfo((const char*)path, *item->GetVideoInfoTag(), params.GetEpisodeId());
653                   else if (params.GetTvShowId() >= 0 )
654                       db.GetTvShowInfo((const char*)path, *item->GetVideoInfoTag(), params.GetTvShowId());
655               }
656
657               // try to grab title from tag
658               if (item->HasVideoInfoTag() && !item->GetVideoInfoTag()->m_strTitle.IsEmpty()) {
659                   item->SetLabel( item->GetVideoInfoTag()->m_strTitle );
660                   item->SetLabelPreformated(true);
661               }
662
663               // try to grab it from the folder
664               if (item->GetLabel().IsEmpty()) {
665                   if (CVideoDatabaseDirectory::GetLabel((const char*)path, label)) {
666                       item->SetLabel(label);
667                       item->SetLabelPreformated(true);
668                   }
669               }
670
671               if (!item->HasThumbnail() )
672                   item->SetCachedVideoThumb();
673           }
674           
675       }
676
677       //not a virtual path directory, new system
678       object = BuildObject(*item.get(), file_path, with_count, context, this);
679       if (!object)
680         return NULL;
681
682       if (parent_id)
683         object->m_ParentID = parent_id;
684
685       return object;
686     }
687
688     path.TrimRight("/");
689     if (file_path.GetLength()) {
690         // make sure the path starts with something that is shared given the share
691         if (!CUPnPVirtualPathDirectory::FindSourcePath(share_name, file_path, true)) goto failure;
692         
693         // this is not a virtual directory
694         object = BuildObject(*item.get(), file_path, with_count, context, this);
695         if (!object) goto failure;
696
697         // override object id & change the class if it's an item
698         // and it's not been set previously
699         if (object->m_ObjectClass.type == "object.item") {
700             if (share_name == "virtualpath://upnpmusic")
701                 object->m_ObjectClass.type = "object.item.audioitem";
702             else if (share_name == "virtualpath://upnpvideo")
703                 object->m_ObjectClass.type = "object.item.videoitem";
704             else if (share_name == "virtualpath://upnppictures")
705                 object->m_ObjectClass.type = "object.item.imageitem";
706         }
707
708         if (parent_id) {
709             object->m_ParentID = parent_id;
710         } else {
711             // populate parentid manually
712             if (CUPnPVirtualPathDirectory::FindSourcePath(share_name, file_path)) {
713                 // found the file_path as one of the path of the share
714                 // this means the parent id is the share
715                 object->m_ParentID = share_name;
716             } else {
717                 // we didn't find the path, find the parent path
718                 NPT_String parent_path = GetParentFolder(file_path);
719                 if (parent_path.IsEmpty()) goto failure;
720
721                 // try again with parent
722                 if (CUPnPVirtualPathDirectory::FindSourcePath(share_name, parent_path)) {
723                     // found the file_path parent folder as one of the path of the share
724                     // this means the parent id is the share
725                     object->m_ParentID = share_name;
726                 } else {
727                     object->m_ParentID = share_name + "/" + parent_path;
728                 }
729             }
730         }
731
732         // old style, needs virtual path prefix
733         if (!object->m_ObjectID.StartsWith("virtualpath://") )
734             object->m_ObjectID = share_name + "/" + object->m_ObjectID;
735
736     } else {
737         object = new PLT_MediaContainer;
738         object->m_Title = item->GetLabel();
739         object->m_ObjectClass.type = "object.container";
740         object->m_ObjectID = path;
741
742         if (path == "virtualpath://upnproot") {
743             // root
744             object->m_ObjectID = "0";
745             object->m_ParentID = "-1";
746             // root has 5 children
747             if (with_count) ((PLT_MediaContainer*)object)->m_ChildrenCount = 5;
748         } else if (share_name.GetLength() == 0) {
749             // no share_name means it's virtualpath://X where X=music, video or pictures
750             object->m_ParentID = "0";
751             if (with_count || true) { // we can always count these, it's quick
752                 ((PLT_MediaContainer*)object)->m_ChildrenCount = 0;
753
754                 // look up number of shares
755                 VECSOURCES *shares = NULL;
756                 if (path == "virtualpath://upnpmusic") {
757                     shares = g_settings.GetSourcesFromType("upnpmusic");
758                 } else if (path == "virtualpath://upnpvideo") {
759                     shares = g_settings.GetSourcesFromType("upnpvideo");
760                 } else if (path == "virtualpath://upnppictures") {
761                     shares = g_settings.GetSourcesFromType("upnppictures");
762                 }
763
764                 // use only shares that would some path with local files
765                 if (shares) {
766                     CUPnPVirtualPathDirectory dir;
767                     for (unsigned int i = 0; i < shares->size(); i++) {
768                         // Does this share contains any local paths?
769                         CMediaSource &share = shares->at(i);
770                         vector<CStdString> paths;
771
772                         // reconstruct share name as it could have been replaced by
773                         // a path if there was just one entry
774                         NPT_String share_name = path + "/";
775                         share_name += share.strName;
776                         if (dir.GetMatchingSource((const char*)share_name, share, paths) && paths.size()) {
777                             ((PLT_MediaContainer*)object)->m_ChildrenCount++;
778                         }
779                     }
780                 }
781             }
782         } else {
783             CStdString mask;
784             // this is a share name
785             if (share_name.StartsWith("virtualpath://upnpmusic")) {
786                 object->m_ParentID = "virtualpath://upnpmusic";
787                 mask = g_stSettings.m_musicExtensions;
788             } else if (share_name.StartsWith("virtualpath://upnpvideo")) {
789                 object->m_ParentID = "virtualpath://upnpvideo";
790                 mask = g_stSettings.m_videoExtensions;
791             } else if (share_name.StartsWith("virtualpath://upnppictures")) {
792                 object->m_ParentID = "virtualpath://upnppictures";
793                 mask = g_stSettings.m_pictureExtensions;
794             } else {
795                 // weird!
796                 goto failure;
797             }
798
799             if (with_count) {
800                 ((PLT_MediaContainer*)object)->m_ChildrenCount = 0;
801
802                 // get all the paths for a given share
803                 CMediaSource share;
804                 CUPnPVirtualPathDirectory dir;
805                 vector<CStdString> paths;
806                 if (!dir.GetMatchingSource((const char*)share_name, share, paths)) goto failure;
807                 for (unsigned int i=0; i<paths.size(); i++) {
808                     // FIXME: this is not efficient, we only need the number of items given a mask
809                     // and not the list of items
810
811                     // retrieve all the files for a given path
812                    CFileItemList items;
813                    if (CDirectory::GetDirectory(paths[i], items, mask)) {
814                        // update childcount
815                        ((PLT_MediaContainer*)object)->m_ChildrenCount += items.Size();
816                    }
817                 }
818             }
819         }
820     }
821
822     return object;
823
824 failure:
825     if (object)
826       delete object;
827     return NULL;
828 }
829
830 /*----------------------------------------------------------------------
831 |   CUPnPServer::OnBrowseMetadata
832 +---------------------------------------------------------------------*/
833 NPT_Result
834 CUPnPServer::OnBrowseMetadata(PLT_ActionReference&          action, 
835                               const char*                   object_id, 
836                               const NPT_HttpRequestContext& context)
837 {
838     NPT_String                     didl;
839     NPT_Reference<PLT_MediaObject> object;
840     NPT_String                     id = object_id;
841     CMediaSource                   share;
842     CUPnPVirtualPathDirectory      dir;
843     vector<CStdString>             paths;
844     CFileItemPtr                   item;
845
846     if (id == "0") {
847         id = "virtualpath://upnproot/";
848     }
849
850     if (id.StartsWith("virtualpath://")) {
851         id.TrimRight("/");
852         if (id == "virtualpath://upnproot") {
853             id += "/";
854             item.reset(new CFileItem((const char*)id, true));
855             item->SetLabel("Root");
856             item->SetLabelPreformated(true);
857             object = Build(item, true, context);
858         } else if (id == "virtualpath://upnpmusic") {
859             id += "/";
860             item.reset(new CFileItem((const char*)id, true));
861             item->SetLabel("Music Files");
862             item->SetLabelPreformated(true);
863             object = Build(item, true, context);
864         } else if (id == "virtualpath://upnpvideo") {
865             id += "/";
866             item.reset(new CFileItem((const char*)id, true));
867             item->SetLabel("Video Files");
868             item->SetLabelPreformated(true);
869             object = Build(item, true, context);
870         } else if (id == "virtualpath://upnppictures") {
871             id += "/";
872             item.reset(new CFileItem((const char*)id, true));
873             item->SetLabel("Picture Files");
874             item->SetLabelPreformated(true);
875             object = Build(item, true, context);
876         } else if (dir.GetMatchingSource((const char*)id, share, paths)) {
877             id += "/";
878             item.reset(new CFileItem((const char*)id, true));
879             item->SetLabel(share.strName);
880             item->SetLabelPreformated(true);
881             object = Build(item, true, context);
882         } else {
883             NPT_String share_name, file_path;
884             if (!CUPnPVirtualPathDirectory::SplitPath(id, share_name, file_path)) 
885                 return NPT_FAILURE;
886
887             NPT_String parent_path = GetParentFolder(file_path);
888             if (parent_path.IsEmpty()) return NPT_FAILURE;
889
890             NPT_DirectoryEntryInfo entry_info;
891             NPT_CHECK(NPT_DirectoryEntry::GetInfo(file_path, entry_info));
892
893             item.reset(new CFileItem((const char*)id, (entry_info.type==NPT_DIRECTORY_TYPE)?true:false));
894             item->SetLabel((const char*)file_path.SubString(parent_path.GetLength()+1));
895             item->SetLabelPreformated(true);
896
897             // get file size
898             if (entry_info.type == NPT_FILE_TYPE) {
899                 item->m_dwSize = entry_info.size;
900             }
901
902             object = Build(item, true, context);
903             if (!object.IsNull()) object->m_ObjectID = id;
904         }
905     } else {
906         if (CDirectory::Exists((const char*)id)) {
907             item.reset(new CFileItem((const char*)id, true));
908         } else {
909             item.reset(new CFileItem((const char*)id, false));            
910         }
911         CStdString parent;
912         if (!CUtil::GetParentPath((const char*)id, parent))
913           parent = "0";
914
915         object = Build(item, true, context, parent.c_str());
916     }
917
918     if (object.IsNull()) return NPT_FAILURE;
919
920     NPT_String filter;
921     NPT_CHECK(action->GetArgumentValue("Filter", filter));
922
923     NPT_String tmp;    
924     NPT_CHECK(PLT_Didl::ToDidl(*object.AsPointer(), filter, tmp));
925
926     /* add didl header and footer */
927     didl = didl_header + tmp + didl_footer;
928
929     NPT_CHECK(action->SetArgumentValue("Result", didl));
930     NPT_CHECK(action->SetArgumentValue("NumberReturned", "1"));
931     NPT_CHECK(action->SetArgumentValue("TotalMatches", "1"));
932
933     // update ID may be wrong here, it should be the one of the container?
934     NPT_CHECK(action->SetArgumentValue("UpdateId", "1"));
935     // TODO: We need to keep track of the overall updateID of the CDS
936
937     return NPT_SUCCESS;
938 }
939
940 /*----------------------------------------------------------------------
941 |   CUPnPServer::OnBrowseDirectChildren
942 +---------------------------------------------------------------------*/
943 NPT_Result
944 CUPnPServer::OnBrowseDirectChildren(PLT_ActionReference&          action, 
945                                     const char*                   object_id, 
946                                     const NPT_HttpRequestContext& context)
947 {
948     NPT_String id = object_id;    
949     CFileItemList items;
950
951     if (id == "0") {
952         id = "virtualpath://upnproot/";
953     }
954     
955     if (id == "15") {
956         // Xbox 360 asking for videos
957         id = "videodb://1/2"; // videodb://1 for folders
958     } else if (id.StartsWith("videodb://1")) {
959         id = "videodb://1/2";
960     } else if (id == "16") {
961         // Xbox 360 asking for photos
962     }
963
964     items.m_strPath = id;
965     if (!items.Load()) {
966         // cache anything that takes more than a second to retrieve
967         DWORD time = GetTickCount() + 1000;
968
969         if (id.StartsWith("virtualpath://")) {
970             CUPnPVirtualPathDirectory dir;
971             dir.GetDirectory((const char*)id, items);
972         } else {
973             CDirectory::GetDirectory((const char*)id, items);
974         }
975         if (items.CacheToDiscAlways() || (items.CacheToDiscIfSlow() && time < GetTickCount()))
976           items.Save();
977     }
978
979
980     return BuildResponse(action, items, context, id);
981 }
982
983 /*----------------------------------------------------------------------
984 |   CUPnPServer::BuildResponse
985 +---------------------------------------------------------------------*/
986 NPT_Result
987 CUPnPServer::BuildResponse(PLT_ActionReference&          action, 
988                            CFileItemList&                items, 
989                            const NPT_HttpRequestContext& context, 
990                            const char*                   parent_id)
991 {
992     NPT_String filter;
993     NPT_String startingInd;
994     NPT_String reqCount;
995
996     NPT_CHECK_SEVERE(action->GetArgumentValue("Filter", filter));
997     NPT_CHECK_SEVERE(action->GetArgumentValue("StartingIndex", startingInd));
998     NPT_CHECK_SEVERE(action->GetArgumentValue("RequestedCount", reqCount));   
999
1000     unsigned long start_index, stop_index, req_count;
1001     NPT_CHECK_SEVERE(startingInd.ToInteger(start_index));
1002     NPT_CHECK_SEVERE(reqCount.ToInteger(req_count));
1003         
1004     stop_index = min(start_index + req_count, (unsigned long)items.Size());
1005
1006     NPT_String didl = didl_header;
1007     PLT_MediaObjectReference item;
1008     for (unsigned long i=start_index; i < stop_index; ++i) {
1009         item = Build(items[i], true, context, parent_id);
1010         if (item.IsNull()) {
1011             /* create a dummy object */
1012             item = new PLT_MediaObject();
1013             item->m_Title = items[i]->GetLabel();
1014         }
1015
1016         NPT_String tmp;
1017         NPT_CHECK(PLT_Didl::ToDidl(*item.AsPointer(), filter, tmp));
1018
1019         // Neptunes string growing is dead slow for small additions
1020         if (didl.GetCapacity() < tmp.GetLength() + didl.GetLength()) {
1021             didl.Reserve((tmp.GetLength() + didl.GetLength())*2);
1022         }
1023         didl += tmp;
1024     }
1025
1026     didl += didl_footer;
1027
1028     NPT_CHECK(action->SetArgumentValue("Result", didl));
1029     NPT_CHECK(action->SetArgumentValue("NumberReturned", NPT_String::FromInteger(stop_index - start_index)));
1030     NPT_CHECK(action->SetArgumentValue("TotalMatches", NPT_String::FromInteger(items.Size())));
1031     NPT_CHECK(action->SetArgumentValue("UpdateId", "1"));
1032     return NPT_SUCCESS;
1033 }
1034
1035 /*----------------------------------------------------------------------
1036 |   CUPnPServer::OnSearch
1037 +---------------------------------------------------------------------*/
1038 NPT_Result
1039 CUPnPServer::OnSearch(PLT_ActionReference&          action, 
1040                       const NPT_String&             object_id, 
1041                       const NPT_String&             searchCriteria,
1042                       const NPT_HttpRequestContext& context)
1043
1044 {
1045     if (object_id.StartsWith("musicdb://")) {
1046         NPT_String id = object_id;
1047         // we browse for all tracks given a genre, artist or album
1048         if (searchCriteria.Find("object.item.audioItem") >= 0) {
1049             if (id.StartsWith("musicdb://1/")) {
1050                 id += "-1/-1/";
1051             } else if (id.StartsWith("musicdb://2/")) {
1052                 id += "-1/";
1053             }
1054         }
1055         return OnBrowseDirectChildren(action, id, context);
1056     } else if (searchCriteria.Find("object.item.audioItem") >= 0) {
1057         // browse all songs
1058         return OnBrowseDirectChildren(action, "musicdb://4", context);
1059     } else if (searchCriteria.Find("object.container.album.musicAlbum") >= 0) {
1060         // 360 hack: artist/album using search
1061         int artist_search = searchCriteria.Find("upnp:artist = \"");
1062         if (artist_search>0) {
1063             NPT_String artist = searchCriteria.Right(searchCriteria.GetLength() - artist_search - 15);
1064             artist = artist.Left(artist.Find("\""));
1065             CMusicDatabase database;
1066             database.Open();
1067             CStdString strPath;
1068             strPath.Format("musicdb://2/%ld/", database.GetArtistByName((const char*)artist));
1069             return OnBrowseDirectChildren(action, "musicdb://3", context);
1070         } else {
1071             return OnBrowseDirectChildren(action, "musicdb://3", context);
1072         }
1073     } else if (searchCriteria.Find("object.container.person.musicArtist") >= 0) {
1074         return OnBrowseDirectChildren(action, "musicdb://2", context);
1075     }  else if (searchCriteria.Find("object.container.genre.musicGenre") >= 0) {
1076         return OnBrowseDirectChildren(action, "musicdb://1", context);
1077     } else if (searchCriteria.Find("object.container.playlistContainer") >= 0) {
1078         return OnBrowseDirectChildren(action, "special://musicplaylists/", context);
1079     } else if (searchCriteria.Find("object.item.videoItem") >= 0) {
1080       CFileItemList items, itemsall;
1081
1082       CVideoDatabase database;
1083       if (!database.Open()) {
1084         action->SetError(800, "Internal Error");
1085         return NPT_SUCCESS;
1086       }
1087
1088       if (!database.GetMoviesNav("videodb://1/2", items)) {
1089         action->SetError(800, "Internal Error");
1090         return NPT_SUCCESS;
1091       }
1092       itemsall.Append(items);
1093       items.Clear();
1094
1095       // TODO - set proper base url for this
1096       if (!database.GetEpisodesNav("videodb://2/0", items)) {
1097         action->SetError(800, "Internal Error");
1098         return NPT_SUCCESS;
1099       }
1100       itemsall.Append(items);
1101       items.Clear();
1102
1103       return BuildResponse(action, itemsall, context, NULL);
1104   }
1105
1106   return NPT_FAILURE;
1107 }
1108
1109 /*----------------------------------------------------------------------
1110 |   CUPnPServer::ServeFile
1111 +---------------------------------------------------------------------*/
1112 NPT_Result 
1113 CUPnPServer::ServeFile(NPT_HttpRequest&              request, 
1114                        const NPT_HttpRequestContext& context,
1115                        NPT_HttpResponse&             response,
1116                        NPT_String                    uri_path,
1117                        NPT_String                    file_path)
1118 {
1119     // File requested
1120     NPT_String path = m_FileBaseUri.GetPath();
1121     if (path.Compare(uri_path.Left(path.GetLength()), true) == 0 && 
1122         file_path.Left(8).Compare("stack://", true) == 0) {
1123         
1124         NPT_List<NPT_String> files = file_path.SubString(8).Split(" , ");
1125         if (files.GetItemCount() == 0) {
1126             response.SetStatus(404, "File Not Found");
1127             return NPT_SUCCESS;
1128         }
1129
1130         NPT_String output;
1131         output.Reserve(file_path.GetLength()*2);
1132
1133         NPT_List<NPT_String>::Iterator url = files.GetFirstItem();
1134         for (;url;url++) {
1135             NPT_HttpUrl uri = m_FileBaseUri;
1136             NPT_HttpUrlQuery query;
1137             query.AddField("path", *url);
1138             uri.SetHost(context.GetLocalAddress().GetIpAddress().ToString());
1139             uri.SetQuery(query.ToString());
1140
1141             output += uri.ToString();
1142             output += "\n\r";
1143         }
1144
1145         PLT_HttpHelper::SetContentType(response, "audio/x-mpegurl");
1146         PLT_HttpHelper::SetBody(response, (const char*)output, output.GetLength());
1147         return NPT_SUCCESS;
1148     }
1149     
1150     return PLT_MediaConnect::ServeFile(request, 
1151                                        context, 
1152                                        response, 
1153                                        uri_path, 
1154                                        file_path);
1155 }
1156
1157 /*----------------------------------------------------------------------
1158 |   CUPnPRenderer
1159 +---------------------------------------------------------------------*/
1160 class CUPnPRenderer : public PLT_MediaRenderer
1161 {
1162 public:
1163     CUPnPRenderer(const char*  friendly_name,
1164                   bool         show_ip = false,
1165                   const char*  uuid = NULL,
1166                   unsigned int port = 0);
1167
1168     void UpdateState();
1169
1170     // AVTransport methods
1171     virtual NPT_Result OnNext(PLT_ActionReference& action);
1172     virtual NPT_Result OnPause(PLT_ActionReference& action);
1173     virtual NPT_Result OnPlay(PLT_ActionReference& action);
1174     virtual NPT_Result OnPrevious(PLT_ActionReference& action);
1175     virtual NPT_Result OnStop(PLT_ActionReference& action);
1176     virtual NPT_Result OnSeek(PLT_ActionReference& action);
1177     virtual NPT_Result OnSetAVTransportURI(PLT_ActionReference& action);
1178
1179     // RenderingControl methods
1180     virtual NPT_Result OnSetVolume(PLT_ActionReference& action);
1181     virtual NPT_Result OnSetMute(PLT_ActionReference& action);
1182     
1183 private:
1184     NPT_Result GetMetadata(NPT_String& meta);
1185     NPT_Result PlayMedia(const char* uri, 
1186                          const char* metadata = NULL, 
1187                          PLT_Action* action = NULL);
1188 };
1189
1190 /*----------------------------------------------------------------------
1191 |   CUPnPRenderer::CUPnPRenderer
1192 +---------------------------------------------------------------------*/
1193 CUPnPRenderer::CUPnPRenderer(const char*  friendly_name,
1194                              bool         show_ip /* = false */,
1195                              const char*  uuid /* = NULL */,
1196                              unsigned int port /* = 0 */) :
1197     PLT_MediaRenderer(NULL, 
1198                       friendly_name, 
1199                       show_ip, 
1200                       uuid, 
1201                       port)
1202 {
1203     // update what we can play
1204     PLT_Service* service = NULL;
1205     NPT_LOG_SEVERE(FindServiceByType("urn:schemas-upnp-org:service:ConnectionManager:1", service));
1206     if (service) {
1207         service->SetStateVariable("SinkProtocolInfo", 
1208             "http-get:*:*:*;http-get:*:video/mpeg:*;http-get:*:audio/mpeg:*", 
1209             false);
1210     }
1211 }
1212
1213 /*----------------------------------------------------------------------
1214 |   CUPnPRenderer::UpdateState
1215 +---------------------------------------------------------------------*/
1216 void 
1217 CUPnPRenderer::UpdateState()
1218 {
1219     PLT_Service *avt, *rct;
1220     if (NPT_FAILED(FindServiceByType("urn:schemas-upnp-org:service:AVTransport:1", avt)))
1221         return;
1222     if (NPT_FAILED(FindServiceByType("urn:schemas-upnp-org:service:RenderingControl:1", rct)))
1223         return;
1224
1225     CStdString buffer;
1226     int volume;
1227     if (g_stSettings.m_bMute) {
1228         rct->SetStateVariable("Mute", "1");
1229         volume = g_stSettings.m_iPreMuteVolumeLevel;
1230     } else {
1231         rct->SetStateVariable("Mute", "0");
1232         volume = g_application.GetVolume();
1233     }
1234
1235     buffer.Format("%d", volume);
1236     rct->SetStateVariable("Volume", buffer.c_str());
1237
1238     buffer.Format("%d", 256 * (volume * 60 - 60) / 100);
1239     rct->SetStateVariable("VolumeDb", buffer.c_str());
1240
1241     if (g_application.IsPlaying() || g_application.IsPaused()) {
1242         if (g_application.IsPaused()) {
1243             avt->SetStateVariable("TransportState", "PAUSED_PLAYBACK");
1244         } else {
1245             avt->SetStateVariable("TransportState", "PLAYING");
1246         }
1247         
1248         avt->SetStateVariable("TransportStatus", "OK");
1249         avt->SetStateVariable("TransportPlaySpeed", (const char*)NPT_String::FromInteger(g_application.GetPlaySpeed()));
1250         avt->SetStateVariable("NumberOfTracks", "1");
1251         avt->SetStateVariable("CurrentTrack", "1");
1252
1253         buffer = g_infoManager.GetCurrentPlayTime(TIME_FORMAT_HH_MM_SS);
1254         avt->SetStateVariable("RelativeTimePosition", buffer.c_str());
1255         StringUtils::SecondsToTimeString((long)g_infoManager.GetTotalPlayTime(), buffer, TIME_FORMAT_HH_MM_SS);
1256         avt->SetStateVariable("AbsoluteTimePosition", buffer.c_str());
1257
1258         buffer = g_infoManager.GetDuration(TIME_FORMAT_HH_MM_SS);
1259         if (buffer.length() > 0) {
1260           avt->SetStateVariable("CurrentTrackDuration", buffer.c_str());
1261           avt->SetStateVariable("CurrentMediaDuration", buffer.c_str());
1262         } else {
1263           avt->SetStateVariable("CurrentTrackDuration", "00:00:00");
1264           avt->SetStateVariable("CurrentMediaDuration", "00:00:00");
1265         }
1266
1267         avt->SetStateVariable("AVTransportURI", g_application.CurrentFile().c_str());
1268         avt->SetStateVariable("CurrentTrackURI", g_application.CurrentFile().c_str());
1269         
1270         NPT_String metadata;
1271         avt->GetStateVariableValue("AVTransportURIMetaData", metadata);
1272         // try to recreate the didl dynamically if not set
1273         if (metadata.IsEmpty()) {
1274             GetMetadata(metadata);
1275         }
1276         avt->SetStateVariable("CurrentTrackMetadata", metadata);
1277         avt->SetStateVariable("AVTransportURIMetaData", metadata);
1278     } else {
1279         avt->SetStateVariable("TransportState", "STOPPED");
1280         avt->SetStateVariable("TransportPlaySpeed", "1");
1281         avt->SetStateVariable("NumberOfTracks", "0");
1282         avt->SetStateVariable("CurrentTrack", "0");
1283         avt->SetStateVariable("RelativeTimePosition", "00:00:00");
1284         avt->SetStateVariable("AbsoluteTimePosition", "00:00:00");
1285         avt->SetStateVariable("CurrentTrackDuration", "00:00:00");
1286         avt->SetStateVariable("CurrentMediaDuration", "00:00:00");
1287     }
1288 }
1289
1290 /*----------------------------------------------------------------------
1291 |   CUPnPRenderer::GetMetadata
1292 +---------------------------------------------------------------------*/
1293 NPT_Result
1294 CUPnPRenderer::GetMetadata(NPT_String& meta)
1295 {
1296     NPT_Result res = NPT_FAILURE;
1297     const CFileItem &item = g_application.CurrentFileItem();
1298     NPT_String file_path;
1299     PLT_MediaObject* object = CUPnPServer::BuildObject(item, 
1300                                                        file_path, 
1301                                                        false, 
1302                                                        NPT_HttpRequestContext()); 
1303     if (object) {
1304         // verify web server is turned on, should we turn it on ourselves?
1305         if (g_application.m_pWebServer) {
1306             // use http api to create thumbnail in web server doc root
1307             CStdString paras[] = {_P("Q:\\web\\ThumbForUPnP.jpg")};
1308             m_pXbmcHttp->xbmcGetCurrentlyPlaying(1, paras);
1309             
1310 #if defined(HAS_LINUX_NETWORK) || defined(HAS_WIN32_NETWORK)
1311             NPT_String ip;
1312             if (g_application.getNetwork().GetFirstConnectedInterface()) {
1313                 ip = g_application.getNetwork().GetFirstConnectedInterface()->GetCurrentIPAddress().c_str();
1314             }
1315 #else
1316             NPT_String ip = g_application.getNetwork().m_networkinfo.ip;
1317 #endif
1318             object->m_ExtraInfo.album_art_uri = NPT_HttpUrl(
1319                 ip, 
1320                 atoi(g_guiSettings.GetString("servers.webserverport")), 
1321                 "/ThumbForUPnP.jpg").ToString();
1322         }
1323         
1324         PLT_Didl didl;
1325         res = didl.ToDidl(*object, "*", meta);   
1326         delete object;
1327     }
1328     return res;
1329 }
1330
1331 /*----------------------------------------------------------------------
1332 |   CUPnPRenderer::OnNext
1333 +---------------------------------------------------------------------*/
1334 NPT_Result
1335 CUPnPRenderer::OnNext(PLT_ActionReference& action)
1336 {
1337     g_application.getApplicationMessenger().PlayListPlayerNext();
1338     return NPT_SUCCESS;
1339 }
1340
1341 /*----------------------------------------------------------------------
1342 |   CUPnPRenderer::OnPause
1343 +---------------------------------------------------------------------*/
1344 NPT_Result
1345 CUPnPRenderer::OnPause(PLT_ActionReference& action)
1346 {
1347     if (!g_application.IsPaused())
1348       g_application.getApplicationMessenger().MediaPause();
1349     return NPT_SUCCESS;
1350 }
1351
1352 /*----------------------------------------------------------------------
1353 |   CUPnPRenderer::OnPlay
1354 +---------------------------------------------------------------------*/
1355 NPT_Result
1356 CUPnPRenderer::OnPlay(PLT_ActionReference& action)
1357 {
1358     if (g_application.IsPaused()) {
1359       g_application.getApplicationMessenger().MediaPause();
1360     } else if (!g_application.IsPlaying()) {
1361         NPT_String uri, meta;
1362         PLT_Service* service;
1363         // look for value set previously by SetAVTransportURI
1364         NPT_CHECK_SEVERE(FindServiceByType("urn:schemas-upnp-org:service:AVTransport:1", service));
1365         NPT_CHECK_SEVERE(service->GetStateVariableValue("AVTransportURI", uri));
1366         NPT_CHECK_SEVERE(service->GetStateVariableValue("AVTransportURIMetaData", meta));
1367         
1368         // if not set, use the current file being played
1369         PlayMedia(uri, meta);
1370     }
1371     return NPT_SUCCESS;
1372 }
1373
1374 /*----------------------------------------------------------------------
1375 |   CUPnPRenderer::OnPrevious
1376 +---------------------------------------------------------------------*/
1377 NPT_Result
1378 CUPnPRenderer::OnPrevious(PLT_ActionReference& action)
1379 {
1380     g_application.getApplicationMessenger().PlayListPlayerPrevious();
1381     return NPT_SUCCESS;
1382 }
1383
1384 /*----------------------------------------------------------------------
1385 |   CUPnPRenderer::OnStop
1386 +---------------------------------------------------------------------*/
1387 NPT_Result
1388 CUPnPRenderer::OnStop(PLT_ActionReference& action)
1389 {
1390     g_application.getApplicationMessenger().MediaStop();
1391     return NPT_SUCCESS;
1392 }
1393
1394 /*----------------------------------------------------------------------
1395 |   CUPnPRenderer::OnSetAVTransportURI
1396 +---------------------------------------------------------------------*/
1397 NPT_Result
1398 CUPnPRenderer::OnSetAVTransportURI(PLT_ActionReference& action)
1399 {
1400     NPT_String uri, meta;
1401     PLT_Service* service;
1402     NPT_CHECK_SEVERE(FindServiceByType("urn:schemas-upnp-org:service:AVTransport:1", service));
1403
1404     NPT_CHECK_SEVERE(action->GetArgumentValue("CurrentURI", uri));
1405     NPT_CHECK_SEVERE(action->GetArgumentValue("CurrentURIMetaData", meta));
1406     
1407     // if not playing already, just keep around uri & metadata
1408     // and wait for play command
1409     if (!g_application.IsPlaying()) {
1410         service->SetStateVariable("TransportState", "STOPPED");
1411         service->SetStateVariable("TransportStatus", "OK");
1412         service->SetStateVariable("TransportPlaySpeed", "1");
1413         service->SetStateVariable("AVTransportURI", uri);
1414         service->SetStateVariable("AVTransportURIMetaData", meta);
1415         
1416         NPT_CHECK_SEVERE(action->SetArgumentsOutFromStateVariable());
1417         return NPT_SUCCESS;
1418     }
1419     
1420     return PlayMedia(uri, meta, action.AsPointer());
1421 }
1422
1423 /*----------------------------------------------------------------------
1424 |   CUPnPRenderer::PlayMedia
1425 +---------------------------------------------------------------------*/
1426 NPT_Result
1427 CUPnPRenderer::PlayMedia(const char* uri, const char* meta, PLT_Action* action)
1428 {
1429     PLT_Service* service;
1430     NPT_CHECK_SEVERE(FindServiceByType("urn:schemas-upnp-org:service:AVTransport:1", service));
1431
1432     service->SetStateVariable("TransportState", "TRANSITIONING");
1433     service->SetStateVariable("TransportStatus", "OK");
1434     service->SetStateVariable("TransportPlaySpeed", "1");
1435
1436     g_application.getApplicationMessenger().MediaPlay((const char*)uri);
1437     if (!g_application.IsPlaying()) {
1438         service->SetStateVariable("TransportState", "STOPPED");
1439         service->SetStateVariable("TransportStatus", "ERROR_OCCURRED");
1440     } else {
1441         service->SetStateVariable("AVTransportURI", uri);
1442         service->SetStateVariable("AVTransportURIMetaData", meta);
1443     }
1444
1445     if (action) {
1446         NPT_CHECK_SEVERE(action->SetArgumentsOutFromStateVariable());
1447     }
1448     return NPT_SUCCESS;
1449 }
1450
1451 /*----------------------------------------------------------------------
1452 |   CUPnPRenderer::OnSetVolume
1453 +---------------------------------------------------------------------*/
1454 NPT_Result 
1455 CUPnPRenderer::OnSetVolume(PLT_ActionReference& action)
1456 {
1457     NPT_String volume;
1458     NPT_CHECK_SEVERE(action->GetArgumentValue("DesiredVolume", volume));
1459     g_application.SetVolume(atoi((const char*)volume));
1460     return NPT_SUCCESS;
1461 }
1462
1463 /*----------------------------------------------------------------------
1464 |   CUPnPRenderer::OnSetMute
1465 +---------------------------------------------------------------------*/
1466 NPT_Result 
1467 CUPnPRenderer::OnSetMute(PLT_ActionReference& action)
1468 {
1469     NPT_String mute;
1470     NPT_CHECK_SEVERE(action->GetArgumentValue("DesiredMute",mute));
1471     if((mute == "1") ^ g_stSettings.m_bMute)
1472         g_application.Mute();
1473     return NPT_SUCCESS;
1474 }
1475
1476 /*----------------------------------------------------------------------
1477 |   CUPnPRenderer::OnSeek
1478 +---------------------------------------------------------------------*/
1479 NPT_Result
1480 CUPnPRenderer::OnSeek(PLT_ActionReference& action)
1481 {
1482     if (!g_application.IsPlaying()) return NPT_ERROR_INVALID_STATE;
1483     
1484     NPT_String unit, target;
1485     NPT_CHECK_SEVERE(action->GetArgumentValue("Unit", unit));
1486     NPT_CHECK_SEVERE(action->GetArgumentValue("Target", target));
1487     
1488     if (!unit.Compare("REL_TIME")) {
1489         // converts target to seconds
1490         NPT_UInt32 seconds;
1491         NPT_CHECK_SEVERE(PLT_Didl::ParseTimeStamp(target, seconds));
1492         g_application.SeekTime(seconds);
1493     }
1494     
1495     return NPT_SUCCESS;
1496 }
1497
1498 /*----------------------------------------------------------------------
1499 |   CRendererReferenceHolder class
1500 +---------------------------------------------------------------------*/
1501 class CRendererReferenceHolder
1502 {
1503 public:
1504     PLT_DeviceHostReference m_Device;
1505 };
1506
1507 /*----------------------------------------------------------------------
1508 |   CMediaBrowser class
1509 +---------------------------------------------------------------------*/
1510 class CMediaBrowser : public PLT_SyncMediaBrowser
1511 {
1512 public:
1513     CMediaBrowser(PLT_CtrlPointReference& ctrlPoint)
1514       : PLT_SyncMediaBrowser(ctrlPoint, true)
1515     {}
1516
1517     virtual void OnMSAddedRemoved(PLT_DeviceDataReference& device, int added)
1518     {
1519       PLT_SyncMediaBrowser::OnMSAddedRemoved(device, added);
1520       CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH);
1521       message.SetStringParam("upnp://");
1522       m_gWindowManager.SendThreadMessage(message);
1523     }
1524     
1525     virtual void OnMSStateVariablesChanged(PLT_Service*                  service, 
1526                                            NPT_List<PLT_StateVariable*>* vars)
1527     {
1528       /* this could be used to find changes in folders */
1529       PLT_SyncMediaBrowser::OnMSStateVariablesChanged(service, vars);
1530     }
1531 };
1532
1533
1534 /*----------------------------------------------------------------------
1535 |   CUPnP::CUPnP
1536 +---------------------------------------------------------------------*/
1537 CUPnP::CUPnP() :
1538     m_ServerHolder(new CDeviceHostReferenceHolder()),
1539     m_RendererHolder(new CRendererReferenceHolder()),
1540     m_CtrlPointHolder(new CCtrlPointReferenceHolder())
1541 {
1542 //#ifdef HAS_XBOX_HARDWARE
1543 //    broadcast = true;
1544 //#else
1545 //    broadcast = false;
1546 //#endif
1547     // xbox can't receive multicast, but it can send it
1548     broadcast = false;
1549     
1550     // initialize upnp in broadcast listening mode for xbmc
1551     m_UPnP = new PLT_UPnP(1900, !broadcast);
1552
1553     // start upnp monitoring
1554     m_UPnP->Start();
1555 }
1556
1557 /*----------------------------------------------------------------------
1558 |   CUPnP::~CUPnP
1559 +---------------------------------------------------------------------*/
1560 CUPnP::~CUPnP()
1561 {
1562     m_UPnP->Stop();
1563     StopClient();
1564     StopServer();
1565
1566     delete m_UPnP;
1567     delete m_ServerHolder;
1568     delete m_RendererHolder;
1569     delete m_CtrlPointHolder;
1570 }
1571
1572 /*----------------------------------------------------------------------
1573 |   CUPnP::GetInstance
1574 +---------------------------------------------------------------------*/
1575 CUPnP*
1576 CUPnP::GetInstance()
1577 {
1578     if (!upnp) {
1579         upnp = new CUPnP();
1580     }
1581
1582     return upnp;
1583 }
1584
1585 /*----------------------------------------------------------------------
1586 |   CUPnP::ReleaseInstance
1587 +---------------------------------------------------------------------*/
1588 void
1589 CUPnP::ReleaseInstance()
1590 {
1591     if (upnp) {
1592         // since it takes a while to clean up
1593         // starts a detached thread to do this
1594         CUPnPCleaner* cleaner = new CUPnPCleaner(upnp);
1595         cleaner->Start();
1596         upnp = NULL;
1597     }
1598 }
1599
1600 /*----------------------------------------------------------------------
1601 |   CUPnP::StartClient
1602 +---------------------------------------------------------------------*/
1603 void
1604 CUPnP::StartClient()
1605 {
1606     if (!m_CtrlPointHolder->m_CtrlPoint.IsNull()) return;
1607
1608     // create controlpoint, pass NULL to avoid sending a multicast search
1609     m_CtrlPointHolder->m_CtrlPoint = new PLT_CtrlPoint(broadcast?NULL:"upnp:rootdevice");
1610
1611     // ignore our own server
1612     if (!m_ServerHolder->m_Device.IsNull()) {
1613         m_CtrlPointHolder->m_CtrlPoint->IgnoreUUID(m_ServerHolder->m_Device->GetUUID());
1614     }
1615
1616     // start it
1617     m_UPnP->AddCtrlPoint(m_CtrlPointHolder->m_CtrlPoint);
1618
1619     // start browser
1620     m_MediaBrowser = new CMediaBrowser(m_CtrlPointHolder->m_CtrlPoint);
1621
1622 }
1623
1624 /*----------------------------------------------------------------------
1625 |   CUPnP::StopClient
1626 +---------------------------------------------------------------------*/
1627 void
1628 CUPnP::StopClient()
1629 {
1630     if (m_CtrlPointHolder->m_CtrlPoint.IsNull()) return;
1631
1632     m_UPnP->RemoveCtrlPoint(m_CtrlPointHolder->m_CtrlPoint);
1633     m_CtrlPointHolder->m_CtrlPoint = NULL;
1634     delete m_MediaBrowser;
1635     m_MediaBrowser = NULL;
1636 }
1637
1638 /*----------------------------------------------------------------------
1639 |   CUPnP::StartServer
1640 +---------------------------------------------------------------------*/
1641 void
1642 CUPnP::StartServer()
1643 {
1644     if (!m_ServerHolder->m_Device.IsNull()) return;
1645
1646 #if defined(HAS_LINUX_NETWORK) || defined(HAS_WIN32_NETWORK)
1647     NPT_String ip;
1648     if (g_application.getNetwork().GetFirstConnectedInterface()) {
1649       ip = g_application.getNetwork().GetFirstConnectedInterface()->GetCurrentIPAddress().c_str();
1650     }
1651 #else
1652     NPT_String ip = g_network.m_networkinfo.ip;
1653 #endif
1654
1655     NPT_List<NPT_String> list;
1656     if (NPT_SUCCEEDED(PLT_UPnPMessageHelper::GetIPAddresses(list))) {
1657         ip = *(list.GetFirstItem());
1658     }
1659
1660     // load upnpserver.xml so that g_settings.m_vecUPnPMusiCMediaSources, etc.. are loaded
1661     CStdString filename;
1662     CUtil::AddFileToFolder(g_settings.GetUserDataFolder(), "upnpserver.xml", filename);
1663     g_settings.LoadUPnPXml(filename);
1664
1665     // create the server with a XBox compatible friendlyname and UUID from upnpserver.xml if found
1666     m_ServerHolder->m_Device = new CUPnPServer("XBMC: Media Server:",
1667         g_settings.m_UPnPUUID.length()?g_settings.m_UPnPUUID.c_str():NULL);
1668
1669     // trying to set optional upnp values for XP UPnP UI Icons to detect us
1670     // but it doesn't work anyways as it requires multicast for XP to detect us
1671     m_ServerHolder->m_Device->m_PresentationURL = 
1672         NPT_HttpUrl(ip,
1673                     atoi(g_guiSettings.GetString("servers.webserverport")), 
1674                     "/").ToString();
1675     // c0diq: For the XBox260 to discover XBMC, the ModelName must stay "Windows Media Connect"
1676     //m_ServerHolder->m_Device->m_ModelName = "XBMC";
1677     m_ServerHolder->m_Device->m_ModelNumber = "2.0";
1678     m_ServerHolder->m_Device->m_ModelDescription = "XBMC Media Center - Media Server";
1679     m_ServerHolder->m_Device->m_ModelURL = "http://www.xbmc.org/";
1680     m_ServerHolder->m_Device->m_Manufacturer = "Team XBMC";
1681     m_ServerHolder->m_Device->m_ManufacturerURL = "http://www.xbmc.org/";
1682
1683     // tell controller to ignore ourselves from list of upnp servers
1684     if (!m_CtrlPointHolder->m_CtrlPoint.IsNull()) {
1685         m_CtrlPointHolder->m_CtrlPoint->IgnoreUUID(m_ServerHolder->m_Device->GetUUID());
1686     }
1687
1688     // start server
1689     NPT_Result res = m_UPnP->AddDevice(m_ServerHolder->m_Device);
1690     if (NPT_FAILED(res)) {
1691         // if we failed to start, most likely it's because we couldn't bind on the port
1692         // instead we bind on anything but then we make it so the Xbox360 don't see us
1693         // since there's not point as it won't stream from us if we're not port 80
1694         ((CUPnPServer*)(m_ServerHolder->m_Device.AsPointer()))->m_FileServerPort = 0;
1695         //((CUPnPServer*)(m_ServerHolder->m_Device.AsPointer()))->m_ModelName = "XBMC";
1696         m_UPnP->AddDevice(m_ServerHolder->m_Device);
1697     }
1698
1699     // save UUID
1700     g_settings.m_UPnPUUID = m_ServerHolder->m_Device->GetUUID();
1701     g_settings.SaveUPnPXml(filename);
1702 }
1703
1704 /*----------------------------------------------------------------------
1705 |   CUPnP::StopServer
1706 +---------------------------------------------------------------------*/
1707 void
1708 CUPnP::StopServer()
1709 {
1710     if (m_ServerHolder->m_Device.IsNull()) return;
1711
1712     m_UPnP->RemoveDevice(m_ServerHolder->m_Device);
1713     m_ServerHolder->m_Device = NULL;
1714 }
1715
1716 /*----------------------------------------------------------------------
1717 |   CUPnP::StartRenderer
1718 +---------------------------------------------------------------------*/
1719 void CUPnP::StartRenderer()
1720 {
1721     if (!m_RendererHolder->m_Device.IsNull()) return;
1722
1723     CStdString filename;
1724     CUtil::AddFileToFolder(g_settings.GetUserDataFolder(), "upnpserver.xml", filename);
1725     g_settings.LoadUPnPXml(filename);
1726
1727 #if defined(HAS_LINUX_NETWORK) || defined(HAS_WIN32_NETWORK)
1728     NPT_String ip;
1729     if (g_application.getNetwork().GetFirstConnectedInterface()) {
1730       ip = g_application.getNetwork().GetFirstConnectedInterface()->GetCurrentIPAddress().c_str();
1731     }
1732 #else
1733     NPT_String ip = g_application.getNetwork().m_networkinfo.ip;
1734 #endif
1735
1736     NPT_List<NPT_String> list;
1737     if (NPT_SUCCEEDED(PLT_UPnPMessageHelper::GetIPAddresses(list))) {
1738         ip = *(list.GetFirstItem());
1739     }
1740
1741     m_RendererHolder->m_Device = new CUPnPRenderer(
1742         "XBMC: Media Renderer",
1743         true, 
1744         (g_settings.m_UPnPUUIDRenderer.length() ? g_settings.m_UPnPUUIDRenderer.c_str() : NULL));
1745
1746     m_RendererHolder->m_Device->m_PresentationURL = 
1747         NPT_HttpUrl(ip, 
1748                     atoi(g_guiSettings.GetString("servers.webserverport")), 
1749                     "/").ToString();
1750     m_RendererHolder->m_Device->m_ModelName = "XBMC";
1751     m_RendererHolder->m_Device->m_ModelNumber = "2.0";
1752     m_RendererHolder->m_Device->m_ModelDescription = "XBMC Media Center - Media Renderer";
1753     m_RendererHolder->m_Device->m_ModelURL = "http://www.xbmc.org/";    
1754     m_RendererHolder->m_Device->m_Manufacturer = "Team XBMC";
1755     m_RendererHolder->m_Device->m_ManufacturerURL = "http://www.xbmc.org/";
1756
1757     m_UPnP->AddDevice(m_RendererHolder->m_Device);
1758
1759     // save UUID
1760     g_settings.m_UPnPUUIDRenderer = m_RendererHolder->m_Device->GetUUID();
1761     g_settings.SaveUPnPXml(filename);
1762 }
1763
1764 /*----------------------------------------------------------------------
1765 |   CUPnP::StopRenderer
1766 +---------------------------------------------------------------------*/
1767 void CUPnP::StopRenderer()
1768 {
1769     if (m_RendererHolder->m_Device.IsNull()) return;
1770
1771     m_UPnP->RemoveDevice(m_RendererHolder->m_Device);
1772     m_RendererHolder->m_Device = NULL;
1773 }
1774
1775 /*----------------------------------------------------------------------
1776 |   CUPnP::UpdateState
1777 +---------------------------------------------------------------------*/
1778 void CUPnP::UpdateState()
1779 {
1780   if (!m_RendererHolder->m_Device.IsNull())
1781       ((CUPnPRenderer*)m_RendererHolder->m_Device.AsPointer())->UpdateState();  
1782 }
1783