X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fservices_discovery%2Fupnp.cpp;h=b427cf75196ed40067cb05c9ec350d78ce5fe7fd;hb=b955f8603cfe9a533abefa5b0bb5cbfe3c7eb59f;hp=ab765aae914b78682da7bd95b6536e4d6186fdf2;hpb=0ec04296a44bd29b973dc71a0a2d3f39aed798d5;p=vlc diff --git a/modules/services_discovery/upnp.cpp b/modules/services_discovery/upnp.cpp index ab765aae91..b427cf7519 100644 --- a/modules/services_discovery/upnp.cpp +++ b/modules/services_discovery/upnp.cpp @@ -7,6 +7,7 @@ * Authors: Rémi Denis-Courmont (original plugin) * Christian Henz * Mirsal Ennaime + * Hugo Beauzée-Luyssen * * UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink * @@ -32,13 +33,16 @@ # include "config.h" #endif -#include "services_discovery/upnp.hpp" +#include "upnp.hpp" +#include #include #include +#include #include #include +#include /* * 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,532 @@ 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_NewWithType( psz_mrl, desc->friendlyName.c_str(), 0, + NULL, 0, -1, ITEM_TYPE_NODE ); + 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::const_iterator it = list_.begin(); + std::vector::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::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( 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_NewWithType( psz_url, title, 0, NULL, + 0, -1, ITEM_TYPE_NODE ); + 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_NewExt( psz_url, title, 0, NULL, 0, duration ); + 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 +714,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 +725,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 +788,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::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( 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; -}