]> git.sesse.net Git - vlc/blobdiff - modules/services_discovery/upnp.cpp
upnp: change item b_net and i_type
[vlc] / modules / services_discovery / upnp.cpp
index ab765aae914b78682da7bd95b6536e4d6186fdf2..221a68def139b8c14b185bcb0c472ab1baf1c688 100644 (file)
@@ -7,6 +7,7 @@
  * Authors: Rémi Denis-Courmont <rem # videolan.org> (original plugin)
  *          Christian Henz <henz # c-lab.de>
  *          Mirsal Ennaime <mirsal dot ennaime at gmail dot com>
+ *          Hugo Beauzée-Luyssen <hugo@beauzee.fr>
  *
  * UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink
  *
 # include "config.h"
 #endif
 
-#include "services_discovery/upnp.hpp"
+#include "upnp.hpp"
 
+#include <vlc_access.h>
 #include <vlc_plugin.h>
 #include <vlc_services_discovery.h>
+#include <vlc_url.h>
 
 #include <assert.h>
 #include <limits.h>
+#include <algorithm>
 
 /*
  * Constants
@@ -51,138 +55,55 @@ const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:Conte
  */
 struct services_discovery_sys_t
 {
-    UpnpClient_Handle client_handle;
-    MediaServerList* p_server_list;
-    vlc_mutex_t callback_lock;
+    UpnpInstanceWrapper* p_upnp;
+    SD::MediaServerList* p_server_list;
 };
 
+struct access_sys_t
+{
+    UpnpInstanceWrapper* p_upnp;
+};
+
+UpnpInstanceWrapper* UpnpInstanceWrapper::s_instance;
+vlc_mutex_t UpnpInstanceWrapper::s_lock = VLC_STATIC_MUTEX;
+
 /*
  * VLC callback prototypes
  */
-static int Open( vlc_object_t* );
-static void Close( vlc_object_t* );
+namespace SD
+{
+    static int Open( vlc_object_t* );
+    static void Close( vlc_object_t* );
+}
+
+namespace Access
+{
+    static int Open( vlc_object_t* );
+    static void Close( vlc_object_t* );
+}
+
 VLC_SD_PROBE_HELPER( "upnp", "Universal Plug'n'Play", SD_CAT_LAN )
 
 /*
  * Module descriptor
  */
-vlc_module_begin();
+vlc_module_begin()
     set_shortname( "UPnP" );
     set_description( N_( "Universal Plug'n'Play" ) );
     set_category( CAT_PLAYLIST );
     set_subcategory( SUBCAT_PLAYLIST_SD );
     set_capability( "services_discovery", 0 );
-    set_callbacks( Open, Close );
-
-    VLC_SD_PROBE_SUBMODULE
-vlc_module_end();
-
-/*
- * Local prototypes
- */
-static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data );
-
-const char* xml_getChildElementValue( IXML_Element* p_parent,
-                                      const char*   psz_tag_name );
-
-const char* xml_getChildElementValue( IXML_Document* p_doc,
-                                      const char*    psz_tag_name );
-
-const char* xml_getChildElementAttributeValue( IXML_Element* p_parent,
-                                        const char* psz_tag_name,
-                                        const char* psz_attribute );
-
-int xml_getNumber( IXML_Document* p_doc,
-                   const char*    psz_tag_name );
-
-IXML_Document* parseBrowseResult( IXML_Document* p_doc );
-
-/*
- * Initializes UPNP instance.
- */
-static int Open( vlc_object_t *p_this )
-{
-    int i_res;
-    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
-    services_discovery_sys_t *p_sys  = ( services_discovery_sys_t * )
-            calloc( 1, sizeof( services_discovery_sys_t ) );
-
-    if( !( p_sd->p_sys = p_sys ) )
-        return VLC_ENOMEM;
-
-#ifdef UPNP_ENABLE_IPV6
-    char* psz_miface;
-    psz_miface = var_InheritString( p_sd, "miface" );
-    msg_Info( p_sd, "Initializing libupnp on '%s' interface", psz_miface );
-    i_res = UpnpInit2( psz_miface, 0 );
-    free( psz_miface );
-#else
-    /* If UpnpInit2 isnt available, initialize on first IPv4-capable interface */
-    i_res = UpnpInit( 0, 0 );
-#endif
-    if( i_res != UPNP_E_SUCCESS )
-    {
-        msg_Err( p_sd, "Initialization failed: %s", UpnpGetErrorMessage( i_res ) );
-        free( p_sys );
-        return VLC_EGENERIC;
-    }
-
-    ixmlRelaxParser( 1 );
-
-    p_sys->p_server_list = new MediaServerList( p_sd );
-    vlc_mutex_init( &p_sys->callback_lock );
-
-    /* Register a control point */
-    i_res = UpnpRegisterClient( Callback, p_sd, &p_sys->client_handle );
-    if( i_res != UPNP_E_SUCCESS )
-    {
-        msg_Err( p_sd, "Client registration failed: %s", UpnpGetErrorMessage( i_res ) );
-        Close( (vlc_object_t*) p_sd );
-        return VLC_EGENERIC;
-    }
-
-    /* Search for media servers */
-    i_res = UpnpSearchAsync( p_sys->client_handle, 5,
-            MEDIA_SERVER_DEVICE_TYPE, p_sd );
-    if( i_res != UPNP_E_SUCCESS )
-    {
-        msg_Err( p_sd, "Error sending search request: %s", UpnpGetErrorMessage( i_res ) );
-        Close( (vlc_object_t*) p_sd );
-        return VLC_EGENERIC;
-    }
-
-    /* libupnp does not treat a maximum content length of 0 as unlimited
-     * until 64dedf (~ pupnp v1.6.7) and provides no sane way to discriminate
-     * between versions */
-    if( (i_res = UpnpSetMaxContentLength( INT_MAX )) != UPNP_E_SUCCESS )
-    {
-        msg_Err( p_sd, "Failed to set maximum content length: %s",
-                UpnpGetErrorMessage( i_res ));
-
-        Close( (vlc_object_t*) p_sd );
-        return VLC_EGENERIC;
-    }
-
-    return VLC_SUCCESS;
-}
-
-/*
- * Releases resources.
- */
-static void Close( vlc_object_t *p_this )
-{
-    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
-
-    UpnpUnRegisterClient( p_sd->p_sys->client_handle );
-    UpnpFinish();
+    set_callbacks( SD::Open, SD::Close );
 
-    delete p_sd->p_sys->p_server_list;
-    vlc_mutex_destroy( &p_sd->p_sys->callback_lock );
+    add_submodule()
+        set_category( CAT_INPUT )
+        set_subcategory( SUBCAT_INPUT_ACCESS )
+        set_callbacks( Access::Open, Access::Close )
+        set_capability( "access", 0 )
 
-    free( p_sd->p_sys );
-}
+    VLC_SD_PROBE_SUBMODULE
+vlc_module_end()
 
