X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fservices_discovery%2Fupnp.cpp;h=483f374d4b837d68a61bb8e4ae5ff578ccf5a0db;hb=643f98a114525241dc3b1acc63e3bb75ca85ac1e;hp=19d81cb1c7fb358c79bc04bf781c8dd3ec0be166;hpb=caeee6e25e41077cf995b4ba6d08761c2dc6d949;p=vlc diff --git a/modules/services_discovery/upnp.cpp b/modules/services_discovery/upnp.cpp index 19d81cb1c7..483f374d4b 100644 --- a/modules/services_discovery/upnp.cpp +++ b/modules/services_discovery/upnp.cpp @@ -1,5 +1,5 @@ /***************************************************************************** - * Upnp.cpp : UPnP discovery module (libupnp) + * upnp.cpp : UPnP discovery module (libupnp) ***************************************************************************** * Copyright (C) 2004-2011 the VideoLAN team * $Id$ @@ -25,23 +25,30 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ +#define __STDC_CONSTANT_MACROS 1 + #undef PACKAGE_NAME #ifdef HAVE_CONFIG_H # include "config.h" #endif -#include "upnp.hpp" +#include "services_discovery/upnp.hpp" #include #include #include +#include -// Constants +/* + * Constants +*/ const char* MEDIA_SERVER_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaServer:1"; const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:ContentDirectory:1"; -// VLC handle +/* + * VLC handle + */ struct services_discovery_sys_t { UpnpClient_Handle client_handle; @@ -49,13 +56,16 @@ struct services_discovery_sys_t vlc_mutex_t callback_lock; }; -// VLC callback prototypes +/* + * VLC callback prototypes + */ 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_SD_PROBE_HELPER( "upnp", "Universal Plug'n'Play", SD_CAT_LAN ) +/* + * Module descriptor + */ vlc_module_begin(); set_shortname( "UPnP" ); set_description( N_( "Universal Plug'n'Play" ) ); @@ -67,19 +77,29 @@ vlc_module_begin(); VLC_SD_PROBE_SUBMODULE vlc_module_end(); - -// More prototypes... - +/* + * 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 ); -IXML_Document* parseBrowseResult( IXML_Document* p_doc ); +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 ); -// VLC callbacks... +IXML_Document* parseBrowseResult( IXML_Document* p_doc ); +/* + * Initializes UPNP instance. + */ static int Open( vlc_object_t *p_this ) { int i_res; @@ -87,28 +107,41 @@ static int Open( vlc_object_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)) + 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, "%s", UpnpGetErrorMessage( i_res ) ); + 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, "%s", UpnpGetErrorMessage( i_res ) ); + 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 ) @@ -118,10 +151,14 @@ static int Open( vlc_object_t *p_this ) return VLC_EGENERIC; } - i_res = UpnpSetMaxContentLength( 262144 ); - if( i_res != UPNP_E_SUCCESS ) + /* 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 ) ); + msg_Err( p_sd, "Failed to set maximum content length: %s", + UpnpGetErrorMessage( i_res )); + Close( (vlc_object_t*) p_sd ); return VLC_EGENERIC; } @@ -129,6 +166,9 @@ static int Open( vlc_object_t *p_this ) return VLC_SUCCESS; } +/* + * Releases resources. + */ static void Close( vlc_object_t *p_this ) { services_discovery_t *p_sd = ( services_discovery_t* )p_this; @@ -142,63 +182,149 @@ static void Close( vlc_object_t *p_this ) free( p_sd->p_sys ); } -// XML utility functions: +/* XML utility functions */ -// Returns the value of a child element, or 0 on error +/* + * Returns the value of a child element, or NULL on error + */ const char* xml_getChildElementValue( IXML_Element* p_parent, - const char* psz_tag_name_ ) + const char* psz_tag_name ) { - if ( !p_parent ) return 0; - if ( !psz_tag_name_ ) return 0; + assert( p_parent ); + assert( psz_tag_name ); - char* psz_tag_name = strdup( psz_tag_name_ ); - IXML_NodeList* p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name ); - free( psz_tag_name ); - if ( !p_node_list ) return 0; + 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 0; + if ( !p_element ) return NULL; IXML_Node* p_text_node = ixmlNode_getFirstChild( p_element ); - if ( !p_text_node ) return 0; + if ( !p_text_node ) return NULL; return ixmlNode_getNodeValue( p_text_node ); } -// Extracts the result document from a SOAP response -IXML_Document* parseBrowseResult( IXML_Document* p_doc ) +/* + * 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 ) { - ixmlRelaxParser(1); + 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; - if ( !p_doc ) return 0; + IXML_Node* p_text_node = ixmlNode_getFirstChild( p_element ); + if ( !p_text_node ) return NULL; + + return ixmlNode_getNodeValue( p_text_node ); +} - IXML_NodeList* p_result_list = ixmlDocument_getElementsByTagName( p_doc, - "Result" ); +/* + * Extracts the result document from a SOAP response + */ +IXML_Document* parseBrowseResult( IXML_Document* p_doc ) +{ + assert( p_doc ); - if ( !p_result_list ) return 0; + /* Missing namespaces confuse the ixml parser. This is a very ugly + * hack but it is needeed until devices start sending valid XML. + * + * It works that way: + * + * The DIDL document is extracted from the Result tag, then wrapped into + * a valid XML header and a new root tag which contains missing namespace + * definitions so the ixml parser understands it. + * + * If you know of a better workaround, please oh please fix it */ + const char* psz_xml_result_fmt = "" + "%s"; - IXML_Node* p_result_node = ixmlNodeList_item( p_result_list, 0 ); + char* psz_xml_result_string = NULL; + const char* psz_raw_didl = xml_getChildElementValue( p_doc, "Result" ); - ixmlNodeList_free( p_result_list ); + if( !psz_raw_didl ) + return NULL; - if ( !p_result_node ) return 0; + if( -1 == asprintf( &psz_xml_result_string, + psz_xml_result_fmt, + psz_raw_didl) ) + return NULL; - IXML_Node* p_text_node = ixmlNode_getFirstChild( p_result_node ); - if ( !p_text_node ) return 0; - const char* psz_result_string = ixmlNode_getNodeValue( p_text_node ); - char* psz_result_xml = strdup( psz_result_string ); + IXML_Document* p_result_doc = ixmlParseBuffer( psz_xml_result_string ); + free( psz_xml_result_string ); - IXML_Document* p_browse_doc = ixmlParseBuffer( psz_result_xml ); + if( !p_result_doc ) + return NULL; - free( psz_result_xml ); + IXML_NodeList *p_elems = ixmlDocument_getElementsByTagName( p_result_doc, + "DIDL-Lite" ); - return p_browse_doc; + IXML_Node *p_node = ixmlNodeList_item( p_elems, 0 ); + ixmlNodeList_free( p_elems ); + + return (IXML_Document*)p_node; } +/* + * Get the number value from a SOAP response + */ +int xml_getNumber( IXML_Document* p_doc, + const char* psz_tag_name ) +{ + assert( p_doc ); + assert( psz_tag_name ); + + const char* psz = xml_getChildElementValue( p_doc, psz_tag_name ); + + if( !psz ) + return 0; + + char *psz_end; + long l = strtol( psz, &psz_end, 10 ); + + if( *psz_end || l < 0 || l > INT_MAX ) + return 0; + + return (int)l; +} -// Handles all UPnP events +/* + * Handles all UPnP events + */ static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data ) { services_discovery_t* p_sd = ( services_discovery_t* ) p_user_data; @@ -236,6 +362,7 @@ static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event; p_sys->p_server_list->removeServer( p_discovery->DeviceId ); + } break; @@ -251,7 +378,7 @@ static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data case UPNP_EVENT_AUTORENEWAL_FAILED: case UPNP_EVENT_SUBSCRIPTION_EXPIRED: { - // Re-subscribe... + /* Re-subscribe. */ Upnp_Event_Subscribe* p_s = ( Upnp_Event_Subscribe* )p_event; @@ -277,9 +404,13 @@ static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data } -// Class implementations... +/* + * Local class implementations. + */ -// MediaServer... +/* + * MediaServer + */ void MediaServer::parseDeviceDescription( IXML_Document* p_doc, const char* p_location, @@ -299,8 +430,8 @@ void MediaServer::parseDeviceDescription( IXML_Document* p_doc, const char* psz_base_url = p_location; - // Try to extract baseURL - IXML_NodeList* p_url_list = ixmlDocument_getElementsByTagName( p_doc, "baseURL" ); + /* Try to extract baseURL */ + IXML_NodeList* p_url_list = ixmlDocument_getElementsByTagName( p_doc, "URLBase" ); if ( p_url_list ) { @@ -313,7 +444,7 @@ void MediaServer::parseDeviceDescription( IXML_Document* p_doc, ixmlNodeList_free( p_url_list ); } - // Get devices + /* Get devices */ IXML_NodeList* p_device_list = ixmlDocument_getElementsByTagName( p_doc, "device" ); @@ -324,25 +455,31 @@ void MediaServer::parseDeviceDescription( IXML_Document* p_doc, IXML_Element* p_device_element = ( IXML_Element* ) ixmlNodeList_item( p_device_list, i ); - const char* psz_device_type = xml_getChildElementValue( p_device_element, - "deviceType" ); + if( !p_device_element ) + continue; + + const char* psz_device_type = + xml_getChildElementValue( p_device_element, "deviceType" ); + if ( !psz_device_type ) { msg_Warn( p_sd, "No deviceType found!" ); continue; } - if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, psz_device_type ) != 0 ) + if ( strncmp( MEDIA_SERVER_DEVICE_TYPE, psz_device_type, + strlen( MEDIA_SERVER_DEVICE_TYPE ) - 1 ) != 0 ) continue; - const char* psz_udn = xml_getChildElementValue( p_device_element, "UDN" ); + const char* psz_udn = xml_getChildElementValue( p_device_element, + "UDN" ); if ( !psz_udn ) { msg_Warn( p_sd, "No UDN!" ); continue; } - // Check if server is already added + /* 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 ); @@ -359,7 +496,8 @@ void MediaServer::parseDeviceDescription( IXML_Document* p_doc, continue; } - MediaServer* p_server = new MediaServer( psz_udn, psz_friendly_name, p_sd ); + MediaServer* p_server = new MediaServer( psz_udn, + psz_friendly_name, p_sd ); if ( !p_sd->p_sys->p_server_list->addServer( p_server ) ) { @@ -368,7 +506,7 @@ void MediaServer::parseDeviceDescription( IXML_Document* p_doc, continue; } - // Check for ContentDirectory service... + /* Check for ContentDirectory service. */ IXML_NodeList* p_service_list = ixmlElement_getElementsByTagName( p_device_element, "service" ); @@ -378,7 +516,7 @@ void MediaServer::parseDeviceDescription( IXML_Document* p_doc, j < ixmlNodeList_length( p_service_list ); j++ ) { IXML_Element* p_service_element = - ( IXML_Element* ) ixmlNodeList_item( p_service_list, j ); + ( IXML_Element* ) ixmlNodeList_item( p_service_list, j ); const char* psz_service_type = xml_getChildElementValue( p_service_element, @@ -389,10 +527,14 @@ void MediaServer::parseDeviceDescription( IXML_Document* p_doc, continue; } - if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE, - psz_service_type ) != 0 ) + int k = strlen( CONTENT_DIRECTORY_SERVICE_TYPE ) - 1; + if ( strncmp( CONTENT_DIRECTORY_SERVICE_TYPE, + psz_service_type, k ) != 0 ) continue; + p_server->_i_content_directory_service_version = + psz_service_type[k]; + const char* psz_event_sub_url = xml_getChildElementValue( p_service_element, "eventSubURL" ); @@ -411,45 +553,35 @@ void MediaServer::parseDeviceDescription( IXML_Document* p_doc, continue; } - // Try to subscribe to ContentDirectory service + /* Try to subscribe to ContentDirectory service */ char* psz_url = ( char* ) malloc( strlen( psz_base_url ) + strlen( psz_event_sub_url ) + 1 ); if ( psz_url ) { - char* psz_s1 = strdup( psz_base_url ); - char* psz_s2 = strdup( psz_event_sub_url ); - - if ( UpnpResolveURL( psz_s1, psz_s2, 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_s1 ); - free( psz_s2 ); free( psz_url ); } - // Try to browse content directory... + /* Try to browse content directory. */ psz_url = ( char* ) malloc( strlen( psz_base_url ) + strlen( psz_control_url ) + 1 ); if ( psz_url ) { - char* psz_s1 = strdup( psz_base_url ); - char* psz_s2 = strdup( psz_control_url ); - - if ( UpnpResolveURL( psz_s1, psz_s2, 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_s1 ); - free( psz_s2 ); free( psz_url ); } } @@ -471,6 +603,7 @@ MediaServer::MediaServer( const char* psz_udn, _p_contents = NULL; _p_input_item = NULL; + _i_content_directory_service_version = 1; } MediaServer::~MediaServer() @@ -480,7 +613,7 @@ MediaServer::~MediaServer() const char* MediaServer::getUDN() const { - return _UDN.c_str(); + return _UDN.c_str(); } const char* MediaServer::getFriendlyName() const @@ -508,6 +641,10 @@ const char* MediaServer::getContentDirectoryControlURL() const return _content_directory_control_url.c_str(); } +/** + * Subscribes current client handle to Content Directory Service. + * CDS exports the server shares to clients. + */ void MediaServer::subscribeToContentDirectory() { const char* psz_url = getContentDirectoryEventURL(); @@ -533,7 +670,9 @@ void MediaServer::subscribeToContentDirectory() getFriendlyName(), UpnpGetErrorMessage( i_res ) ); } } - +/* + * Constructs UpnpAction to browse available content. + */ IXML_Document* MediaServer::_browseAction( const char* psz_object_id_, const char* psz_browser_flag_, const char* psz_filter_, @@ -551,18 +690,15 @@ IXML_Document* MediaServer::_browseAction( const char* psz_object_id_, return 0; } - char* psz_object_id = strdup( psz_object_id_ ); - char* psz_browse_flag = strdup( psz_browser_flag_ ); - char* psz_filter = strdup( psz_filter_ ); - char* psz_starting_index = strdup( psz_starting_index_ ); - char* psz_requested_count = strdup( psz_requested_count_ ); - char* psz_sort_criteria = strdup( psz_sort_criteria_ ); 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 ); + psz_service_type, "ObjectID", psz_object_id_ ); if ( i_res != UPNP_E_SUCCESS ) { @@ -572,7 +708,7 @@ IXML_Document* MediaServer::_browseAction( const char* psz_object_id_, } i_res = UpnpAddToAction( &p_action, "Browse", - psz_service_type, "BrowseFlag", psz_browse_flag ); + psz_service_type, "BrowseFlag", psz_browser_flag_ ); if ( i_res != UPNP_E_SUCCESS ) { @@ -582,7 +718,7 @@ IXML_Document* MediaServer::_browseAction( const char* psz_object_id_, } i_res = UpnpAddToAction( &p_action, "Browse", - psz_service_type, "Filter", psz_filter ); + psz_service_type, "Filter", psz_filter_ ); if ( i_res != UPNP_E_SUCCESS ) { @@ -592,7 +728,7 @@ IXML_Document* MediaServer::_browseAction( const char* psz_object_id_, } i_res = UpnpAddToAction( &p_action, "Browse", - psz_service_type, "StartingIndex", psz_starting_index ); + psz_service_type, "StartingIndex", psz_starting_index_ ); if ( i_res != UPNP_E_SUCCESS ) { @@ -602,7 +738,7 @@ IXML_Document* MediaServer::_browseAction( const char* psz_object_id_, } i_res = UpnpAddToAction( &p_action, "Browse", - psz_service_type, "RequestedCount", psz_requested_count ); + psz_service_type, "RequestedCount", psz_requested_count_ ); if ( i_res != UPNP_E_SUCCESS ) { @@ -612,7 +748,7 @@ IXML_Document* MediaServer::_browseAction( const char* psz_object_id_, } i_res = UpnpAddToAction( &p_action, "Browse", - psz_service_type, "SortCriteria", psz_sort_criteria ); + psz_service_type, "SortCriteria", psz_sort_criteria_ ); if ( i_res != UPNP_E_SUCCESS ) { @@ -623,8 +759,8 @@ IXML_Document* MediaServer::_browseAction( const char* psz_object_id_, i_res = UpnpSendAction( _p_sd->p_sys->client_handle, psz_url, - CONTENT_DIRECTORY_SERVICE_TYPE, - 0, + psz_service_type, + 0, /* ignored in SDK, must be NULL */ p_action, &p_response ); @@ -639,13 +775,6 @@ IXML_Document* MediaServer::_browseAction( const char* psz_object_id_, browseActionCleanup: - free( psz_object_id ); - free( psz_browse_flag ); - free( psz_filter ); - free( psz_starting_index ); - free( psz_requested_count ); - free( psz_sort_criteria ); - free( psz_service_type ); ixmlDocument_free( p_action ); @@ -654,7 +783,7 @@ browseActionCleanup: void MediaServer::fetchContents() { - // Delete previous contents to prevent duplicate entries + /* Delete previous contents to prevent duplicate entries */ if ( _p_contents ) { delete _p_contents; @@ -664,7 +793,7 @@ void MediaServer::fetchContents() Container* root = new Container( 0, "0", getFriendlyName() ); - _fetchContents( root ); + _fetchContents( root, 0 ); _p_contents = root; _p_contents->setInputItem( _p_input_item ); @@ -672,7 +801,10 @@ void MediaServer::fetchContents() _buildPlaylist( _p_contents, NULL ); } -bool MediaServer::_fetchContents( Container* p_parent ) +/* + * Fetches and parses the UPNP response + */ +bool MediaServer::_fetchContents( Container* p_parent, int i_offset ) { if (!p_parent) { @@ -680,9 +812,23 @@ bool MediaServer::_fetchContents( Container* p_parent ) return false; } + char* psz_starting_index; + if( asprintf( &psz_starting_index, "%d", i_offset ) < 0 ) + { + msg_Err( _p_sd, "asprintf error:%d", i_offset ); + return false; + } + IXML_Document* p_response = _browseAction( p_parent->getObjectID(), "BrowseDirectChildren", - "*", "0", "0", "" ); + "id,dc:title,res," /* Filter */ + "sec:CaptionInfo,sec:CaptionInfoEx," + "pv:subtitlefile", + psz_starting_index, /* StartingIndex */ + "0", /* RequestedCount */ + "" /* SortCriteria */ + ); + free( psz_starting_index ); if ( !p_response ) { msg_Err( _p_sd, "No response from browse() action" ); @@ -690,6 +836,14 @@ bool MediaServer::_fetchContents( Container* p_parent ) } 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 ) @@ -697,12 +851,9 @@ bool MediaServer::_fetchContents( Container* p_parent ) msg_Err( _p_sd, "browse() response parsing failed" ); return false; } + #ifndef NDEBUG - else - { - msg_Dbg( _p_sd, "Got DIDL document: %s", - ixmlPrintDocument( p_result ) ); - } + msg_Dbg( _p_sd, "Got DIDL document: %s", ixmlPrintDocument( p_result ) ); #endif IXML_NodeList* containerNodeList = @@ -721,36 +872,15 @@ bool MediaServer::_fetchContents( Container* p_parent ) if ( !objectID ) continue; - const char* childCountStr = - ixmlElement_getAttribute( containerElement, "childCount" ); - - if ( !childCountStr ) - continue; - - int childCount = atoi( childCountStr ); const char* title = xml_getChildElementValue( containerElement, "dc:title" ); if ( !title ) continue; - const char* resource = xml_getChildElementValue( containerElement, - "res" ); - - if ( resource && childCount < 1 ) - { - Item* item = new Item( p_parent, objectID, title, resource ); - p_parent->addItem( item ); - } - - else - { - Container* container = new Container( p_parent, objectID, title ); - p_parent->addContainer( container ); - - if ( childCount > 0 ) - _fetchContents( container ); - } + Container* container = new Container( p_parent, objectID, title ); + p_parent->addContainer( container ); + _fetchContents( container, 0 ); } ixmlNodeList_free( containerNodeList ); } @@ -776,19 +906,56 @@ bool MediaServer::_fetchContents( Container* p_parent ) if ( !title ) continue; - const char* resource = - xml_getChildElementValue( itemElement, "res" ); + const char* psz_subtitles = xml_getChildElementValue( itemElement, + "sec:CaptionInfo" ); - if ( !resource ) - continue; + if ( !psz_subtitles ) + psz_subtitles = xml_getChildElementValue( itemElement, + "sec:CaptionInfoEx" ); + + if ( !psz_subtitles ) + psz_subtitles = xml_getChildElementValue( itemElement, + "pv:subtitlefile" ); + + /* Try to extract all resources in DIDL */ + IXML_NodeList* p_resource_list = ixmlDocument_getElementsByTagName( (IXML_Document*) itemElement, "res" ); + if ( p_resource_list ) + { + int i_length = ixmlNodeList_length( p_resource_list ); + for ( int i = 0; i < i_length; i++ ) + { + mtime_t i_duration = -1; + int i_hours, i_minutes, i_seconds; + IXML_Element* p_resource = ( IXML_Element* ) ixmlNodeList_item( p_resource_list, i ); + const char* psz_resource_url = xml_getChildElementValue( p_resource, "res" ); + if( !psz_resource_url ) + continue; + const char* psz_duration = ixmlElement_getAttribute( p_resource, "duration" ); - Item* item = new Item( p_parent, objectID, title, resource ); - p_parent->addItem( item ); + if ( psz_duration ) + { + if( sscanf( psz_duration, "%d:%02d:%02d", + &i_hours, &i_minutes, &i_seconds ) ) + i_duration = INT64_C(1000000) * ( i_hours*3600 + + i_minutes*60 + + i_seconds ); + } + + Item* item = new Item( p_parent, objectID, title, psz_resource_url, psz_subtitles, i_duration ); + p_parent->addItem( item ); + } + ixmlNodeList_free( p_resource_list ); + } + 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; } @@ -825,6 +992,10 @@ bool MediaServer::_fetchContents( Container* p_parent ) // 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 ) { bool b_send = p_input_node == NULL; @@ -835,7 +1006,7 @@ void MediaServer::_buildPlaylist( Container* p_parent, input_item_node_t *p_inpu { Container* p_container = p_parent->getContainer( i ); - input_item_t* p_input_item = input_item_New( _p_sd, "vlc://nop", + 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 ); @@ -848,10 +1019,32 @@ void MediaServer::_buildPlaylist( Container* p_parent, input_item_node_t *p_inpu { Item* p_item = p_parent->getItem( i ); - input_item_t* p_input_item = input_item_New( _p_sd, - p_item->getResource(), - p_item->getTitle() ); + 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() ); + assert( p_input_item ); + if( ppsz_opts ) + { + free( ppsz_opts[0] ); + free( ppsz_opts[1] ); + free( ppsz_opts ); + + psz_input_slave = NULL; + } + input_item_node_AppendItem( p_input_node, p_input_item ); p_item->setInputItem( p_input_item ); } @@ -862,10 +1055,10 @@ void MediaServer::_buildPlaylist( Container* p_parent, input_item_node_t *p_inpu void MediaServer::setInputItem( input_item_t* p_input_item ) { - if(_p_input_item == p_input_item) + if( _p_input_item == p_input_item ) return; - if(_p_input_item) + if( _p_input_item ) vlc_gc_decref( _p_input_item ); vlc_gc_incref( p_input_item ); @@ -883,8 +1076,9 @@ bool MediaServer::compareSID( const char* psz_sid ) } -// MediaServerList... - +/* + * MediaServerList class + */ MediaServerList::MediaServerList( services_discovery_t* p_sd ) { _p_sd = p_sd; @@ -905,8 +1099,10 @@ bool MediaServerList::addServer( MediaServer* p_server ) msg_Dbg( _p_sd, "Adding server '%s' with uuid '%s'", p_server->getFriendlyName(), p_server->getUDN() ); - p_input_item = input_item_New( _p_sd, "vlc://nop", - p_server->getFriendlyName() ); + 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 ); @@ -970,23 +1166,28 @@ void MediaServerList::removeServer( const char* psz_udn ) } -// Item... - -Item::Item( Container* p_parent, const char* psz_object_id, const char* psz_title, - const char* psz_resource ) +/* + * 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) + if( _p_input_item ) vlc_gc_decref( _p_input_item ); } @@ -1005,20 +1206,90 @@ 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, '/' ) ) + { + free( psz_uri_scheme ); + return NULL; + } + + 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) + if( _p_input_item == p_input_item ) return; - if(_p_input_item) + if( _p_input_item ) vlc_gc_decref( _p_input_item ); vlc_gc_incref( p_input_item ); _p_input_item = p_input_item; } -// Container... - +/* + * Container class + */ Container::Container( Container* p_parent, const char* psz_object_id, const char* psz_title ) @@ -1043,7 +1314,7 @@ Container::~Container() delete _items[i]; } - if(_p_input_item ) + if( _p_input_item ) vlc_gc_decref( _p_input_item ); } @@ -1096,10 +1367,10 @@ Container* Container::getParent() void Container::setInputItem( input_item_t* p_input_item ) { - if(_p_input_item == p_input_item) + if( _p_input_item == p_input_item ) return; - if(_p_input_item) + if( _p_input_item ) vlc_gc_decref( _p_input_item ); vlc_gc_incref( p_input_item );