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 res = UpnpInit( 0, 0 );
94 if( res != UPNP_E_SUCCESS )
96 msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
100 p_sys->serverList = new MediaServerList( p_sd );
101 p_sys->callbackLock = new Lockable();
103 res = UpnpRegisterClient( Callback, p_sd, &p_sys->clientHandle );
104 if( res != UPNP_E_SUCCESS )
106 msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
107 Close( (vlc_object_t*) p_sd );
111 res = UpnpSearchAsync( p_sys->clientHandle, 5,
112 MEDIA_SERVER_DEVICE_TYPE, p_sd );
114 if( res != UPNP_E_SUCCESS )
116 msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
117 Close( (vlc_object_t*) p_sd );
124 static void Close( vlc_object_t *p_this )
126 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
129 delete p_sd->p_sys->serverList;
130 delete p_sd->p_sys->callbackLock;
135 // XML utility functions:
137 // Returns the value of a child element, or 0 on error
138 const char* xml_getChildElementValue( IXML_Element* parent,
139 const char* tagName )
141 if ( !parent ) return 0;
142 if ( !tagName ) return 0;
144 char* s = strdup( tagName );
145 IXML_NodeList* nodeList = ixmlElement_getElementsByTagName( parent, s );
147 if ( !nodeList ) return 0;
149 IXML_Node* element = ixmlNodeList_item( nodeList, 0 );
150 ixmlNodeList_free( nodeList );
151 if ( !element ) return 0;
153 IXML_Node* textNode = ixmlNode_getFirstChild( element );
154 if ( !textNode ) return 0;
156 return ixmlNode_getNodeValue( textNode );
159 // Extracts the result document from a SOAP response
160 IXML_Document* parseBrowseResult( IXML_Document* doc )
164 if ( !doc ) return 0;
166 IXML_NodeList* resultList = ixmlDocument_getElementsByTagName( doc,
169 if ( !resultList ) return 0;
171 IXML_Node* resultNode = ixmlNodeList_item( resultList, 0 );
173 ixmlNodeList_free( resultList );
175 if ( !resultNode ) return 0;
177 IXML_Node* textNode = ixmlNode_getFirstChild( resultNode );
178 if ( !textNode ) return 0;
180 const char* resultString = ixmlNode_getNodeValue( textNode );
181 char* resultXML = strdup( resultString );
183 IXML_Document* browseDoc = ixmlParseBuffer( resultXML );
191 // Handles all UPnP events
192 static int Callback( Upnp_EventType eventType, void* event, void* user_data )
194 services_discovery_t *p_sd = ( services_discovery_t* ) user_data;
195 services_discovery_sys_t* p_sys = p_sd->p_sys;
196 Locker locker( p_sys->callbackLock );
198 switch( eventType ) {
200 case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
201 case UPNP_DISCOVERY_SEARCH_RESULT:
203 struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
205 IXML_Document *descriptionDoc = 0;
208 res = UpnpDownloadXmlDoc( discovery->Location, &descriptionDoc );
209 if ( res != UPNP_E_SUCCESS )
212 "%s:%d: Could not download device description!",
213 __FILE__, __LINE__ );
217 MediaServer::parseDeviceDescription( descriptionDoc,
218 discovery->Location, p_sd );
220 ixmlDocument_free( descriptionDoc );
224 case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
226 struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
228 p_sys->serverList->removeServer( discovery->DeviceId );
232 case UPNP_EVENT_RECEIVED:
234 Upnp_Event* e = ( Upnp_Event* )event;
236 MediaServer* server = p_sys->serverList->getServerBySID( e->Sid );
237 if ( server ) server->fetchContents();
241 case UPNP_EVENT_AUTORENEWAL_FAILED:
242 case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
246 Upnp_Event_Subscribe* s = ( Upnp_Event_Subscribe* )event;
248 MediaServer* server = p_sys->serverList->getServerBySID( s->Sid );
249 if ( server ) server->subscribeToContentDirectory();
253 case UPNP_EVENT_SUBSCRIBE_COMPLETE:
254 msg_Warn( p_sd, "subscription complete" );
257 case UPNP_DISCOVERY_SEARCH_TIMEOUT:
258 msg_Warn( p_sd, "search timeout" );
263 "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )",
264 __FILE__, __LINE__, eventType );
268 return UPNP_E_SUCCESS;
272 // Class implementations...
276 void MediaServer::parseDeviceDescription( IXML_Document* doc,
277 const char* location,
278 services_discovery_t* p_sd )
282 msg_Dbg( p_sd, "%s:%d: NULL", __FILE__, __LINE__ );
288 msg_Dbg( p_sd, "%s:%d: NULL", __FILE__, __LINE__ );
292 const char* baseURL = location;
294 // Try to extract baseURL
296 IXML_NodeList* urlList = ixmlDocument_getElementsByTagName( doc, "baseURL" );
300 if ( IXML_Node* urlNode = ixmlNodeList_item( urlList, 0 ) )
302 IXML_Node* textNode = ixmlNode_getFirstChild( urlNode );
303 if ( textNode ) baseURL = ixmlNode_getNodeValue( textNode );
306 ixmlNodeList_free( urlList );
311 IXML_NodeList* deviceList =
312 ixmlDocument_getElementsByTagName( doc, "device" );
316 for ( unsigned int i = 0; i < ixmlNodeList_length( deviceList ); i++ )
318 IXML_Element* deviceElement =
319 ( IXML_Element* ) ixmlNodeList_item( deviceList, i );
321 const char* deviceType = xml_getChildElementValue( deviceElement,
326 "%s:%d: no deviceType!",
327 __FILE__, __LINE__ );
331 if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, deviceType ) != 0 )
334 const char* UDN = xml_getChildElementValue( deviceElement, "UDN" );
337 msg_Dbg( p_sd, "%s:%d: no UDN!",
338 __FILE__, __LINE__ );
342 if ( p_sd->p_sys->serverList->getServer( UDN ) != 0 )
345 const char* friendlyName =
346 xml_getChildElementValue( deviceElement,
351 msg_Dbg( p_sd, "%s:%d: no friendlyName!", __FILE__, __LINE__ );
355 MediaServer* server = new MediaServer( UDN, friendlyName, p_sd );
357 if ( !p_sd->p_sys->serverList->addServer( server ) )
365 // Check for ContentDirectory service...
366 IXML_NodeList* serviceList =
367 ixmlElement_getElementsByTagName( deviceElement,
371 for ( unsigned int j = 0;
372 j < ixmlNodeList_length( serviceList ); j++ )
374 IXML_Element* serviceElement =
375 ( IXML_Element* ) ixmlNodeList_item( serviceList, j );
377 const char* serviceType =
378 xml_getChildElementValue( serviceElement,
383 if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE,
387 const char* eventSubURL =
388 xml_getChildElementValue( serviceElement,
393 const char* controlURL =
394 xml_getChildElementValue( serviceElement,
399 // Try to subscribe to ContentDirectory service
401 char* url = ( char* ) malloc( strlen( baseURL ) +
402 strlen( eventSubURL ) + 1 );
405 char* s1 = strdup( baseURL );
406 char* s2 = strdup( eventSubURL );
408 if ( UpnpResolveURL( s1, s2, url ) ==
411 server->setContentDirectoryEventURL( url );
412 server->subscribeToContentDirectory();
420 // Try to browse content directory...
422 url = ( char* ) malloc( strlen( baseURL ) +
423 strlen( controlURL ) + 1 );
426 char* s1 = strdup( baseURL );
427 char* s2 = strdup( controlURL );
429 if ( UpnpResolveURL( s1, s2, url ) ==
432 server->setContentDirectoryControlURL( url );
433 server->fetchContents();
441 ixmlNodeList_free( serviceList );
444 ixmlNodeList_free( deviceList );
448 MediaServer::MediaServer( const char* UDN,
449 const char* friendlyName,
450 services_discovery_t* p_sd )
455 _friendlyName = friendlyName;
461 MediaServer::~MediaServer()
466 const char* MediaServer::getUDN() const
468 const char* s = _UDN.c_str();
472 const char* MediaServer::getFriendlyName() const
474 const char* s = _friendlyName.c_str();
478 void MediaServer::setContentDirectoryEventURL( const char* url )
480 _contentDirectoryEventURL = url;
483 const char* MediaServer::getContentDirectoryEventURL() const
485 const char* s = _contentDirectoryEventURL.c_str();
489 void MediaServer::setContentDirectoryControlURL( const char* url )
491 _contentDirectoryControlURL = url;
494 const char* MediaServer::getContentDirectoryControlURL() const
496 return _contentDirectoryControlURL.c_str();
499 void MediaServer::subscribeToContentDirectory()
501 const char* url = getContentDirectoryEventURL();
502 if ( !url || strcmp( url, "" ) == 0 )
504 msg_Dbg( _p_sd, "No subscription url set!" );
511 int res = UpnpSubscribe( _p_sd->p_sys->clientHandle, url, &timeOut, sid );
513 if ( res == UPNP_E_SUCCESS )
515 _subscriptionTimeOut = timeOut;
516 memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) );
521 "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__,
522 getFriendlyName(), UpnpGetErrorMessage( res ) );
526 IXML_Document* MediaServer::_browseAction( const char* pObjectID,
527 const char* pBrowseFlag,
529 const char* pStartingIndex,
530 const char* pRequestedCount,
531 const char* pSortCriteria )
533 IXML_Document* action = 0;
534 IXML_Document* response = 0;
535 const char* url = getContentDirectoryControlURL();
537 if ( !url || strcmp( url, "" ) == 0 )
539 msg_Dbg( _p_sd, "No subscription url set!" );
543 char* ObjectID = strdup( pObjectID );
544 char* BrowseFlag = strdup( pBrowseFlag );
545 char* Filter = strdup( pFilter );
546 char* StartingIndex = strdup( pStartingIndex );
547 char* RequestedCount = strdup( pRequestedCount );
548 char* SortCriteria = strdup( pSortCriteria );
549 char* serviceType = strdup( CONTENT_DIRECTORY_SERVICE_TYPE );
553 res = UpnpAddToAction( &action, "Browse",
554 serviceType, "ObjectID", ObjectID );
556 if ( res != UPNP_E_SUCCESS )
559 "%s:%d: ERROR: %s", __FILE__, __LINE__,
560 UpnpGetErrorMessage( res ) );
561 goto browseActionCleanup;
564 res = UpnpAddToAction( &action, "Browse",
565 serviceType, "BrowseFlag", BrowseFlag );
567 if ( res != UPNP_E_SUCCESS )
570 "%s:%d: ERROR: %s", __FILE__, __LINE__,
571 UpnpGetErrorMessage( res ) );
572 goto browseActionCleanup;
575 res = UpnpAddToAction( &action, "Browse",
576 serviceType, "Filter", Filter );
578 if ( res != UPNP_E_SUCCESS )
581 "%s:%d: ERROR: %s", __FILE__, __LINE__,
582 UpnpGetErrorMessage( res ) );
583 goto browseActionCleanup;
586 res = UpnpAddToAction( &action, "Browse",
587 serviceType, "StartingIndex", StartingIndex );
589 if ( res != UPNP_E_SUCCESS )
592 "%s:%d: ERROR: %s", __FILE__, __LINE__,
593 UpnpGetErrorMessage( res ) );
594 goto browseActionCleanup;
597 res = UpnpAddToAction( &action, "Browse",
598 serviceType, "RequestedCount", RequestedCount );
600 if ( res != UPNP_E_SUCCESS )
603 "%s:%d: ERROR: %s", __FILE__, __LINE__,
604 UpnpGetErrorMessage( res ) ); goto browseActionCleanup; }
606 res = UpnpAddToAction( &action, "Browse",
607 serviceType, "SortCriteria", SortCriteria );
609 if ( res != UPNP_E_SUCCESS )
612 "%s:%d: ERROR: %s", __FILE__, __LINE__,
613 UpnpGetErrorMessage( res ) );
614 goto browseActionCleanup;
617 res = UpnpSendAction( _p_sd->p_sys->clientHandle,
619 CONTENT_DIRECTORY_SERVICE_TYPE,
624 if ( res != UPNP_E_SUCCESS )
627 "%s:%d: ERROR: %s when trying the send() action with URL: %s",
629 UpnpGetErrorMessage( res ), url );
631 ixmlDocument_free( response );
640 free( StartingIndex );
641 free( RequestedCount );
642 free( SortCriteria );
646 ixmlDocument_free( action );
650 void MediaServer::fetchContents()
652 Container* root = new Container( 0, "0", getFriendlyName() );
653 _fetchContents( root );
658 // playlist_NodeEmpty( p_playlist, _playlistNode, true );
664 _contents->setInputItem( _inputItem );
666 _buildPlaylist( _contents );
669 bool MediaServer::_fetchContents( Container* parent )
674 "%s:%d: parent==NULL", __FILE__, __LINE__ );
678 IXML_Document* response = _browseAction( parent->getObjectID(),
679 "BrowseDirectChildren",
684 "%s:%d: ERROR! No response from browse() action",
685 __FILE__, __LINE__ );
689 IXML_Document* result = parseBrowseResult( response );
690 ixmlDocument_free( response );
695 "%s:%d: ERROR! browse() response parsing failed",
696 __FILE__, __LINE__ );
700 IXML_NodeList* containerNodeList =
701 ixmlDocument_getElementsByTagName( result, "container" );
703 if ( containerNodeList )
705 for ( unsigned int i = 0;
706 i < ixmlNodeList_length( containerNodeList ); i++ )
708 IXML_Element* containerElement =
709 ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
711 const char* objectID = ixmlElement_getAttribute( containerElement,
716 const char* childCountStr =
717 ixmlElement_getAttribute( containerElement, "childCount" );
719 if ( !childCountStr )
722 int childCount = atoi( childCountStr );
723 const char* title = xml_getChildElementValue( containerElement,
729 const char* resource = xml_getChildElementValue( containerElement,
732 if ( resource && childCount < 1 )
734 Item* item = new Item( parent, objectID, title, resource );
735 parent->addItem( item );
740 Container* container = new Container( parent, objectID, title );
741 parent->addContainer( container );
743 if ( childCount > 0 )
744 _fetchContents( container );
747 ixmlNodeList_free( containerNodeList );
750 IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( result,
754 for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
756 IXML_Element* itemElement =
757 ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );
759 const char* objectID =
760 ixmlElement_getAttribute( itemElement, "id" );
766 xml_getChildElementValue( itemElement, "dc:title" );
771 const char* resource =
772 xml_getChildElementValue( itemElement, "res" );
777 Item* item = new Item( parent, objectID, title, resource );
778 parent->addItem( item );
780 ixmlNodeList_free( itemNodeList );
783 ixmlDocument_free( result );
787 void MediaServer::_buildPlaylist( Container* parent )
789 for ( unsigned int i = 0; i < parent->getNumContainers(); i++ )
791 Container* container = parent->getContainer( i );
793 input_item_t* p_input_item = input_item_New( _p_sd, "vlc://nop", parent->getTitle() );
794 input_item_AddSubItem( parent->getInputItem(), p_input_item );
796 container->setInputItem( p_input_item );
797 _buildPlaylist( container );
800 for ( unsigned int i = 0; i < parent->getNumItems(); i++ )
802 Item* item = parent->getItem( i );
804 input_item_t* p_input_item = input_item_New( _p_sd,
807 assert( p_input_item );
808 input_item_AddSubItem( parent->getInputItem(), p_input_item );
809 item->setInputItem( p_input_item );
813 void MediaServer::setInputItem( input_item_t* p_input_item )
815 if(_inputItem == p_input_item)
819 vlc_gc_decref( _inputItem );
821 vlc_gc_incref( p_input_item );
822 _inputItem = p_input_item;
825 bool MediaServer::compareSID( const char* sid )
827 return ( strncmp( _subscriptionID, sid, sizeof( Upnp_SID ) ) == 0 );
831 // MediaServerList...
833 MediaServerList::MediaServerList( services_discovery_t* p_sd )
838 MediaServerList::~MediaServerList()
840 for ( unsigned int i = 0; i < _list.size(); i++ )
846 bool MediaServerList::addServer( MediaServer* s )
848 input_item_t* p_input_item = NULL;
849 if ( getServer( s->getUDN() ) != 0 ) return false;
851 msg_Dbg( _p_sd, "Adding server '%s'",
852 s->getFriendlyName() );
854 services_discovery_t* p_sd = _p_sd;
856 p_input_item = input_item_New( p_sd, "vlc://nop", s->getFriendlyName() );
857 s->setInputItem( p_input_item );
859 services_discovery_AddItem( p_sd, p_input_item, NULL );
861 _list.push_back( s );
866 MediaServer* MediaServerList::getServer( const char* UDN )
868 MediaServer* result = 0;
870 for ( unsigned int i = 0; i < _list.size(); i++ )
872 if( strcmp( UDN, _list[i]->getUDN() ) == 0 )
882 MediaServer* MediaServerList::getServerBySID( const char* sid )
884 MediaServer* server = 0;
886 for ( unsigned int i = 0; i < _list.size(); i++ )
888 if ( _list[i]->compareSID( sid ) )
898 void MediaServerList::removeServer( const char* UDN )
900 MediaServer* server = getServer( UDN );
901 if ( !server ) return;
904 "Removing server '%s'", server->getFriendlyName() );
906 std::vector<MediaServer*>::iterator it;
907 for ( it = _list.begin(); it != _list.end(); it++ )
921 Item::Item( Container* parent, const char* objectID, const char* title, const char* resource )
925 _objectID = objectID;
927 _resource = resource;
935 vlc_gc_decref( _inputItem );
938 const char* Item::getObjectID() const
940 return _objectID.c_str();
943 const char* Item::getTitle() const
945 return _title.c_str();
948 const char* Item::getResource() const
950 return _resource.c_str();
953 void Item::setInputItem( input_item_t* p_input_item )
955 if(_inputItem == p_input_item)
959 vlc_gc_decref( _inputItem );
961 vlc_gc_incref( p_input_item );
962 _inputItem = p_input_item;
965 input_item_t* Item::getInputItem() const
973 Container::Container( Container* parent,
974 const char* objectID,
979 _objectID = objectID;
985 Container::~Container()
987 for ( unsigned int i = 0; i < _containers.size(); i++ )
989 delete _containers[i];
992 for ( unsigned int i = 0; i < _items.size(); i++ )
998 vlc_gc_decref( _inputItem );
1001 void Container::addItem( Item* item )
1003 _items.push_back( item );
1006 void Container::addContainer( Container* container )
1008 _containers.push_back( container );
1011 const char* Container::getObjectID() const
1013 return _objectID.c_str();
1016 const char* Container::getTitle() const
1018 return _title.c_str();
1021 unsigned int Container::getNumItems() const
1023 return _items.size();
1026 unsigned int Container::getNumContainers() const
1028 return _containers.size();
1031 Item* Container::getItem( unsigned int i ) const
1033 if ( i < _items.size() ) return _items[i];
1037 Container* Container::getContainer( unsigned int i ) const
1039 if ( i < _containers.size() ) return _containers[i];
1043 Container* Container::getParent()
1048 void Container::setInputItem( input_item_t* p_input_item )
1050 if(_inputItem == p_input_item)
1054 vlc_gc_decref( _inputItem );
1056 vlc_gc_incref( p_input_item );
1057 _inputItem = p_input_item;
1060 input_item_t* Container::getInputItem() const