-/* XML utility functions */
 
 /*
  * Returns the value of a child element, or NULL on error
@@ -207,51 +128,6 @@ const char* xml_getChildElementValue( IXML_Element* p_parent,
     return ixmlNode_getNodeValue( p_text_node );
 }
 
-/*
- * Returns the value of a child element's attribute, or NULL on error
- */
-const char* xml_getChildElementAttributeValue( IXML_Element* p_parent,
-                                        const char* psz_tag_name,
-                                        const char* psz_attribute )
-{
-    assert( p_parent );
-    assert( psz_tag_name );
-    assert( psz_attribute );
-
-    IXML_NodeList* p_node_list;
-    p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name );
-    if ( !p_node_list )   return NULL;
-
-    IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
-    ixmlNodeList_free( p_node_list );
-    if ( !p_element )     return NULL;
-
-    return ixmlElement_getAttribute( (IXML_Element*) p_element, psz_attribute );
-}
-
-/*
- * Returns the value of a child element, or NULL on error
- */
-const char* xml_getChildElementValue( IXML_Document*  p_doc,
-                                      const char*     psz_tag_name )
-{
-    assert( p_doc );
-    assert( psz_tag_name );
-
-    IXML_NodeList* p_node_list;
-    p_node_list = ixmlDocument_getElementsByTagName( p_doc, psz_tag_name );
-    if ( !p_node_list )  return NULL;
-
-    IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
-    ixmlNodeList_free( p_node_list );
-    if ( !p_element )    return NULL;
-
-    IXML_Node* p_text_node = ixmlNode_getFirstChild( p_element );
-    if ( !p_text_node )  return NULL;
-
-    return ixmlNode_getNodeValue( p_text_node );
-}
-
 /*
  * Extracts the result document from a SOAP response
  */
@@ -259,7 +135,10 @@ IXML_Document* parseBrowseResult( IXML_Document* p_doc )
 {
     assert( p_doc );
 
-    const char* psz_raw_didl = xml_getChildElementValue( p_doc, "Result" );
+    // ixml*_getElementsByTagName will ultimately only case the pointer to a Node
+    // pointer, and pass it to a private function. Don't bother have a IXML_Document
+    // version of getChildElementValue
+    const char* psz_raw_didl = xml_getChildElementValue( (IXML_Element*)p_doc, "Result" );
 
     if( !psz_raw_didl )
         return NULL;
@@ -302,561 +181,533 @@ IXML_Document* parseBrowseResult( IXML_Document* p_doc )
     return (IXML_Document*)p_node;
 }
 
+namespace SD
+{
+
 /*
- * Get the number value from a SOAP response
+ * Initializes UPNP instance.
  */
-int xml_getNumber( IXML_Document* p_doc,
-                   const char* psz_tag_name )
+static int Open( vlc_object_t *p_this )
 {
-    assert( p_doc );
-    assert( psz_tag_name );
+    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
+    services_discovery_sys_t *p_sys  = ( services_discovery_sys_t * )
+            calloc( 1, sizeof( services_discovery_sys_t ) );
 
-    const char* psz = xml_getChildElementValue( p_doc, psz_tag_name );
+    if( !( p_sd->p_sys = p_sys ) )
+        return VLC_ENOMEM;
 
-    if( !psz )
-        return 0;
+    p_sys->p_server_list = new(std::nothrow) SD::MediaServerList( p_sd );
+    if ( unlikely( p_sys->p_server_list == NULL ) )
+    {
+        return VLC_ENOMEM;
+    }
 
-    char *psz_end;
-    long l = strtol( psz, &psz_end, 10 );
+    p_sys->p_upnp = UpnpInstanceWrapper::get( p_this, SD::MediaServerList::Callback, p_sys->p_server_list );
+    if ( !p_sys->p_upnp )
+    {
+        Close( p_this );
+        return VLC_EGENERIC;
+    }
 
-    if( *psz_end || l < 0 || l > INT_MAX )
-        return 0;
+    /* Search for media servers */
+    int i_res = UpnpSearchAsync( p_sys->p_upnp->handle(), 5,
+            MEDIA_SERVER_DEVICE_TYPE, p_sys->p_upnp );
+    if( i_res != UPNP_E_SUCCESS )
+    {
+        msg_Err( p_sd, "Error sending search request: %s", UpnpGetErrorMessage( i_res ) );
+        Close( p_this );
+        return VLC_EGENERIC;
+    }
 
-    return (int)l;
+    return VLC_SUCCESS;
 }
 
 /*
- * Handles all UPnP events
+ * Releases resources.
  */
-static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data )
+static void Close( vlc_object_t *p_this )
 {
-    services_discovery_t* p_sd = ( services_discovery_t* ) p_user_data;
-    services_discovery_sys_t* p_sys = p_sd->p_sys;
-    vlc_mutex_locker locker( &p_sys->callback_lock );
+    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
+    services_discovery_sys_t *p_sys = p_sd->p_sys;
 
-    switch( event_type )
-    {
-    case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
-    case UPNP_DISCOVERY_SEARCH_RESULT:
-    {
-        struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
+    if (p_sys->p_upnp)
+        p_sys->p_upnp->release( true );
+    delete p_sys->p_server_list;
+    free( p_sys );
+}
 
-        IXML_Document *p_description_doc = 0;
+MediaServerDesc::MediaServerDesc(const std::string& udn, const std::string& fName, const std::string& loc)
+    : UDN( udn )
+    , friendlyName( fName )
+    , location( loc )
+    , inputItem( NULL )
+{
+}
 
-        int i_res;
-        i_res = UpnpDownloadXmlDoc( p_discovery->Location, &p_description_doc );
-        if ( i_res != UPNP_E_SUCCESS )
-        {
-            msg_Warn( p_sd, "Could not download device description! "
-                            "Fetching data from %s failed: %s",
-                            p_discovery->Location, UpnpGetErrorMessage( i_res ) );
-            return i_res;
-        }
+MediaServerDesc::~MediaServerDesc()
+{
+    if (inputItem)
+        vlc_gc_decref( inputItem );
+}
 
-        MediaServer::parseDeviceDescription( p_description_doc,
-                p_discovery->Location, p_sd );
+/*
+ * MediaServerList class
+ */
+MediaServerList::MediaServerList( services_discovery_t* p_sd )
+    : p_sd_( p_sd )
+{
+    vlc_mutex_init( &lock_ );
+}
 
-        ixmlDocument_free( p_description_doc );
-    }
-    break;
+MediaServerList::~MediaServerList()
+{
+    vlc_delete_all(list_);
+    vlc_mutex_destroy( &lock_ );
+}
 
-    case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
-    {
-        struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
+bool MediaServerList::addServer( MediaServerDesc* desc )
+{
+    vlc_mutex_locker lock( &lock_ );
+    input_item_t* p_input_item = NULL;
+    if ( getServer( desc->UDN ) )
+        return false;
 
-        p_sys->p_server_list->removeServer( p_discovery->DeviceId );
+    msg_Dbg( p_sd_, "Adding server '%s' with uuid '%s'", desc->friendlyName.c_str(), desc->UDN.c_str() );
 
-    }
-    break;
+    char* psz_mrl;
+    if( asprintf(&psz_mrl, "upnp://%s?ObjectID=%s", desc->location.c_str(), desc->UDN.c_str() ) < 0 )
+        return false;
 
-    case UPNP_EVENT_RECEIVED:
-    {
-        Upnp_Event* p_e = ( Upnp_Event* )p_event;
+    p_input_item = input_item_NewWithTypeExt( psz_mrl, desc->friendlyName.c_str(), 0,
+                                              NULL, 0, -1, ITEM_TYPE_NODE, 1);
+    free( psz_mrl );
+    if ( !p_input_item )
+        return false;
+    desc->inputItem = p_input_item;
+    input_item_SetDescription( p_input_item, desc->UDN.c_str() );
+    services_discovery_AddItem( p_sd_, p_input_item, NULL );
+    list_.push_back( desc );
+    return true;
+}
 
-        MediaServer* p_server = p_sys->p_server_list->getServerBySID( p_e->Sid );
-        if ( p_server ) p_server->fetchContents();
-    }
-    break;
+MediaServerDesc* MediaServerList::getServer( const std::string& udn )
+{
+    std::vector<MediaServerDesc*>::const_iterator it = list_.begin();
+    std::vector<MediaServerDesc*>::const_iterator ite = list_.end();
 
-    case UPNP_EVENT_AUTORENEWAL_FAILED:
-    case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
+    for ( ; it != ite; ++it )
     {
-        /* Re-subscribe. */
-
-        Upnp_Event_Subscribe* p_s = ( Upnp_Event_Subscribe* )p_event;
-
-        MediaServer* p_server = p_sys->p_server_list->getServerBySID( p_s->Sid );
-        if ( p_server ) p_server->subscribeToContentDirectory();
-    }
-    break;
-
-    case UPNP_EVENT_SUBSCRIBE_COMPLETE:
-        msg_Warn( p_sd, "subscription complete" );
-        break;
-
-    case UPNP_DISCOVERY_SEARCH_TIMEOUT:
-        msg_Warn( p_sd, "search timeout" );
-        break;
-
-    default:
-        msg_Err( p_sd, "Unhandled event, please report ( type=%d )", event_type );
-        break;
+        if( udn == (*it)->UDN )
+        {
+            return *it;
+        }
     }
-
-    return UPNP_E_SUCCESS;
+    return NULL;
 }
 
