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 vlc_mutex_t callbackLock;
55 // VLC callback prototypes
56 static int Open( vlc_object_t* );
57 static void Close( vlc_object_t* );
58 VLC_SD_PROBE_HELPER("upnp_intel", "Universal Plug'n'Play", SD_CAT_LAN)
63 set_shortname( "UPnP" );
64 set_description( N_( "Universal Plug'n'Play" ) );
65 set_category( CAT_PLAYLIST );
66 set_subcategory( SUBCAT_PLAYLIST_SD );
67 set_capability( "services_discovery", 0 );
68 set_callbacks( Open, Close );
70 VLC_SD_PROBE_SUBMODULE
76 static int Callback( Upnp_EventType eventType, void* event, void* user_data );
78 const char* xml_getChildElementValue( IXML_Element* parent,
79 const char* tagName );
81 IXML_Document* parseBrowseResult( IXML_Document* doc );
86 static int Open( vlc_object_t *p_this )
89 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
90 services_discovery_sys_t *p_sys = ( services_discovery_sys_t * )
91 calloc( 1, sizeof( services_discovery_sys_t ) );
93 if(!(p_sd->p_sys = p_sys))
96 res = UpnpInit( 0, 0 );
97 if( res != UPNP_E_SUCCESS )
99 msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
104 p_sys->serverList = new MediaServerList( p_sd );
105 vlc_mutex_init( &p_sys->callbackLock );
107 res = UpnpRegisterClient( Callback, p_sd, &p_sys->clientHandle );
108 if( res != UPNP_E_SUCCESS )
110 msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
111 Close( (vlc_object_t*) p_sd );
115 res = UpnpSearchAsync( p_sys->clientHandle, 5,
116 MEDIA_SERVER_DEVICE_TYPE, p_sd );
118 if( res != UPNP_E_SUCCESS )
120 msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
121 Close( (vlc_object_t*) p_sd );
125 res = UpnpSetMaxContentLength( 262144 );
126 if( res != UPNP_E_SUCCESS )
128 msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
129 Close( (vlc_object_t*) p_sd );
136 static void Close( vlc_object_t *p_this )
138 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
141 delete p_sd->p_sys->serverList;
142 vlc_mutex_destroy( &p_sd->p_sys->callbackLock );
147 // XML utility functions:
149 // Returns the value of a child element, or 0 on error
150 const char* xml_getChildElementValue( IXML_Element* parent,
151 const char* tagName )
153 if ( !parent ) return 0;
154 if ( !tagName ) return 0;
156 char* s = strdup( tagName );
157 IXML_NodeList* nodeList = ixmlElement_getElementsByTagName( parent, s );
159 if ( !nodeList ) return 0;
161 IXML_Node* element = ixmlNodeList_item( nodeList, 0 );
162 ixmlNodeList_free( nodeList );
163 if ( !element ) return 0;
165 IXML_Node* textNode = ixmlNode_getFirstChild( element );
166 if ( !textNode ) return 0;
168 return ixmlNode_getNodeValue( textNode );
171 // Extracts the result document from a SOAP response
172 IXML_Document* parseBrowseResult( IXML_Document* doc )
176 if ( !doc ) return 0;
178 IXML_NodeList* resultList = ixmlDocument_getElementsByTagName( doc,
181 if ( !resultList ) return 0;
183 IXML_Node* resultNode = ixmlNodeList_item( resultList, 0 );
185 ixmlNodeList_free( resultList );
187 if ( !resultNode ) return 0;
189 IXML_Node* textNode = ixmlNode_getFirstChild( resultNode );
190 if ( !textNode ) return 0;
192 const char* resultString = ixmlNode_getNodeValue( textNode );
193 char* resultXML = strdup( resultString );
195 IXML_Document* browseDoc = ixmlParseBuffer( resultXML );
203 // Handles all UPnP events
204 static int Callback( Upnp_EventType eventType, void* event, void* user_data )
206 services_discovery_t *p_sd = ( services_discovery_t* ) user_data;
207 services_discovery_sys_t* p_sys = p_sd->p_sys;
208 vlc_mutex_locker locker( &p_sys->callbackLock );
210 switch( eventType ) {
212 case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
213 case UPNP_DISCOVERY_SEARCH_RESULT:
215 struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
217 IXML_Document *descriptionDoc = 0;
220 res = UpnpDownloadXmlDoc( discovery->Location, &descriptionDoc );
221 if ( res != UPNP_E_SUCCESS )
224 "%s:%d: Could not download device description!",
225 __FILE__, __LINE__ );
229 MediaServer::parseDeviceDescription( descriptionDoc,
230 discovery->Location, p_sd );
232 ixmlDocument_free( descriptionDoc );
236 case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
238 struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
240 p_sys->serverList->removeServer( discovery->DeviceId );
244 case UPNP_EVENT_RECEIVED:
246 Upnp_Event* e = ( Upnp_Event* )event;
248 MediaServer* server = p_sys->serverList->getServerBySID( e->Sid );
249 if ( server ) server->fetchContents();
253 case UPNP_EVENT_AUTORENEWAL_FAILED:
254 case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
258 Upnp_Event_Subscribe* s = ( Upnp_Event_Subscribe* )event;
260 MediaServer* server = p_sys->serverList->getServerBySID( s->Sid );
261 if ( server ) server->subscribeToContentDirectory();
265 case UPNP_EVENT_SUBSCRIBE_COMPLETE:
266 msg_Warn( p_sd, "subscription complete" );
269 case UPNP_DISCOVERY_SEARCH_TIMEOUT:
270 msg_Warn( p_sd, "search timeout" );
275 "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )",
276 __FILE__, __LINE__, eventType );
280 return UPNP_E_SUCCESS;
284 // Class implementations...
288 void MediaServer::parseDeviceDescription( IXML_Document* doc,
289 const char* location,
290 services_discovery_t* p_sd )
294 msg_Dbg( p_sd, "%s:%d: NULL", __FILE__, __LINE__ );
300 msg_Dbg( p_sd, "%s:%d: NULL", __FILE__, __LINE__ );
304 const char* baseURL = location;
306 // Try to extract baseURL
308 IXML_NodeList* urlList = ixmlDocument_getElementsByTagName( doc, "baseURL" );
312 if ( IXML_Node* urlNode = ixmlNodeList_item( urlList, 0 ) )
314 IXML_Node* textNode = ixmlNode_getFirstChild( urlNode );
315 if ( textNode ) baseURL = ixmlNode_getNodeValue( textNode );
318 ixmlNodeList_free( urlList );
323 IXML_NodeList* deviceList =
324 ixmlDocument_getElementsByTagName( doc, "device" );
328 for ( unsigned int i = 0; i < ixmlNodeList_length( deviceList ); i++ )
330 IXML_Element* deviceElement =
331 ( IXML_Element* ) ixmlNodeList_item( deviceList, i );
333 const char* deviceType = xml_getChildElementValue( deviceElement,
338 "%s:%d: no deviceType!",
339 __FILE__, __LINE__ );
343 if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, deviceType ) != 0 )
346 const char* UDN = xml_getChildElementValue( deviceElement, "UDN" );
349 msg_Dbg( p_sd, "%s:%d: no UDN!",
350 __FILE__, __LINE__ );
354 if ( p_sd->p_sys->serverList->getServer( UDN ) != 0 )
357 const char* friendlyName =
358 xml_getChildElementValue( deviceElement,
363 msg_Dbg( p_sd, "%s:%d: no friendlyName!", __FILE__, __LINE__ );
367 MediaServer* server = new MediaServer( UDN, friendlyName, p_sd );
369 if ( !p_sd->p_sys->serverList->addServer( server ) )
377 // Check for ContentDirectory service...
378 IXML_NodeList* serviceList =
379 ixmlElement_getElementsByTagName( deviceElement,
383 for ( unsigned int j = 0;
384 j < ixmlNodeList_length( serviceList ); j++ )
386 IXML_Element* serviceElement =
387 ( IXML_Element* ) ixmlNodeList_item( serviceList, j );
389 const char* serviceType =
390 xml_getChildElementValue( serviceElement,
395 if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE,
399 const char* eventSubURL =
400 xml_getChildElementValue( serviceElement,
405 const char* controlURL =
406 xml_getChildElementValue( serviceElement,
411 // Try to subscribe to ContentDirectory service
413 char* url = ( char* ) malloc( strlen( baseURL ) +
414 strlen( eventSubURL ) + 1 );
417 char* s1 = strdup( baseURL );
418 char* s2 = strdup( eventSubURL );
420 if ( UpnpResolveURL( s1, s2, url ) ==
423 server->setContentDirectoryEventURL( url );
424 server->subscribeToContentDirectory();
432 // Try to browse content directory...
434 url = ( char* ) malloc( strlen( baseURL ) +
435 strlen( controlURL ) + 1 );
438 char* s1 = strdup( baseURL );
439 char* s2 = strdup( controlURL );
441 if ( UpnpResolveURL( s1, s2, url ) ==
444 server->setContentDirectoryControlURL( url );
445 server->fetchContents();
453 ixmlNodeList_free( serviceList );
456 ixmlNodeList_free( deviceList );
460 MediaServer::MediaServer( const char* UDN,
461 const char* friendlyName,
462 services_discovery_t* p_sd )
467 _friendlyName = friendlyName;
473 MediaServer::~MediaServer()
478 const char* MediaServer::getUDN() const
480 const char* s = _UDN.c_str();
484 const char* MediaServer::getFriendlyName() const
486 const char* s = _friendlyName.c_str();
490 void MediaServer::setContentDirectoryEventURL( const char* url )
492 _contentDirectoryEventURL = url;
495 const char* MediaServer::getContentDirectoryEventURL() const
497 const char* s = _contentDirectoryEventURL.c_str();
501 void MediaServer::setContentDirectoryControlURL( const char* url )
503 _contentDirectoryControlURL = url;
506 const char* MediaServer::getContentDirectoryControlURL() const
508 return _contentDirectoryControlURL.c_str();
511 void MediaServer::subscribeToContentDirectory()
513 const char* url = getContentDirectoryEventURL();
514 if ( !url || strcmp( url, "" ) == 0 )
516 msg_Dbg( _p_sd, "No subscription url set!" );
523 int res = UpnpSubscribe( _p_sd->p_sys->clientHandle, url, &timeOut, sid );
525 if ( res == UPNP_E_SUCCESS )
527 _subscriptionTimeOut = timeOut;
528 memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) );
533 "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__,
534 getFriendlyName(), UpnpGetErrorMessage( res ) );
538 IXML_Document* MediaServer::_browseAction( const char* pObjectID,
539 const char* pBrowseFlag,
541 const char* pStartingIndex,
542 const char* pRequestedCount,
543 const char* pSortCriteria )
545 IXML_Document* action = 0;
546 IXML_Document* response = 0;
547 const char* url = getContentDirectoryControlURL();
549 if ( !url || strcmp( url, "" ) == 0 )
551 msg_Dbg( _p_sd, "No subscription url set!" );
555 char* ObjectID = strdup( pObjectID );
556 char* BrowseFlag = strdup( pBrowseFlag );
557 char* Filter = strdup( pFilter );
558 char* StartingIndex = strdup( pStartingIndex );
559 char* RequestedCount = strdup( pRequestedCount );
560 char* SortCriteria = strdup( pSortCriteria );
561 char* serviceType = strdup( CONTENT_DIRECTORY_SERVICE_TYPE );
565 res = UpnpAddToAction( &action, "Browse",
566 serviceType, "ObjectID", ObjectID );
568 if ( res != UPNP_E_SUCCESS )
571 "%s:%d: ERROR: %s", __FILE__, __LINE__,
572 UpnpGetErrorMessage( res ) );
573 goto browseActionCleanup;
576 res = UpnpAddToAction( &action, "Browse",
577 serviceType, "BrowseFlag", BrowseFlag );
579 if ( res != UPNP_E_SUCCESS )
582 "%s:%d: ERROR: %s", __FILE__, __LINE__,
583 UpnpGetErrorMessage( res ) );
584 goto browseActionCleanup;
587 res = UpnpAddToAction( &action, "Browse",
588 serviceType, "Filter", Filter );
590 if ( res != UPNP_E_SUCCESS )
593 "%s:%d: ERROR: %s", __FILE__, __LINE__,
594 UpnpGetErrorMessage( res ) );
595 goto browseActionCleanup;
598 res = UpnpAddToAction( &action, "Browse",
599 serviceType, "StartingIndex", StartingIndex );
601 if ( res != UPNP_E_SUCCESS )
604 "%s:%d: ERROR: %s", __FILE__, __LINE__,
605 UpnpGetErrorMessage( res ) );
606 goto browseActionCleanup;
609 res = UpnpAddToAction( &action, "Browse",
610 serviceType, "RequestedCount", RequestedCount );
612 if ( res != UPNP_E_SUCCESS )
615 "%s:%d: ERROR: %s", __FILE__, __LINE__,
616 UpnpGetErrorMessage( res ) ); goto browseActionCleanup; }
618 res = UpnpAddToAction( &action, "Browse",
619 serviceType, "SortCriteria", SortCriteria );
621 if ( res != UPNP_E_SUCCESS )
624 "%s:%d: ERROR: %s", __FILE__, __LINE__,
625 UpnpGetErrorMessage( res ) );
626 goto browseActionCleanup;
629 res = UpnpSendAction( _p_sd->p_sys->clientHandle,
631 CONTENT_DIRECTORY_SERVICE_TYPE,
636 if ( res != UPNP_E_SUCCESS )
639 "%s:%d: ERROR: %s when trying the send() action with URL: %s",
641 UpnpGetErrorMessage( res ), url );
643 ixmlDocument_free( response );
652 free( StartingIndex );
653 free( RequestedCount );
654 free( SortCriteria );
658 ixmlDocument_free( action );
662 void MediaServer::fetchContents()
664 Container* root = new Container( 0, "0", getFriendlyName() );
665 _fetchContents( root );
668 _contents->setInputItem( _inputItem );
670 _buildPlaylist( _contents, NULL );
673 bool MediaServer::_fetchContents( Container* parent )
678 "%s:%d: parent==NULL", __FILE__, __LINE__ );
682 IXML_Document* response = _browseAction( parent->getObjectID(),
683 "BrowseDirectChildren",
688 "%s:%d: ERROR! No response from browse() action",
689 __FILE__, __LINE__ );
693 IXML_Document* result = parseBrowseResult( response );
694 ixmlDocument_free( response );
699 "%s:%d: ERROR! browse() response parsing failed",
700 __FILE__, __LINE__ );
704 IXML_NodeList* containerNodeList =
705 ixmlDocument_getElementsByTagName( result, "container" );
707 if ( containerNodeList )
709 for ( unsigned int i = 0;
710 i < ixmlNodeList_length( containerNodeList ); i++ )
712 IXML_Element* containerElement =
713 ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
715 const char* objectID = ixmlElement_getAttribute( containerElement,
720 const char* childCountStr =
721 ixmlElement_getAttribute( containerElement, "childCount" );
723 if ( !childCountStr )
726 int childCount = atoi( childCountStr );
727 const char* title = xml_getChildElementValue( containerElement,
733 const char* resource = xml_getChildElementValue( containerElement,
736 if ( resource && childCount < 1 )
738 Item* item = new Item( parent, objectID, title, resource );
739 parent->addItem( item );
744 Container* container = new Container( parent, objectID, title );
745 parent->addContainer( container );
747 if ( childCount > 0 )
748 _fetchContents( container );
751 ixmlNodeList_free( containerNodeList );
754 IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( result,
758 for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
760 IXML_Element* itemElement =
761 ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );
763 const char* objectID =
764 ixmlElement_getAttribute( itemElement, "id" );
770 xml_getChildElementValue( itemElement, "dc:title" );
775 const char* resource =
776 xml_getChildElementValue( itemElement, "res" );
781 Item* item = new Item( parent, objectID, title, resource );
782 parent->addItem( item );
784 ixmlNodeList_free( itemNodeList );
787 ixmlDocument_free( result );
791 void MediaServer::_buildPlaylist( Container* parent, input_item_node_t *p_input_node )
793 bool send = p_input_node == NULL;
795 p_input_node = input_item_node_Create( parent->getInputItem() );
797 for ( unsigned int i = 0; i < parent->getNumContainers(); i++ )
799 Container* container = parent->getContainer( i );
801 input_item_t* p_input_item = input_item_New( _p_sd, "vlc://nop", container->getTitle() );
802 input_item_node_t *p_new_node =
803 input_item_node_AppendItem( p_input_node, p_input_item );
805 container->setInputItem( p_input_item );
806 _buildPlaylist( container, p_new_node );
809 for ( unsigned int i = 0; i < parent->getNumItems(); i++ )
811 Item* item = parent->getItem( i );
813 input_item_t* p_input_item = input_item_New( _p_sd,
816 assert( p_input_item );
817 input_item_node_AppendItem( p_input_node, p_input_item );
818 item->setInputItem( p_input_item );
822 input_item_node_PostAndDelete( p_input_node );
825 void MediaServer::setInputItem( input_item_t* p_input_item )
827 if(_inputItem == p_input_item)
831 vlc_gc_decref( _inputItem );
833 vlc_gc_incref( p_input_item );
834 _inputItem = p_input_item;
837 bool MediaServer::compareSID( const char* sid )
839 return ( strncmp( _subscriptionID, sid, sizeof( Upnp_SID ) ) == 0 );
843 // MediaServerList...
845 MediaServerList::MediaServerList( services_discovery_t* p_sd )
850 MediaServerList::~MediaServerList()
852 for ( unsigned int i = 0; i < _list.size(); i++ )
858 bool MediaServerList::addServer( MediaServer* s )
860 input_item_t* p_input_item = NULL;
861 if ( getServer( s->getUDN() ) != 0 ) return false;
863 msg_Dbg( _p_sd, "Adding server '%s'",
864 s->getFriendlyName() );
866 services_discovery_t* p_sd = _p_sd;
868 p_input_item = input_item_New( p_sd, "vlc://nop", s->getFriendlyName() );
869 s->setInputItem( p_input_item );
871 services_discovery_AddItem( p_sd, p_input_item, NULL );
873 _list.push_back( s );
878 MediaServer* MediaServerList::getServer( const char* UDN )
880 MediaServer* result = 0;
882 for ( unsigned int i = 0; i < _list.size(); i++ )
884 if( strcmp( UDN, _list[i]->getUDN() ) == 0 )
894 MediaServer* MediaServerList::getServerBySID( const char* sid )
896 MediaServer* server = 0;
898 for ( unsigned int i = 0; i < _list.size(); i++ )
900 if ( _list[i]->compareSID( sid ) )
910 void MediaServerList::removeServer( const char* UDN )
912 MediaServer* server = getServer( UDN );
913 if ( !server ) return;
916 "Removing server '%s'", server->getFriendlyName() );
918 std::vector<MediaServer*>::iterator it;
919 for ( it = _list.begin(); it != _list.end(); it++ )
933 Item::Item( Container* parent, const char* objectID, const char* title, const char* resource )
937 _objectID = objectID;
939 _resource = resource;
947 vlc_gc_decref( _inputItem );
950 const char* Item::getObjectID() const
952 return _objectID.c_str();
955 const char* Item::getTitle() const
957 return _title.c_str();
960 const char* Item::getResource() const
962 return _resource.c_str();
965 void Item::setInputItem( input_item_t* p_input_item )
967 if(_inputItem == p_input_item)
971 vlc_gc_decref( _inputItem );
973 vlc_gc_incref( p_input_item );
974 _inputItem = p_input_item;
977 input_item_t* Item::getInputItem() const
985 Container::Container( Container* parent,
986 const char* objectID,
991 _objectID = objectID;
997 Container::~Container()
999 for ( unsigned int i = 0; i < _containers.size(); i++ )
1001 delete _containers[i];
1004 for ( unsigned int i = 0; i < _items.size(); i++ )
1010 vlc_gc_decref( _inputItem );
1013 void Container::addItem( Item* item )
1015 _items.push_back( item );
1018 void Container::addContainer( Container* container )
1020 _containers.push_back( container );
1023 const char* Container::getObjectID() const
1025 return _objectID.c_str();
1028 const char* Container::getTitle() const
1030 return _title.c_str();
1033 unsigned int Container::getNumItems() const
1035 return _items.size();
1038 unsigned int Container::getNumContainers() const
1040 return _containers.size();
1043 Item* Container::getItem( unsigned int i ) const
1045 if ( i < _items.size() ) return _items[i];
1049 Container* Container::getContainer( unsigned int i ) const
1051 if ( i < _containers.size() ) return _containers[i];
1055 Container* Container::getParent()
1060 void Container::setInputItem( input_item_t* p_input_item )
1062 if(_inputItem == p_input_item)
1066 vlc_gc_decref( _inputItem );
1068 vlc_gc_incref( p_input_item );
1069 _inputItem = p_input_item;
1072 input_item_t* Container::getInputItem() const