]> git.sesse.net Git - vlc/blobdiff - modules/services_discovery/upnp.cpp
podcasts SD: fix variable handling and minor cleanup (close #8947)
[vlc] / modules / services_discovery / upnp.cpp
index 3a992e5ca583a2526101378e0cfa0b0f05f967a7..66223fa091369d81af1e0579b1606be2848dc761 100644 (file)
@@ -1,5 +1,5 @@
 /*****************************************************************************
- * Upnp.cpp :  UPnP discovery module (libupnp)
+ * upnp.cpp :  UPnP discovery module (libupnp)
  *****************************************************************************
  * Copyright (C) 2004-2011 the VideoLAN team
  * $Id$
@@ -38,6 +38,7 @@
 #include <vlc_services_discovery.h>
 
 #include <assert.h>
+#include <limits.h>
 
 /*
  * Constants
@@ -84,9 +85,15 @@ 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_ );
+                                        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 );
 
@@ -103,10 +110,16 @@ static int Open( vlc_object_t *p_this )
     if( !( p_sd->p_sys = p_sys ) )
         return VLC_ENOMEM;
 
-    /* Initialize on first IPv4-capable adapter and first open port
-     * TODO: use UpnpInit2() to utilize IPv6.
-     */
+#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 ) );
@@ -114,6 +127,8 @@ static int Open( vlc_object_t *p_this )
         return VLC_EGENERIC;
     }
 
+    ixmlRelaxParser( 1 );
+
     p_sys->p_server_list = new MediaServerList( p_sd );
     vlc_mutex_init( &p_sys->callback_lock );
 
@@ -136,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;
     }
@@ -169,17 +188,18 @@ static void Close( vlc_object_t *p_this )
  * 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 NULL;
-    if ( !psz_tag_name_ ) return NULL;
+    assert( p_parent );
+    assert( psz_tag_name );
 
-    IXML_NodeList* p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name_ );
+    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;
+    if ( !p_element )   return NULL;
 
     IXML_Node* p_text_node = ixmlNode_getFirstChild( p_element );
     if ( !p_text_node ) return NULL;
@@ -191,21 +211,45 @@ const char* xml_getChildElementValue( IXML_Element* p_parent,
  * 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_ )
+                                        const char* psz_tag_name,
+                                        const char* psz_attribute )
 {
-    if ( !p_parent ) return NULL;
-    if ( !psz_tag_name_ ) return NULL;
-    if ( !psz_attribute_ ) return NULL;
+    assert( p_parent );
+    assert( psz_tag_name );
+    assert( psz_attribute );
 
-    IXML_NodeList* p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name_ );
-    if ( !p_node_list ) return NULL;
+    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;
+    if ( !p_element )    return NULL;
 
