]> git.sesse.net Git - vlc/blobdiff - modules/services_discovery/upnp_intel.cpp
Use pl_Locked and pl_Unlocked
[vlc] / modules / services_discovery / upnp_intel.cpp
index de045bf9e6031959ac22cffc54636e51e480e717..a65036a014da9ff07d314e40972d44435ba307b2 100644 (file)
@@ -5,8 +5,8 @@
  * $Id$
  *
  * Authors: RĂ©mi Denis-Courmont <rem # videolan.org> (original plugin)
- *          Christian Henz <henz # c-lab.de> 
- * 
+ *          Christian Henz <henz # c-lab.de>
+ *
  * UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink
  *
  * This program is free software; you can redistribute it and/or modify
@@ -27,9 +27,9 @@
 /*
   \TODO: Debug messages: "__FILE__, __LINE__" ok ???, Wrn/Err ???
   \TODO: Change names to VLC standard ???
+  \TODO: Rewrite this using the new service discovery API (see sap.c, shout.c).
 */
 
-#include <stdlib.h>
 
 #include <vector>
 #include <string>
 #include <upnp/upnptools.h>
 
 #undef PACKAGE_NAME
-#include <vlc/vlc.h>
-#include <vlc/intf.h>
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_playlist.h>
 
 
 // VLC handle
 
 struct services_discovery_sys_t
 {
-    playlist_item_t *p_node;
     playlist_t *p_playlist;
+    playlist_item_t *p_node_cat;
+    playlist_item_t *p_node_one;
 };
 
 
@@ -57,7 +63,7 @@ const char* MEDIA_SERVER_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaServer:
 const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:ContentDirectory:1";
 
 
-// Classes 
+// Classes
 
 class MediaServer;
 class MediaServerList;
@@ -66,51 +72,51 @@ class Container;
 
 // Cookie that is passed to the callback
 
-typedef struct 
+typedef struct
 {
     services_discovery_t* serviceDiscovery;
     UpnpClient_Handle clientHandle;
-    MediaServerList* serverList; 
+    MediaServerList* serverList;
 } Cookie;
 
 
 // Class definitions...
 
-class Lockable 
-{  
+class Lockable
+{
 public:
 
-    Lockable( Cookie* c ) 
+    Lockable( Cookie* c )
     {
-       vlc_mutex_init( c->serviceDiscovery, &_mutex );
+    vlc_mutex_init( &_mutex );
     }
 
-    ~Lockable() 
+    ~Lockable()
     {
-       vlc_mutex_destroy( &_mutex );
+    vlc_mutex_destroy( &_mutex );
     }
 
     void lock() { vlc_mutex_lock( &_mutex ); }
     void unlock() { vlc_mutex_unlock( &_mutex ); }
 
 private:
-    
+
     vlc_mutex_t _mutex;
 };
 
 
-class Locker 
+class Locker
 {
 public:
-    Locker( Lockable* l ) 
+    Locker( Lockable* l )
     {
-       _lockable = l;
-       _lockable->lock();
+    _lockable = l;
+    _lockable->lock();
     }
 
-    ~Locker() 
+    ~Locker()
     {
-       _lockable->unlock();
+    _lockable->unlock();
     }
 
 private:
@@ -123,10 +129,10 @@ class MediaServer
 public:
 
     static void parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie );
-    
+
     MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie );
     ~MediaServer();
-    
+
     const char* getUDN() const;
     const char* getFriendlyName() const;
 
@@ -150,13 +156,13 @@ private:
     IXML_Document* _browseAction( const char*, const char*, const char*, const char*, const char*, const char* );
 
     Cookie* _cookie;
-  
+
     Container* _contents;
     playlist_item_t* _playlistNode;
 
     std::string _UDN;
     std::string _friendlyName;
-  
+
     std::string _contentDirectoryEventURL;
     std::string _contentDirectoryControlURL;
 
@@ -186,7 +192,7 @@ private:
 };
 
 
-class Item 
+class Item
 {
 public:
 
@@ -202,7 +208,7 @@ public:
 private:
 
     playlist_item_t* _playlistNode;
+
     Container* _parent;
     std::string _objectID;
     std::string _title;
@@ -210,7 +216,7 @@ private:
 };
 
 
-class Container 
+class Container
 {
 public:
 
@@ -250,12 +256,16 @@ private:
 static int Open( vlc_object_t* );
 static void Close( vlc_object_t* );
 static void Run( services_discovery_t *p_sd );
+static playlist_t *pl_Get( services_discovery_t *p_sd )
+{
+    return p_sd->p_sys->p_playlist;
+}
 
 // Module descriptor
 
 vlc_module_begin();
 set_shortname( "UPnP" );
-set_description( _( "Universal Plug'n'Play discovery ( Intel SDK )" ) );
+set_description( N_( "Universal Plug'n'Play discovery ( Intel SDK )" ) );
 set_category( CAT_PLAYLIST );
 set_subcategory( SUBCAT_PLAYLIST_SD );
 set_capability( "services_discovery", 0 );
@@ -268,7 +278,6 @@ vlc_module_end();
 static Lockable* CallbackLock;
 static int Callback( Upnp_EventType eventType, void* event, void* pCookie );
 
-char* xml_makeSpecialChars( const char* in );
 const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName );
 IXML_Document* parseBrowseResult( IXML_Document* doc );
 