-
-/*
- * Local class implementations.
- */
-
-/*
- * MediaServer
- */
-
-void MediaServer::parseDeviceDescription( IXML_Document* p_doc,
-                                          const char*    p_location,
-                                          services_discovery_t* p_sd )
+void MediaServerList::parseNewServer( IXML_Document *doc, const std::string &location )
 {
-    if ( !p_doc )
+    if ( !doc )
     {
-        msg_Err( p_sd, "Null IXML_Document" );
+        msg_Err( p_sd_, "Null IXML_Document" );
         return;
     }
 
-    if ( !p_location )
+    if ( location.empty() )
     {
-        msg_Err( p_sd, "Null location" );
+        msg_Err( p_sd_, "Empty location" );
         return;
     }
 
-    const char* psz_base_url = p_location;
+    const char* psz_base_url = location.c_str();
 
     /* Try to extract baseURL */
-    IXML_NodeList* p_url_list = ixmlDocument_getElementsByTagName( p_doc, "URLBase" );
+    IXML_NodeList* p_url_list = ixmlDocument_getElementsByTagName( doc, "URLBase" );
     if ( p_url_list )
     {
-
         if ( IXML_Node* p_url_node = ixmlNodeList_item( p_url_list, 0 ) )
         {
             IXML_Node* p_text_node = ixmlNode_getFirstChild( p_url_node );
-            if ( p_text_node ) psz_base_url = ixmlNode_getNodeValue( p_text_node );
+            if ( p_text_node )
+                psz_base_url = ixmlNode_getNodeValue( p_text_node );
         }
-
         ixmlNodeList_free( p_url_list );
     }
 
     /* Get devices */
-    IXML_NodeList* p_device_list =
-                ixmlDocument_getElementsByTagName( p_doc, "device" );
+    IXML_NodeList* p_device_list = ixmlDocument_getElementsByTagName( doc, "device" );
 
-    if ( p_device_list )
+    if ( !p_device_list )
+        return;
+    for ( unsigned int i = 0; i < ixmlNodeList_length( p_device_list ); i++ )
     {
-        for ( unsigned int i = 0; i < ixmlNodeList_length( p_device_list ); i++ )
+        IXML_Element* p_device_element = ( IXML_Element* ) ixmlNodeList_item( p_device_list, i );
+
+        if( !p_device_element )
+            continue;
+
+        const char* psz_device_type = xml_getChildElementValue( p_device_element, "deviceType" );
+
+        if ( !psz_device_type )
         {
-            IXML_Element* p_device_element =
-                   ( IXML_Element* ) ixmlNodeList_item( p_device_list, i );
+            msg_Warn( p_sd_, "No deviceType found!" );
+            continue;
+        }
 
-            if( !p_device_element )
-                continue;
+        if ( strncmp( MEDIA_SERVER_DEVICE_TYPE, psz_device_type,
+                strlen( MEDIA_SERVER_DEVICE_TYPE ) - 1 ) )
+            continue;
 
-            const char* psz_device_type =
-                xml_getChildElementValue( p_device_element, "deviceType" );
+        const char* psz_udn = xml_getChildElementValue( p_device_element,
+                                                        "UDN" );
+        if ( !psz_udn )
+        {
+            msg_Warn( p_sd_, "No UDN!" );
+            continue;
+        }
 
-            if ( !psz_device_type )
-            {
-                msg_Warn( p_sd, "No deviceType found!" );
-                continue;
-            }
+        /* Check if server is already added */
+        if ( p_sd_->p_sys->p_server_list->getServer( psz_udn ) )
+        {
+            msg_Warn( p_sd_, "Server with uuid '%s' already exists.", psz_udn );
+            continue;
+        }
 
-            if ( strncmp( MEDIA_SERVER_DEVICE_TYPE, psz_device_type,
-                    strlen( MEDIA_SERVER_DEVICE_TYPE ) - 1 ) != 0 )
-                continue;
+        const char* psz_friendly_name =
+                   xml_getChildElementValue( p_device_element,
+                                             "friendlyName" );
 
-            const char* psz_udn = xml_getChildElementValue( p_device_element,
-                                                            "UDN" );
-            if ( !psz_udn )
-            {
-                msg_Warn( p_sd, "No UDN!" );
-                continue;
-            }
+        if ( !psz_friendly_name )
+        {
+            msg_Dbg( p_sd_, "No friendlyName!" );
+            continue;
+        }
 
-            /* Check if server is already added */
-            if ( p_sd->p_sys->p_server_list->getServer( psz_udn ) != 0 )
-            {
-                msg_Warn( p_sd, "Server with uuid '%s' already exists.", psz_udn );
-                continue;
-            }
+        // We now have basic info, we need to get the content browsing url
+        // so the access module can browse without fetching the manifest again
 
-            const char* psz_friendly_name =
-                       xml_getChildElementValue( p_device_element,
-                                                 "friendlyName" );
+        /* Check for ContentDirectory service. */
+        IXML_NodeList* p_service_list = ixmlElement_getElementsByTagName( p_device_element, "service" );
+        if ( !p_service_list )
+            continue;
+        for ( unsigned int j = 0; j < ixmlNodeList_length( p_service_list ); j++ )
+        {
+            IXML_Element* p_service_element = (IXML_Element*)ixmlNodeList_item( p_service_list, j );
 
-            if ( !psz_friendly_name )
+            const char* psz_service_type = xml_getChildElementValue( p_service_element, "serviceType" );
+            if ( !psz_service_type )
             {
-                msg_Dbg( p_sd, "No friendlyName!" );
+                msg_Warn( p_sd_, "No service type found." );
                 continue;
             }
 
-            MediaServer* p_server = new MediaServer( psz_udn,
-                    psz_friendly_name, p_sd );
+            int k = strlen( CONTENT_DIRECTORY_SERVICE_TYPE ) - 1;
+            if ( strncmp( CONTENT_DIRECTORY_SERVICE_TYPE,
+                        psz_service_type, k ) )
+                continue;
 
-            if ( !p_sd->p_sys->p_server_list->addServer( p_server ) )
+            const char* psz_control_url = xml_getChildElementValue( p_service_element,
+                                          "controlURL" );
+            if ( !psz_control_url )
             {
-                delete p_server;
-                p_server = 0;
+                msg_Warn( p_sd_, "No control url found." );
                 continue;
             }
 
-            /* Check for ContentDirectory service. */
-            IXML_NodeList* p_service_list =
-                       ixmlElement_getElementsByTagName( p_device_element,
-                                                         "service" );
-            if ( p_service_list )
+            /* Try to browse content directory. */
+            char* psz_url = ( char* ) malloc( strlen( psz_base_url ) + strlen( psz_control_url ) + 1 );
+            if ( psz_url )
             {
-                for ( unsigned int j = 0;
-                      j < ixmlNodeList_length( p_service_list ); j++ )
+                if ( UpnpResolveURL( psz_base_url, psz_control_url, psz_url ) == UPNP_E_SUCCESS )
                 {
-                    IXML_Element* p_service_element =
-                       ( IXML_Element* ) ixmlNodeList_item( p_service_list, j );
+                    SD::MediaServerDesc* p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn,
+                            psz_friendly_name, psz_url );
+                    free( psz_url );
+                    if ( unlikely( !p_server ) )
+                        break;
 
-                    const char* psz_service_type =
-                        xml_getChildElementValue( p_service_element,
-                                                  "serviceType" );
-                    if ( !psz_service_type )
+                    if ( !addServer( p_server ) )
                     {
-                        msg_Warn( p_sd, "No service type found." );
+                        delete p_server;
                         continue;
                     }
+                }
+                else
+                    free( psz_url );
+            }
+        }
+        ixmlNodeList_free( p_service_list );
+    }
+    ixmlNodeList_free( p_device_list );
+}
 
