1 /*****************************************************************************
2 * upnp.cpp : UPnP discovery module (libupnp)
3 *****************************************************************************
4 * Copyright (C) 2004-2011 the VideoLAN team
7 * Authors: RĂ©mi Denis-Courmont <rem # videolan.org> (original plugin)
8 * Christian Henz <henz # c-lab.de>
9 * Mirsal Ennaime <mirsal dot ennaime at gmail dot com>
11 * UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26 *****************************************************************************/
28 #define __STDC_CONSTANT_MACROS 1
35 #include "services_discovery/upnp.hpp"
37 #include <vlc_plugin.h>
38 #include <vlc_services_discovery.h>
46 const char* MEDIA_SERVER_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaServer:1";
47 const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:ContentDirectory:1";
52 struct services_discovery_sys_t
54 UpnpClient_Handle client_handle;
55 MediaServerList* p_server_list;
56 vlc_mutex_t callback_lock;
60 * VLC callback prototypes
62 static int Open( vlc_object_t* );
63 static void Close( vlc_object_t* );
64 VLC_SD_PROBE_HELPER( "upnp", "Universal Plug'n'Play", SD_CAT_LAN )
70 set_shortname( "UPnP" );
71 set_description( N_( "Universal Plug'n'Play" ) );
72 set_category( CAT_PLAYLIST );
73 set_subcategory( SUBCAT_PLAYLIST_SD );
74 set_capability( "services_discovery", 0 );
75 set_callbacks( Open, Close );
77 VLC_SD_PROBE_SUBMODULE
83 static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data );
85 const char* xml_getChildElementValue( IXML_Element* p_parent,
86 const char* psz_tag_name );
88 const char* xml_getChildElementValue( IXML_Document* p_doc,
89 const char* psz_tag_name );
91 const char* xml_getChildElementAttributeValue( IXML_Element* p_parent,
92 const char* psz_tag_name,
93 const char* psz_attribute );
95 int xml_getNumber( IXML_Document* p_doc,
96 const char* psz_tag_name );
98 IXML_Document* parseBrowseResult( IXML_Document* p_doc );
101 * Initializes UPNP instance.
103 static int Open( vlc_object_t *p_this )
106 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
107 services_discovery_sys_t *p_sys = ( services_discovery_sys_t * )
108 calloc( 1, sizeof( services_discovery_sys_t ) );
110 if( !( p_sd->p_sys = p_sys ) )
113 #ifdef UPNP_ENABLE_IPV6
115 psz_miface = var_InheritString( p_sd, "miface" );
116 msg_Info( p_sd, "Initializing libupnp on '%s' interface", psz_miface );
117 i_res = UpnpInit2( psz_miface, 0 );
120 /* If UpnpInit2 isnt available, initialize on first IPv4-capable interface */
121 i_res = UpnpInit( 0, 0 );
123 if( i_res != UPNP_E_SUCCESS )
125 msg_Err( p_sd, "Initialization failed: %s", UpnpGetErrorMessage( i_res ) );
130 ixmlRelaxParser( 1 );
132 p_sys->p_server_list = new MediaServerList( p_sd );
133 vlc_mutex_init( &p_sys->callback_lock );
135 /* Register a control point */
136 i_res = UpnpRegisterClient( Callback, p_sd, &p_sys->client_handle );
137 if( i_res != UPNP_E_SUCCESS )
139 msg_Err( p_sd, "Client registration failed: %s", UpnpGetErrorMessage( i_res ) );
140 Close( (vlc_object_t*) p_sd );
144 /* Search for media servers */
145 i_res = UpnpSearchAsync( p_sys->client_handle, 5,
146 MEDIA_SERVER_DEVICE_TYPE, p_sd );
147 if( i_res != UPNP_E_SUCCESS )
149 msg_Err( p_sd, "Error sending search request: %s", UpnpGetErrorMessage( i_res ) );
150 Close( (vlc_object_t*) p_sd );
154 /* libupnp does not treat a maximum content length of 0 as unlimited
155 * until 64dedf (~ pupnp v1.6.7) and provides no sane way to discriminate
156 * between versions */
157 if( (i_res = UpnpSetMaxContentLength( INT_MAX )) != UPNP_E_SUCCESS )
159 msg_Err( p_sd, "Failed to set maximum content length: %s",
160 UpnpGetErrorMessage( i_res ));
162 Close( (vlc_object_t*) p_sd );
170 * Releases resources.
172 static void Close( vlc_object_t *p_this )
174 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
176 UpnpUnRegisterClient( p_sd->p_sys->client_handle );
179 delete p_sd->p_sys->p_server_list;
180 vlc_mutex_destroy( &p_sd->p_sys->callback_lock );
185 /* XML utility functions */
188 * Returns the value of a child element, or NULL on error
190 const char* xml_getChildElementValue( IXML_Element* p_parent,
191 const char* psz_tag_name )
194 assert( psz_tag_name );
196 IXML_NodeList* p_node_list;
197 p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name );
198 if ( !p_node_list ) return NULL;
200 IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
201 ixmlNodeList_free( p_node_list );
202 if ( !p_element ) return NULL;
204 IXML_Node* p_text_node = ixmlNode_getFirstChild( p_element );
205 if ( !p_text_node ) return NULL;
207 return ixmlNode_getNodeValue( p_text_node );
211 * Returns the value of a child element's attribute, or NULL on error
213 const char* xml_getChildElementAttributeValue( IXML_Element* p_parent,
214 const char* psz_tag_name,
215 const char* psz_attribute )
218 assert( psz_tag_name );
219 assert( psz_attribute );
221 IXML_NodeList* p_node_list;
222 p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name );
223 if ( !p_node_list ) return NULL;
225 IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
226 ixmlNodeList_free( p_node_list );
227 if ( !p_element ) return NULL;
229 return ixmlElement_getAttribute( (IXML_Element*) p_element, psz_attribute );
233 * Returns the value of a child element, or NULL on error
235 const char* xml_getChildElementValue( IXML_Document* p_doc,
236 const char* psz_tag_name )
239 assert( psz_tag_name );
241 IXML_NodeList* p_node_list;
242 p_node_list = ixmlDocument_getElementsByTagName( p_doc, psz_tag_name );
243 if ( !p_node_list ) return NULL;
245 IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
246 ixmlNodeList_free( p_node_list );
247 if ( !p_element ) return NULL;
249 IXML_Node* p_text_node = ixmlNode_getFirstChild( p_element );
250 if ( !p_text_node ) return NULL;
252 return ixmlNode_getNodeValue( p_text_node );
256 * Extracts the result document from a SOAP response
258 IXML_Document* parseBrowseResult( IXML_Document* p_doc )
262 const char* psz_raw_didl = xml_getChildElementValue( p_doc, "Result" );
267 /* First, try parsing the buffer as is */
268 IXML_Document* p_result_doc = ixmlParseBuffer( psz_raw_didl );
269 if( !p_result_doc ) {
270 /* Missing namespaces confuse the ixml parser. This is a very ugly
271 * hack but it is needeed until devices start sending valid XML.
275 * The DIDL document is extracted from the Result tag, then wrapped into
276 * a valid XML header and a new root tag which contains missing namespace
277 * definitions so the ixml parser understands it.
279 * If you know of a better workaround, please oh please fix it */
280 const char* psz_xml_result_fmt = "<?xml version=\"1.0\" ?>"
281 "<Result xmlns:sec=\"urn:samsung:metadata:2009\">%s</Result>";
283 char* psz_xml_result_string = NULL;
284 if( -1 == asprintf( &psz_xml_result_string,
289 p_result_doc = ixmlParseBuffer( psz_xml_result_string );
290 free( psz_xml_result_string );
296 IXML_NodeList *p_elems = ixmlDocument_getElementsByTagName( p_result_doc,
299 IXML_Node *p_node = ixmlNodeList_item( p_elems, 0 );
300 ixmlNodeList_free( p_elems );
302 return (IXML_Document*)p_node;
306 * Get the number value from a SOAP response
308 int xml_getNumber( IXML_Document* p_doc,
309 const char* psz_tag_name )
312 assert( psz_tag_name );
314 const char* psz = xml_getChildElementValue( p_doc, psz_tag_name );
320 long l = strtol( psz, &psz_end, 10 );
322 if( *psz_end || l < 0 || l > INT_MAX )
329 * Handles all UPnP events
331 static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data )
333 services_discovery_t* p_sd = ( services_discovery_t* ) p_user_data;
334 services_discovery_sys_t* p_sys = p_sd->p_sys;
335 vlc_mutex_locker locker( &p_sys->callback_lock );
339 case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
340 case UPNP_DISCOVERY_SEARCH_RESULT:
342 struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
344 IXML_Document *p_description_doc = 0;
347 i_res = UpnpDownloadXmlDoc( p_discovery->Location, &p_description_doc );
348 if ( i_res != UPNP_E_SUCCESS )
350 msg_Warn( p_sd, "Could not download device description! "
351 "Fetching data from %s failed: %s",
352 p_discovery->Location, UpnpGetErrorMessage( i_res ) );
356 MediaServer::parseDeviceDescription( p_description_doc,
357 p_discovery->Location, p_sd );
359 ixmlDocument_free( p_description_doc );
363 case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
365 struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
367 p_sys->p_server_list->removeServer( p_discovery->DeviceId );
372 case UPNP_EVENT_RECEIVED:
374 Upnp_Event* p_e = ( Upnp_Event* )p_event;
376 MediaServer* p_server = p_sys->p_server_list->getServerBySID( p_e->Sid );
377 if ( p_server ) p_server->fetchContents();
381 case UPNP_EVENT_AUTORENEWAL_FAILED:
382 case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
386 Upnp_Event_Subscribe* p_s = ( Upnp_Event_Subscribe* )p_event;
388 MediaServer* p_server = p_sys->p_server_list->getServerBySID( p_s->Sid );
389 if ( p_server ) p_server->subscribeToContentDirectory();
393 case UPNP_EVENT_SUBSCRIBE_COMPLETE:
394 msg_Warn( p_sd, "subscription complete" );
397 case UPNP_DISCOVERY_SEARCH_TIMEOUT:
398 msg_Warn( p_sd, "search timeout" );
402 msg_Err( p_sd, "Unhandled event, please report ( type=%d )", event_type );
406 return UPNP_E_SUCCESS;
411 * Local class implementations.
418 void MediaServer::parseDeviceDescription( IXML_Document* p_doc,
419 const char* p_location,
420 services_discovery_t* p_sd )
424 msg_Err( p_sd, "Null IXML_Document" );
430 msg_Err( p_sd, "Null location" );
434 const char* psz_base_url = p_location;
436 /* Try to extract baseURL */
437 IXML_NodeList* p_url_list = ixmlDocument_getElementsByTagName( p_doc, "URLBase" );
441 if ( IXML_Node* p_url_node = ixmlNodeList_item( p_url_list, 0 ) )
443 IXML_Node* p_text_node = ixmlNode_getFirstChild( p_url_node );
444 if ( p_text_node ) psz_base_url = ixmlNode_getNodeValue( p_text_node );
447 ixmlNodeList_free( p_url_list );
451 IXML_NodeList* p_device_list =
452 ixmlDocument_getElementsByTagName( p_doc, "device" );
456 for ( unsigned int i = 0; i < ixmlNodeList_length( p_device_list ); i++ )
458 IXML_Element* p_device_element =
459 ( IXML_Element* ) ixmlNodeList_item( p_device_list, i );
461 if( !p_device_element )
464 const char* psz_device_type =
465 xml_getChildElementValue( p_device_element, "deviceType" );
467 if ( !psz_device_type )
469 msg_Warn( p_sd, "No deviceType found!" );
473 if ( strncmp( MEDIA_SERVER_DEVICE_TYPE, psz_device_type,
474 strlen( MEDIA_SERVER_DEVICE_TYPE ) - 1 ) != 0 )
477 const char* psz_udn = xml_getChildElementValue( p_device_element,
481 msg_Warn( p_sd, "No UDN!" );
485 /* Check if server is already added */
486 if ( p_sd->p_sys->p_server_list->getServer( psz_udn ) != 0 )
488 msg_Warn( p_sd, "Server with uuid '%s' already exists.", psz_udn );
492 const char* psz_friendly_name =
493 xml_getChildElementValue( p_device_element,
496 if ( !psz_friendly_name )
498 msg_Dbg( p_sd, "No friendlyName!" );
502 MediaServer* p_server = new MediaServer( psz_udn,
503 psz_friendly_name, p_sd );
505 if ( !p_sd->p_sys->p_server_list->addServer( p_server ) )
512 /* Check for ContentDirectory service. */
513 IXML_NodeList* p_service_list =
514 ixmlElement_getElementsByTagName( p_device_element,
516 if ( p_service_list )
518 for ( unsigned int j = 0;
519 j < ixmlNodeList_length( p_service_list ); j++ )
521 IXML_Element* p_service_element =
522 ( IXML_Element* ) ixmlNodeList_item( p_service_list, j );
524 const char* psz_service_type =
525 xml_getChildElementValue( p_service_element,
527 if ( !psz_service_type )
529 msg_Warn( p_sd, "No service type found." );
533 int k = strlen( CONTENT_DIRECTORY_SERVICE_TYPE ) - 1;
534 if ( strncmp( CONTENT_DIRECTORY_SERVICE_TYPE,
535 psz_service_type, k ) != 0 )
538 p_server->_i_content_directory_service_version =
541 const char* psz_event_sub_url =
542 xml_getChildElementValue( p_service_element,
544 if ( !psz_event_sub_url )
546 msg_Warn( p_sd, "No event subscription url found." );
550 const char* psz_control_url =
551 xml_getChildElementValue( p_service_element,
553 if ( !psz_control_url )
555 msg_Warn( p_sd, "No control url found." );
559 /* Try to subscribe to ContentDirectory service */
561 char* psz_url = ( char* ) malloc( strlen( psz_base_url ) +
562 strlen( psz_event_sub_url ) + 1 );
565 if ( UpnpResolveURL( psz_base_url, psz_event_sub_url, psz_url ) ==
568 p_server->setContentDirectoryEventURL( psz_url );
569 p_server->subscribeToContentDirectory();
575 /* Try to browse content directory. */
577 psz_url = ( char* ) malloc( strlen( psz_base_url ) +
578 strlen( psz_control_url ) + 1 );
581 if ( UpnpResolveURL( psz_base_url, psz_control_url, psz_url ) ==
584 p_server->setContentDirectoryControlURL( psz_url );
585 p_server->fetchContents();
591 ixmlNodeList_free( p_service_list );
594 ixmlNodeList_free( p_device_list );
598 MediaServer::MediaServer( const char* psz_udn,
599 const char* psz_friendly_name,
600 services_discovery_t* p_sd )
605 _friendly_name = psz_friendly_name;
608 _p_input_item = NULL;
609 _i_content_directory_service_version = 1;
612 MediaServer::~MediaServer()
617 const char* MediaServer::getUDN() const
622 const char* MediaServer::getFriendlyName() const
624 return _friendly_name.c_str();
627 void MediaServer::setContentDirectoryEventURL( const char* psz_url )
629 _content_directory_event_url = psz_url;
632 const char* MediaServer::getContentDirectoryEventURL() const
634 return _content_directory_event_url.c_str();
637 void MediaServer::setContentDirectoryControlURL( const char* psz_url )
639 _content_directory_control_url = psz_url;
642 const char* MediaServer::getContentDirectoryControlURL() const
644 return _content_directory_control_url.c_str();
648 * Subscribes current client handle to Content Directory Service.
649 * CDS exports the server shares to clients.
651 void MediaServer::subscribeToContentDirectory()
653 const char* psz_url = getContentDirectoryEventURL();
656 msg_Dbg( _p_sd, "No subscription url set!" );
660 int i_timeout = 1810;
663 int i_res = UpnpSubscribe( _p_sd->p_sys->client_handle, psz_url, &i_timeout, sid );
665 if ( i_res == UPNP_E_SUCCESS )
667 _i_subscription_timeout = i_timeout;
668 memcpy( _subscription_id, sid, sizeof( Upnp_SID ) );
672 msg_Dbg( _p_sd, "Subscribe failed: '%s': %s",
673 getFriendlyName(), UpnpGetErrorMessage( i_res ) );
677 * Constructs UpnpAction to browse available content.
679 IXML_Document* MediaServer::_browseAction( const char* psz_object_id_,
680 const char* psz_browser_flag_,
681 const char* psz_filter_,
682 const char* psz_starting_index_,
683 const char* psz_requested_count_,
684 const char* psz_sort_criteria_ )
686 IXML_Document* p_action = 0;
687 IXML_Document* p_response = 0;
688 const char* psz_url = getContentDirectoryControlURL();
692 msg_Dbg( _p_sd, "No subscription url set!" );
696 char* psz_service_type = strdup( CONTENT_DIRECTORY_SERVICE_TYPE );
698 psz_service_type[strlen( psz_service_type ) - 1] =
699 _i_content_directory_service_version;
703 i_res = UpnpAddToAction( &p_action, "Browse",
704 psz_service_type, "ObjectID", psz_object_id_ );
706 if ( i_res != UPNP_E_SUCCESS )
708 msg_Dbg( _p_sd, "AddToAction 'ObjectID' failed: %s",
709 UpnpGetErrorMessage( i_res ) );
710 goto browseActionCleanup;
713 i_res = UpnpAddToAction( &p_action, "Browse",
714 psz_service_type, "BrowseFlag", psz_browser_flag_ );
716 if ( i_res != UPNP_E_SUCCESS )
718 msg_Dbg( _p_sd, "AddToAction 'BrowseFlag' failed: %s",
719 UpnpGetErrorMessage( i_res ) );
720 goto browseActionCleanup;
723 i_res = UpnpAddToAction( &p_action, "Browse",
724 psz_service_type, "Filter", psz_filter_ );
726 if ( i_res != UPNP_E_SUCCESS )
728 msg_Dbg( _p_sd, "AddToAction 'Filter' failed: %s",
729 UpnpGetErrorMessage( i_res ) );
730 goto browseActionCleanup;
733 i_res = UpnpAddToAction( &p_action, "Browse",
734 psz_service_type, "StartingIndex", psz_starting_index_ );
736 if ( i_res != UPNP_E_SUCCESS )
738 msg_Dbg( _p_sd, "AddToAction 'StartingIndex' failed: %s",
739 UpnpGetErrorMessage( i_res ) );
740 goto browseActionCleanup;
743 i_res = UpnpAddToAction( &p_action, "Browse",
744 psz_service_type, "RequestedCount", psz_requested_count_ );
746 if ( i_res != UPNP_E_SUCCESS )
748 msg_Dbg( _p_sd, "AddToAction 'RequestedCount' failed: %s",
749 UpnpGetErrorMessage( i_res ) );
750 goto browseActionCleanup;
753 i_res = UpnpAddToAction( &p_action, "Browse",
754 psz_service_type, "SortCriteria", psz_sort_criteria_ );
756 if ( i_res != UPNP_E_SUCCESS )
758 msg_Dbg( _p_sd, "AddToAction 'SortCriteria' failed: %s",
759 UpnpGetErrorMessage( i_res ) );
760 goto browseActionCleanup;
763 i_res = UpnpSendAction( _p_sd->p_sys->client_handle,
766 0, /* ignored in SDK, must be NULL */
770 if ( i_res != UPNP_E_SUCCESS )
772 msg_Err( _p_sd, "%s when trying the send() action with URL: %s",
773 UpnpGetErrorMessage( i_res ), psz_url );
775 ixmlDocument_free( p_response );
781 free( psz_service_type );
783 ixmlDocument_free( p_action );
787 void MediaServer::fetchContents()
789 /* Delete previous contents to prevent duplicate entries */
793 services_discovery_RemoveItem( _p_sd, _p_input_item );
794 services_discovery_AddItem( _p_sd, _p_input_item, NULL );
797 Container* root = new Container( 0, "0", getFriendlyName() );
799 _fetchContents( root, 0 );
802 _p_contents->setInputItem( _p_input_item );
804 _buildPlaylist( _p_contents, NULL );
808 * Fetches and parses the UPNP response
810 bool MediaServer::_fetchContents( Container* p_parent, int i_offset )
814 msg_Err( _p_sd, "No parent" );
818 char* psz_starting_index;
819 if( asprintf( &psz_starting_index, "%d", i_offset ) < 0 )
821 msg_Err( _p_sd, "asprintf error:%d", i_offset );
825 IXML_Document* p_response = _browseAction( p_parent->getObjectID(),
826 "BrowseDirectChildren",
827 "id,dc:title,res," /* Filter */
828 "sec:CaptionInfo,sec:CaptionInfoEx,"
830 psz_starting_index, /* StartingIndex */
831 "0", /* RequestedCount */
832 "" /* SortCriteria */
834 free( psz_starting_index );
837 msg_Err( _p_sd, "No response from browse() action" );
841 IXML_Document* p_result = parseBrowseResult( p_response );
842 int i_number_returned = xml_getNumber( p_response, "NumberReturned" );
843 int i_total_matches = xml_getNumber( p_response , "TotalMatches" );
846 msg_Dbg( _p_sd, "i_offset[%d]i_number_returned[%d]_total_matches[%d]\n",
847 i_offset, i_number_returned, i_total_matches );
850 ixmlDocument_free( p_response );
854 msg_Err( _p_sd, "browse() response parsing failed" );
859 msg_Dbg( _p_sd, "Got DIDL document: %s", ixmlPrintDocument( p_result ) );
862 IXML_NodeList* containerNodeList =
863 ixmlDocument_getElementsByTagName( p_result, "container" );
865 if ( containerNodeList )
867 for ( unsigned int i = 0;
868 i < ixmlNodeList_length( containerNodeList ); i++ )
870 IXML_Element* containerElement =
871 ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
873 const char* objectID = ixmlElement_getAttribute( containerElement,
878 const char* title = xml_getChildElementValue( containerElement,
884 Container* container = new Container( p_parent, objectID, title );
885 p_parent->addContainer( container );
886 _fetchContents( container, 0 );
888 ixmlNodeList_free( containerNodeList );
891 IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( p_result,
895 for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
897 IXML_Element* itemElement =
898 ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );
900 const char* objectID =
901 ixmlElement_getAttribute( itemElement, "id" );
907 xml_getChildElementValue( itemElement, "dc:title" );
912 const char* psz_subtitles = xml_getChildElementValue( itemElement,
915 if ( !psz_subtitles )
916 psz_subtitles = xml_getChildElementValue( itemElement,
917 "sec:CaptionInfoEx" );
919 if ( !psz_subtitles )
920 psz_subtitles = xml_getChildElementValue( itemElement,
923 /* Try to extract all resources in DIDL */
924 IXML_NodeList* p_resource_list = ixmlDocument_getElementsByTagName( (IXML_Document*) itemElement, "res" );
925 if ( p_resource_list )
927 int i_length = ixmlNodeList_length( p_resource_list );
928 for ( int i = 0; i < i_length; i++ )
930 mtime_t i_duration = -1;
931 int i_hours, i_minutes, i_seconds;
932 IXML_Element* p_resource = ( IXML_Element* ) ixmlNodeList_item( p_resource_list, i );
933 const char* psz_resource_url = xml_getChildElementValue( p_resource, "res" );
934 if( !psz_resource_url )
936 const char* psz_duration = ixmlElement_getAttribute( p_resource, "duration" );
940 if( sscanf( psz_duration, "%d:%02d:%02d",
941 &i_hours, &i_minutes, &i_seconds ) )
942 i_duration = INT64_C(1000000) * ( i_hours*3600 +
947 Item* item = new Item( p_parent, objectID, title, psz_resource_url, psz_subtitles, i_duration );
948 p_parent->addItem( item );
950 ixmlNodeList_free( p_resource_list );
954 ixmlNodeList_free( itemNodeList );
957 ixmlDocument_free( p_result );
959 if( i_offset + i_number_returned < i_total_matches )
960 return _fetchContents( p_parent, i_offset + i_number_returned );
965 // TODO: Create a permanent fix for the item duplication bug. The current fix
966 // is essentially only a small hack. Although it fixes the problem, it introduces
967 // annoying cosmetic issues with the playlist. For example, when the UPnP Server
968 // rebroadcasts it's directory structure, the VLC Client deletes the old directory
969 // structure, causing the user to go back to the root node of the directory. The
970 // directory is then rebuilt, and the user is forced to traverse through the directory
971 // to find the item they were looking for. Some servers may not push the directory
972 // structure too often, but we cannot rely on this fix.
974 // I have thought up another fix, but this would require certain features to
975 // be present within the VLC services discovery. Currently, services_discovery_AddItem
976 // does not allow the programmer to nest items. It only allows a "2 deep" scope.
977 // An example of the limitation is below:
983 // services_discovery_AddItem will not let the programmer specify a child-node to
984 // insert items into, so we would not be able to do the following:
990 // + Sub Item 1 of Item 2
991 // + Sub-Sub Item 1 of Sub Item 1
993 // This creates a HUGE limitation on what we are able to do. If we were able to do
994 // the above, we could simply preserve the old directory listing, and compare what items
995 // do not exist in the new directory listing, then remove them from the shown listing using
996 // services_discovery_RemoveItem. If new files were introduced within an already existing
997 // container, we could simply do so with services_discovery_AddItem.
1000 * Builds playlist based on available input items.
1002 void MediaServer::_buildPlaylist( Container* p_parent, input_item_node_t *p_input_node )
1004 bool b_send = p_input_node == NULL;
1006 p_input_node = input_item_node_Create( p_parent->getInputItem() );
1008 for ( unsigned int i = 0; i < p_parent->getNumContainers(); i++ )
1010 Container* p_container = p_parent->getContainer( i );
1012 input_item_t* p_input_item = input_item_New( "vlc://nop",
1013 p_container->getTitle() );
1014 input_item_node_t *p_new_node =
1015 input_item_node_AppendItem( p_input_node, p_input_item );
1017 p_container->setInputItem( p_input_item );
1018 _buildPlaylist( p_container, p_new_node );
1021 for ( unsigned int i = 0; i < p_parent->getNumItems(); i++ )
1023 Item* p_item = p_parent->getItem( i );
1025 char **ppsz_opts = NULL;
1026 char *psz_input_slave = p_item->buildInputSlaveOption();
1027 if( psz_input_slave )
1029 ppsz_opts = (char**)malloc( 2 * sizeof( char* ) );
1030 ppsz_opts[0] = psz_input_slave;
1031 ppsz_opts[1] = p_item->buildSubTrackIdOption();
1034 input_item_t* p_input_item = input_item_NewExt( p_item->getResource(),
1036 psz_input_slave ? 2 : 0,
1037 psz_input_slave ? ppsz_opts : NULL,
1038 VLC_INPUT_OPTION_TRUSTED, /* XXX */
1039 p_item->getDuration() );
1041 assert( p_input_item );
1044 free( ppsz_opts[0] );
1045 free( ppsz_opts[1] );
1048 psz_input_slave = NULL;
1051 input_item_node_AppendItem( p_input_node, p_input_item );
1052 p_item->setInputItem( p_input_item );
1056 input_item_node_PostAndDelete( p_input_node );
1059 void MediaServer::setInputItem( input_item_t* p_input_item )
1061 if( _p_input_item == p_input_item )
1065 vlc_gc_decref( _p_input_item );
1067 vlc_gc_incref( p_input_item );
1068 _p_input_item = p_input_item;
1071 input_item_t* MediaServer::getInputItem() const
1073 return _p_input_item;
1076 bool MediaServer::compareSID( const char* psz_sid )
1078 return ( strncmp( _subscription_id, psz_sid, sizeof( Upnp_SID ) ) == 0 );
1083 * MediaServerList class
1085 MediaServerList::MediaServerList( services_discovery_t* p_sd )
1090 MediaServerList::~MediaServerList()
1092 for ( unsigned int i = 0; i < _list.size(); i++ )
1098 bool MediaServerList::addServer( MediaServer* p_server )
1100 input_item_t* p_input_item = NULL;
1101 if ( getServer( p_server->getUDN() ) != 0 ) return false;
1103 msg_Dbg( _p_sd, "Adding server '%s' with uuid '%s'", p_server->getFriendlyName(), p_server->getUDN() );
1105 p_input_item = input_item_New( "vlc://nop", p_server->getFriendlyName() );
1107 input_item_SetDescription( p_input_item, p_server->getUDN() );
1109 p_server->setInputItem( p_input_item );
1111 services_discovery_AddItem( _p_sd, p_input_item, NULL );
1113 _list.push_back( p_server );
1118 MediaServer* MediaServerList::getServer( const char* psz_udn )
1120 MediaServer* p_result = 0;
1122 for ( unsigned int i = 0; i < _list.size(); i++ )
1124 if( strcmp( psz_udn, _list[i]->getUDN() ) == 0 )
1126 p_result = _list[i];
1134 MediaServer* MediaServerList::getServerBySID( const char* psz_sid )
1136 MediaServer* p_server = 0;
1138 for ( unsigned int i = 0; i < _list.size(); i++ )
1140 if ( _list[i]->compareSID( psz_sid ) )
1142 p_server = _list[i];
1150 void MediaServerList::removeServer( const char* psz_udn )
1152 MediaServer* p_server = getServer( psz_udn );
1153 if ( !p_server ) return;
1155 msg_Dbg( _p_sd, "Removing server '%s'", p_server->getFriendlyName() );
1157 services_discovery_RemoveItem( _p_sd, p_server->getInputItem() );
1159 std::vector<MediaServer*>::iterator it;
1160 for ( it = _list.begin(); it != _list.end(); ++it )
1162 if ( *it == p_server )
1175 Item::Item( Container* p_parent,
1176 const char* psz_object_id, const char* psz_title,
1177 const char* psz_resource, const char* psz_subtitles,
1178 mtime_t i_duration )
1182 _objectID = psz_object_id;
1184 _resource = psz_resource;
1185 _subtitles = psz_subtitles ? psz_subtitles : "";
1186 _duration = i_duration;
1188 _p_input_item = NULL;
1194 vlc_gc_decref( _p_input_item );
1197 const char* Item::getObjectID() const
1199 return _objectID.c_str();
1202 const char* Item::getTitle() const
1204 return _title.c_str();
1207 const char* Item::getResource() const
1209 return _resource.c_str();
1212 const char* Item::getSubtitles() const
1214 if( !_subtitles.size() )
1217 return _subtitles.c_str();
1220 mtime_t Item::getDuration() const
1225 char* Item::buildInputSlaveOption() const
1227 const char *psz_subtitles = getSubtitles();
1229 const char *psz_scheme_delim = "://";
1230 const char *psz_sub_opt_fmt = ":input-slave=%s/%s://%s";
1231 const char *psz_demux = "subtitle";
1233 char *psz_uri_scheme = NULL;
1234 const char *psz_scheme_end = NULL;
1235 const char *psz_uri_location = NULL;
1236 char *psz_input_slave = NULL;
1238 size_t i_scheme_len;
1240 if( !psz_subtitles )
1243 psz_scheme_end = strstr( psz_subtitles, psz_scheme_delim );
1245 /* subtitles not being an URI would make no sense */
1246 if( !psz_scheme_end )
1249 i_scheme_len = psz_scheme_end - psz_subtitles;
1250 psz_uri_scheme = (char*)malloc( i_scheme_len + 1 );
1252 if( !psz_uri_scheme )
1255 memcpy( psz_uri_scheme, psz_subtitles, i_scheme_len );
1256 psz_uri_scheme[i_scheme_len] = '\0';
1258 /* If the subtitles try to force a vlc demux,
1259 * then something is very wrong */
1260 if( strchr( psz_uri_scheme, '/' ) )
1262 free( psz_uri_scheme );
1266 psz_uri_location = psz_scheme_end + strlen( psz_scheme_delim );
1268 if( -1 == asprintf( &psz_input_slave, psz_sub_opt_fmt,
1269 psz_uri_scheme, psz_demux, psz_uri_location ) )
1270 psz_input_slave = NULL;
1272 free( psz_uri_scheme );
1273 return psz_input_slave;
1276 char* Item::buildSubTrackIdOption() const
1278 return strdup( ":sub-track-id=2" );
1281 void Item::setInputItem( input_item_t* p_input_item )
1283 if( _p_input_item == p_input_item )
1287 vlc_gc_decref( _p_input_item );
1289 vlc_gc_incref( p_input_item );
1290 _p_input_item = p_input_item;
1296 Container::Container( Container* p_parent,
1297 const char* psz_object_id,
1298 const char* psz_title )
1302 _objectID = psz_object_id;
1305 _p_input_item = NULL;
1308 Container::~Container()
1310 for ( unsigned int i = 0; i < _containers.size(); i++ )
1312 delete _containers[i];
1315 for ( unsigned int i = 0; i < _items.size(); i++ )
1321 vlc_gc_decref( _p_input_item );
1324 void Container::addItem( Item* item )
1326 _items.push_back( item );
1329 void Container::addContainer( Container* p_container )
1331 _containers.push_back( p_container );
1334 const char* Container::getObjectID() const
1336 return _objectID.c_str();
1339 const char* Container::getTitle() const
1341 return _title.c_str();
1344 unsigned int Container::getNumItems() const
1346 return _items.size();
1349 unsigned int Container::getNumContainers() const
1351 return _containers.size();
1354 Item* Container::getItem( unsigned int i_index ) const
1356 if ( i_index < _items.size() ) return _items[i_index];
1360 Container* Container::getContainer( unsigned int i_index ) const
1362 if ( i_index < _containers.size() ) return _containers[i_index];
1366 Container* Container::getParent()
1371 void Container::setInputItem( input_item_t* p_input_item )
1373 if( _p_input_item == p_input_item )
1377 vlc_gc_decref( _p_input_item );
1379 vlc_gc_incref( p_input_item );
1380 _p_input_item = p_input_item;
1383 input_item_t* Container::getInputItem() const
1385 return _p_input_item;