@@ -279,31 +288,16 @@ static int Open( vlc_object_t *p_this )
 {
     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
     services_discovery_sys_t *p_sys  = ( services_discovery_sys_t * )
-       malloc( sizeof( services_discovery_sys_t ) );
-    
-    playlist_view_t *p_view;
-    vlc_value_t val;
+    malloc( sizeof( services_discovery_sys_t ) );
 
     p_sd->pf_run = Run;
     p_sd->p_sys = p_sys;
+    p_sys->p_playlist = pl_Yield( p_sd );
 
     /* Create our playlist node */
-    p_sys->p_playlist = ( playlist_t * )vlc_object_find( p_sd,
-                                                        VLC_OBJECT_PLAYLIST,
-                                                        FIND_ANYWHERE );
-    if( !p_sys->p_playlist )
-    {
-       msg_Warn( p_sd, "unable to find playlist, cancelling UPnP listening" );
-       return VLC_EGENERIC;
-    }
-
-    p_view = playlist_ViewFind( p_sys->p_playlist, VIEW_CATEGORY );
-    p_sys->p_node = playlist_NodeCreate( p_sys->p_playlist, VIEW_CATEGORY,
-                                         "UPnP", p_view->p_root );
-    p_sys->p_node->i_flags |= PLAYLIST_RO_FLAG;
-    p_sys->p_node->i_flags &= ~PLAYLIST_SKIP_FLAG;
-    val.b_bool = VLC_TRUE;
-    var_Set( p_sys->p_playlist, "intf-change", val );
+    playlist_NodesPairCreate( pl_Get( p_sd ), _("Devices"),
+                              &p_sys->p_node_cat, &p_sys->p_node_one,
+                              true );
 
     return VLC_SUCCESS;
 }
@@ -313,25 +307,23 @@ static void Close( vlc_object_t *p_this )
     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
     services_discovery_sys_t *p_sys = p_sd->p_sys;
 
-    if( p_sys->p_playlist )
-    {
-       playlist_NodeDelete( p_sys->p_playlist, p_sys->p_node, VLC_TRUE,
-                            VLC_TRUE );
-       vlc_object_release( p_sys->p_playlist );
-    }
-
+    playlist_NodeDelete( pl_Get( p_sd ), p_sys->p_node_one, true,
+                         true );
+    playlist_NodeDelete( pl_Get( p_sd ), p_sys->p_node_cat, true,
+                         true );
+    pl_Release( p_sd );
     free( p_sys );
 }
 
 static void Run( services_discovery_t* p_sd )
 {
     int res;
-  
+
     res = UpnpInit( 0, 0 );
-    if( res != UPNP_E_SUCCESS ) 
+    if( res != UPNP_E_SUCCESS )
     {
-       msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
-       return;
+        msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
+        return;
     }
 
     Cookie cookie;
@@ -341,23 +333,23 @@ static void Run( services_discovery_t* p_sd )
     CallbackLock = new Lockable( &cookie );
 
     res = UpnpRegisterClient( Callback, &cookie, &cookie.clientHandle );
-    if( res != UPNP_E_SUCCESS ) 
+    if( res != UPNP_E_SUCCESS )
     {
-       msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
-       goto shutDown;
+        msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
+        goto shutDown;
     }
 
     res = UpnpSearchAsync( cookie.clientHandle, 5, MEDIA_SERVER_DEVICE_TYPE, &cookie );
-    if( res != UPNP_E_SUCCESS ) 
+    if( res != UPNP_E_SUCCESS )
     {
-       msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
-       goto shutDown;
+        msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
+        goto shutDown;
     }
+
     msg_Dbg( p_sd, "UPnP discovery started" );
-    while( !p_sd->b_die ) 
+    while( vlc_object_alive (p_sd) )
     {
-       msleep( 500 );
+        msleep( 500 );
     }
 
     msg_Dbg( p_sd, "UPnP discovery stopped" );
@@ -372,7 +364,7 @@ static void Run( services_discovery_t* p_sd )
 // XML utility functions:
 
 // Returns the value of a child element, or 0 on error
-const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName ) 
+const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName )
 {
     if ( !parent ) return 0;
     if ( !tagName ) return 0;
@@ -392,72 +384,14 @@ const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName
     return ixmlNode_getNodeValue( textNode );
 }
 
-
-// Replaces "&lt;" with "<" etc.
-// Returns a newly created string that has to be freed by the caller.
-// Returns 0 on error ( out of mem )
-// \TODO: Probably not very robust!!!
-char* xml_makeSpecialChars( const char* in ) 
-{
-    if ( !in ) return 0;
-
-    char* result = ( char* )malloc( strlen( in ) + 1 );
-    if ( !result ) return 0;
-
-    char* out = result;
-
-    while( *in ) 
-    {
-       if ( strncmp( "&amp;", in, 5 ) == 0 ) 
-       {
-           *out = '&';
-
-           in += 5;
-           out++;
-       } 
-       else if ( strncmp( "&quot;", in, 6 ) == 0 ) 
-       {
-           *out = '"';
-
-           in += 6;
-           out++;
-       } 
-       else if ( strncmp( "&gt;", in, 4 ) == 0 ) 
-       {
-           *out = '>';
-           
-           in += 4;
-           out++;
-       } 
-       else if ( strncmp( "&lt;", in, 4 ) == 0 ) 
-       {
-           *out = '<';
-
-           in += 4;
-           out++;
-       } 
-       else 
-       {
-           *out = *in;
-
-           in++;
-           out++;
-       }
-    }
-
-    *out = '\0';
-    return result;
-}
-
-
 // Extracts the result document from a SOAP response