-                    int k = strlen( CONTENT_DIRECTORY_SERVICE_TYPE ) - 1;
-                    if ( strncmp( CONTENT_DIRECTORY_SERVICE_TYPE,
-                                psz_service_type, k ) != 0 )
-                        continue;
+void MediaServerList::removeServer( const std::string& udn )
+{
+    vlc_mutex_locker lock( &lock_ );
 
-                   p_server->_i_content_directory_service_version =
-                       psz_service_type[k];
+    MediaServerDesc* p_server = getServer( udn );
+    if ( !p_server )
+        return;
 
-                    const char* psz_event_sub_url =
-                        xml_getChildElementValue( p_service_element,
-                                                  "eventSubURL" );
-                    if ( !psz_event_sub_url )
-                    {
-                        msg_Warn( p_sd, "No event subscription url found." );
-                        continue;
-                    }
+    msg_Dbg( p_sd_, "Removing server '%s'", p_server->friendlyName.c_str() );
 
-                    const char* psz_control_url =
-                        xml_getChildElementValue( p_service_element,
-                                                  "controlURL" );
-                    if ( !psz_control_url )
-                    {
-                        msg_Warn( p_sd, "No control url found." );
-                        continue;
-                    }
+    assert(p_server->inputItem);
+    services_discovery_RemoveItem( p_sd_, p_server->inputItem );
 
-                    /* Try to subscribe to ContentDirectory service */
+    std::vector<MediaServerDesc*>::iterator it = std::find(list_.begin(), list_.end(), p_server);
+    if (it != list_.end())
+    {
+        list_.erase( it );
+    }
+    delete p_server;
+}
 
-                    char* psz_url = ( char* ) malloc( strlen( psz_base_url ) +
-                            strlen( psz_event_sub_url ) + 1 );
-                    if ( psz_url )
-                    {
-                        if ( UpnpResolveURL( psz_base_url, psz_event_sub_url, psz_url ) ==
-                                UPNP_E_SUCCESS )
-                        {
-                            p_server->setContentDirectoryEventURL( psz_url );
-                            p_server->subscribeToContentDirectory();
-                        }
-
-                        free( psz_url );
-                    }
+/*
+ * Handles servers listing UPnP events
+ */
+int MediaServerList::Callback( Upnp_EventType event_type, void* p_event, void* p_user_data )
+{
+    MediaServerList* self = static_cast<MediaServerList*>( p_user_data );
+    services_discovery_t* p_sd = self->p_sd_;
+
+    switch( event_type )
+    {
+    case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
+    case UPNP_DISCOVERY_SEARCH_RESULT:
+    {
+        struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
+
+        IXML_Document *p_description_doc = NULL;
+
+        int i_res;
+        i_res = UpnpDownloadXmlDoc( p_discovery->Location, &p_description_doc );
+        if ( i_res != UPNP_E_SUCCESS )
+        {
+            msg_Warn( p_sd, "Could not download device description! "
+                            "Fetching data from %s failed: %s",
+                            p_discovery->Location, UpnpGetErrorMessage( i_res ) );
+            return i_res;
+        }
+        self->parseNewServer( p_description_doc, p_discovery->Location );
+        ixmlDocument_free( p_description_doc );
+    }
+    break;
 
-                    /* Try to browse content directory. */
+    case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
+    {
+        struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
 
-                    psz_url = ( char* ) malloc( strlen( psz_base_url ) +
-                            strlen( psz_control_url ) + 1 );
-                    if ( psz_url )
-                    {
-                        if ( UpnpResolveURL( psz_base_url, psz_control_url, psz_url ) ==
-                                UPNP_E_SUCCESS )
-                        {
-                            p_server->setContentDirectoryControlURL( psz_url );
-                            p_server->fetchContents();
-                        }
-
-                        free( psz_url );
-                    }
-               }
-               ixmlNodeList_free( p_service_list );
-           }
-       }
-       ixmlNodeList_free( p_device_list );
+        self->removeServer( p_discovery->DeviceId );
     }
-}
-
-MediaServer::MediaServer( const char* psz_udn,
-                          const char* psz_friendly_name,
-                          services_discovery_t* p_sd )
-{
-    _p_sd = p_sd;
+    break;
 
-    _UDN = psz_udn;
-    _friendly_name = psz_friendly_name;
+    case UPNP_EVENT_SUBSCRIBE_COMPLETE:
+        msg_Warn( p_sd, "subscription complete" );
+        break;
 
-    _p_contents = NULL;
-    _p_input_item = NULL;
-    _i_content_directory_service_version = 1;
-}
+    case UPNP_DISCOVERY_SEARCH_TIMEOUT:
+        msg_Warn( p_sd, "search timeout" );
+        break;
 
-MediaServer::~MediaServer()
-{
-    delete _p_contents;
-}
+    case UPNP_EVENT_RECEIVED:
+    case UPNP_EVENT_AUTORENEWAL_FAILED:
+    case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
+        // Those are for the access part
+        break;
 
-const char* MediaServer::getUDN() const
-{
-    return _UDN.c_str();
-}
+    default:
+        msg_Err( p_sd, "Unhandled event, please report ( type=%d )", event_type );
+        break;
+    }
 
-const char* MediaServer::getFriendlyName() const
-{
-    return _friendly_name.c_str();
+    return UPNP_E_SUCCESS;
 }
 
-void MediaServer::setContentDirectoryEventURL( const char* psz_url )
-{
-    _content_directory_event_url = psz_url;
 }
 
-const char* MediaServer::getContentDirectoryEventURL() const
+namespace Access
 {
-    return _content_directory_event_url.c_str();
-}
 
-void MediaServer::setContentDirectoryControlURL( const char* psz_url )
+MediaServer::MediaServer(const char *psz_url, access_t *p_access, input_item_node_t *node)
+    : url_( psz_url )
+    , access_( p_access )
+    , node_( node )
 {
-    _content_directory_control_url = psz_url;
 }
 
-const char* MediaServer::getContentDirectoryControlURL() const
+void MediaServer::addItem(const char *objectID, const char *title )
 {
-    return _content_directory_control_url.c_str();
-}
+    vlc_url_t url;
+    vlc_UrlParse( &url, url_.c_str(), '?' );
+    char* psz_url;
 