-    return ixmlElement_getAttribute( (IXML_Element*) p_element, psz_attribute_ );
+    IXML_Node* p_text_node = ixmlNode_getFirstChild( p_element );
+    if ( !p_text_node )  return NULL;
+
+    return ixmlNode_getNodeValue( p_text_node );
 }
 
 /*
@@ -213,31 +257,70 @@ const char* xml_getChildElementAttributeValue( IXML_Element* p_parent,
  */
 IXML_Document* parseBrowseResult( IXML_Document* p_doc )
 {
-    ixmlRelaxParser( 1 );
+    assert( p_doc );
 
-    if ( !p_doc ) 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 = "<?xml version=\"1.0\" ?>"
+        "<Result xmlns:sec=\"urn:samsung:metadata:2009\">%s</Result>";
 
-    IXML_NodeList* p_result_list = ixmlDocument_getElementsByTagName( p_doc,
-                                                                   "Result" );
+    char* psz_xml_result_string = NULL;
+    const char* psz_raw_didl = xml_getChildElementValue( p_doc, "Result" );
 
-    if ( !p_result_list ) return 0;
+    if( !psz_raw_didl )
+        return NULL;
 
-    IXML_Node* p_result_node = ixmlNodeList_item( p_result_list, 0 );
+    if( -1 == asprintf( &psz_xml_result_string,
+                         psz_xml_result_fmt,
+                         psz_raw_didl) )
+        return NULL;
 
-    ixmlNodeList_free( p_result_list );
 
-    if ( !p_result_node ) return 0;
+    IXML_Document* p_result_doc = ixmlParseBuffer( psz_xml_result_string );
+    free( psz_xml_result_string );
 
-    IXML_Node* p_text_node = ixmlNode_getFirstChild( p_result_node );
-    if ( !p_text_node ) return 0;
+    if( !p_result_doc )
+        return NULL;
 
-    const char* psz_result_string = ixmlNode_getNodeValue( p_text_node );
+    IXML_NodeList *p_elems = ixmlDocument_getElementsByTagName( p_result_doc,
+                                                                "DIDL-Lite" );
 
-    IXML_Document* p_browse_doc = ixmlParseBuffer( psz_result_string );
+    IXML_Node *p_node = ixmlNodeList_item( p_elems, 0 );
+    ixmlNodeList_free( p_elems );
 
-    return p_browse_doc;
+    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
@@ -348,7 +431,7 @@ 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" );
+    IXML_NodeList* p_url_list = ixmlDocument_getElementsByTagName( p_doc, "URLBase" );
     if ( p_url_list )
     {
 
@@ -372,18 +455,24 @@ 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!" );
@@ -407,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 ) )
             {
@@ -426,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,
@@ -437,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" );
@@ -509,6 +603,7 @@ MediaServer::MediaServer( const char* psz_udn,
 
     _p_contents = NULL;
     _p_input_item = NULL;
+    _i_content_directory_service_version = 1;
 }
 
 MediaServer::~MediaServer()
@@ -597,6 +692,9 @@ IXML_Document* MediaServer::_browseAction( const char* psz_object_id_,
 
     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",
@@ -695,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 );
@@ -706,7 +804,7 @@ void MediaServer::fetchContents()
 /*
  * Fetches and parses the UPNP response
  */
-bool MediaServer::_fetchContents( Container* p_parent )
+bool MediaServer::_fetchContents( Container* p_parent, int i_offset )
 {
     if (!p_parent)
     {
@@ -714,9 +812,22 @@ 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",
+                                      psz_starting_index, /* StartingIndex */
+                                      "0", /* RequestedCount */
+                                      "" /* SortCriteria */
+                                      );
+    free( psz_starting_index );
     if ( !p_response )
     {
         msg_Err( _p_sd, "No response from browse() action" );
@@ -724,6 +835,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 )
@@ -731,12 +850,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 =
@@ -755,36 +871,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, -1 );
-                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 );
     }
@@ -810,36 +905,52 @@ 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" );
 
-            const char* psz_duration = xml_getChildElementAttributeValue( itemElement,
-                                                                    "res",
-                                                                    "duration" );
+            /* 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" );
 
-            mtime_t i_duration = -1;
-            int i_hours, i_minutes, i_seconds, i_decis;
+                    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 );
+                    }
 
-            if ( psz_duration )
-            {
-                if( sscanf( psz_duration, "%02d:%02d:%02d.%d",
-                        &i_hours, &i_minutes, &i_seconds, &i_decis ))
-                    i_duration = INT64_C(1000000) * ( i_hours*3600 +
-                                                      i_minutes*60 +
-                                                      i_seconds ) +
-                                 INT64_C(100000) * i_decis;
+                    Item* item = new Item( p_parent, objectID, title, psz_resource_url, psz_subtitles, i_duration );
+                    p_parent->addItem( item );
+                }
+                ixmlNodeList_free( p_resource_list );
             }
-
-            Item* item = new Item( p_parent, objectID, title, resource, i_duration );
-            p_parent->addItem( item );
+            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;
 }
 
@@ -903,14 +1014,32 @@ void MediaServer::_buildPlaylist( Container* p_parent, input_item_node_t *p_inpu
     {
         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(),
-                                               0,
-                                               NULL,
-                                               0,
-                                               p_item->getDuration() );
+                                           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 );
     }
@@ -1035,14 +1164,17 @@ void MediaServerList::removeServer( const char* psz_udn )
 /*
  * Item class
  */
-Item::Item( Container* p_parent, const char* psz_object_id, const char* psz_title,
-           const char* psz_resource, mtime_t i_duration )
+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;
@@ -1069,11 +1201,75 @@ 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 )