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>
10 * Hugo Beauzée-Luyssen <hugo@beauzee.fr>
12 * UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
27 *****************************************************************************/
29 #define __STDC_CONSTANT_MACROS 1
38 #include <vlc_access.h>
39 #include <vlc_plugin.h>
40 #include <vlc_services_discovery.h>
50 const char* MEDIA_SERVER_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaServer:1";
51 const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:ContentDirectory:1";
56 struct services_discovery_sys_t
58 UpnpInstanceWrapper* p_upnp;
59 SD::MediaServerList* p_server_list;
64 UpnpInstanceWrapper* p_upnp;
67 UpnpInstanceWrapper* UpnpInstanceWrapper::s_instance;
68 vlc_mutex_t UpnpInstanceWrapper::s_lock = VLC_STATIC_MUTEX;
71 * VLC callback prototypes
75 static int Open( vlc_object_t* );
76 static void Close( vlc_object_t* );
81 static int Open( vlc_object_t* );
82 static void Close( vlc_object_t* );
85 VLC_SD_PROBE_HELPER( "upnp", "Universal Plug'n'Play", SD_CAT_LAN )
91 set_shortname( "UPnP" );
92 set_description( N_( "Universal Plug'n'Play" ) );
93 set_category( CAT_PLAYLIST );
94 set_subcategory( SUBCAT_PLAYLIST_SD );
95 set_capability( "services_discovery", 0 );
96 set_callbacks( SD::Open, SD::Close );
99 set_category( CAT_INPUT )
100 set_subcategory( SUBCAT_INPUT_ACCESS )
101 set_callbacks( Access::Open, Access::Close )
102 set_capability( "access", 0 )
104 VLC_SD_PROBE_SUBMODULE
109 * Returns the value of a child element, or NULL on error
111 const char* xml_getChildElementValue( IXML_Element* p_parent,
112 const char* psz_tag_name )
115 assert( psz_tag_name );
117 IXML_NodeList* p_node_list;
118 p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name );
119 if ( !p_node_list ) return NULL;
121 IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
122 ixmlNodeList_free( p_node_list );
123 if ( !p_element ) return NULL;
125 IXML_Node* p_text_node = ixmlNode_getFirstChild( p_element );
126 if ( !p_text_node ) return NULL;
128 return ixmlNode_getNodeValue( p_text_node );
132 * Extracts the result document from a SOAP response
134 IXML_Document* parseBrowseResult( IXML_Document* p_doc )
138 // ixml*_getElementsByTagName will ultimately only case the pointer to a Node
139 // pointer, and pass it to a private function. Don't bother have a IXML_Document
140 // version of getChildElementValue
141 const char* psz_raw_didl = xml_getChildElementValue( (IXML_Element*)p_doc, "Result" );
146 /* First, try parsing the buffer as is */
147 IXML_Document* p_result_doc = ixmlParseBuffer( psz_raw_didl );
148 if( !p_result_doc ) {
149 /* Missing namespaces confuse the ixml parser. This is a very ugly
150 * hack but it is needeed until devices start sending valid XML.
154 * The DIDL document is extracted from the Result tag, then wrapped into
155 * a valid XML header and a new root tag which contains missing namespace
156 * definitions so the ixml parser understands it.
158 * If you know of a better workaround, please oh please fix it */
159 const char* psz_xml_result_fmt = "<?xml version=\"1.0\" ?>"
160 "<Result xmlns:sec=\"urn:samsung:metadata:2009\">%s</Result>";
162 char* psz_xml_result_string = NULL;
163 if( -1 == asprintf( &psz_xml_result_string,
168 p_result_doc = ixmlParseBuffer( psz_xml_result_string );
169 free( psz_xml_result_string );
175 IXML_NodeList *p_elems = ixmlDocument_getElementsByTagName( p_result_doc,
178 IXML_Node *p_node = ixmlNodeList_item( p_elems, 0 );
179 ixmlNodeList_free( p_elems );
181 return (IXML_Document*)p_node;
188 * Initializes UPNP instance.
190 static int Open( vlc_object_t *p_this )
192 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
193 services_discovery_sys_t *p_sys = ( services_discovery_sys_t * )
194 calloc( 1, sizeof( services_discovery_sys_t ) );
196 if( !( p_sd->p_sys = p_sys ) )
199 p_sys->p_server_list = new(std::nothrow) SD::MediaServerList( p_sd );
200 if ( unlikely( p_sys->p_server_list == NULL ) )
205 p_sys->p_upnp = UpnpInstanceWrapper::get( p_this, SD::MediaServerList::Callback, p_sys->p_server_list );
206 if ( !p_sys->p_upnp )
212 /* Search for media servers */
213 int i_res = UpnpSearchAsync( p_sys->p_upnp->handle(), 5,
214 MEDIA_SERVER_DEVICE_TYPE, p_sys->p_upnp );
215 if( i_res != UPNP_E_SUCCESS )
217 msg_Err( p_sd, "Error sending search request: %s", UpnpGetErrorMessage( i_res ) );
226 * Releases resources.
228 static void Close( vlc_object_t *p_this )
230 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
231 services_discovery_sys_t *p_sys = p_sd->p_sys;
234 p_sys->p_upnp->release( true );
235 delete p_sys->p_server_list;
239 MediaServerDesc::MediaServerDesc(const std::string& udn, const std::string& fName, const std::string& loc)
241 , friendlyName( fName )
247 MediaServerDesc::~MediaServerDesc()
250 vlc_gc_decref( inputItem );
254 * MediaServerList class
256 MediaServerList::MediaServerList( services_discovery_t* p_sd )
259 vlc_mutex_init( &lock_ );
262 MediaServerList::~MediaServerList()
264 vlc_delete_all(list_);
265 vlc_mutex_destroy( &lock_ );
268 bool MediaServerList::addServer( MediaServerDesc* desc )
270 vlc_mutex_locker lock( &lock_ );
271 input_item_t* p_input_item = NULL;
272 if ( getServer( desc->UDN ) )
275 msg_Dbg( p_sd_, "Adding server '%s' with uuid '%s'", desc->friendlyName.c_str(), desc->UDN.c_str() );
278 if( asprintf(&psz_mrl, "upnp://%s?ObjectID=%s", desc->location.c_str(), desc->UDN.c_str() ) < 0 )
281 p_input_item = input_item_NewWithTypeExt( psz_mrl, desc->friendlyName.c_str(), 0,
282 NULL, 0, -1, ITEM_TYPE_NODE, 1);
286 desc->inputItem = p_input_item;
287 input_item_SetDescription( p_input_item, desc->UDN.c_str() );
288 services_discovery_AddItem( p_sd_, p_input_item, NULL );
289 list_.push_back( desc );
293 MediaServerDesc* MediaServerList::getServer( const std::string& udn )
295 std::vector<MediaServerDesc*>::const_iterator it = list_.begin();
296 std::vector<MediaServerDesc*>::const_iterator ite = list_.end();
298 for ( ; it != ite; ++it )
300 if( udn == (*it)->UDN )
308 void MediaServerList::parseNewServer( IXML_Document *doc, const std::string &location )
312 msg_Err( p_sd_, "Null IXML_Document" );
316 if ( location.empty() )
318 msg_Err( p_sd_, "Empty location" );
322 const char* psz_base_url = location.c_str();
324 /* Try to extract baseURL */
325 IXML_NodeList* p_url_list = ixmlDocument_getElementsByTagName( doc, "URLBase" );
328 if ( IXML_Node* p_url_node = ixmlNodeList_item( p_url_list, 0 ) )
330 IXML_Node* p_text_node = ixmlNode_getFirstChild( p_url_node );
332 psz_base_url = ixmlNode_getNodeValue( p_text_node );
334 ixmlNodeList_free( p_url_list );
338 IXML_NodeList* p_device_list = ixmlDocument_getElementsByTagName( doc, "device" );
340 if ( !p_device_list )
342 for ( unsigned int i = 0; i < ixmlNodeList_length( p_device_list ); i++ )
344 IXML_Element* p_device_element = ( IXML_Element* ) ixmlNodeList_item( p_device_list, i );
346 if( !p_device_element )
349 const char* psz_device_type = xml_getChildElementValue( p_device_element, "deviceType" );
351 if ( !psz_device_type )
353 msg_Warn( p_sd_, "No deviceType found!" );
357 if ( strncmp( MEDIA_SERVER_DEVICE_TYPE, psz_device_type,
358 strlen( MEDIA_SERVER_DEVICE_TYPE ) - 1 ) )
361 const char* psz_udn = xml_getChildElementValue( p_device_element,
365 msg_Warn( p_sd_, "No UDN!" );
369 /* Check if server is already added */
370 if ( p_sd_->p_sys->p_server_list->getServer( psz_udn ) )
372 msg_Warn( p_sd_, "Server with uuid '%s' already exists.", psz_udn );
376 const char* psz_friendly_name =
377 xml_getChildElementValue( p_device_element,
380 if ( !psz_friendly_name )
382 msg_Dbg( p_sd_, "No friendlyName!" );
386 // We now have basic info, we need to get the content browsing url
387 // so the access module can browse without fetching the manifest again
389 /* Check for ContentDirectory service. */
390 IXML_NodeList* p_service_list = ixmlElement_getElementsByTagName( p_device_element, "service" );
391 if ( !p_service_list )
393 for ( unsigned int j = 0; j < ixmlNodeList_length( p_service_list ); j++ )
395 IXML_Element* p_service_element = (IXML_Element*)ixmlNodeList_item( p_service_list, j );
397 const char* psz_service_type = xml_getChildElementValue( p_service_element, "serviceType" );
398 if ( !psz_service_type )
400 msg_Warn( p_sd_, "No service type found." );
404 int k = strlen( CONTENT_DIRECTORY_SERVICE_TYPE ) - 1;
405 if ( strncmp( CONTENT_DIRECTORY_SERVICE_TYPE,
406 psz_service_type, k ) )
409 const char* psz_control_url = xml_getChildElementValue( p_service_element,
411 if ( !psz_control_url )
413 msg_Warn( p_sd_, "No control url found." );
417 /* Try to browse content directory. */
418 char* psz_url = ( char* ) malloc( strlen( psz_base_url ) + strlen( psz_control_url ) + 1 );
421 if ( UpnpResolveURL( psz_base_url, psz_control_url, psz_url ) == UPNP_E_SUCCESS )
423 SD::MediaServerDesc* p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn,
424 psz_friendly_name, psz_url );
426 if ( unlikely( !p_server ) )
429 if ( !addServer( p_server ) )
439 ixmlNodeList_free( p_service_list );
441 ixmlNodeList_free( p_device_list );
444 void MediaServerList::removeServer( const std::string& udn )
446 vlc_mutex_locker lock( &lock_ );
448 MediaServerDesc* p_server = getServer( udn );
452 msg_Dbg( p_sd_, "Removing server '%s'", p_server->friendlyName.c_str() );
454 assert(p_server->inputItem);
455 services_discovery_RemoveItem( p_sd_, p_server->inputItem );
457 std::vector<MediaServerDesc*>::iterator it = std::find(list_.begin(), list_.end(), p_server);
458 if (it != list_.end())
466 * Handles servers listing UPnP events
468 int MediaServerList::Callback( Upnp_EventType event_type, void* p_event, void* p_user_data )
470 MediaServerList* self = static_cast<MediaServerList*>( p_user_data );
471 services_discovery_t* p_sd = self->p_sd_;
475 case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
476 case UPNP_DISCOVERY_SEARCH_RESULT:
478 struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
480 IXML_Document *p_description_doc = NULL;
483 i_res = UpnpDownloadXmlDoc( p_discovery->Location, &p_description_doc );
484 if ( i_res != UPNP_E_SUCCESS )
486 msg_Warn( p_sd, "Could not download device description! "
487 "Fetching data from %s failed: %s",
488 p_discovery->Location, UpnpGetErrorMessage( i_res ) );
491 self->parseNewServer( p_description_doc, p_discovery->Location );
492 ixmlDocument_free( p_description_doc );
496 case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
498 struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
500 self->removeServer( p_discovery->DeviceId );
504 case UPNP_EVENT_SUBSCRIBE_COMPLETE:
505 msg_Warn( p_sd, "subscription complete" );
508 case UPNP_DISCOVERY_SEARCH_TIMEOUT:
509 msg_Warn( p_sd, "search timeout" );
512 case UPNP_EVENT_RECEIVED:
513 case UPNP_EVENT_AUTORENEWAL_FAILED:
514 case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
515 // Those are for the access part
519 msg_Err( p_sd, "Unhandled event, please report ( type=%d )", event_type );
523 return UPNP_E_SUCCESS;
531 MediaServer::MediaServer(const char *psz_url, access_t *p_access, input_item_node_t *node)
533 , access_( p_access )
538 void MediaServer::addItem(const char *objectID, const char *title )
541 vlc_UrlParse( &url, url_.c_str(), '?' );
544 if (asprintf( &psz_url, "upnp://%s://%s:%u%s?ObjectID=%s", url.psz_protocol,
545 url.psz_host, url.i_port ? url.i_port : 80, url.psz_path, objectID ) < 0 )
547 vlc_UrlClean( &url );
550 vlc_UrlClean( &url );
552 input_item_t* p_item = input_item_NewWithTypeExt( psz_url, title, 0, NULL,
553 0, -1, ITEM_TYPE_DIRECTORY, 1 );
557 input_item_CopyOptions( node_->p_item, p_item );
558 input_item_node_AppendItem( node_, p_item );
559 input_item_Release( p_item );
562 void MediaServer::addItem(const char* title, const char*, const char*,
563 mtime_t duration, const char* psz_url)
565 input_item_t* p_item = input_item_NewWithTypeExt( psz_url, title, 0, NULL, 0,
566 duration, ITEM_TYPE_FILE, 1 );
567 input_item_node_AppendItem( node_, p_item );
568 input_item_Release( p_item );
572 IXML_Document* MediaServer::_browseAction( const char* psz_object_id_,
573 const char* psz_browser_flag_,
574 const char* psz_filter_,
575 const char* psz_requested_count_,
576 const char* psz_sort_criteria_ )
578 IXML_Document* p_action = NULL;
579 IXML_Document* p_response = NULL;
580 const char* psz_url = url_.c_str();
584 msg_Dbg( access_, "No subscription url set!" );
590 i_res = UpnpAddToAction( &p_action, "Browse",
591 CONTENT_DIRECTORY_SERVICE_TYPE, "ObjectID", psz_object_id_ );
593 if ( i_res != UPNP_E_SUCCESS )
595 msg_Dbg( access_, "AddToAction 'ObjectID' failed: %s",
596 UpnpGetErrorMessage( i_res ) );
597 goto browseActionCleanup;
600 i_res = UpnpAddToAction( &p_action, "Browse",
601 CONTENT_DIRECTORY_SERVICE_TYPE, "StartingIndex", "0" );
602 if ( i_res != UPNP_E_SUCCESS )
604 msg_Dbg( access_, "AddToAction 'StartingIndex' failed: %s",
605 UpnpGetErrorMessage( i_res ) );
606 goto browseActionCleanup;
609 i_res = UpnpAddToAction( &p_action, "Browse",
610 CONTENT_DIRECTORY_SERVICE_TYPE, "BrowseFlag", psz_browser_flag_ );
612 if ( i_res != UPNP_E_SUCCESS )
614 msg_Dbg( access_, "AddToAction 'BrowseFlag' failed: %s",
615 UpnpGetErrorMessage( i_res ) );
616 goto browseActionCleanup;
619 i_res = UpnpAddToAction( &p_action, "Browse",
620 CONTENT_DIRECTORY_SERVICE_TYPE, "Filter", psz_filter_ );
622 if ( i_res != UPNP_E_SUCCESS )
624 msg_Dbg( access_, "AddToAction 'Filter' failed: %s",
625 UpnpGetErrorMessage( i_res ) );
626 goto browseActionCleanup;
629 i_res = UpnpAddToAction( &p_action, "Browse",
630 CONTENT_DIRECTORY_SERVICE_TYPE, "RequestedCount", psz_requested_count_ );
632 if ( i_res != UPNP_E_SUCCESS )
634 msg_Dbg( access_, "AddToAction 'RequestedCount' failed: %s",
635 UpnpGetErrorMessage( i_res ) );
636 goto browseActionCleanup;
639 i_res = UpnpAddToAction( &p_action, "Browse",
640 CONTENT_DIRECTORY_SERVICE_TYPE, "SortCriteria", psz_sort_criteria_ );
642 if ( i_res != UPNP_E_SUCCESS )
644 msg_Dbg( access_, "AddToAction 'SortCriteria' failed: %s",
645 UpnpGetErrorMessage( i_res ) );
646 goto browseActionCleanup;
649 i_res = UpnpSendAction( access_->p_sys->p_upnp->handle(),
651 CONTENT_DIRECTORY_SERVICE_TYPE,
652 NULL, /* ignored in SDK, must be NULL */
656 if ( i_res != UPNP_E_SUCCESS )
658 msg_Err( access_, "%s when trying the send() action with URL: %s",
659 UpnpGetErrorMessage( i_res ), psz_url );
661 ixmlDocument_free( p_response );
666 ixmlDocument_free( p_action );
671 * Fetches and parses the UPNP response
673 bool MediaServer::fetchContents()
675 const char* objectID = "";
677 vlc_UrlParse( &url, access_->psz_location, '?');
679 if ( url.psz_option && !strncmp( url.psz_option, "ObjectID=", strlen( "ObjectID=" ) ) )
681 objectID = &url.psz_option[strlen( "ObjectID=" )];
684 IXML_Document* p_response = _browseAction( objectID,
685 "BrowseDirectChildren",
686 "id,dc:title,res," /* Filter */
687 "sec:CaptionInfo,sec:CaptionInfoEx,"
689 "0", /* RequestedCount */
690 "" /* SortCriteria */
692 vlc_UrlClean( &url );
695 msg_Err( access_, "No response from browse() action" );
699 IXML_Document* p_result = parseBrowseResult( p_response );
701 ixmlDocument_free( p_response );
705 msg_Err( access_, "browse() response parsing failed" );
710 msg_Dbg( access_, "Got DIDL document: %s", ixmlPrintDocument( p_result ) );
713 IXML_NodeList* containerNodeList =
714 ixmlDocument_getElementsByTagName( p_result, "container" );
716 if ( containerNodeList )
718 for ( unsigned int i = 0; i < ixmlNodeList_length( containerNodeList ); i++ )
720 IXML_Element* containerElement = (IXML_Element*)ixmlNodeList_item( containerNodeList, i );
722 const char* objectID = ixmlElement_getAttribute( containerElement,
727 const char* title = xml_getChildElementValue( containerElement,
731 addItem(objectID, title);
733 ixmlNodeList_free( containerNodeList );
736 IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( p_result,
740 for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
742 IXML_Element* itemElement =
743 ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );
745 const char* objectID =
746 ixmlElement_getAttribute( itemElement, "id" );
752 xml_getChildElementValue( itemElement, "dc:title" );
757 const char* psz_subtitles = xml_getChildElementValue( itemElement,
760 if ( !psz_subtitles )
761 psz_subtitles = xml_getChildElementValue( itemElement,
762 "sec:CaptionInfoEx" );
764 if ( !psz_subtitles )
765 psz_subtitles = xml_getChildElementValue( itemElement,
768 /* Try to extract all resources in DIDL */
769 IXML_NodeList* p_resource_list = ixmlDocument_getElementsByTagName( (IXML_Document*) itemElement, "res" );
770 if ( p_resource_list )
772 int i_length = ixmlNodeList_length( p_resource_list );
773 for ( int i = 0; i < i_length; i++ )
775 mtime_t i_duration = -1;
776 int i_hours, i_minutes, i_seconds;
777 IXML_Element* p_resource = ( IXML_Element* ) ixmlNodeList_item( p_resource_list, i );
778 const char* psz_resource_url = xml_getChildElementValue( p_resource, "res" );
779 if( !psz_resource_url )
781 const char* psz_duration = ixmlElement_getAttribute( p_resource, "duration" );
785 if( sscanf( psz_duration, "%d:%02d:%02d",
786 &i_hours, &i_minutes, &i_seconds ) )
787 i_duration = INT64_C(1000000) * ( i_hours*3600 +
792 addItem( title, objectID, psz_subtitles, i_duration, psz_resource_url );
794 ixmlNodeList_free( p_resource_list );
799 ixmlNodeList_free( itemNodeList );
802 ixmlDocument_free( p_result );
806 static int ReadDirectory( access_t *p_access, input_item_node_t* p_node )
808 MediaServer server( p_access->psz_location, p_access, p_node );
810 if ( !server.fetchContents() )
815 static int Control( access_t *, int i_query, va_list args )
819 case ACCESS_CAN_SEEK:
820 case ACCESS_CAN_FASTSEEK:
821 case ACCESS_CAN_PAUSE:
822 case ACCESS_CAN_CONTROL_PACE:
823 *va_arg( args, bool* ) = false;
826 case ACCESS_GET_SIZE:
828 *va_arg( args, uint64_t * ) = 0;
831 case ACCESS_GET_PTS_DELAY:
832 *va_arg( args, int64_t * ) = 0;
835 case ACCESS_SET_PAUSE_STATE:
845 static int Open( vlc_object_t *p_this )
847 access_t* p_access = (access_t*)p_this;
848 access_sys_t* p_sys = new(std::nothrow) access_sys_t;
849 if ( unlikely( !p_sys ) )
852 p_access->p_sys = p_sys;
853 p_sys->p_upnp = UpnpInstanceWrapper::get( p_this, NULL, NULL );
854 if ( !p_sys->p_upnp )
860 p_access->pf_readdir = ReadDirectory;
861 ACCESS_SET_CALLBACKS( NULL, NULL, Control, NULL );
866 static void Close( vlc_object_t* p_this )
868 access_t* p_access = (access_t*)p_this;
869 p_access->p_sys->p_upnp->release( false );
870 delete p_access->p_sys;
875 UpnpInstanceWrapper::UpnpInstanceWrapper()
883 UpnpInstanceWrapper::~UpnpInstanceWrapper()
885 UpnpUnRegisterClient( handle_ );
889 UpnpInstanceWrapper *UpnpInstanceWrapper::get(vlc_object_t *p_obj, Upnp_FunPtr callback, SD::MediaServerList *opaque)
891 vlc_mutex_locker lock( &s_lock );
892 if ( s_instance == NULL )
894 UpnpInstanceWrapper* instance = new(std::nothrow) UpnpInstanceWrapper;
895 if ( unlikely( !instance ) )
898 #ifdef UPNP_ENABLE_IPV6
899 char* psz_miface = var_InheritString( p_obj, "miface" );
900 msg_Info( p_obj, "Initializing libupnp on '%s' interface", psz_miface );
901 int i_res = UpnpInit2( psz_miface, 0 );
904 /* If UpnpInit2 isnt available, initialize on first IPv4-capable interface */
905 int i_res = UpnpInit( 0, 0 );
907 if( i_res != UPNP_E_SUCCESS )
909 msg_Err( p_obj, "Initialization failed: %s", UpnpGetErrorMessage( i_res ) );
914 ixmlRelaxParser( 1 );
916 /* Register a control point */
917 i_res = UpnpRegisterClient( Callback, instance, &instance->handle_ );
918 if( i_res != UPNP_E_SUCCESS )
920 msg_Err( p_obj, "Client registration failed: %s", UpnpGetErrorMessage( i_res ) );
925 /* libupnp does not treat a maximum content length of 0 as unlimited
926 * until 64dedf (~ pupnp v1.6.7) and provides no sane way to discriminate
927 * between versions */
928 if( (i_res = UpnpSetMaxContentLength( INT_MAX )) != UPNP_E_SUCCESS )
930 msg_Err( p_obj, "Failed to set maximum content length: %s",
931 UpnpGetErrorMessage( i_res ));
935 s_instance = instance;
937 s_instance->refcount_++;
938 // This assumes a single UPNP SD instance
939 if (callback && opaque)
941 assert(!s_instance->callback_ && !s_instance->opaque_);
942 s_instance->opaque_ = opaque;
943 s_instance->callback_ = callback;
948 void UpnpInstanceWrapper::release(bool isSd)
950 vlc_mutex_locker lock( &s_lock );
956 if (--s_instance->refcount_ == 0)
963 UpnpClient_Handle UpnpInstanceWrapper::handle() const
968 int UpnpInstanceWrapper::Callback(Upnp_EventType event_type, void *p_event, void *p_user_data)
970 UpnpInstanceWrapper* self = static_cast<UpnpInstanceWrapper*>( p_user_data );
971 vlc_mutex_locker lock( &self->s_lock );
972 if ( !self->callback_ )
974 self->callback_( event_type, p_event, self->opaque_ );