-/**
- * Subscribes current client handle to Content Directory Service.
- * CDS exports the server shares to clients.
- */
-void MediaServer::subscribeToContentDirectory()
-{
-    const char* psz_url = getContentDirectoryEventURL();
-    if ( !psz_url )
+    if (asprintf( &psz_url, "upnp://%s://%s:%u%s?ObjectID=%s", url.psz_protocol,
+                  url.psz_host, url.i_port ? url.i_port : 80, url.psz_path, objectID ) < 0 )
     {
-        msg_Dbg( _p_sd, "No subscription url set!" );
-        return;
+        vlc_UrlClean( &url );
+        return ;
     }
+    vlc_UrlClean( &url );
 
-    int i_timeout = 1810;
-    Upnp_SID sid;
-
-    int i_res = UpnpSubscribe( _p_sd->p_sys->client_handle, psz_url, &i_timeout, sid );
+    input_item_t* p_item = input_item_NewWithTypeExt( psz_url, title, 0, NULL,
+                                                      0, -1, ITEM_TYPE_DIRECTORY, 1 );
+    free( psz_url);
+    if ( !p_item )
+        return;
+    input_item_CopyOptions( node_->p_item, p_item );
+    input_item_node_AppendItem( node_, p_item );
+    input_item_Release( p_item );
+}
 
-    if ( i_res == UPNP_E_SUCCESS )
-    {
-        _i_subscription_timeout = i_timeout;
-        memcpy( _subscription_id, sid, sizeof( Upnp_SID ) );
-    }
-    else
-    {
-        msg_Dbg( _p_sd, "Subscribe failed: '%s': %s",
-                getFriendlyName(), UpnpGetErrorMessage( i_res ) );
-    }
+void MediaServer::addItem(const char* title, const char*, const char*,
+                          mtime_t duration, const char* psz_url)
+{
+    input_item_t* p_item = input_item_NewWithTypeExt( psz_url, title, 0, NULL, 0,
+                                                      duration, ITEM_TYPE_FILE, 1 );
+    input_item_node_AppendItem( node_, p_item );
+    input_item_Release( p_item );
 }
-/*
- * Constructs UpnpAction to browse available content.
- */
+
+/* Access part */
 IXML_Document* MediaServer::_browseAction( const char* psz_object_id_,
                                            const char* psz_browser_flag_,
                                            const char* psz_filter_,
-                                           const char* psz_starting_index_,
                                            const char* psz_requested_count_,
                                            const char* psz_sort_criteria_ )
 {
-    IXML_Document* p_action = 0;
-    IXML_Document* p_response = 0;
-    const char* psz_url = getContentDirectoryControlURL();
+    IXML_Document* p_action = NULL;
+    IXML_Document* p_response = NULL;
+    const char* psz_url = url_.c_str();
 
-    if ( !psz_url )
+    if ( url_.empty() )
     {
-        msg_Dbg( _p_sd, "No subscription url set!" );
-        return 0;
+        msg_Dbg( access_, "No subscription url set!" );
+        return NULL;
     }
 
-    char* psz_service_type = strdup( CONTENT_DIRECTORY_SERVICE_TYPE );
-
-    psz_service_type[strlen( psz_service_type ) - 1] =
-       _i_content_directory_service_version;
-
     int i_res;
 
     i_res = UpnpAddToAction( &p_action, "Browse",
-            psz_service_type, "ObjectID", psz_object_id_ );
+            CONTENT_DIRECTORY_SERVICE_TYPE, "ObjectID", psz_object_id_ );
 
     if ( i_res != UPNP_E_SUCCESS )
     {
-        msg_Dbg( _p_sd, "AddToAction 'ObjectID' failed: %s",
+        msg_Dbg( access_, "AddToAction 'ObjectID' failed: %s",
                 UpnpGetErrorMessage( i_res ) );
         goto browseActionCleanup;
     }
 
     i_res = UpnpAddToAction( &p_action, "Browse",
-            psz_service_type, "BrowseFlag", psz_browser_flag_ );
-
+            CONTENT_DIRECTORY_SERVICE_TYPE, "StartingIndex", "0" );
     if ( i_res != UPNP_E_SUCCESS )
     {
-        msg_Dbg( _p_sd, "AddToAction 'BrowseFlag' failed: %s", 
+        msg_Dbg( access_, "AddToAction 'StartingIndex' failed: %s",
                 UpnpGetErrorMessage( i_res ) );
         goto browseActionCleanup;
     }
 
     i_res = UpnpAddToAction( &p_action, "Browse",
-            psz_service_type, "Filter", psz_filter_ );
+            CONTENT_DIRECTORY_SERVICE_TYPE, "BrowseFlag", psz_browser_flag_ );
 
     if ( i_res != UPNP_E_SUCCESS )
     {
-        msg_Dbg( _p_sd, "AddToAction 'Filter' failed: %s",
+        msg_Dbg( access_, "AddToAction 'BrowseFlag' failed: %s",
                 UpnpGetErrorMessage( i_res ) );
         goto browseActionCleanup;
     }
 
     i_res = UpnpAddToAction( &p_action, "Browse",
-            psz_service_type, "StartingIndex", psz_starting_index_ );
+            CONTENT_DIRECTORY_SERVICE_TYPE, "Filter", psz_filter_ );
 
     if ( i_res != UPNP_E_SUCCESS )
     {
-        msg_Dbg( _p_sd, "AddToAction 'StartingIndex' failed: %s",
+        msg_Dbg( access_, "AddToAction 'Filter' failed: %s",
                 UpnpGetErrorMessage( i_res ) );
         goto browseActionCleanup;
     }
 
     i_res = UpnpAddToAction( &p_action, "Browse",
-            psz_service_type, "RequestedCount", psz_requested_count_ );
+            CONTENT_DIRECTORY_SERVICE_TYPE, "RequestedCount", psz_requested_count_ );
 
     if ( i_res != UPNP_E_SUCCESS )
     {
-        msg_Dbg( _p_sd, "AddToAction 'RequestedCount' failed: %s",
+        msg_Dbg( access_, "AddToAction 'RequestedCount' failed: %s",
                 UpnpGetErrorMessage( i_res ) );
         goto browseActionCleanup;
     }
 
     i_res = UpnpAddToAction( &p_action, "Browse",
-            psz_service_type, "SortCriteria", psz_sort_criteria_ );
+            CONTENT_DIRECTORY_SERVICE_TYPE, "SortCriteria", psz_sort_criteria_ );
 
     if ( i_res != UPNP_E_SUCCESS )
     {
-        msg_Dbg( _p_sd, "AddToAction 'SortCriteria' failed: %s",
+        msg_Dbg( access_, "AddToAction 'SortCriteria' failed: %s",
                 UpnpGetErrorMessage( i_res ) );
         goto browseActionCleanup;
     }
 
-    i_res = UpnpSendAction( _p_sd->p_sys->client_handle,
+    i_res = UpnpSendAction( access_->p_sys->p_upnp->handle(),
               psz_url,
-              psz_service_type,
-              0, /* ignored in SDK, must be NULL */
+              CONTENT_DIRECTORY_SERVICE_TYPE,
+              NULL, /* ignored in SDK, must be NULL */
               p_action,
               &p_response );
 
     if ( i_res != UPNP_E_SUCCESS )
     {
-        msg_Err( _p_sd, "%s when trying the send() action with URL: %s",
+        msg_Err( access_, "%s when trying the send() action with URL: %s",
                 UpnpGetErrorMessage( i_res ), psz_url );
 
         ixmlDocument_free( p_response );
-        p_response = 0;
+        p_response = NULL;
     }
 
 browseActionCleanup:
-
-    free( psz_service_type );
-
     ixmlDocument_free( p_action );
     return p_response;
 }
 
-void MediaServer::fetchContents()
-{
-    /* Delete previous contents to prevent duplicate entries */
-    if ( _p_contents )
-    {
-        delete _p_contents;
-        services_discovery_RemoveItem( _p_sd, _p_input_item );
-        services_discovery_AddItem( _p_sd, _p_input_item, NULL );
-    }
-
-    Container* root = new Container( 0, "0", getFriendlyName() );
-
-    _fetchContents( root, 0 );
-
-    _p_contents = root;
-    _p_contents->setInputItem( _p_input_item );
-
-    _buildPlaylist( _p_contents, NULL );
-}
-
 /*
  * Fetches and parses the UPNP response
  */
-bool MediaServer::_fetchContents( Container* p_parent, int i_offset )
+bool MediaServer::fetchContents()
 {
-    if (!p_parent)
-    {
-        msg_Err( _p_sd, "No parent" );
-        return false;
-    }
+    const char* objectID = "";
+    vlc_url_t url;
+    vlc_UrlParse( &url, access_->psz_location, '?');
 
-    char* psz_starting_index;
-    if( asprintf( &psz_starting_index, "%d", i_offset ) < 0 )
+    if ( url.psz_option && !strncmp( url.psz_option, "ObjectID=", strlen( "ObjectID=" ) ) )
     {
-        msg_Err( _p_sd, "asprintf error:%d", i_offset );
-        return false;
+        objectID = &url.psz_option[strlen( "ObjectID=" )];
     }
 
-    IXML_Document* p_response = _browseAction( p_parent->getObjectID(),
+    IXML_Document* p_response = _browseAction( objectID,
                                       "BrowseDirectChildren",
                                       "id,dc:title,res," /* Filter */
                                       "sec:CaptionInfo,sec:CaptionInfoEx,"
                                       "pv:subtitlefile",
-                                      psz_starting_index, /* StartingIndex */
                                       "0", /* RequestedCount */
                                       "" /* SortCriteria */
                                       );
-    free( psz_starting_index );
+    vlc_UrlClean( &url );
     if ( !p_response )
     {
-        msg_Err( _p_sd, "No response from browse() action" );
+        msg_Err( access_, "No response from browse() action" );
         return false;
     }
 
     IXML_Document* p_result = parseBrowseResult( p_response );
-    int i_number_returned = xml_getNumber( p_response, "NumberReturned" );
-    int i_total_matches   = xml_getNumber( p_response , "TotalMatches" );
-
-#ifndef NDEBUG
-    msg_Dbg( _p_sd, "i_offset[%d]i_number_returned[%d]_total_matches[%d]\n",
-             i_offset, i_number_returned, i_total_matches );
-#endif
 
     ixmlDocument_free( p_response );
 
     if ( !p_result )
     {
-        msg_Err( _p_sd, "browse() response parsing failed" );
+        msg_Err( access_, "browse() response parsing failed" );
         return false;
     }
 
 #ifndef NDEBUG
-    msg_Dbg( _p_sd, "Got DIDL document: %s", ixmlPrintDocument( p_result ) );
+    msg_Dbg( access_, "Got DIDL document: %s", ixmlPrintDocument( p_result ) );
 #endif
 
     IXML_NodeList* containerNodeList =
@@ -864,11 +715,9 @@ bool MediaServer::_fetchContents( Container* p_parent, int i_offset )
 
     if ( containerNodeList )
     {
-        for ( unsigned int i = 0;
-                i < ixmlNodeList_length( containerNodeList ); i++ )
+        for ( unsigned int i = 0; i < ixmlNodeList_length( containerNodeList ); i++ )
         {
-            IXML_Element* containerElement =
-                  ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
+            IXML_Element* containerElement = (IXML_Element*)ixmlNodeList_item( containerNodeList, i );
 
             const char* objectID = ixmlElement_getAttribute( containerElement,
                                                              "id" );
@@ -877,13 +726,9 @@ bool MediaServer::_fetchContents( Container* p_parent, int i_offset )
 
             const char* title = xml_getChildElementValue( containerElement,
                                                           "dc:title" );
-
             if ( !title )
                 continue;
-
-            Container* container = new Container( p_parent, objectID, title );
-            p_parent->addContainer( container );
-            _fetchContents( container, 0 );
+            addItem(objectID, title);
         }
         ixmlNodeList_free( containerNodeList );
     }
@@ -944,443 +789,188 @@ bool MediaServer::_fetchContents( Container* p_parent, int i_offset )
                                                               i_seconds );
                     }
 
-                    Item* item = new Item( p_parent, objectID, title, psz_resource_url, psz_subtitles, i_duration );
-                    p_parent->addItem( item );
+                    addItem( title, objectID, psz_subtitles, i_duration, psz_resource_url );
                 }
                 ixmlNodeList_free( p_resource_list );
             }
