1 /*****************************************************************************
2 * Upnp_intel.cpp : UPnP discovery module (Intel SDK)
3 *****************************************************************************
4 * Copyright (C) 2004-2008 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 *****************************************************************************/
29 \TODO: Debug messages: "__FILE__, __LINE__" ok ???, Wrn/Err ???
30 \TODO: Change names to VLC standard ???
37 #include "upnp_intel.hpp"
39 #include <vlc_plugin.h>
40 #include <vlc_services_discovery.h>
44 const char* MEDIA_SERVER_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaServer:1";
45 const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:ContentDirectory:1";
48 struct services_discovery_sys_t
50 UpnpClient_Handle clientHandle;
51 MediaServerList* serverList;
52 Lockable* callbackLock;
55 // VLC callback prototypes
56 static int Open( vlc_object_t* );
57 static void Close( vlc_object_t* );
62 set_shortname( "UPnP" );
63 set_description( N_( "Universal Plug'n'Play discovery" ) );
64 set_category( CAT_PLAYLIST );
65 set_subcategory( SUBCAT_PLAYLIST_SD );
66 set_capability( "services_discovery", 0 );
67 set_callbacks( Open, Close );
73 static int Callback( Upnp_EventType eventType, void* event, void* user_data );
75 const char* xml_getChildElementValue( IXML_Element* parent,
76 const char* tagName );
78 IXML_Document* parseBrowseResult( IXML_Document* doc );
83 static int Open( vlc_object_t *p_this )
86 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
87 services_discovery_sys_t *p_sys = ( services_discovery_sys_t * )
88 calloc( 1, sizeof( services_discovery_sys_t ) );
90 if(!(p_sd->p_sys = p_sys))
93 services_discovery_SetLocalizedName( p_sd, _("UPnP devices") );
95 res = UpnpInit( 0, 0 );
96 if( res != UPNP_E_SUCCESS )
98 msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
102 p_sys->serverList = new MediaServerList( p_sd );
103 p_sys->callbackLock = new Lockable();
105 res = UpnpRegisterClient( Callback, p_sd, &p_sys->clientHandle );
106 if( res != UPNP_E_SUCCESS )
108 msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
109 Close( (vlc_object_t*) p_sd );
113 res = UpnpSearchAsync( p_sys->clientHandle, 5,
114 MEDIA_SERVER_DEVICE_TYPE, p_sd );
116 if( res != UPNP_E_SUCCESS )
118 msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
119 Close( (vlc_object_t*) p_sd );
126 static void Close( vlc_object_t *p_this )
128 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
131 delete p_sd->p_sys->serverList;
132 delete p_sd->p_sys->callbackLock;
137 // XML utility functions:
139 // Returns the value of a child element, or 0 on error
140 const char* xml_getChildElementValue( IXML_Element* parent,
141 const char* tagName )
143 if ( !parent ) return 0;
144 if ( !tagName ) return 0;
146 char* s = strdup( tagName );
147 IXML_NodeList* nodeList = ixmlElement_getElementsByTagName( parent, s );
149 if ( !nodeList ) return 0;
151 IXML_Node* element = ixmlNodeList_item( nodeList, 0 );
152 ixmlNodeList_free( nodeList );
153 if ( !element ) return 0;
155 IXML_Node* textNode = ixmlNode_getFirstChild( element );
156 if ( !textNode ) return 0;
158 return ixmlNode_getNodeValue( textNode );
161 // Extracts the result document from a SOAP response
162 IXML_Document* parseBrowseResult( IXML_Document* doc )
166 if ( !doc ) return 0;
168 IXML_NodeList* resultList = ixmlDocument_getElementsByTagName( doc,
171 if ( !resultList ) return 0;
173 IXML_Node* resultNode = ixmlNodeList_item( resultList, 0 );
175 ixmlNodeList_free( resultList );
177 if ( !resultNode ) return 0;
179 IXML_Node* textNode = ixmlNode_getFirstChild( resultNode );
180 if ( !textNode ) return 0;
182 const char* resultString = ixmlNode_getNodeValue( textNode );
183 char* resultXML = strdup( resultString );
185 IXML_Document* browseDoc = ixmlParseBuffer( resultXML );
193 // Handles all UPnP events
194 static int Callback( Upnp_EventType eventType, void* event, void* user_data )
196 services_discovery_t *p_sd = ( services_discovery_t* ) user_data;
197 services_discovery_sys_t* p_sys = p_sd->p_sys;
198 Locker locker( p_sys->callbackLock );
200 switch( eventType ) {
202 case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
203 case UPNP_DISCOVERY_SEARCH_RESULT:
205 struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
207 IXML_Document *descriptionDoc = 0;
210 res = UpnpDownloadXmlDoc( discovery->Location, &descriptionDoc );
211 if ( res != UPNP_E_SUCCESS )
214 "%s:%d: Could not download device description!",
215 __FILE__, __LINE__ );
219 MediaServer::parseDeviceDescription( descriptionDoc,
220 discovery->Location, p_sd );
222 ixmlDocument_free( descriptionDoc );
226 case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
228 struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
230 p_sys->serverList->removeServer( discovery->DeviceId );
234 case UPNP_EVENT_RECEIVED:
236 Upnp_Event* e = ( Upnp_Event* )event;
238 MediaServer* server = p_sys->serverList->getServerBySID( e->Sid );
239 if ( server ) server->fetchContents();
243 case UPNP_EVENT_AUTORENEWAL_FAILED:
244 case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
248 Upnp_Event_Subscribe* s = ( Upnp_Event_Subscribe* )event;
250 MediaServer* server = p_sys->serverList->getServerBySID( s->Sid );
251 if ( server ) server->subscribeToContentDirectory();
255 case UPNP_EVENT_SUBSCRIBE_COMPLETE:
256 msg_Warn( p_sd, "subscription complete" );
259 case UPNP_DISCOVERY_SEARCH_TIMEOUT:
260 msg_Warn( p_sd, "search timeout" );
265 "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )",
266 __FILE__, __LINE__, eventType );
270 return UPNP_E_SUCCESS;
274 // Class implementations...
278 void MediaServer::parseDeviceDescription( IXML_Document* doc,
279 const char* location,
280 services_discovery_t* p_sd )
284 msg_Dbg( p_sd, "%s:%d: NULL", __FILE__, __LINE__ );
290 msg_Dbg( p_sd, "%s:%d: NULL", __FILE__, __LINE__ );
294 const char* baseURL = location;
296 // Try to extract baseURL
298 IXML_NodeList* urlList = ixmlDocument_getElementsByTagName( doc, "baseURL" );
302 if ( IXML_Node* urlNode = ixmlNodeList_item( urlList, 0 ) )
304 IXML_Node* textNode = ixmlNode_getFirstChild( urlNode );
305 if ( textNode ) baseURL = ixmlNode_getNodeValue( textNode );
308 ixmlNodeList_free( urlList );
313 IXML_NodeList* deviceList =
314 ixmlDocument_getElementsByTagName( doc, "device" );
318 for ( unsigned int i = 0; i < ixmlNodeList_length( deviceList ); i++ )
320 IXML_Element* deviceElement =
321 ( IXML_Element* ) ixmlNodeList_item( deviceList, i );
323 const char* deviceType = xml_getChildElementValue( deviceElement,
328 "%s:%d: no deviceType!",
329 __FILE__, __LINE__ );
333 if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, deviceType ) != 0 )
336 const char* UDN = xml_getChildElementValue( deviceElement, "UDN" );
339 msg_Dbg( p_sd, "%s:%d: no UDN!",
340 __FILE__, __LINE__ );
344 if ( p_sd->p_sys->serverList->getServer( UDN ) != 0 )
347 const char* friendlyName =
348 xml_getChildElementValue( deviceElement,
353 msg_Dbg( p_sd, "%s:%d: no friendlyName!", __FILE__, __LINE__ );
357 MediaServer* server = new MediaServer( UDN, friendlyName, p_sd );
359 if ( !p_sd->p_sys->serverList->addServer( server ) )
367 // Check for ContentDirectory service...
368 IXML_NodeList* serviceList =
369 ixmlElement_getElementsByTagName( deviceElement,
373 for ( unsigned int j = 0;
374 j < ixmlNodeList_length( serviceList ); j++ )
376 IXML_Element* serviceElement =
377 ( IXML_Element* ) ixmlNodeList_item( serviceList, j );
379 const char* serviceType =
380 xml_getChildElementValue( serviceElement,
385 if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE,
389 const char* eventSubURL =
390 xml_getChildElementValue( serviceElement,
395 const char* controlURL =
396 xml_getChildElementValue( serviceElement,
401 // Try to subscribe to ContentDirectory service
403 char* url = ( char* ) malloc( strlen( baseURL ) +
404 strlen( eventSubURL ) + 1 );
407 char* s1 = strdup( baseURL );
408 char* s2 = strdup( eventSubURL );
410 if ( UpnpResolveURL( s1, s2, url ) ==
413 server->setContentDirectoryEventURL( url );
414 server->subscribeToContentDirectory();
422 // Try to browse content directory...
424 url = ( char* ) malloc( strlen( baseURL ) +
425 strlen( controlURL ) + 1 );
428 char* s1 = strdup( baseURL );
429 char* s2 = strdup( controlURL );
431 if ( UpnpResolveURL( s1, s2, url ) ==
434 server->setContentDirectoryControlURL( url );
435 server->fetchContents();
443 ixmlNodeList_free( serviceList );
446 ixmlNodeList_free( deviceList );
450 MediaServer::MediaServer( const char* UDN,
451 const char* friendlyName,
452 services_discovery_t* p_sd )
457 _friendlyName = friendlyName;
463 MediaServer::~MediaServer()
468 const char* MediaServer::getUDN() const
470 const char* s = _UDN.c_str();
474 const char* MediaServer::getFriendlyName() const
476 const char* s = _friendlyName.c_str();
480 void MediaServer::setContentDirectoryEventURL( const char* url )
482 _contentDirectoryEventURL = url;
485 const char* MediaServer::getContentDirectoryEventURL() const
487 const char* s = _contentDirectoryEventURL.c_str();
491 void MediaServer::setContentDirectoryControlURL( const char* url )
493 _contentDirectoryControlURL = url;
496 const char* MediaServer::getContentDirectoryControlURL() const
498 return _contentDirectoryControlURL.c_str();
501 void MediaServer::subscribeToContentDirectory()
503 const char* url = getContentDirectoryEventURL();
504 if ( !url || strcmp( url, "" ) == 0 )
506 msg_Dbg( _p_sd, "No subscription url set!" );
513 int res = UpnpSubscribe( _p_sd->p_sys->clientHandle, url, &timeOut, sid );
515 if ( res == UPNP_E_SUCCESS )
517 _subscriptionTimeOut = timeOut;
518 memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) );
523 "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__,
524 getFriendlyName(), UpnpGetErrorMessage( res ) );
528 IXML_Document* MediaServer::_browseAction( const char* pObjectID,
529 const char* pBrowseFlag,
531 const char* pStartingIndex,
532 const char* pRequestedCount,
533 const char* pSortCriteria )
535 IXML_Document* action = 0;
536 IXML_Document* response = 0;
537 const char* url = getContentDirectoryControlURL();
539 if ( !url || strcmp( url, "" ) == 0 )
541 msg_Dbg( _p_sd, "No subscription url set!" );
545 char* ObjectID = strdup( pObjectID );
546 char* BrowseFlag = strdup( pBrowseFlag );
547 char* Filter = strdup( pFilter );
548 char* StartingIndex = strdup( pStartingIndex );
549 char* RequestedCount = strdup( pRequestedCount );
550 char* SortCriteria = strdup( pSortCriteria );
551 char* serviceType = strdup( CONTENT_DIRECTORY_SERVICE_TYPE );
555 res = UpnpAddToAction( &action, "Browse",
556 serviceType, "ObjectID", ObjectID );
558 if ( res != UPNP_E_SUCCESS )
561 "%s:%d: ERROR: %s", __FILE__, __LINE__,
562 UpnpGetErrorMessage( res ) );
563 goto browseActionCleanup;
566 res = UpnpAddToAction( &action, "Browse",
567 serviceType, "BrowseFlag", BrowseFlag );
569 if ( res != UPNP_E_SUCCESS )
572 "%s:%d: ERROR: %s", __FILE__, __LINE__,
573 UpnpGetErrorMessage( res ) );
574 goto browseActionCleanup;
577 res = UpnpAddToAction( &action, "Browse",
578 serviceType, "Filter", Filter );
580 if ( res != UPNP_E_SUCCESS )
583 "%s:%d: ERROR: %s", __FILE__, __LINE__,
584 UpnpGetErrorMessage( res ) );
585 goto browseActionCleanup;
588 res = UpnpAddToAction( &action, "Browse",
589 serviceType, "StartingIndex", StartingIndex );
591 if ( res != UPNP_E_SUCCESS )
594 "%s:%d: ERROR: %s", __FILE__, __LINE__,
595 UpnpGetErrorMessage( res ) );
596 goto browseActionCleanup;
599 res = UpnpAddToAction( &action, "Browse",
600 serviceType, "RequestedCount", RequestedCount );
602 if ( res != UPNP_E_SUCCESS )
605 "%s:%d: ERROR: %s", __FILE__, __LINE__,
606 UpnpGetErrorMessage( res ) ); goto browseActionCleanup; }
608 res = UpnpAddToAction( &action, "Browse",
609 serviceType, "SortCriteria", SortCriteria );
611 if ( res != UPNP_E_SUCCESS )
614 "%s:%d: ERROR: %s", __FILE__, __LINE__,
615 UpnpGetErrorMessage( res ) );
616 goto browseActionCleanup;
619 res = UpnpSendAction( _p_sd->p_sys->clientHandle,
621 CONTENT_DIRECTORY_SERVICE_TYPE,
626 if ( res != UPNP_E_SUCCESS )
629 "%s:%d: ERROR: %s when trying the send() action with URL: %s",
631 UpnpGetErrorMessage( res ), url );
633 ixmlDocument_free( response );
642 free( StartingIndex );
643 free( RequestedCount );
644 free( SortCriteria );
648 ixmlDocument_free( action );
652 void MediaServer::fetchContents()
654 Container* root = new Container( 0, "0", getFriendlyName() );
655 _fetchContents( root );
660 // playlist_NodeEmpty( p_playlist, _playlistNode, true );
666 _contents->setInputItem( _inputItem );
668 _buildPlaylist( _contents );
671 bool MediaServer::_fetchContents( Container* parent )
676 "%s:%d: parent==NULL", __FILE__, __LINE__ );
680 IXML_Document* response = _browseAction( parent->getObjectID(),
681 "BrowseDirectChildren",
686 "%s:%d: ERROR! No response from browse() action",
687 __FILE__, __LINE__ );
691 IXML_Document* result = parseBrowseResult( response );
692 ixmlDocument_free( response );
697 "%s:%d: ERROR! browse() response parsing failed",
698 __FILE__, __LINE__ );
702 IXML_NodeList* containerNodeList =
703 ixmlDocument_getElementsByTagName( result, "container" );
705 if ( containerNodeList )
707 for ( unsigned int i = 0;
708 i < ixmlNodeList_length( containerNodeList ); i++ )
710 IXML_Element* containerElement =
711 ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
713 const char* objectID = ixmlElement_getAttribute( containerElement,
718 const char* childCountStr =
719 ixmlElement_getAttribute( containerElement, "childCount" );
721 if ( !childCountStr )
724 int childCount = atoi( childCountStr );
725 const char* title = xml_getChildElementValue( containerElement,
731 const char* resource = xml_getChildElementValue( containerElement,
734 if ( resource && childCount < 1 )
736 Item* item = new Item( parent, objectID, title, resource );
737 parent->addItem( item );
742 Container* container = new Container( parent, objectID, title );
743 parent->addContainer( container );
745 if ( childCount > 0 )
746 _fetchContents( container );
749 ixmlNodeList_free( containerNodeList );
752 IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( result,
756 for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
758 IXML_Element* itemElement =
759 ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );
761 const char* objectID =
762 ixmlElement_getAttribute( itemElement, "id" );
768 xml_getChildElementValue( itemElement, "dc:title" );
773 const char* resource =
774 xml_getChildElementValue( itemElement, "res" );
779 Item* item = new Item( parent, objectID, title, resource );
780 parent->addItem( item );
782 ixmlNodeList_free( itemNodeList );
785 ixmlDocument_free( result );
789 void MediaServer::_buildPlaylist( Container* parent )
791 for ( unsigned int i = 0; i < parent->getNumContainers(); i++ )
793 Container* container = parent->getContainer( i );
795 input_item_t* p_input_item = input_item_New( _p_sd, "vlc://nop", parent->getTitle() );
796 input_item_AddSubItem( parent->getInputItem(), p_input_item );
798 container->setInputItem( p_input_item );
799 _buildPlaylist( container );
802 for ( unsigned int i = 0; i < parent->getNumItems(); i++ )
804 Item* item = parent->getItem( i );
806 input_item_t* p_input_item = input_item_New( _p_sd,
809 assert( p_input_item );
810 input_item_AddSubItem( parent->getInputItem(), p_input_item );
811 item->setInputItem( p_input_item );
815 void MediaServer::setInputItem( input_item_t* p_input_item )
817 if(_inputItem == p_input_item)
821 vlc_gc_decref( _inputItem );
823 vlc_gc_incref( p_input_item );
824 _inputItem = p_input_item;
827 bool MediaServer::compareSID( const char* sid )
829 return ( strncmp( _subscriptionID, sid, sizeof( Upnp_SID ) ) == 0 );
833 // MediaServerList...
835 MediaServerList::MediaServerList( services_discovery_t* p_sd )
840 MediaServerList::~MediaServerList()
842 for ( unsigned int i = 0; i < _list.size(); i++ )
848 bool MediaServerList::addServer( MediaServer* s )
850 input_item_t* p_input_item = NULL;
851 if ( getServer( s->getUDN() ) != 0 ) return false;
853 msg_Dbg( _p_sd, "Adding server '%s'",
854 s->getFriendlyName() );
856 services_discovery_t* p_sd = _p_sd;
858 p_input_item = input_item_New( p_sd, "vlc://nop", s->getFriendlyName() );
859 s->setInputItem( p_input_item );
861 services_discovery_AddItem( p_sd, p_input_item, NULL );
863 _list.push_back( s );
868 MediaServer* MediaServerList::getServer( const char* UDN )
870 MediaServer* result = 0;
872 for ( unsigned int i = 0; i < _list.size(); i++ )
874 if( strcmp( UDN, _list[i]->getUDN() ) == 0 )
884 MediaServer* MediaServerList::getServerBySID( const char* sid )
886 MediaServer* server = 0;
888 for ( unsigned int i = 0; i < _list.size(); i++ )
890 if ( _list[i]->compareSID( sid ) )
900 void MediaServerList::removeServer( const char* UDN )
902 MediaServer* server = getServer( UDN );
903 if ( !server ) return;
906 "Removing server '%s'", server->getFriendlyName() );
908 std::vector<MediaServer*>::iterator it;
909 for ( it = _list.begin(); it != _list.end(); it++ )
923 Item::Item( Container* parent, const char* objectID, const char* title, const char* resource )
927 _objectID = objectID;
929 _resource = resource;
937 vlc_gc_decref( _inputItem );
940 const char* Item::getObjectID() const
942 return _objectID.c_str();
945 const char* Item::getTitle() const
947 return _title.c_str();
950 const char* Item::getResource() const
952 return _resource.c_str();
955 void Item::setInputItem( input_item_t* p_input_item )
957 if(_inputItem == p_input_item)
961 vlc_gc_decref( _inputItem );
963 vlc_gc_incref( p_input_item );
964 _inputItem = p_input_item;
967 input_item_t* Item::getInputItem() const
975 Container::Container( Container* parent,
976 const char* objectID,
981 _objectID = objectID;
987 Container::~Container()
989 for ( unsigned int i = 0; i < _containers.size(); i++ )
991 delete _containers[i];
994 for ( unsigned int i = 0; i < _items.size(); i++ )
1000 vlc_gc_decref( _inputItem );
1003 void Container::addItem( Item* item )
1005 _items.push_back( item );
1008 void Container::addContainer( Container* container )
1010 _containers.push_back( container );
1013 const char* Container::getObjectID() const
1015 return _objectID.c_str();
1018 const char* Container::getTitle() const
1020 return _title.c_str();
1023 unsigned int Container::getNumItems() const
1025 return _items.size();
1028 unsigned int Container::getNumContainers() const
1030 return _containers.size();
1033 Item* Container::getItem( unsigned int i ) const
1035 if ( i < _items.size() ) return _items[i];
1039 Container* Container::getContainer( unsigned int i ) const
1041 if ( i < _containers.size() ) return _containers[i];
1045 Container* Container::getParent()
1050 void Container::setInputItem( input_item_t* p_input_item )
1052 if(_inputItem == p_input_item)
1056 vlc_gc_decref( _inputItem );
1058 vlc_gc_incref( p_input_item );
1059 _inputItem = p_input_item;
1062 input_item_t* Container::getInputItem() const