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
37 #include <vlc_plugin.h>
38 #include <vlc_services_discovery.h>
43 const char* MEDIA_SERVER_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaServer:1";
44 const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:ContentDirectory:1";
47 struct services_discovery_sys_t
49 UpnpClient_Handle client_handle;
50 MediaServerList* p_server_list;
51 vlc_mutex_t callback_lock;
54 // VLC callback prototypes
55 static int Open( vlc_object_t* );
56 static void Close( vlc_object_t* );
57 VLC_SD_PROBE_HELPER( "upnp", "Universal Plug'n'Play", SD_CAT_LAN )
62 set_shortname( "UPnP" );
63 set_description( N_( "Universal Plug'n'Play" ) );
64 set_category( CAT_PLAYLIST );
65 set_subcategory( SUBCAT_PLAYLIST_SD );
66 set_capability( "services_discovery", 0 );
67 set_callbacks( Open, Close );
69 VLC_SD_PROBE_SUBMODULE
75 static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data );
77 const char* xml_getChildElementValue( IXML_Element* p_parent,
78 const char* psz_tag_name );
80 const char* xml_getChildElementAttributeValue( IXML_Element* p_parent,
81 const char* psz_tag_name_,
82 const char* psz_attribute_ );
84 IXML_Document* parseBrowseResult( IXML_Document* p_doc );
89 static int Open( vlc_object_t *p_this )
92 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
93 services_discovery_sys_t *p_sys = ( services_discovery_sys_t * )
94 calloc( 1, sizeof( services_discovery_sys_t ) );
96 if( !( p_sd->p_sys = p_sys ) )
99 i_res = UpnpInit( 0, 0 );
100 if( i_res != UPNP_E_SUCCESS )
102 msg_Err( p_sd, "%s", UpnpGetErrorMessage( i_res ) );
107 p_sys->p_server_list = new MediaServerList( p_sd );
108 vlc_mutex_init( &p_sys->callback_lock );
110 i_res = UpnpRegisterClient( Callback, p_sd, &p_sys->client_handle );
111 if( i_res != UPNP_E_SUCCESS )
113 msg_Err( p_sd, "%s", UpnpGetErrorMessage( i_res ) );
114 Close( (vlc_object_t*) p_sd );
118 i_res = UpnpSearchAsync( p_sys->client_handle, 5,
119 MEDIA_SERVER_DEVICE_TYPE, p_sd );
120 if( i_res != UPNP_E_SUCCESS )
122 msg_Err( p_sd, "Error sending search request: %s", UpnpGetErrorMessage( i_res ) );
123 Close( (vlc_object_t*) p_sd );
127 i_res = UpnpSetMaxContentLength( 262144 );
128 if( i_res != UPNP_E_SUCCESS )
130 msg_Err( p_sd, "Failed to set maximum content length: %s", UpnpGetErrorMessage( i_res ) );
131 Close( (vlc_object_t*) p_sd );
138 static void Close( vlc_object_t *p_this )
140 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
142 UpnpUnRegisterClient( p_sd->p_sys->client_handle );
145 delete p_sd->p_sys->p_server_list;
146 vlc_mutex_destroy( &p_sd->p_sys->callback_lock );
151 // XML utility functions:
153 // Returns the value of a child element, or 0 on error
154 const char* xml_getChildElementValue( IXML_Element* p_parent,
155 const char* psz_tag_name_ )
157 if ( !p_parent ) return NULL;
158 if ( !psz_tag_name_ ) return NULL;
160 IXML_NodeList* p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name_ );
161 if ( !p_node_list ) return NULL;
163 IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
164 ixmlNodeList_free( p_node_list );
165 if ( !p_element ) return NULL;
167 IXML_Node* p_text_node = ixmlNode_getFirstChild( p_element );
168 if ( !p_text_node ) return NULL;
170 return ixmlNode_getNodeValue( p_text_node );
173 const char* xml_getChildElementAttributeValue( IXML_Element* p_parent,
174 const char* psz_tag_name_,
175 const char* psz_attribute_ )
177 if ( !p_parent ) return NULL;
178 if ( !psz_tag_name_ ) return NULL;
179 if ( !psz_attribute_ ) return NULL;
181 IXML_NodeList* p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name_ );
182 if ( !p_node_list ) return NULL;
184 IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
185 ixmlNodeList_free( p_node_list );
186 if ( !p_element ) return NULL;
188 return ixmlElement_getAttribute( (IXML_Element*) p_element, psz_attribute_ );
191 // Extracts the result document from a SOAP response
192 IXML_Document* parseBrowseResult( IXML_Document* p_doc )
194 ixmlRelaxParser( 1 );
196 if ( !p_doc ) return 0;
198 IXML_NodeList* p_result_list = ixmlDocument_getElementsByTagName( p_doc,
201 if ( !p_result_list ) return 0;
203 IXML_Node* p_result_node = ixmlNodeList_item( p_result_list, 0 );
205 ixmlNodeList_free( p_result_list );
207 if ( !p_result_node ) return 0;
209 IXML_Node* p_text_node = ixmlNode_getFirstChild( p_result_node );
210 if ( !p_text_node ) return 0;
212 const char* psz_result_string = ixmlNode_getNodeValue( p_text_node );
214 IXML_Document* p_browse_doc = ixmlParseBuffer( psz_result_string );
220 // Handles all UPnP events
221 static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data )
223 services_discovery_t* p_sd = ( services_discovery_t* ) p_user_data;
224 services_discovery_sys_t* p_sys = p_sd->p_sys;
225 vlc_mutex_locker locker( &p_sys->callback_lock );
229 case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
230 case UPNP_DISCOVERY_SEARCH_RESULT:
232 struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
234 IXML_Document *p_description_doc = 0;
237 i_res = UpnpDownloadXmlDoc( p_discovery->Location, &p_description_doc );
238 if ( i_res != UPNP_E_SUCCESS )
240 msg_Warn( p_sd, "Could not download device description! "
241 "Fetching data from %s failed: %s",
242 p_discovery->Location, UpnpGetErrorMessage( i_res ) );
246 MediaServer::parseDeviceDescription( p_description_doc,
247 p_discovery->Location, p_sd );
249 ixmlDocument_free( p_description_doc );
253 case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
255 struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
257 p_sys->p_server_list->removeServer( p_discovery->DeviceId );
262 case UPNP_EVENT_RECEIVED:
264 Upnp_Event* p_e = ( Upnp_Event* )p_event;
266 MediaServer* p_server = p_sys->p_server_list->getServerBySID( p_e->Sid );
267 if ( p_server ) p_server->fetchContents();
271 case UPNP_EVENT_AUTORENEWAL_FAILED:
272 case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
276 Upnp_Event_Subscribe* p_s = ( Upnp_Event_Subscribe* )p_event;
278 MediaServer* p_server = p_sys->p_server_list->getServerBySID( p_s->Sid );
279 if ( p_server ) p_server->subscribeToContentDirectory();
283 case UPNP_EVENT_SUBSCRIBE_COMPLETE:
284 msg_Warn( p_sd, "subscription complete" );
287 case UPNP_DISCOVERY_SEARCH_TIMEOUT:
288 msg_Warn( p_sd, "search timeout" );
292 msg_Err( p_sd, "Unhandled event, please report ( type=%d )", event_type );
296 return UPNP_E_SUCCESS;
300 // Class implementations...
304 void MediaServer::parseDeviceDescription( IXML_Document* p_doc,
305 const char* p_location,
306 services_discovery_t* p_sd )
310 msg_Err( p_sd, "Null IXML_Document" );
316 msg_Err( p_sd, "Null location" );
320 const char* psz_base_url = p_location;
322 // Try to extract baseURL
323 IXML_NodeList* p_url_list = ixmlDocument_getElementsByTagName( p_doc, "baseURL" );
327 if ( IXML_Node* p_url_node = ixmlNodeList_item( p_url_list, 0 ) )
329 IXML_Node* p_text_node = ixmlNode_getFirstChild( p_url_node );
330 if ( p_text_node ) psz_base_url = ixmlNode_getNodeValue( p_text_node );
333 ixmlNodeList_free( p_url_list );
337 IXML_NodeList* p_device_list =
338 ixmlDocument_getElementsByTagName( p_doc, "device" );
342 for ( unsigned int i = 0; i < ixmlNodeList_length( p_device_list ); i++ )
344 IXML_Element* p_device_element =
345 ( IXML_Element* ) ixmlNodeList_item( p_device_list, i );
347 const char* psz_device_type = xml_getChildElementValue( p_device_element,
349 if ( !psz_device_type )
351 msg_Warn( p_sd, "No deviceType found!" );
355 if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, psz_device_type ) != 0 )
358 const char* psz_udn = xml_getChildElementValue( p_device_element, "UDN" );
361 msg_Warn( p_sd, "No UDN!" );
365 // Check if server is already added
366 if ( p_sd->p_sys->p_server_list->getServer( psz_udn ) != 0 )
368 msg_Warn( p_sd, "Server with uuid '%s' already exists.", psz_udn );
372 const char* psz_friendly_name =
373 xml_getChildElementValue( p_device_element,
376 if ( !psz_friendly_name )
378 msg_Dbg( p_sd, "No friendlyName!" );
382 MediaServer* p_server = new MediaServer( psz_udn, psz_friendly_name, p_sd );
384 if ( !p_sd->p_sys->p_server_list->addServer( p_server ) )
391 // Check for ContentDirectory service...
392 IXML_NodeList* p_service_list =
393 ixmlElement_getElementsByTagName( p_device_element,
395 if ( p_service_list )
397 for ( unsigned int j = 0;
398 j < ixmlNodeList_length( p_service_list ); j++ )
400 IXML_Element* p_service_element =
401 ( IXML_Element* ) ixmlNodeList_item( p_service_list, j );
403 const char* psz_service_type =
404 xml_getChildElementValue( p_service_element,
406 if ( !psz_service_type )
408 msg_Warn( p_sd, "No service type found." );
412 if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE,
413 psz_service_type ) != 0 )
416 const char* psz_event_sub_url =
417 xml_getChildElementValue( p_service_element,
419 if ( !psz_event_sub_url )
421 msg_Warn( p_sd, "No event subscription url found." );
425 const char* psz_control_url =
426 xml_getChildElementValue( p_service_element,
428 if ( !psz_control_url )
430 msg_Warn( p_sd, "No control url found." );
434 // Try to subscribe to ContentDirectory service
436 char* psz_url = ( char* ) malloc( strlen( psz_base_url ) +
437 strlen( psz_event_sub_url ) + 1 );
440 if ( UpnpResolveURL( psz_base_url, psz_event_sub_url, psz_url ) ==
443 p_server->setContentDirectoryEventURL( psz_url );
444 p_server->subscribeToContentDirectory();
450 // Try to browse content directory...
452 psz_url = ( char* ) malloc( strlen( psz_base_url ) +
453 strlen( psz_control_url ) + 1 );
456 if ( UpnpResolveURL( psz_base_url, psz_control_url, psz_url ) ==
459 p_server->setContentDirectoryControlURL( psz_url );
460 p_server->fetchContents();
466 ixmlNodeList_free( p_service_list );
469 ixmlNodeList_free( p_device_list );
473 MediaServer::MediaServer( const char* psz_udn,
474 const char* psz_friendly_name,
475 services_discovery_t* p_sd )
480 _friendly_name = psz_friendly_name;
483 _p_input_item = NULL;
486 MediaServer::~MediaServer()
491 const char* MediaServer::getUDN() const
496 const char* MediaServer::getFriendlyName() const
498 return _friendly_name.c_str();
501 void MediaServer::setContentDirectoryEventURL( const char* psz_url )
503 _content_directory_event_url = psz_url;
506 const char* MediaServer::getContentDirectoryEventURL() const
508 return _content_directory_event_url.c_str();
511 void MediaServer::setContentDirectoryControlURL( const char* psz_url )
513 _content_directory_control_url = psz_url;
516 const char* MediaServer::getContentDirectoryControlURL() const
518 return _content_directory_control_url.c_str();
521 void MediaServer::subscribeToContentDirectory()
523 const char* psz_url = getContentDirectoryEventURL();
526 msg_Dbg( _p_sd, "No subscription url set!" );
530 int i_timeout = 1810;
533 int i_res = UpnpSubscribe( _p_sd->p_sys->client_handle, psz_url, &i_timeout, sid );
535 if ( i_res == UPNP_E_SUCCESS )
537 _i_subscription_timeout = i_timeout;
538 memcpy( _subscription_id, sid, sizeof( Upnp_SID ) );
542 msg_Dbg( _p_sd, "Subscribe failed: '%s': %s",
543 getFriendlyName(), UpnpGetErrorMessage( i_res ) );
547 IXML_Document* MediaServer::_browseAction( const char* psz_object_id_,
548 const char* psz_browser_flag_,
549 const char* psz_filter_,
550 const char* psz_starting_index_,
551 const char* psz_requested_count_,
552 const char* psz_sort_criteria_ )
554 IXML_Document* p_action = 0;
555 IXML_Document* p_response = 0;
556 const char* psz_url = getContentDirectoryControlURL();
560 msg_Dbg( _p_sd, "No subscription url set!" );
564 char* psz_service_type = strdup( CONTENT_DIRECTORY_SERVICE_TYPE );
568 i_res = UpnpAddToAction( &p_action, "Browse",
569 psz_service_type, "ObjectID", psz_object_id_ );
571 if ( i_res != UPNP_E_SUCCESS )
573 msg_Dbg( _p_sd, "AddToAction 'ObjectID' failed: %s",
574 UpnpGetErrorMessage( i_res ) );
575 goto browseActionCleanup;
578 i_res = UpnpAddToAction( &p_action, "Browse",
579 psz_service_type, "BrowseFlag", psz_browser_flag_ );
581 if ( i_res != UPNP_E_SUCCESS )
583 msg_Dbg( _p_sd, "AddToAction 'BrowseFlag' failed: %s",
584 UpnpGetErrorMessage( i_res ) );
585 goto browseActionCleanup;
588 i_res = UpnpAddToAction( &p_action, "Browse",
589 psz_service_type, "Filter", psz_filter_ );
591 if ( i_res != UPNP_E_SUCCESS )
593 msg_Dbg( _p_sd, "AddToAction 'Filter' failed: %s",
594 UpnpGetErrorMessage( i_res ) );
595 goto browseActionCleanup;
598 i_res = UpnpAddToAction( &p_action, "Browse",
599 psz_service_type, "StartingIndex", psz_starting_index_ );
601 if ( i_res != UPNP_E_SUCCESS )
603 msg_Dbg( _p_sd, "AddToAction 'StartingIndex' failed: %s",
604 UpnpGetErrorMessage( i_res ) );
605 goto browseActionCleanup;
608 i_res = UpnpAddToAction( &p_action, "Browse",
609 psz_service_type, "RequestedCount", psz_requested_count_ );
611 if ( i_res != UPNP_E_SUCCESS )
613 msg_Dbg( _p_sd, "AddToAction 'RequestedCount' failed: %s",
614 UpnpGetErrorMessage( i_res ) );
615 goto browseActionCleanup;
618 i_res = UpnpAddToAction( &p_action, "Browse",
619 psz_service_type, "SortCriteria", psz_sort_criteria_ );
621 if ( i_res != UPNP_E_SUCCESS )
623 msg_Dbg( _p_sd, "AddToAction 'SortCriteria' failed: %s",
624 UpnpGetErrorMessage( i_res ) );
625 goto browseActionCleanup;
628 i_res = UpnpSendAction( _p_sd->p_sys->client_handle,
631 0, // ignored in SDK, must be NULL
635 if ( i_res != UPNP_E_SUCCESS )
637 msg_Err( _p_sd, "%s when trying the send() action with URL: %s",
638 UpnpGetErrorMessage( i_res ), psz_url );
640 ixmlDocument_free( p_response );
646 free( psz_service_type );
648 ixmlDocument_free( p_action );
652 void MediaServer::fetchContents()
654 // Delete previous contents to prevent duplicate entries
658 services_discovery_RemoveItem( _p_sd, _p_input_item );
659 services_discovery_AddItem( _p_sd, _p_input_item, NULL );
662 Container* root = new Container( 0, "0", getFriendlyName() );
664 _fetchContents( root );
667 _p_contents->setInputItem( _p_input_item );
669 _buildPlaylist( _p_contents, NULL );
672 bool MediaServer::_fetchContents( Container* p_parent )
676 msg_Err( _p_sd, "No parent" );
680 IXML_Document* p_response = _browseAction( p_parent->getObjectID(),
681 "BrowseDirectChildren",
685 msg_Err( _p_sd, "No response from browse() action" );
689 IXML_Document* p_result = parseBrowseResult( p_response );
690 ixmlDocument_free( p_response );
694 msg_Err( _p_sd, "browse() response parsing failed" );
700 msg_Dbg( _p_sd, "Got DIDL document: %s",
701 ixmlPrintDocument( p_result ) );
705 IXML_NodeList* containerNodeList =
706 ixmlDocument_getElementsByTagName( p_result, "container" );
708 if ( containerNodeList )
710 for ( unsigned int i = 0;
711 i < ixmlNodeList_length( containerNodeList ); i++ )
713 IXML_Element* containerElement =
714 ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
716 const char* objectID = ixmlElement_getAttribute( containerElement,
721 const char* childCountStr =
722 ixmlElement_getAttribute( containerElement, "childCount" );
724 if ( !childCountStr )
727 int childCount = atoi( childCountStr );
728 const char* title = xml_getChildElementValue( containerElement,
734 const char* resource = xml_getChildElementValue( containerElement,
737 if ( resource && childCount < 1 )
739 Item* item = new Item( p_parent, objectID, title, resource, -1 );
740 p_parent->addItem( item );
745 Container* container = new Container( p_parent, objectID, title );
746 p_parent->addContainer( container );
748 if ( childCount > 0 )
749 _fetchContents( container );
752 ixmlNodeList_free( containerNodeList );
755 IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( p_result,
759 for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
761 IXML_Element* itemElement =
762 ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );
764 const char* objectID =
765 ixmlElement_getAttribute( itemElement, "id" );
771 xml_getChildElementValue( itemElement, "dc:title" );
776 const char* resource =
777 xml_getChildElementValue( itemElement, "res" );
782 const char* psz_duration = xml_getChildElementAttributeValue( itemElement,
786 mtime_t i_duration = -1;
787 int i_hours, i_minutes, i_seconds, i_decis;
791 if( sscanf( psz_duration, "%02d:%02d:%02d.%d",
792 &i_hours, &i_minutes, &i_seconds, &i_decis ))
793 i_duration = INT64_C(1000000) * ( i_hours*3600 +
796 INT64_C(100000) * i_decis;
799 Item* item = new Item( p_parent, objectID, title, resource, i_duration );
800 p_parent->addItem( item );
802 ixmlNodeList_free( itemNodeList );
805 ixmlDocument_free( p_result );
809 // TODO: Create a permanent fix for the item duplication bug. The current fix
810 // is essentially only a small hack. Although it fixes the problem, it introduces
811 // annoying cosmetic issues with the playlist. For example, when the UPnP Server
812 // rebroadcasts it's directory structure, the VLC Client deletes the old directory
813 // structure, causing the user to go back to the root node of the directory. The
814 // directory is then rebuilt, and the user is forced to traverse through the directory
815 // to find the item they were looking for. Some servers may not push the directory
816 // structure too often, but we cannot rely on this fix.
818 // I have thought up another fix, but this would require certain features to
819 // be present within the VLC services discovery. Currently, services_discovery_AddItem
820 // does not allow the programmer to nest items. It only allows a "2 deep" scope.
821 // An example of the limitation is below:
827 // services_discovery_AddItem will not let the programmer specify a child-node to
828 // insert items into, so we would not be able to do the following:
834 // + Sub Item 1 of Item 2
835 // + Sub-Sub Item 1 of Sub Item 1
837 // This creates a HUGE limitation on what we are able to do. If we were able to do
838 // the above, we could simply preserve the old directory listing, and compare what items
839 // do not exist in the new directory listing, then remove them from the shown listing using
840 // services_discovery_RemoveItem. If new files were introduced within an already existing
841 // container, we could simply do so with services_discovery_AddItem.
842 void MediaServer::_buildPlaylist( Container* p_parent, input_item_node_t *p_input_node )
844 bool b_send = p_input_node == NULL;
846 p_input_node = input_item_node_Create( p_parent->getInputItem() );
848 for ( unsigned int i = 0; i < p_parent->getNumContainers(); i++ )
850 Container* p_container = p_parent->getContainer( i );
852 input_item_t* p_input_item = input_item_New( _p_sd, "vlc://nop",
853 p_container->getTitle() );
854 input_item_node_t *p_new_node =
855 input_item_node_AppendItem( p_input_node, p_input_item );
857 p_container->setInputItem( p_input_item );
858 _buildPlaylist( p_container, p_new_node );
861 for ( unsigned int i = 0; i < p_parent->getNumItems(); i++ )
863 Item* p_item = p_parent->getItem( i );
865 input_item_t* p_input_item = input_item_NewExt( _p_sd,
866 p_item->getResource(),
871 p_item->getDuration() );
873 assert( p_input_item );
874 input_item_node_AppendItem( p_input_node, p_input_item );
875 p_item->setInputItem( p_input_item );
879 input_item_node_PostAndDelete( p_input_node );
882 void MediaServer::setInputItem( input_item_t* p_input_item )
884 if( _p_input_item == p_input_item )
888 vlc_gc_decref( _p_input_item );
890 vlc_gc_incref( p_input_item );
891 _p_input_item = p_input_item;
894 input_item_t* MediaServer::getInputItem() const
896 return _p_input_item;
899 bool MediaServer::compareSID( const char* psz_sid )
901 return ( strncmp( _subscription_id, psz_sid, sizeof( Upnp_SID ) ) == 0 );
905 // MediaServerList...
907 MediaServerList::MediaServerList( services_discovery_t* p_sd )
912 MediaServerList::~MediaServerList()
914 for ( unsigned int i = 0; i < _list.size(); i++ )
920 bool MediaServerList::addServer( MediaServer* p_server )
922 input_item_t* p_input_item = NULL;
923 if ( getServer( p_server->getUDN() ) != 0 ) return false;
925 msg_Dbg( _p_sd, "Adding server '%s' with uuid '%s'", p_server->getFriendlyName(), p_server->getUDN() );
927 p_input_item = input_item_New( _p_sd, "vlc://nop",
928 p_server->getFriendlyName() );
929 p_server->setInputItem( p_input_item );
931 services_discovery_AddItem( _p_sd, p_input_item, NULL );
933 _list.push_back( p_server );
938 MediaServer* MediaServerList::getServer( const char* psz_udn )
940 MediaServer* p_result = 0;
942 for ( unsigned int i = 0; i < _list.size(); i++ )
944 if( strcmp( psz_udn, _list[i]->getUDN() ) == 0 )
954 MediaServer* MediaServerList::getServerBySID( const char* psz_sid )
956 MediaServer* p_server = 0;
958 for ( unsigned int i = 0; i < _list.size(); i++ )
960 if ( _list[i]->compareSID( psz_sid ) )
970 void MediaServerList::removeServer( const char* psz_udn )
972 MediaServer* p_server = getServer( psz_udn );
973 if ( !p_server ) return;
975 msg_Dbg( _p_sd, "Removing server '%s'", p_server->getFriendlyName() );
977 services_discovery_RemoveItem( _p_sd, p_server->getInputItem() );
979 std::vector<MediaServer*>::iterator it;
980 for ( it = _list.begin(); it != _list.end(); ++it )
982 if ( *it == p_server )
994 Item::Item( Container* p_parent, const char* psz_object_id, const char* psz_title,
995 const char* psz_resource, mtime_t i_duration )
999 _objectID = psz_object_id;
1001 _resource = psz_resource;
1002 _duration = i_duration;
1004 _p_input_item = NULL;
1010 vlc_gc_decref( _p_input_item );
1013 const char* Item::getObjectID() const
1015 return _objectID.c_str();
1018 const char* Item::getTitle() const
1020 return _title.c_str();
1023 const char* Item::getResource() const
1025 return _resource.c_str();
1028 const mtime_t Item::getDuration() const
1033 void Item::setInputItem( input_item_t* p_input_item )
1035 if( _p_input_item == p_input_item )
1039 vlc_gc_decref( _p_input_item );
1041 vlc_gc_incref( p_input_item );
1042 _p_input_item = p_input_item;
1047 Container::Container( Container* p_parent,
1048 const char* psz_object_id,
1049 const char* psz_title )
1053 _objectID = psz_object_id;
1056 _p_input_item = NULL;
1059 Container::~Container()
1061 for ( unsigned int i = 0; i < _containers.size(); i++ )
1063 delete _containers[i];
1066 for ( unsigned int i = 0; i < _items.size(); i++ )
1072 vlc_gc_decref( _p_input_item );
1075 void Container::addItem( Item* item )
1077 _items.push_back( item );
1080 void Container::addContainer( Container* p_container )
1082 _containers.push_back( p_container );
1085 const char* Container::getObjectID() const
1087 return _objectID.c_str();
1090 const char* Container::getTitle() const
1092 return _title.c_str();
1095 unsigned int Container::getNumItems() const
1097 return _items.size();
1100 unsigned int Container::getNumContainers() const
1102 return _containers.size();
1105 Item* Container::getItem( unsigned int i_index ) const
1107 if ( i_index < _items.size() ) return _items[i_index];
1111 Container* Container::getContainer( unsigned int i_index ) const
1113 if ( i_index < _containers.size() ) return _containers[i_index];
1117 Container* Container::getParent()
1122 void Container::setInputItem( input_item_t* p_input_item )
1124 if( _p_input_item == p_input_item )
1128 vlc_gc_decref( _p_input_item );
1130 vlc_gc_incref( p_input_item );
1131 _p_input_item = p_input_item;
1134 input_item_t* Container::getInputItem() const
1136 return _p_input_item;