-IXML_Document* parseBrowseResult( IXML_Document* doc ) 
+IXML_Document* parseBrowseResult( IXML_Document* doc )
 {
     if ( !doc ) return 0;
-  
+
     IXML_NodeList* resultList = ixmlDocument_getElementsByTagName( doc, "Result" );
     if ( !resultList ) return 0;
-  
+
     IXML_Node* resultNode = ixmlNodeList_item( resultList, 0 );
 
     ixmlNodeList_free( resultList );
@@ -468,7 +402,7 @@ IXML_Document* parseBrowseResult( IXML_Document* doc )
     if ( !textNode ) return 0;
 
     const char* resultString = ixmlNode_getNodeValue( textNode );
-    char* resultXML = xml_makeSpecialChars( resultString );
+    char* resultXML = strdup( resultString );
 
     IXML_Document* browseDoc = ixmlParseBuffer( resultXML );
 
@@ -479,67 +413,75 @@ IXML_Document* parseBrowseResult( IXML_Document* doc )
 
 
 // Handles all UPnP events
-static int Callback( Upnp_EventType eventType, void* event, void* pCookie ) 
+static int Callback( Upnp_EventType eventType, void* event, void* pCookie )
 {
     Locker locker( CallbackLock );
 
     Cookie* cookie = ( Cookie* )pCookie;
 
     switch( eventType ) {
-    
+
     case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
     case UPNP_DISCOVERY_SEARCH_RESULT:
-       {
-           struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
-  
-           IXML_Document *descriptionDoc = 0;
-      
-           int res;
-           res = UpnpDownloadXmlDoc( discovery->Location, &descriptionDoc );
-           if ( res != UPNP_E_SUCCESS ) 
-           {
-             msg_Dbg( cookie->serviceDiscovery, "%s:%d: Could not download device description!", __FILE__, __LINE__ );
-             return res;
-           }
-
-           MediaServer::parseDeviceDescription( descriptionDoc, discovery->Location, cookie );
-
-           ixmlDocument_free( descriptionDoc );
-       }
-       break;
-    
+    {
+        struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
+
+        IXML_Document *descriptionDoc = 0;
+
+        int res;
+        res = UpnpDownloadXmlDoc( discovery->Location, &descriptionDoc );
+        if ( res != UPNP_E_SUCCESS )
+        {
+          msg_Dbg( cookie->serviceDiscovery, "%s:%d: Could not download device description!", __FILE__, __LINE__ );
+          return res;
+        }
+
+        MediaServer::parseDeviceDescription( descriptionDoc, discovery->Location, cookie );
+
+        ixmlDocument_free( descriptionDoc );
+    }
+    break;
+
     case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
-       {
-           struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
+    {
+        struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
 
-           cookie->serverList->removeServer( discovery->DeviceId );
-       }
-       break;
+        cookie->serverList->removeServer( discovery->DeviceId );
+    }
+    break;
 
     case UPNP_EVENT_RECEIVED:
-       {
-           Upnp_Event* e = ( Upnp_Event* )event;
+    {
+        Upnp_Event* e = ( Upnp_Event* )event;
 
-           MediaServer* server = cookie->serverList->getServerBySID( e->Sid );
-           if ( server ) server->fetchContents(); 
-       }
-       break;
+        MediaServer* server = cookie->serverList->getServerBySID( e->Sid );
+        if ( server ) server->fetchContents();
+    }
+    break;
 
     case UPNP_EVENT_AUTORENEWAL_FAILED:
     case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
-       {
-           // Re-subscribe...
+    {
+        // Re-subscribe...
+
+        Upnp_Event_Subscribe* s = ( Upnp_Event_Subscribe* )event;
 
-           Upnp_Event_Subscribe* s = ( Upnp_Event_Subscribe* )event;
+        MediaServer* server = cookie->serverList->getServerBySID( s->Sid );
+        if ( server ) server->subscribeToContentDirectory();
+    }
+    break;
 
-           MediaServer* server = cookie->serverList->getServerBySID( s->Sid );
-           if ( server ) server->subscribeToContentDirectory();
-       }
-       break;
-       
+    case UPNP_EVENT_SUBSCRIBE_COMPLETE:
+        msg_Warn( cookie->serviceDiscovery, "subscription complete" );
+        break;
+    case UPNP_DISCOVERY_SEARCH_TIMEOUT:
+        msg_Warn( cookie->serviceDiscovery, "search timeout" );
+        break;
     default:
-       msg_Dbg( cookie->serviceDiscovery, "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )", __FILE__, __LINE__, eventType );
-       break;
+    msg_Dbg( cookie->serviceDiscovery, "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )", __FILE__, __LINE__, eventType );
+    break;
     }
 
     return UPNP_E_SUCCESS;
@@ -550,127 +492,127 @@ static int Callback( Upnp_EventType eventType, void* event, void* pCookie )
 
 // MediaServer...
 
-void MediaServer::parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie ) 
+void MediaServer::parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie )
 {
     if ( !doc ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: NULL", __FILE__, __LINE__ ); return; }
     if ( !location ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: NULL", __FILE__, __LINE__ ); return; }
-  
+
     const char* baseURL = location;
 
     // Try to extract baseURL
 
     IXML_NodeList* urlList = ixmlDocument_getElementsByTagName( doc, "baseURL" );
-    if ( urlList ) 
-    {  
-       if ( IXML_Node* urlNode = ixmlNodeList_item( urlList, 0 ) ) 
-       {
-           IXML_Node* textNode = ixmlNode_getFirstChild( urlNode );
-           if ( textNode ) baseURL = ixmlNode_getNodeValue( textNode );
-       }
-      
-       ixmlNodeList_free( urlList );
+    if ( urlList )
+    {
+    if ( IXML_Node* urlNode = ixmlNodeList_item( urlList, 0 ) )
+    {
+        IXML_Node* textNode = ixmlNode_getFirstChild( urlNode );
+        if ( textNode ) baseURL = ixmlNode_getNodeValue( textNode );
     }
-  
+
+    ixmlNodeList_free( urlList );
+    }
+
     // Get devices
 
     IXML_NodeList* deviceList = ixmlDocument_getElementsByTagName( doc, "device" );
     if ( deviceList )
     {
-  
-       for ( unsigned int i = 0; i < ixmlNodeList_length( deviceList ); i++ ) 
-       {
-           IXML_Element* deviceElement = ( IXML_Element* )ixmlNodeList_item( deviceList, i );
-    
-           const char* deviceType = xml_getChildElementValue( deviceElement, "deviceType" );
-           if ( !deviceType ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no deviceType!", __FILE__, __LINE__ ); continue; }
-           if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, deviceType ) != 0 ) continue;
-   
-           const char* UDN = xml_getChildElementValue( deviceElement, "UDN" );
-           if ( !UDN ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no UDN!", __FILE__, __LINE__ ); continue; }    
-           if ( cookie->serverList->getServer( UDN ) != 0 ) continue;
-    
-           const char* friendlyName = xml_getChildElementValue( deviceElement, "friendlyName" );
-           if ( !friendlyName ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no friendlyName!", __FILE__, __LINE__ ); continue; }
-    
-           MediaServer* server = new MediaServer( UDN, friendlyName, cookie );
-           if ( !cookie->serverList->addServer( server ) ) {
-
-               delete server;
-               server = 0;
-               continue;
-           }
-           
-           // Check for ContentDirectory service...
-           
-           IXML_NodeList* serviceList = ixmlElement_getElementsByTagName( deviceElement, "service" );
-           if ( serviceList ) 
-           {
-               for ( unsigned int j = 0; j < ixmlNodeList_length( serviceList ); j++ ) 
-               {
-                   IXML_Element* serviceElement = ( IXML_Element* )ixmlNodeList_item( serviceList, j );
-               
-                   const char* serviceType = xml_getChildElementValue( serviceElement, "serviceType" );
-                   if ( !serviceType ) continue;
-                   if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE, serviceType ) != 0 ) continue;
-                   
-                   const char* eventSubURL = xml_getChildElementValue( serviceElement, "eventSubURL" );
-                   if ( !eventSubURL ) continue;
-               
-                   const char* controlURL = xml_getChildElementValue( serviceElement, "controlURL" );
-                   if ( !controlURL ) continue;
-
-                   // Try to subscribe to ContentDirectory service
-      
-                   char* url = ( char* )malloc( strlen( baseURL ) + strlen( eventSubURL ) + 1 );
-                   if ( url ) 
-                   {
-                           char* s1 = strdup( baseURL );
-                           char* s2 = strdup( eventSubURL );
-                   
-                           if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS ) 
-                           {
-                               // msg_Dbg( cookie->serviceDiscovery, "CDS EVENT URL: %s", url );
-                               
-                               server->setContentDirectoryEventURL( url );
-                               server->subscribeToContentDirectory();
-                           }
-
-                           free( s1 );
-                           free( s2 );
-                           free( url );
-                   }
-                   
-                   // Try to browse content directory...
-                   
-                   url = ( char* )malloc( strlen( baseURL ) + strlen( controlURL ) + 1 );
-                   if ( url ) 
-                   {
-                       char* s1 = strdup( baseURL );
-                       char* s2 = strdup( controlURL );
-                   
-                       if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS ) 
-                       {
-                           // msg_Dbg( cookie->serviceDiscovery, "CDS CTRL URL: %s", url );
-                           
-                           server->setContentDirectoryControlURL( url );
-                           server->fetchContents();
-                       }
-       
-                       free( s1 );
-                       free( s2 );
-                       free( url );
-                   }
-               }
-                               
-               ixmlNodeList_free( serviceList );
-           }
-       }
-
-       ixmlNodeList_free( deviceList );
+
+    for ( unsigned int i = 0; i < ixmlNodeList_length( deviceList ); i++ )
+    {
+        IXML_Element* deviceElement = ( IXML_Element* )ixmlNodeList_item( deviceList, i );
+
+        const char* deviceType = xml_getChildElementValue( deviceElement, "deviceType" );
+        if ( !deviceType ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no deviceType!", __FILE__, __LINE__ ); continue; }
+        if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, deviceType ) != 0 ) continue;
+
+        const char* UDN = xml_getChildElementValue( deviceElement, "UDN" );
+        if ( !UDN ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no UDN!", __FILE__, __LINE__ ); continue; }
+        if ( cookie->serverList->getServer( UDN ) != 0 ) continue;
+
+        const char* friendlyName = xml_getChildElementValue( deviceElement, "friendlyName" );
+        if ( !friendlyName ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no friendlyName!", __FILE__, __LINE__ ); continue; }
+
+        MediaServer* server = new MediaServer( UDN, friendlyName, cookie );
+        if ( !cookie->serverList->addServer( server ) ) {
+
+        delete server;
+        server = 0;
+        continue;
+        }
+
+        // Check for ContentDirectory service...
+
+        IXML_NodeList* serviceList = ixmlElement_getElementsByTagName( deviceElement, "service" );
+        if ( serviceList )
+        {
+            for ( unsigned int j = 0; j < ixmlNodeList_length( serviceList ); j++ )
+        {
+            IXML_Element* serviceElement = ( IXML_Element* )ixmlNodeList_item( serviceList, j );
+
+            const char* serviceType = xml_getChildElementValue( serviceElement, "serviceType" );
+            if ( !serviceType ) continue;
+            if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE, serviceType ) != 0 ) continue;
+
+            const char* eventSubURL = xml_getChildElementValue( serviceElement, "eventSubURL" );
+            if ( !eventSubURL ) continue;
+
+            const char* controlURL = xml_getChildElementValue( serviceElement, "controlURL" );
+            if ( !controlURL ) continue;
+
+            // Try to subscribe to ContentDirectory service
+
+            char* url = ( char* )malloc( strlen( baseURL ) + strlen( eventSubURL ) + 1 );
+            if ( url )
+            {
+                char* s1 = strdup( baseURL );
+                char* s2 = strdup( eventSubURL );
+
+                if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS )
+                {
+                // msg_Dbg( cookie->serviceDiscovery, "CDS EVENT URL: %s", url );
+
+                server->setContentDirectoryEventURL( url );
+                server->subscribeToContentDirectory();
+                }
+
+                free( s1 );
+                free( s2 );
+                free( url );
+            }
+
+            // Try to browse content directory...
+
+            url = ( char* )malloc( strlen( baseURL ) + strlen( controlURL ) + 1 );
+            if ( url )
+            {
+            char* s1 = strdup( baseURL );
+            char* s2 = strdup( controlURL );
+
+            if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS )
+            {
+                // msg_Dbg( cookie->serviceDiscovery, "CDS CTRL URL: %s", url );
+
+                server->setContentDirectoryControlURL( url );
+                server->fetchContents();
+            }
+
+            free( s1 );
+            free( s2 );
+            free( url );
+            }
+        }
+
+        ixmlNodeList_free( serviceList );
+        }
+    }
+
+    ixmlNodeList_free( deviceList );
     }
 }
 