-            else continue;
+            else
+                continue;
         }
         ixmlNodeList_free( itemNodeList );
     }
 
     ixmlDocument_free( p_result );
-
-    if( i_offset + i_number_returned < i_total_matches )
-        return _fetchContents( p_parent, i_offset + i_number_returned );
-
     return true;
 }
 
-// TODO: Create a permanent fix for the item duplication bug. The current fix
-// is essentially only a small hack. Although it fixes the problem, it introduces
-// annoying cosmetic issues with the playlist. For example, when the UPnP Server
-// rebroadcasts it's directory structure, the VLC Client deletes the old directory
-// structure, causing the user to go back to the root node of the directory. The
-// directory is then rebuilt, and the user is forced to traverse through the directory
-// to find the item they were looking for. Some servers may not push the directory
-// structure too often, but we cannot rely on this fix.
-//
-// I have thought up another fix, but this would require certain features to
-// be present within the VLC services discovery. Currently, services_discovery_AddItem
-// does not allow the programmer to nest items. It only allows a "2 deep" scope.
-// An example of the limitation is below:
-//
-// Root Directory
-// + Item 1
-// + Item 2
-//
-// services_discovery_AddItem will not let the programmer specify a child-node to
-// insert items into, so we would not be able to do the following:
-//
-// Root Directory
-// + Item 1
-//   + Sub Item 1
-// + Item 2
-//   + Sub Item 1 of Item 2
-//     + Sub-Sub Item 1 of Sub Item 1
-//
-// This creates a HUGE limitation on what we are able to do. If we were able to do
-// the above, we could simply preserve the old directory listing, and compare what items
-// do not exist in the new directory listing, then remove them from the shown listing using
-// services_discovery_RemoveItem. If new files were introduced within an already existing
-// container, we could simply do so with services_discovery_AddItem.
-
-/*
- * Builds playlist based on available input items.
- */
-void MediaServer::_buildPlaylist( Container* p_parent, input_item_node_t *p_input_node )
+static int ReadDirectory( access_t *p_access, input_item_node_t* p_node )
 {
-    bool b_send = p_input_node == NULL;
-    if( b_send )
-        p_input_node = input_item_node_Create( p_parent->getInputItem() );
-
-    for ( unsigned int i = 0; i < p_parent->getNumContainers(); i++ )
-    {
-        Container* p_container = p_parent->getContainer( i );
-
-        input_item_t* p_input_item = input_item_New( "vlc://nop",
-                                                    p_container->getTitle() );
-        input_item_node_t *p_new_node =
-            input_item_node_AppendItem( p_input_node, p_input_item );
+    MediaServer server( p_access->psz_location, p_access, p_node );
 
-        p_container->setInputItem( p_input_item );
-        _buildPlaylist( p_container, p_new_node );
-    }
+    if ( !server.fetchContents() )
+        return VLC_EGENERIC;
+    return VLC_SUCCESS;
+}
 
