/*****************************************************************************
- * Upnp.cpp : UPnP discovery module (libupnp)
+ * upnp.cpp : UPnP discovery module (libupnp)
*****************************************************************************
* Copyright (C) 2004-2011 the VideoLAN team
* $Id$
* 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 <vlc_plugin.h>
#include <vlc_services_discovery.h>
#include <assert.h>
+#include <limits.h>
-// 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;
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" ) );
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;
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 )
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;
}
return VLC_SUCCESS;
}
+/*
+ * Releases resources.
+ */
static void Close( vlc_object_t *p_this )
{
services_discovery_t *p_sd = ( services_discovery_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 )
{
- ixmlRelaxParser(1);
+ assert( p_parent );
+ assert( psz_tag_name );
+ assert( psz_attribute );
- if ( !p_doc ) return 0;
+ IXML_NodeList* p_node_list;
+ p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name );
+ if ( !p_node_list ) return NULL;
- IXML_NodeList* p_result_list = ixmlDocument_getElementsByTagName( p_doc,
- "Result" );
+ IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
+ ixmlNodeList_free( p_node_list );
+ if ( !p_element ) return NULL;
- if ( !p_result_list ) return 0;
+ return ixmlElement_getAttribute( (IXML_Element*) p_element, psz_attribute );
+}
- IXML_Node* p_result_node = ixmlNodeList_item( p_result_list, 0 );
+/*
+ * 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 );
- ixmlNodeList_free( p_result_list );
+ IXML_NodeList* p_node_list;
+ p_node_list = ixmlDocument_getElementsByTagName( p_doc, psz_tag_name );
+ if ( !p_node_list ) return NULL;
- if ( !p_result_node ) return 0;
+ 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_result_node );
- if ( !p_text_node ) return 0;
+ IXML_Node* p_text_node = ixmlNode_getFirstChild( p_element );
+ if ( !p_text_node ) return NULL;
- const char* psz_result_string = ixmlNode_getNodeValue( p_text_node );
- char* psz_result_xml = strdup( psz_result_string );
+ return ixmlNode_getNodeValue( p_text_node );
+}
- IXML_Document* p_browse_doc = ixmlParseBuffer( psz_result_xml );
+/*
+ * Extracts the result document from a SOAP response
+ */
+IXML_Document* parseBrowseResult( IXML_Document* p_doc )
+{
+ assert( p_doc );
+
+ const char* psz_raw_didl = xml_getChildElementValue( p_doc, "Result" );
+
+ if( !psz_raw_didl )
+ return NULL;
+
+ /* First, try parsing the buffer as is */
+ IXML_Document* p_result_doc = ixmlParseBuffer( psz_raw_didl );
+ if( !p_result_doc ) {
+ /* 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 = "<?xml version=\"1.0\" ?>"
+ "<Result xmlns:sec=\"urn:samsung:metadata:2009\">%s</Result>";
+
+ char* psz_xml_result_string = NULL;
+ if( -1 == asprintf( &psz_xml_result_string,
+ psz_xml_result_fmt,
+ psz_raw_didl) )
+ return NULL;
+
+ p_result_doc = ixmlParseBuffer( psz_xml_result_string );
+ free( psz_xml_result_string );
+ }
- free( psz_result_xml );
+ if( !p_result_doc )
+ return NULL;
- return p_browse_doc;
+ IXML_NodeList *p_elems = ixmlDocument_getElementsByTagName( p_result_doc,
+ "DIDL-Lite" );
+
+ 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 );
-// Handles all UPnP events
+ if( *psz_end || l < 0 || l > INT_MAX )
+ return 0;
+
+ return (int)l;
+}
+
+/*
+ * 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;
struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
p_sys->p_server_list->removeServer( p_discovery->DeviceId );
+
}
break;
case UPNP_EVENT_AUTORENEWAL_FAILED:
case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
{
- // Re-subscribe...
+ /* Re-subscribe. */
Upnp_Event_Subscribe* p_s = ( Upnp_Event_Subscribe* )p_event;
}
-// Class implementations...
+/*
+ * Local class implementations.
+ */
-// MediaServer...
+/*
+ * MediaServer
+ */
void MediaServer::parseDeviceDescription( IXML_Document* p_doc,
const char* p_location,
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 )
{
ixmlNodeList_free( p_url_list );
}
- // Get devices
+ /* Get devices */
IXML_NodeList* p_device_list =
ixmlDocument_getElementsByTagName( p_doc, "device" );
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 );
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 ) )
{
continue;
}
- // Check for ContentDirectory service...
+ /* Check for ContentDirectory service. */
IXML_NodeList* p_service_list =
ixmlElement_getElementsByTagName( p_device_element,
"service" );
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,
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" );
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 );
}
}
_p_contents = NULL;
_p_input_item = NULL;
+ _i_content_directory_service_version = 1;
}
MediaServer::~MediaServer()
const char* MediaServer::getUDN() const
{
- return _UDN.c_str();
+ return _UDN.c_str();
}
const char* MediaServer::getFriendlyName() 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();
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_,
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 )
{
}
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 )
{
}
i_res = UpnpAddToAction( &p_action, "Browse",
- psz_service_type, "Filter", psz_filter );
+ psz_service_type, "Filter", psz_filter_ );
if ( i_res != UPNP_E_SUCCESS )
{
}
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 )
{
}
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 )
{
}
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 )
{
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 );
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 );
void MediaServer::fetchContents()
{
- // Delete previous contents to prevent duplicate entries
+ /* Delete previous contents to prevent duplicate entries */
if ( _p_contents )
{
delete _p_contents;
Container* root = new Container( 0, "0", getFriendlyName() );
- _fetchContents( root );
+ _fetchContents( root, 0 );
_p_contents = root;
_p_contents->setInputItem( _p_input_item );
_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)
{
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" );
}
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" );
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 =
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 );
}
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" );
+
+ 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, resource );
- p_parent->addItem( item );
+ 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;
}
// 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;
{
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 );
{
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 );
}
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 );
}
-// MediaServerList...
-
+/*
+ * MediaServerList class
+ */
MediaServerList::MediaServerList( services_discovery_t* p_sd )
{
_p_sd = p_sd;
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 );
}
-// 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 );
}
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 )
delete _items[i];
}
- if(_p_input_item )
+ if( _p_input_item )
vlc_gc_decref( _p_input_item );
}
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 );