-MediaServer::MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie ) 
+MediaServer::MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie )
 {
     _cookie = cookie;
 
@@ -681,14 +623,12 @@ MediaServer::MediaServer( const char* UDN, const char* friendlyName, Cookie* coo
     _playlistNode = 0;
 }
 
-MediaServer::~MediaServer() 
-{ 
-    if ( _contents ) 
+MediaServer::~MediaServer()
+{
+    if ( _contents )
     {
-       playlist_NodeDelete( _cookie->serviceDiscovery->p_sys->p_playlist, 
-                           _playlistNode,
-                           true,
-                           true );
+        playlist_NodeDelete( pl_Get( _cookie->serviceDiscovery ) ,
+                             _playlistNode, true, true );
     }
 
     delete _contents;
@@ -706,7 +646,7 @@ const char* MediaServer::getFriendlyName() const
     return s;
 }
 
-void MediaServer::setContentDirectoryEventURL( const char* url ) 
+void MediaServer::setContentDirectoryEventURL( const char* url )
 {
     _contentDirectoryEventURL = url;
 }
@@ -717,7 +657,7 @@ const char* MediaServer::getContentDirectoryEventURL() const
     return s;
 }
 
-void MediaServer::setContentDirectoryControlURL( const char* url ) 
+void MediaServer::setContentDirectoryControlURL( const char* url )
 {
     _contentDirectoryControlURL = url;
 }
@@ -727,33 +667,33 @@ const char* MediaServer::getContentDirectoryControlURL() const
     return _contentDirectoryControlURL.c_str();
 }
 