-    for ( unsigned int i = 0; i < p_parent->getNumItems(); i++ )
+static int Control( access_t *, int i_query, va_list args )
+{
+    switch ( i_query )
     {
-        Item* p_item = p_parent->getItem( i );
-
-        char **ppsz_opts = NULL;
-        char *psz_input_slave = p_item->buildInputSlaveOption();
-        if( psz_input_slave )
-        {
-            ppsz_opts = (char**)malloc( 2 * sizeof( char* ) );
-            ppsz_opts[0] = psz_input_slave;
-            ppsz_opts[1] = p_item->buildSubTrackIdOption();
-        }
-
-        input_item_t* p_input_item = input_item_NewExt( p_item->getResource(),
-                                           p_item->getTitle(),
-                                           psz_input_slave ? 2 : 0,
-                                           psz_input_slave ? ppsz_opts : NULL,
-                                           VLC_INPUT_OPTION_TRUSTED, /* XXX */
-                                           p_item->getDuration() );
+    case ACCESS_CAN_SEEK:
+    case ACCESS_CAN_FASTSEEK:
+    case ACCESS_CAN_PAUSE:
+    case ACCESS_CAN_CONTROL_PACE:
+        *va_arg( args, bool* ) = false;
+        break;
 
-        assert( p_input_item );
-        if( ppsz_opts )
-        {
-            free( ppsz_opts[0] );
-            free( ppsz_opts[1] );
-            free( ppsz_opts );
+    case ACCESS_GET_SIZE:
+    {
+        *va_arg( args, uint64_t * ) = 0;
+        break;
+    }
+    case ACCESS_GET_PTS_DELAY:
+        *va_arg( args, int64_t * ) = 0;
+        break;
 
-            psz_input_slave = NULL;
-        }
+    case ACCESS_SET_PAUSE_STATE:
+        /* Nothing to do */
+        break;
 
-        input_item_node_AppendItem( p_input_node, p_input_item );
-        p_item->setInputItem( p_input_item );
+    default:
+        return VLC_EGENERIC;
     }
-
-    if( b_send )
-        input_item_node_PostAndDelete( p_input_node );
+    return VLC_SUCCESS;
 }
 
-void MediaServer::setInputItem( input_item_t* p_input_item )
+static int Open( vlc_object_t *p_this )
 {
-    if( _p_input_item == p_input_item )
-        return;
+    access_t* p_access = (access_t*)p_this;
+    access_sys_t* p_sys = new(std::nothrow) access_sys_t;
+    if ( unlikely( !p_sys ) )
+        return VLC_ENOMEM;
 
-    if( _p_input_item )
-        vlc_gc_decref( _p_input_item );
+    p_access->p_sys = p_sys;
+    p_sys->p_upnp = UpnpInstanceWrapper::get( p_this, NULL, NULL );
+    if ( !p_sys->p_upnp )
+    {
+        delete p_sys;
+        return VLC_EGENERIC;
+    }
 
-    vlc_gc_incref( p_input_item );
-    _p_input_item = p_input_item;
-}
+    p_access->pf_readdir = ReadDirectory;
+    ACCESS_SET_CALLBACKS( NULL, NULL, Control, NULL );
 
-input_item_t* MediaServer::getInputItem() const
-{
-    return _p_input_item;
+    return VLC_SUCCESS;
 }
 
-bool MediaServer::compareSID( const char* psz_sid )
+static void Close( vlc_object_t* p_this )
 {
-    return ( strncmp( _subscription_id, psz_sid, sizeof( Upnp_SID ) ) == 0 );
+    access_t* p_access = (access_t*)p_this;
+    p_access->p_sys->p_upnp->release( false );
+    delete p_access->p_sys;
 }
 
-
-/*
- * MediaServerList class
- */
-MediaServerList::MediaServerList( services_discovery_t* p_sd )
-{
-    _p_sd = p_sd;
 }
 
-MediaServerList::~MediaServerList()
+UpnpInstanceWrapper::UpnpInstanceWrapper()
+    : handle_( -1 )
+    , opaque_( NULL )
+    , callback_( NULL )
+    , refcount_( 0 )
 {
-    for ( unsigned int i = 0; i < _list.size(); i++ )
-    {
-        delete _list[i];
-    }
 }
 
-bool MediaServerList::addServer( MediaServer* p_server )
+UpnpInstanceWrapper::~UpnpInstanceWrapper()
 {
-    input_item_t* p_input_item = NULL;
-    if ( getServer( p_server->getUDN() ) != 0 ) return false;
-
-    msg_Dbg( _p_sd, "Adding server '%s' with uuid '%s'", p_server->getFriendlyName(), p_server->getUDN() );
-
-    p_input_item = input_item_New( "vlc://nop", p_server->getFriendlyName() );
-
-    input_item_SetDescription( p_input_item, p_server->getUDN() );
-
-    p_server->setInputItem( p_input_item );
-
-    services_discovery_AddItem( _p_sd, p_input_item, NULL );
-
-    _list.push_back( p_server );
-
-    return true;
+    UpnpUnRegisterClient( handle_ );
+    UpnpFinish();
 }
 
-MediaServer* MediaServerList::getServer( const char* psz_udn )
+UpnpInstanceWrapper *UpnpInstanceWrapper::get(vlc_object_t *p_obj, Upnp_FunPtr callback, SD::MediaServerList *opaque)
 {
-    MediaServer* p_result = 0;
-
-    for ( unsigned int i = 0; i < _list.size(); i++ )
+    vlc_mutex_locker lock( &s_lock );
+    if ( s_instance == NULL )
     {
-        if( strcmp( psz_udn, _list[i]->getUDN() ) == 0 )
+        UpnpInstanceWrapper* instance = new(std::nothrow) UpnpInstanceWrapper;
+        if ( unlikely( !instance ) )
+            return NULL;
+
+    #ifdef UPNP_ENABLE_IPV6
+        char* psz_miface = var_InheritString( p_obj, "miface" );
+        msg_Info( p_obj, "Initializing libupnp on '%s' interface", psz_miface );
+        int i_res = UpnpInit2( psz_miface, 0 );
+        free( psz_miface );
+    #else
+        /* If UpnpInit2 isnt available, initialize on first IPv4-capable interface */
+        int i_res = UpnpInit( 0, 0 );
+    #endif
+        if( i_res != UPNP_E_SUCCESS )
         {
-            p_result = _list[i];
-            break;
+            msg_Err( p_obj, "Initialization failed: %s", UpnpGetErrorMessage( i_res ) );
+            delete instance;
+            return NULL;
         }
-    }
 
-    return p_result;
-}
-
-MediaServer* MediaServerList::getServerBySID( const char* psz_sid )
-{
-    MediaServer* p_server = 0;
+        ixmlRelaxParser( 1 );
 
-    for ( unsigned int i = 0; i < _list.size(); i++ )
-    {
-        if ( _list[i]->compareSID( psz_sid ) )
+        /* Register a control point */
+        i_res = UpnpRegisterClient( Callback, instance, &instance->handle_ );
+        if( i_res != UPNP_E_SUCCESS )
         {
-            p_server = _list[i];
-            break;
+            msg_Err( p_obj, "Client registration failed: %s", UpnpGetErrorMessage( i_res ) );
+            delete instance;
+            return NULL;
         }
-    }
-
-    return p_server;
-}
 