-void MediaServer::subscribeToContentDirectory() 
+void MediaServer::subscribeToContentDirectory()
 {
     const char* url = getContentDirectoryEventURL();
-    if ( !url || strcmp( url, "" ) == 0 ) 
-    { 
-       msg_Dbg( _cookie->serviceDiscovery, "No subscription url set!" ); 
-       return;
+    if ( !url || strcmp( url, "" ) == 0 )
+    {
+    msg_Dbg( _cookie->serviceDiscovery, "No subscription url set!" );
+    return;
     }
 
     int timeOut = 1810;
     Upnp_SID sid;
-  
+
     int res = UpnpSubscribe( _cookie->clientHandle, url, &timeOut, sid );
 
-    if ( res == UPNP_E_SUCCESS ) 
+    if ( res == UPNP_E_SUCCESS )
     {
-       _subscriptionTimeOut = timeOut;
-       memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) );
-    } 
-    else 
+    _subscriptionTimeOut = timeOut;
+    memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) );
+    }
+    else
     {
-       msg_Dbg( _cookie->serviceDiscovery, "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__, getFriendlyName(), UpnpGetErrorMessage( res ) );
+    msg_Dbg( _cookie->serviceDiscovery, "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__, getFriendlyName(), UpnpGetErrorMessage( res ) );
     }
 }
 
-IXML_Document* MediaServer::_browseAction( const char* pObjectID, const char* pBrowseFlag, const char* pFilter, 
-                                          const char* pStartingIndex, const char* pRequestedCount, const char* pSortCriteria ) 
+IXML_Document* MediaServer::_browseAction( const char* pObjectID, const char* pBrowseFlag, const char* pFilter,
+                       const char* pStartingIndex, const char* pRequestedCount, const char* pSortCriteria )
 {
     IXML_Document* action = 0;
     IXML_Document* response = 0;
@@ -789,18 +729,18 @@ IXML_Document* MediaServer::_browseAction( const char* pObjectID, const char* pB
 
     res = UpnpAddToAction( &action, "Browse", serviceType, "SortCriteria", SortCriteria );
     if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
-  
-    res = UpnpSendAction( _cookie->clientHandle, 
-                         url,
-                         CONTENT_DIRECTORY_SERVICE_TYPE, 
-                         0, 
-                         action, 
-                         &response );
-    if ( res != UPNP_E_SUCCESS ) 
-    { 
-       msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); 
-       ixmlDocument_free( response );
-       response = 0;
+
+    res = UpnpSendAction( _cookie->clientHandle,
+              url,
+              CONTENT_DIRECTORY_SERVICE_TYPE,
+              0,
+              action,
+              &response );
+    if ( res != UPNP_E_SUCCESS )
+    {
+    msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) );
+    ixmlDocument_free( response );
+    response = 0;
     }
 
  browseActionCleanup:
@@ -818,22 +758,18 @@ IXML_Document* MediaServer::_browseAction( const char* pObjectID, const char* pB
     return response;
 }
 
-void MediaServer::fetchContents() 
+void MediaServer::fetchContents()
 {
     Container* root = new Container( 0, "0", getFriendlyName() );
+    playlist_t * p_playlist = pl_Get( _cookie->serviceDiscovery );
     _fetchContents( root );
 
-    if ( _contents ) 
+    if ( _contents )
     {
-       vlc_mutex_lock( &_cookie->serviceDiscovery->p_sys->p_playlist->object_lock );
-
-       playlist_NodeEmpty( _cookie->serviceDiscovery->p_sys->p_playlist, 
-                          _playlistNode,
-                          true );
-
-       vlc_mutex_unlock( &_cookie->serviceDiscovery->p_sys->p_playlist->object_lock );
-    
-       delete _contents;
+        PL_LOCK;
+        playlist_NodeEmpty( p_playlist, _playlistNode, true );
+        PL_UNLOCK;
+        delete _contents;
     }
 
     _contents = root;
@@ -842,7 +778,7 @@ void MediaServer::fetchContents()
     _buildPlaylist( _contents );
 }
 
-bool MediaServer::_fetchContents( Container* parent ) 
+bool MediaServer::_fetchContents( Container* parent )
 {
     if (!parent) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: parent==NULL", __FILE__, __LINE__ ); return false; }
 
@@ -854,111 +790,112 @@ bool MediaServer::_fetchContents( Container* parent )
     if ( !result ) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR!", __FILE__, __LINE__ ); return false; }
 
     IXML_NodeList* containerNodeList = ixmlDocument_getElementsByTagName( result, "container" );
-    if ( containerNodeList ) 
+    if ( containerNodeList )
     {
-       for ( unsigned int i = 0; i < ixmlNodeList_length( containerNodeList ); i++ ) 
-       {
-           IXML_Element* containerElement = ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
-      
-           const char* objectID = ixmlElement_getAttribute( containerElement, "id" );
-           if ( !objectID ) continue;
-      
-           const char* childCountStr = ixmlElement_getAttribute( containerElement, "childCount" );
-           if ( !childCountStr ) continue;
-           int childCount = atoi( childCountStr );
-      
-           const char* title = xml_getChildElementValue( containerElement, "dc:title" );
-           if ( !title ) continue;
-      
-           const char* resource = xml_getChildElementValue( containerElement, "res" );
-
-           if ( resource && childCount < 1 ) 
-           { 
-               Item* item = new Item( parent, objectID, title, resource );
-               parent->addItem( item );
-           } 
-           else 
-           {
-               Container* container = new Container( parent, objectID, title );
-               parent->addContainer( container );
-
-               if ( childCount > 0 ) _fetchContents( container );
-           }
-       }
-
-       ixmlNodeList_free( containerNodeList );
+    for ( unsigned int i = 0; i < ixmlNodeList_length( containerNodeList ); i++ )
+    {
+              IXML_Element* containerElement = ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
+
+        const char* objectID = ixmlElement_getAttribute( containerElement, "id" );
+        if ( !objectID ) continue;
+
+        const char* childCountStr = ixmlElement_getAttribute( containerElement, "childCount" );
+        if ( !childCountStr ) continue;
+        int childCount = atoi( childCountStr );
+
+        const char* title = xml_getChildElementValue( containerElement, "dc:title" );
+        if ( !title ) continue;
+
+        const char* resource = xml_getChildElementValue( containerElement, "res" );
+
+        if ( resource && childCount < 1 )
+        {
+        Item* item = new Item( parent, objectID, title, resource );
+        parent->addItem( item );
+        }
+        else
+        {
+        Container* container = new Container( parent, objectID, title );
+        parent->addContainer( container );
+
+        if ( childCount > 0 ) _fetchContents( container );
+        }
+    }
+
+    ixmlNodeList_free( containerNodeList );
     }
 
     IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( result, "item" );
-    if ( itemNodeList ) 
+    if ( itemNodeList )
+    {
+        for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
     {
-       for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ ) 
-       {
-           IXML_Element* itemElement = ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );
-       
-           const char* objectID = ixmlElement_getAttribute( itemElement, "id" );
-           if ( !objectID ) continue;
-      
-           const char* title = xml_getChildElementValue( itemElement, "dc:title" );
-           if ( !title ) continue;
-      
-           const char* resource = xml_getChildElementValue( itemElement, "res" );
-           if ( !resource ) continue;
-      
-           Item* item = new Item( parent, objectID, title, resource );
-           parent->addItem( item );
-       }
-    
-       ixmlNodeList_free( itemNodeList );
+        IXML_Element* itemElement = ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );
+
+        const char* objectID = ixmlElement_getAttribute( itemElement, "id" );
+        if ( !objectID ) continue;
+
+        const char* title = xml_getChildElementValue( itemElement, "dc:title" );
+        if ( !title ) continue;
+
+        const char* resource = xml_getChildElementValue( itemElement, "res" );
+        if ( !resource ) continue;
+
+        Item* item = new Item( parent, objectID, title, resource );
+        parent->addItem( item );
     }
-  
+
+    ixmlNodeList_free( itemNodeList );
+    }
+
     ixmlDocument_free( result );
-  
+
     return true;
 }
 
-void MediaServer::_buildPlaylist( Container* parent ) 
-{ 
-    for ( unsigned int i = 0; i < parent->getNumContainers(); i++ ) 
+void MediaServer::_buildPlaylist( Container* parent )
+{
+    playlist_t *p_playlist = pl_Get( _cookie->serviceDiscovery );
+    for ( unsigned int i = 0; i < parent->getNumContainers(); i++ )
     {
-       Container* container = parent->getContainer( i );
-       playlist_item_t* parentNode = parent->getPlaylistNode();
-
-       char* title = strdup( container->getTitle() );
-       playlist_item_t* node = playlist_NodeCreate( _cookie->serviceDiscovery->p_sys->p_playlist,
-                                                    VIEW_CATEGORY,
-                                                    title,
-                                                    parentNode );
-       free( title );
-       
-       container->setPlaylistNode( node );
-       _buildPlaylist( container );
+        Container* container = parent->getContainer( i );
+        playlist_item_t* parentNode = parent->getPlaylistNode();
+
+        char* title = strdup( container->getTitle() );
+        playlist_item_t* node = playlist_NodeCreate( p_playlist, title, parentNode, 0, NULL );
+        free( title );
+
+        container->setPlaylistNode( node );
+        _buildPlaylist( container );
     }
 
-    for ( unsigned int i = 0; i < parent->getNumItems(); i++ ) 
+    for ( unsigned int i = 0; i < parent->getNumItems(); i++ )
     {
-       Item* item = parent->getItem( i );
-       playlist_item_t* parentNode = parent->getPlaylistNode();
-
-       playlist_item_t* node = playlist_ItemNew( _cookie->serviceDiscovery, 
-                                                item->getResource(), 
-                                                item->getTitle() );
-    
-       playlist_NodeAddItem( _cookie->serviceDiscovery->p_sys->p_playlist, 
-                            node, 
-                            VIEW_CATEGORY,
-                            parentNode, PLAYLIST_APPEND, PLAYLIST_END );
-
-       item->setPlaylistNode( node );
+        Item* item = parent->getItem( i );
+        playlist_item_t* parentNode = parent->getPlaylistNode();
+
+        input_item_t* p_input = input_ItemNew( _cookie->serviceDiscovery,
+                                               item->getResource(),
+                                               item->getTitle() );
+        int i_cat;
+        /* FIXME: playlist_AddInput() can fail */
+        playlist_BothAddInput( p_playlist, p_input, parentNode,
+                               PLAYLIST_APPEND, PLAYLIST_END, &i_cat, NULL,
+                               pl_Unlocked );
+        vlc_gc_decref( p_input );
+        /* TODO: do this better by storing ids */
+        playlist_item_t *p_node = playlist_ItemGetById( p_playlist, i_cat, false );
+        assert( p_node );
+        item->setPlaylistNode( p_node );
     }
 }
 
-void MediaServer::setPlaylistNode( playlist_item_t* playlistNode ) 
+void MediaServer::setPlaylistNode( playlist_item_t* playlistNode )
 {
     _playlistNode = playlistNode;
 }
 
-bool MediaServer::compareSID( const char* sid ) 
+bool MediaServer::compareSID( const char* sid )
 {
     return ( strncmp( _subscriptionID, sid, sizeof( Upnp_SID ) ) == 0 );
 }
@@ -966,20 +903,20 @@ bool MediaServer::compareSID( const char* sid )
 
 // MediaServerList...
 
-MediaServerList::MediaServerList( Cookie* cookie ) 
+MediaServerList::MediaServerList( Cookie* cookie )
 {
     _cookie = cookie;
 }
 
-MediaServerList::~MediaServerList() 
+MediaServerList::~MediaServerList()
 {
     for ( unsigned int i = 0; i < _list.size(); i++ )
     {
-       delete _list[i];
+    delete _list[i];
     }
 }
 
-bool MediaServerList::addServer( MediaServer* s ) 
+bool MediaServerList::addServer( MediaServer* s )
 {
     if ( getServer( s->getUDN() ) != 0 ) return false;
 
@@ -988,74 +925,73 @@ bool MediaServerList::addServer( MediaServer* s )
     _list.push_back( s );
 
     char* name = strdup( s->getFriendlyName() );
-    playlist_item_t* node = playlist_NodeCreate( _cookie->serviceDiscovery->p_sys->p_playlist,
-                                               VIEW_CATEGORY,
-                                               name,
-                                               _cookie->serviceDiscovery->p_sys->p_node );
+    playlist_item_t* node = playlist_NodeCreate( pl_Get( _cookie->serviceDiscovery ),
+                                                 name,
+                                          _cookie->serviceDiscovery->p_sys->p_node_cat, 0, NULL );
     free( name );
     s->setPlaylistNode( node );
 
     return true;
 }
 
-MediaServer* MediaServerList::getServer( const char* UDN ) 
+MediaServer* MediaServerList::getServer( const char* UDN )
 {
     MediaServer* result = 0;
-    
-    for ( unsigned int i = 0; i < _list.size(); i++ ) 
+
+    for ( unsigned int i = 0; i < _list.size(); i++ )
     {
-       if( strcmp( UDN, _list[i]->getUDN() ) == 0 ) 
-       { 
-           result = _list[i]; 
-           break;
-       }
+        if( strcmp( UDN, _list[i]->getUDN() ) == 0 )
+    {
+        result = _list[i];
+        break;
+    }
     }
 
     return result;
 }
 
-MediaServer* MediaServerList::getServerBySID( const char* sid ) 
-{ 
+MediaServer* MediaServerList::getServerBySID( const char* sid )
+{
     MediaServer* server = 0;
 
-    for ( unsigned int i = 0; i < _list.size(); i++ ) 
+    for ( unsigned int i = 0; i < _list.size(); i++ )
     {
-       if ( _list[i]->compareSID( sid ) ) 
-       { 
-           server = _list[i];
-           break;
-       }
+    if ( _list[i]->compareSID( sid ) )
+    {
+        server = _list[i];
+        break;
+    }
     }
-  
+
     return server;
 }
 
-void MediaServerList::removeServer( const char* UDN ) 
+void MediaServerList::removeServer( const char* UDN )
 {
     MediaServer* server = getServer( UDN );
     if ( !server ) return;
 
-    msg_Dbg( _cookie->serviceDiscovery, "Removing server '%s'", server->getFriendlyName() );  
+    msg_Dbg( _cookie->serviceDiscovery, "Removing server '%s'", server->getFriendlyName() );
 
     std::vector<MediaServer*>::iterator it;
-    for ( it = _list.begin(); it != _list.end(); it++ ) 
+    for ( it = _list.begin(); it != _list.end(); it++ )
     {
-       if ( *it == server ) 
-       {
-           _list.erase( it );
-           delete server;
-           break;
-       }
+        if ( *it == server )
+    {
+              _list.erase( it );
+        delete server;
+        break;
+    }
     }
 }
 
 
 // Item...
 
-Item::Item( Container* parent, const char* objectID, const char* title, const char* resource ) 
+Item::Item( Container* parent, const char* objectID, const char* title, const char* resource )
 {
     _parent = parent;
-  
+
     _objectID = objectID;
     _title = title;
     _resource = resource;
@@ -1063,105 +999,105 @@ Item::Item( Container* parent, const char* objectID, const char* title, const ch
     _playlistNode = 0;
 }
 
-const char* Item::getObjectID() const 
+const char* Item::getObjectID() const
 {
     return _objectID.c_str();
 }
 
-const char* Item::getTitle() const 
+const char* Item::getTitle() const
 {
     return _title.c_str();
 }
 
-const char* Item::getResource() const 
+const char* Item::getResource() const
 {
     return _resource.c_str();
 }
 
-void Item::setPlaylistNode( playlist_item_t* node ) 
-{ 
-    _playlistNode = node; 
+void Item::setPlaylistNode( playlist_item_t* node )
+{
+    _playlistNode = node;
 }
 
-playlist_item_t* Item::getPlaylistNode() const 
-{ 
-    return _playlistNode; 
+playlist_item_t* Item::getPlaylistNode() const
+{
+    return _playlistNode;
 }
 
 
 // Container...
 
-Container::Container( Container* parent, const char* objectID, const char* title ) 
+Container::Container( Container* parent, const char* objectID, const char* title )
 {
     _parent = parent;
-    
+
     _objectID = objectID;
     _title = title;
 
     _playlistNode = 0;
 }
 
-Container::~Container() 
+Container::~Container()
 {
-    for ( unsigned int i = 0; i < _containers.size(); i++ ) 
+    for ( unsigned int i = 0; i < _containers.size(); i++ )
     {
-       delete _containers[i];
+    delete _containers[i];
     }
 
-    for ( unsigned int i = 0; i < _items.size(); i++ ) 
+    for ( unsigned int i = 0; i < _items.size(); i++ )
     {
-       delete _items[i];
+    delete _items[i];
     }
 }
 
-void Container::addItem( Item* item ) 
+void Container::addItem( Item* item )
 {
     _items.push_back( item );
 }
 
-void Container::addContainer( Container* container ) 
+void Container::addContainer( Container* container )
 {
     _containers.push_back( container );
 }
 
-const char* Container::getObjectID() const 
-{ 
-    return _objectID.c_str(); 
+const char* Container::getObjectID() const
+{
+    return _objectID.c_str();
 }
 
-const char* Container::getTitle() const 
-{ 
-    return _title.c_str(); 
+const char* Container::getTitle() const
+{
+    return _title.c_str();
 }
 
-unsigned int Container::getNumItems() const 
-{ 
-    return _items.size(); 
+unsigned int Container::getNumItems() const
+{
+    return _items.size();
 }
 
-unsigned int Container::getNumContainers() const 
-{ 
-    return _containers.size(); 
+unsigned int Container::getNumContainers() const
+{
+    return _containers.size();
 }
 
-Item* Container::getItem( unsigned int i ) const 
+Item* Container::getItem( unsigned int i ) const
 {
     if ( i < _items.size() ) return _items[i];
     return 0;
 }
 
-Container* Container::getContainer( unsigned int i ) const 
+Container* Container::getContainer( unsigned int i ) const
 {
     if ( i < _containers.size() ) return _containers[i];
     return 0;
 }
 
-void Container::setPlaylistNode( playlist_item_t* node ) 
-{ 
-    _playlistNode = node; 
+void Container::setPlaylistNode( playlist_item_t* node )
+{
+    _playlistNode = node;
 }
 
-playlist_item_t* Container::getPlaylistNode() const 
-{ 
-    return _playlistNode; 
+playlist_item_t* Container::getPlaylistNode() const
+{
+    return _playlistNode;
 }