-void MediaServerList::removeServer( const char* psz_udn )
-{
-    MediaServer* p_server = getServer( psz_udn );
-    if ( !p_server ) return;
-
-    msg_Dbg( _p_sd, "Removing server '%s'", p_server->getFriendlyName() );
-
-    services_discovery_RemoveItem( _p_sd, p_server->getInputItem() );
-
-    std::vector<MediaServer*>::iterator it;
-    for ( it = _list.begin(); it != _list.end(); ++it )
-    {
-        if ( *it == p_server )
+        /* libupnp does not treat a maximum content length of 0 as unlimited
+         * until 64dedf (~ pupnp v1.6.7) and provides no sane way to discriminate
+         * between versions */
+        if( (i_res = UpnpSetMaxContentLength( INT_MAX )) != UPNP_E_SUCCESS )
         {
-            _list.erase( it );
-            delete p_server;
-            break;
+            msg_Err( p_obj, "Failed to set maximum content length: %s",
+                    UpnpGetErrorMessage( i_res ));
+            delete instance;
+            return NULL;
         }
+        s_instance = instance;
     }
-}
-
-
-/*
- * Item class
- */
-Item::Item( Container* p_parent,
-        const char* psz_object_id, const char* psz_title,
-        const char* psz_resource, const char* psz_subtitles,
-        mtime_t i_duration )
-{
-    _parent = p_parent;
-
-    _objectID = psz_object_id;
-    _title = psz_title;
-    _resource = psz_resource;
-    _subtitles = psz_subtitles ? psz_subtitles : "";
-    _duration = i_duration;
-
-    _p_input_item = NULL;
-}
-
-Item::~Item()
-{
-    if( _p_input_item )
-        vlc_gc_decref( _p_input_item );
-}
-
-const char* Item::getObjectID() const
-{
-    return _objectID.c_str();
-}
-
-const char* Item::getTitle() const
-{
-    return _title.c_str();
-}
-
-const char* Item::getResource() const
-{
-    return _resource.c_str();
-}
-
-const char* Item::getSubtitles() const
-{
-    if( !_subtitles.size() )
-        return NULL;
-
-    return _subtitles.c_str();
-}
-
-mtime_t Item::getDuration() const
-{
-    return _duration;
-}
-
-char* Item::buildInputSlaveOption() const
-{
-    const char *psz_subtitles    = getSubtitles();
-
-    const char *psz_scheme_delim = "://";
-    const char *psz_sub_opt_fmt  = ":input-slave=%s/%s://%s";
-    const char *psz_demux        = "subtitle";
-
-    char       *psz_uri_scheme   = NULL;
-    const char *psz_scheme_end   = NULL;
-    const char *psz_uri_location = NULL;
-    char       *psz_input_slave  = NULL;
-
-    size_t i_scheme_len;
-
-    if( !psz_subtitles )
-        return NULL;
-
-    psz_scheme_end = strstr( psz_subtitles, psz_scheme_delim );
-
-    /* subtitles not being an URI would make no sense */
-    if( !psz_scheme_end )
-        return NULL;
-
-    i_scheme_len   = psz_scheme_end - psz_subtitles;
-    psz_uri_scheme = (char*)malloc( i_scheme_len + 1 );
-
-    if( !psz_uri_scheme )
-        return NULL;
-
-    memcpy( psz_uri_scheme, psz_subtitles, i_scheme_len );
-    psz_uri_scheme[i_scheme_len] = '\0';
-
-    /* If the subtitles try to force a vlc demux,
-     * then something is very wrong */
-    if( strchr( psz_uri_scheme, '/' ) )
+    s_instance->refcount_++;
+    // This assumes a single UPNP SD instance
+    if (callback && opaque)
     {
-        free( psz_uri_scheme );
-        return NULL;
+        assert(!s_instance->callback_ && !s_instance->opaque_);
+        s_instance->opaque_ = opaque;
+        s_instance->callback_ = callback;
     }
-
-    psz_uri_location = psz_scheme_end + strlen( psz_scheme_delim );
-
-    if( -1 == asprintf( &psz_input_slave, psz_sub_opt_fmt,
-            psz_uri_scheme, psz_demux, psz_uri_location ) )
-        psz_input_slave = NULL;
-
-    free( psz_uri_scheme );
-    return psz_input_slave;
-}
-
-char* Item::buildSubTrackIdOption() const
-{
-    return strdup( ":sub-track-id=2" );
-}
-
-void Item::setInputItem( input_item_t* p_input_item )
-{
-    if( _p_input_item == p_input_item )
-        return;
-
-    if( _p_input_item )
-        vlc_gc_decref( _p_input_item );
-
-    vlc_gc_incref( p_input_item );
-    _p_input_item = p_input_item;
-}
-
-/*
- * Container class
- */
-Container::Container( Container*  p_parent,
-                      const char* psz_object_id,
-                      const char* psz_title )
-{
-    _parent = p_parent;
-
-    _objectID = psz_object_id;
-    _title = psz_title;
-
-    _p_input_item = NULL;
+    return s_instance;
 }
 
-Container::~Container()
+void UpnpInstanceWrapper::release(bool isSd)
 {
-    for ( unsigned int i = 0; i < _containers.size(); i++ )
+    vlc_mutex_locker lock( &s_lock );
+    if ( isSd )
     {
-        delete _containers[i];
+        callback_ = NULL;
+        opaque_ = NULL;
     }
-
-    for ( unsigned int i = 0; i < _items.size(); i++ )
+    if (--s_instance->refcount_ == 0)
     {
-        delete _items[i];
+        delete s_instance;
+        s_instance = NULL;
     }
-
-    if( _p_input_item )
-        vlc_gc_decref( _p_input_item );
 }
 
-void Container::addItem( Item* item )
+UpnpClient_Handle UpnpInstanceWrapper::handle() const
 {
-    _items.push_back( item );
+    return handle_;
 }
 
-void Container::addContainer( Container* p_container )
+int UpnpInstanceWrapper::Callback(Upnp_EventType event_type, void *p_event, void *p_user_data)
 {
-    _containers.push_back( p_container );
-}
-
-const char* Container::getObjectID() const
-{
-    return _objectID.c_str();
-}
-
-const char* Container::getTitle() const
-{
-    return _title.c_str();
-}
-
-unsigned int Container::getNumItems() const
-{
-    return _items.size();
-}
-
-unsigned int Container::getNumContainers() const
-{
-    return _containers.size();
-}
-
-Item* Container::getItem( unsigned int i_index ) const
-{
-    if ( i_index < _items.size() ) return _items[i_index];
-    return 0;
-}
-
-Container* Container::getContainer( unsigned int i_index ) const
-{
-    if ( i_index < _containers.size() ) return _containers[i_index];
+    UpnpInstanceWrapper* self = static_cast<UpnpInstanceWrapper*>( p_user_data );
+    vlc_mutex_locker lock( &self->s_lock );
+    if ( !self->callback_ )
+        return 0;
+    self->callback_( event_type, p_event, self->opaque_ );
     return 0;
 }
-
-Container* Container::getParent()
-{
-    return _parent;
-}
-
-void Container::setInputItem( input_item_t* p_input_item )
-{
-    if( _p_input_item == p_input_item )
-        return;
-
-    if( _p_input_item )
-        vlc_gc_decref( _p_input_item );
-
-    vlc_gc_incref( p_input_item );
-    _p_input_item = p_input_item;
-}
-
-input_item_t* Container::getInputItem() const
-{
-    return _p_input_item;
-}