video_filter: erase: use C99 loop declarations
[vlc.git] / modules / services_discovery / upnp.cpp
blob2eaaf20f8e6119d4f298f97dcb008e31f4ad5610
1 /*****************************************************************************
2 * upnp.cpp : UPnP discovery module (libupnp)
3 *****************************************************************************
4 * Copyright (C) 2004-2011 the VideoLAN team
5 * $Id$
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 #include "upnp.hpp"
31 #include <vlc_access.h>
32 #include <vlc_plugin.h>
33 #include <vlc_services_discovery.h>
34 #include <vlc_url.h>
36 #include <assert.h>
37 #include <limits.h>
38 #include <algorithm>
41 * Constants
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 * VLC handle
49 struct services_discovery_sys_t
51 UpnpInstanceWrapper* p_upnp;
52 SD::MediaServerList* p_server_list;
55 struct access_sys_t
57 UpnpInstanceWrapper* p_upnp;
58 Access::MediaServer* p_server;
61 UpnpInstanceWrapper* UpnpInstanceWrapper::s_instance;
62 vlc_mutex_t UpnpInstanceWrapper::s_lock = VLC_STATIC_MUTEX;
65 * VLC callback prototypes
67 namespace SD
69 static int Open( vlc_object_t* );
70 static void Close( vlc_object_t* );
73 namespace Access
75 static int Open( vlc_object_t* );
76 static void Close( vlc_object_t* );
79 VLC_SD_PROBE_HELPER( "upnp", "Universal Plug'n'Play", SD_CAT_LAN )
82 * Module descriptor
84 vlc_module_begin()
85 set_shortname( "UPnP" );
86 set_description( N_( "Universal Plug'n'Play" ) );
87 set_category( CAT_PLAYLIST );
88 set_subcategory( SUBCAT_PLAYLIST_SD );
89 set_capability( "services_discovery", 0 );
90 set_callbacks( SD::Open, SD::Close );
92 add_submodule()
93 set_category( CAT_INPUT )
94 set_subcategory( SUBCAT_INPUT_ACCESS )
95 set_callbacks( Access::Open, Access::Close )
96 set_capability( "access", 0 )
98 VLC_SD_PROBE_SUBMODULE
99 vlc_module_end()
103 * Returns the value of a child element, or NULL on error
105 const char* xml_getChildElementValue( IXML_Element* p_parent,
106 const char* psz_tag_name )
108 assert( p_parent );
109 assert( psz_tag_name );
111 IXML_NodeList* p_node_list;
112 p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name );
113 if ( !p_node_list ) return NULL;
115 IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
116 ixmlNodeList_free( p_node_list );
117 if ( !p_element ) return NULL;
119 IXML_Node* p_text_node = ixmlNode_getFirstChild( p_element );
120 if ( !p_text_node ) return NULL;
122 return ixmlNode_getNodeValue( p_text_node );
126 * Extracts the result document from a SOAP response
128 IXML_Document* parseBrowseResult( IXML_Document* p_doc )
130 assert( p_doc );
132 // ixml*_getElementsByTagName will ultimately only case the pointer to a Node
133 // pointer, and pass it to a private function. Don't bother have a IXML_Document
134 // version of getChildElementValue
135 const char* psz_raw_didl = xml_getChildElementValue( (IXML_Element*)p_doc, "Result" );
137 if( !psz_raw_didl )
138 return NULL;
140 /* First, try parsing the buffer as is */
141 IXML_Document* p_result_doc = ixmlParseBuffer( psz_raw_didl );
142 if( !p_result_doc ) {
143 /* Missing namespaces confuse the ixml parser. This is a very ugly
144 * hack but it is needeed until devices start sending valid XML.
146 * It works that way:
148 * The DIDL document is extracted from the Result tag, then wrapped into
149 * a valid XML header and a new root tag which contains missing namespace
150 * definitions so the ixml parser understands it.
152 * If you know of a better workaround, please oh please fix it */
153 const char* psz_xml_result_fmt = "<?xml version=\"1.0\" ?>"
154 "<Result xmlns:sec=\"urn:samsung:metadata:2009\">%s</Result>";
156 char* psz_xml_result_string = NULL;
157 if( -1 == asprintf( &psz_xml_result_string,
158 psz_xml_result_fmt,
159 psz_raw_didl) )
160 return NULL;
162 p_result_doc = ixmlParseBuffer( psz_xml_result_string );
163 free( psz_xml_result_string );
166 if( !p_result_doc )
167 return NULL;
169 IXML_NodeList *p_elems = ixmlDocument_getElementsByTagName( p_result_doc,
170 "DIDL-Lite" );
172 IXML_Node *p_node = ixmlNodeList_item( p_elems, 0 );
173 ixmlNodeList_free( p_elems );
175 return (IXML_Document*)p_node;
178 namespace SD
182 * Initializes UPNP instance.
184 static int Open( vlc_object_t *p_this )
186 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
187 services_discovery_sys_t *p_sys = ( services_discovery_sys_t * )
188 calloc( 1, sizeof( services_discovery_sys_t ) );
190 if( !( p_sd->p_sys = p_sys ) )
191 return VLC_ENOMEM;
193 p_sys->p_server_list = new(std::nothrow) SD::MediaServerList( p_sd );
194 if ( unlikely( p_sys->p_server_list == NULL ) )
196 return VLC_ENOMEM;
199 p_sys->p_upnp = UpnpInstanceWrapper::get( p_this, SD::MediaServerList::Callback, p_sys->p_server_list );
200 if ( !p_sys->p_upnp )
202 Close( p_this );
203 return VLC_EGENERIC;
206 /* Search for media servers */
207 int i_res = UpnpSearchAsync( p_sys->p_upnp->handle(), 5,
208 MEDIA_SERVER_DEVICE_TYPE, p_sys->p_upnp );
209 if( i_res != UPNP_E_SUCCESS )
211 msg_Err( p_sd, "Error sending search request: %s", UpnpGetErrorMessage( i_res ) );
212 Close( p_this );
213 return VLC_EGENERIC;
216 return VLC_SUCCESS;
220 * Releases resources.
222 static void Close( vlc_object_t *p_this )
224 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
225 services_discovery_sys_t *p_sys = p_sd->p_sys;
227 if (p_sys->p_upnp)
228 p_sys->p_upnp->release( true );
229 delete p_sys->p_server_list;
230 free( p_sys );
233 MediaServerDesc::MediaServerDesc(const std::string& udn, const std::string& fName, const std::string& loc)
234 : UDN( udn )
235 , friendlyName( fName )
236 , location( loc )
237 , inputItem( NULL )
241 MediaServerDesc::~MediaServerDesc()
243 if (inputItem)
244 vlc_gc_decref( inputItem );
248 * MediaServerList class
250 MediaServerList::MediaServerList( services_discovery_t* p_sd )
251 : p_sd_( p_sd )
253 vlc_mutex_init( &lock_ );
256 MediaServerList::~MediaServerList()
258 vlc_delete_all(list_);
259 vlc_mutex_destroy( &lock_ );
262 bool MediaServerList::addServer( MediaServerDesc* desc )
264 vlc_mutex_locker lock( &lock_ );
265 input_item_t* p_input_item = NULL;
266 if ( getServer( desc->UDN ) )
267 return false;
269 msg_Dbg( p_sd_, "Adding server '%s' with uuid '%s'", desc->friendlyName.c_str(), desc->UDN.c_str() );
271 char* psz_mrl;
272 if( asprintf(&psz_mrl, "upnp://%s?ObjectID=0", desc->location.c_str() ) < 0 )
273 return false;
275 p_input_item = input_item_NewWithTypeExt( psz_mrl, desc->friendlyName.c_str(), 0,
276 NULL, 0, -1, ITEM_TYPE_NODE, 1);
277 free( psz_mrl );
278 if ( !p_input_item )
279 return false;
280 desc->inputItem = p_input_item;
281 input_item_SetDescription( p_input_item, desc->UDN.c_str() );
282 services_discovery_AddItem( p_sd_, p_input_item, NULL );
283 list_.push_back( desc );
284 return true;
287 MediaServerDesc* MediaServerList::getServer( const std::string& udn )
289 std::vector<MediaServerDesc*>::const_iterator it = list_.begin();
290 std::vector<MediaServerDesc*>::const_iterator ite = list_.end();
292 for ( ; it != ite; ++it )
294 if( udn == (*it)->UDN )
296 return *it;
299 return NULL;
302 void MediaServerList::parseNewServer( IXML_Document *doc, const std::string &location )
304 if ( !doc )
306 msg_Err( p_sd_, "Null IXML_Document" );
307 return;
310 if ( location.empty() )
312 msg_Err( p_sd_, "Empty location" );
313 return;
316 const char* psz_base_url = location.c_str();
318 /* Try to extract baseURL */
319 IXML_NodeList* p_url_list = ixmlDocument_getElementsByTagName( doc, "URLBase" );
320 if ( p_url_list )
322 if ( IXML_Node* p_url_node = ixmlNodeList_item( p_url_list, 0 ) )
324 IXML_Node* p_text_node = ixmlNode_getFirstChild( p_url_node );
325 if ( p_text_node )
326 psz_base_url = ixmlNode_getNodeValue( p_text_node );
328 ixmlNodeList_free( p_url_list );
331 /* Get devices */
332 IXML_NodeList* p_device_list = ixmlDocument_getElementsByTagName( doc, "device" );
334 if ( !p_device_list )
335 return;
336 for ( unsigned int i = 0; i < ixmlNodeList_length( p_device_list ); i++ )
338 IXML_Element* p_device_element = ( IXML_Element* ) ixmlNodeList_item( p_device_list, i );
340 if( !p_device_element )
341 continue;
343 const char* psz_device_type = xml_getChildElementValue( p_device_element, "deviceType" );
345 if ( !psz_device_type )
347 msg_Warn( p_sd_, "No deviceType found!" );
348 continue;
351 if ( strncmp( MEDIA_SERVER_DEVICE_TYPE, psz_device_type,
352 strlen( MEDIA_SERVER_DEVICE_TYPE ) - 1 ) )
353 continue;
355 const char* psz_udn = xml_getChildElementValue( p_device_element,
356 "UDN" );
357 if ( !psz_udn )
359 msg_Warn( p_sd_, "No UDN!" );
360 continue;
363 /* Check if server is already added */
364 if ( p_sd_->p_sys->p_server_list->getServer( psz_udn ) )
366 msg_Warn( p_sd_, "Server with uuid '%s' already exists.", psz_udn );
367 continue;
370 const char* psz_friendly_name =
371 xml_getChildElementValue( p_device_element,
372 "friendlyName" );
374 if ( !psz_friendly_name )
376 msg_Dbg( p_sd_, "No friendlyName!" );
377 continue;
380 // We now have basic info, we need to get the content browsing url
381 // so the access module can browse without fetching the manifest again
383 /* Check for ContentDirectory service. */
384 IXML_NodeList* p_service_list = ixmlElement_getElementsByTagName( p_device_element, "service" );
385 if ( !p_service_list )
386 continue;
387 for ( unsigned int j = 0; j < ixmlNodeList_length( p_service_list ); j++ )
389 IXML_Element* p_service_element = (IXML_Element*)ixmlNodeList_item( p_service_list, j );
391 const char* psz_service_type = xml_getChildElementValue( p_service_element, "serviceType" );
392 if ( !psz_service_type )
394 msg_Warn( p_sd_, "No service type found." );
395 continue;
398 int k = strlen( CONTENT_DIRECTORY_SERVICE_TYPE ) - 1;
399 if ( strncmp( CONTENT_DIRECTORY_SERVICE_TYPE,
400 psz_service_type, k ) )
401 continue;
403 const char* psz_control_url = xml_getChildElementValue( p_service_element,
404 "controlURL" );
405 if ( !psz_control_url )
407 msg_Warn( p_sd_, "No control url found." );
408 continue;
411 /* Try to browse content directory. */
412 char* psz_url = ( char* ) malloc( strlen( psz_base_url ) + strlen( psz_control_url ) + 1 );
413 if ( psz_url )
415 if ( UpnpResolveURL( psz_base_url, psz_control_url, psz_url ) == UPNP_E_SUCCESS )
417 SD::MediaServerDesc* p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn,
418 psz_friendly_name, psz_url );
419 free( psz_url );
420 if ( unlikely( !p_server ) )
421 break;
423 if ( !addServer( p_server ) )
425 delete p_server;
426 continue;
429 else
430 free( psz_url );
433 ixmlNodeList_free( p_service_list );
435 ixmlNodeList_free( p_device_list );
438 void MediaServerList::removeServer( const std::string& udn )
440 vlc_mutex_locker lock( &lock_ );
442 MediaServerDesc* p_server = getServer( udn );
443 if ( !p_server )
444 return;
446 msg_Dbg( p_sd_, "Removing server '%s'", p_server->friendlyName.c_str() );
448 assert(p_server->inputItem);
449 services_discovery_RemoveItem( p_sd_, p_server->inputItem );
451 std::vector<MediaServerDesc*>::iterator it = std::find(list_.begin(), list_.end(), p_server);
452 if (it != list_.end())
454 list_.erase( it );
456 delete p_server;
460 * Handles servers listing UPnP events
462 int MediaServerList::Callback( Upnp_EventType event_type, void* p_event, void* p_user_data )
464 MediaServerList* self = static_cast<MediaServerList*>( p_user_data );
465 services_discovery_t* p_sd = self->p_sd_;
467 switch( event_type )
469 case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
470 case UPNP_DISCOVERY_SEARCH_RESULT:
472 struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
474 IXML_Document *p_description_doc = NULL;
476 int i_res;
477 i_res = UpnpDownloadXmlDoc( p_discovery->Location, &p_description_doc );
478 if ( i_res != UPNP_E_SUCCESS )
480 msg_Warn( p_sd, "Could not download device description! "
481 "Fetching data from %s failed: %s",
482 p_discovery->Location, UpnpGetErrorMessage( i_res ) );
483 return i_res;
485 self->parseNewServer( p_description_doc, p_discovery->Location );
486 ixmlDocument_free( p_description_doc );
488 break;
490 case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
492 struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
494 self->removeServer( p_discovery->DeviceId );
496 break;
498 case UPNP_EVENT_SUBSCRIBE_COMPLETE:
499 msg_Warn( p_sd, "subscription complete" );
500 break;
502 case UPNP_DISCOVERY_SEARCH_TIMEOUT:
503 msg_Warn( p_sd, "search timeout" );
504 break;
506 case UPNP_EVENT_RECEIVED:
507 case UPNP_EVENT_AUTORENEWAL_FAILED:
508 case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
509 // Those are for the access part
510 break;
512 default:
513 msg_Err( p_sd, "Unhandled event, please report ( type=%d )", event_type );
514 break;
517 return UPNP_E_SUCCESS;
522 namespace Access
525 MediaServer::MediaServer(const char *psz_url, access_t *p_access)
526 : url_( psz_url )
527 , access_( p_access )
528 , xmlDocument_( NULL )
529 , containerNodeList_( NULL )
530 , containerNodeIndex_( 0 )
531 , itemNodeList_( NULL )
532 , itemNodeIndex_( 0 )
536 MediaServer::~MediaServer()
538 ixmlNodeList_free( containerNodeList_ );
539 ixmlNodeList_free( itemNodeList_ );
540 ixmlDocument_free( xmlDocument_ );
543 input_item_t* MediaServer::newItem(const char *objectID, const char *title )
545 vlc_url_t url;
546 vlc_UrlParse( &url, url_.c_str() );
547 char* psz_url;
549 if (asprintf( &psz_url, "upnp://%s://%s:%u%s?ObjectID=%s", url.psz_protocol,
550 url.psz_host, url.i_port ? url.i_port : 80, url.psz_path, objectID ) < 0 )
552 vlc_UrlClean( &url );
553 return NULL;
555 vlc_UrlClean( &url );
557 input_item_t* p_item = input_item_NewWithTypeExt( psz_url, title, 0, NULL,
558 0, -1, ITEM_TYPE_DIRECTORY, 1 );
559 free( psz_url);
560 return p_item;
563 input_item_t* MediaServer::newItem(const char* title, const char*, const char*,
564 mtime_t duration, const char* psz_url)
566 return input_item_NewWithTypeExt( psz_url, title, 0, NULL, 0,
567 duration, ITEM_TYPE_FILE, 1 );
570 /* Access part */
571 IXML_Document* MediaServer::_browseAction( const char* psz_object_id_,
572 const char* psz_browser_flag_,
573 const char* psz_filter_,
574 const char* psz_requested_count_,
575 const char* psz_sort_criteria_ )
577 IXML_Document* p_action = NULL;
578 IXML_Document* p_response = NULL;
579 const char* psz_url = url_.c_str();
581 if ( url_.empty() )
583 msg_Dbg( access_, "No subscription url set!" );
584 return NULL;
587 int i_res;
589 i_res = UpnpAddToAction( &p_action, "Browse",
590 CONTENT_DIRECTORY_SERVICE_TYPE, "ObjectID", psz_object_id_ );
592 if ( i_res != UPNP_E_SUCCESS )
594 msg_Dbg( access_, "AddToAction 'ObjectID' failed: %s",
595 UpnpGetErrorMessage( i_res ) );
596 goto browseActionCleanup;
599 i_res = UpnpAddToAction( &p_action, "Browse",
600 CONTENT_DIRECTORY_SERVICE_TYPE, "StartingIndex", "0" );
601 if ( i_res != UPNP_E_SUCCESS )
603 msg_Dbg( access_, "AddToAction 'StartingIndex' failed: %s",
604 UpnpGetErrorMessage( i_res ) );
605 goto browseActionCleanup;
608 i_res = UpnpAddToAction( &p_action, "Browse",
609 CONTENT_DIRECTORY_SERVICE_TYPE, "BrowseFlag", psz_browser_flag_ );
611 if ( i_res != UPNP_E_SUCCESS )
613 msg_Dbg( access_, "AddToAction 'BrowseFlag' failed: %s",
614 UpnpGetErrorMessage( i_res ) );
615 goto browseActionCleanup;
618 i_res = UpnpAddToAction( &p_action, "Browse",
619 CONTENT_DIRECTORY_SERVICE_TYPE, "Filter", psz_filter_ );
621 if ( i_res != UPNP_E_SUCCESS )
623 msg_Dbg( access_, "AddToAction 'Filter' failed: %s",
624 UpnpGetErrorMessage( i_res ) );
625 goto browseActionCleanup;
628 i_res = UpnpAddToAction( &p_action, "Browse",
629 CONTENT_DIRECTORY_SERVICE_TYPE, "RequestedCount", psz_requested_count_ );
631 if ( i_res != UPNP_E_SUCCESS )
633 msg_Dbg( access_, "AddToAction 'RequestedCount' failed: %s",
634 UpnpGetErrorMessage( i_res ) );
635 goto browseActionCleanup;
638 i_res = UpnpAddToAction( &p_action, "Browse",
639 CONTENT_DIRECTORY_SERVICE_TYPE, "SortCriteria", psz_sort_criteria_ );
641 if ( i_res != UPNP_E_SUCCESS )
643 msg_Dbg( access_, "AddToAction 'SortCriteria' failed: %s",
644 UpnpGetErrorMessage( i_res ) );
645 goto browseActionCleanup;
648 i_res = UpnpSendAction( access_->p_sys->p_upnp->handle(),
649 psz_url,
650 CONTENT_DIRECTORY_SERVICE_TYPE,
651 NULL, /* ignored in SDK, must be NULL */
652 p_action,
653 &p_response );
655 if ( i_res != UPNP_E_SUCCESS )
657 msg_Err( access_, "%s when trying the send() action with URL: %s",
658 UpnpGetErrorMessage( i_res ), psz_url );
660 ixmlDocument_free( p_response );
661 p_response = NULL;
664 browseActionCleanup:
665 ixmlDocument_free( p_action );
666 return p_response;
670 * Fetches and parses the UPNP response
672 void MediaServer::fetchContents()
674 const char* objectID = "";
675 vlc_url_t url;
676 vlc_UrlParse( &url, access_->psz_location );
678 if ( url.psz_option && !strncmp( url.psz_option, "ObjectID=", strlen( "ObjectID=" ) ) )
680 objectID = &url.psz_option[strlen( "ObjectID=" )];
683 IXML_Document* p_response = _browseAction( objectID,
684 "BrowseDirectChildren",
685 "id,dc:title,res," /* Filter */
686 "sec:CaptionInfo,sec:CaptionInfoEx,"
687 "pv:subtitlefile",
688 "0", /* RequestedCount */
689 "" /* SortCriteria */
691 vlc_UrlClean( &url );
692 if ( !p_response )
694 msg_Err( access_, "No response from browse() action" );
695 return;
698 xmlDocument_ = parseBrowseResult( p_response );
700 ixmlDocument_free( p_response );
702 if ( !xmlDocument_ )
704 msg_Err( access_, "browse() response parsing failed" );
705 return;
708 #ifndef NDEBUG
709 msg_Dbg( access_, "Got DIDL document: %s", ixmlPrintDocument( xmlDocument_ ) );
710 #endif
712 containerNodeList_ = ixmlDocument_getElementsByTagName( xmlDocument_, "container" );
713 itemNodeList_ = ixmlDocument_getElementsByTagName( xmlDocument_, "item" );
716 input_item_t* MediaServer::getNextItem()
718 input_item_t *p_item = NULL;
720 if( !xmlDocument_ )
722 fetchContents();
723 if( !xmlDocument_ )
724 return NULL;
727 if ( containerNodeList_ )
729 for ( ; !p_item && containerNodeIndex_ < ixmlNodeList_length( containerNodeList_ )
730 ; containerNodeIndex_++ )
732 IXML_Element* containerElement = (IXML_Element*)ixmlNodeList_item( containerNodeList_,
733 containerNodeIndex_ );
735 const char* objectID = ixmlElement_getAttribute( containerElement,
736 "id" );
737 if ( !objectID )
738 continue;
740 const char* title = xml_getChildElementValue( containerElement,
741 "dc:title" );
742 if ( !title )
743 continue;
744 p_item = newItem(objectID, title);
748 if( itemNodeList_ )
750 for ( ; !p_item && itemNodeIndex_ < ixmlNodeList_length( itemNodeList_ )
751 ; itemNodeIndex_++ )
753 IXML_Element* itemElement =
754 ( IXML_Element* )ixmlNodeList_item( itemNodeList_,
755 itemNodeIndex_ );
757 const char* objectID =
758 ixmlElement_getAttribute( itemElement, "id" );
760 if ( !objectID )
761 continue;
763 const char* title =
764 xml_getChildElementValue( itemElement, "dc:title" );
766 if ( !title )
767 continue;
769 const char* psz_subtitles = xml_getChildElementValue( itemElement,
770 "sec:CaptionInfo" );
772 if ( !psz_subtitles )
773 psz_subtitles = xml_getChildElementValue( itemElement,
774 "sec:CaptionInfoEx" );
776 if ( !psz_subtitles )
777 psz_subtitles = xml_getChildElementValue( itemElement,
778 "pv:subtitlefile" );
780 /* Try to extract all resources in DIDL */
781 IXML_NodeList* p_resource_list = ixmlDocument_getElementsByTagName( (IXML_Document*) itemElement, "res" );
782 if ( p_resource_list && ixmlNodeList_length( p_resource_list ) > 0 )
784 mtime_t i_duration = -1;
785 int i_hours, i_minutes, i_seconds;
786 IXML_Element* p_resource = ( IXML_Element* ) ixmlNodeList_item( p_resource_list, 0 );
787 const char* psz_resource_url = xml_getChildElementValue( p_resource, "res" );
788 if( !psz_resource_url )
789 continue;
790 const char* psz_duration = ixmlElement_getAttribute( p_resource, "duration" );
792 if ( psz_duration )
794 if( sscanf( psz_duration, "%d:%02d:%02d",
795 &i_hours, &i_minutes, &i_seconds ) )
796 i_duration = INT64_C(1000000) * ( i_hours*3600 +
797 i_minutes*60 +
798 i_seconds );
801 p_item = newItem( title, objectID, psz_subtitles, i_duration,
802 psz_resource_url );
804 ixmlNodeList_free( p_resource_list );
808 return p_item;
811 static input_item_t* ReadDirectory( access_t *p_access )
813 return p_access->p_sys->p_server->getNextItem();
816 static int Open( vlc_object_t *p_this )
818 access_t* p_access = (access_t*)p_this;
819 access_sys_t* p_sys = new(std::nothrow) access_sys_t;
820 if ( unlikely( !p_sys ) )
821 return VLC_ENOMEM;
823 p_access->p_sys = p_sys;
824 p_sys->p_server = new(std::nothrow) MediaServer( p_access->psz_location,
825 p_access );
826 if ( !p_sys->p_server )
828 delete p_sys;
829 return VLC_EGENERIC;
831 p_sys->p_upnp = UpnpInstanceWrapper::get( p_this, NULL, NULL );
832 if ( !p_sys->p_upnp )
834 delete p_sys->p_server;
835 delete p_sys;
836 return VLC_EGENERIC;
839 p_access->pf_readdir = ReadDirectory;
840 p_access->pf_control = access_vaDirectoryControlHelper;
841 p_access->info.b_dir_sorted = true;
842 p_access->info.b_dir_can_loop = true;
844 return VLC_SUCCESS;
847 static void Close( vlc_object_t* p_this )
849 access_t* p_access = (access_t*)p_this;
850 p_access->p_sys->p_upnp->release( false );
851 delete p_access->p_sys->p_server;
852 delete p_access->p_sys;
857 UpnpInstanceWrapper::UpnpInstanceWrapper()
858 : handle_( -1 )
859 , opaque_( NULL )
860 , callback_( NULL )
861 , refcount_( 0 )
865 UpnpInstanceWrapper::~UpnpInstanceWrapper()
867 UpnpUnRegisterClient( handle_ );
868 UpnpFinish();
871 UpnpInstanceWrapper *UpnpInstanceWrapper::get(vlc_object_t *p_obj, Upnp_FunPtr callback, SD::MediaServerList *opaque)
873 vlc_mutex_locker lock( &s_lock );
874 if ( s_instance == NULL )
876 UpnpInstanceWrapper* instance = new(std::nothrow) UpnpInstanceWrapper;
877 if ( unlikely( !instance ) )
878 return NULL;
880 #ifdef UPNP_ENABLE_IPV6
881 char* psz_miface = var_InheritString( p_obj, "miface" );
882 msg_Info( p_obj, "Initializing libupnp on '%s' interface", psz_miface );
883 int i_res = UpnpInit2( psz_miface, 0 );
884 free( psz_miface );
885 #else
886 /* If UpnpInit2 isnt available, initialize on first IPv4-capable interface */
887 int i_res = UpnpInit( 0, 0 );
888 #endif
889 if( i_res != UPNP_E_SUCCESS )
891 msg_Err( p_obj, "Initialization failed: %s", UpnpGetErrorMessage( i_res ) );
892 delete instance;
893 return NULL;
896 ixmlRelaxParser( 1 );
898 /* Register a control point */
899 i_res = UpnpRegisterClient( Callback, instance, &instance->handle_ );
900 if( i_res != UPNP_E_SUCCESS )
902 msg_Err( p_obj, "Client registration failed: %s", UpnpGetErrorMessage( i_res ) );
903 delete instance;
904 return NULL;
907 /* libupnp does not treat a maximum content length of 0 as unlimited
908 * until 64dedf (~ pupnp v1.6.7) and provides no sane way to discriminate
909 * between versions */
910 if( (i_res = UpnpSetMaxContentLength( INT_MAX )) != UPNP_E_SUCCESS )
912 msg_Err( p_obj, "Failed to set maximum content length: %s",
913 UpnpGetErrorMessage( i_res ));
914 delete instance;
915 return NULL;
917 s_instance = instance;
919 s_instance->refcount_++;
920 // This assumes a single UPNP SD instance
921 if (callback && opaque)
923 assert(!s_instance->callback_ && !s_instance->opaque_);
924 s_instance->opaque_ = opaque;
925 s_instance->callback_ = callback;
927 return s_instance;
930 void UpnpInstanceWrapper::release(bool isSd)
932 vlc_mutex_locker lock( &s_lock );
933 if ( isSd )
935 callback_ = NULL;
936 opaque_ = NULL;
938 if (--s_instance->refcount_ == 0)
940 delete s_instance;
941 s_instance = NULL;
945 UpnpClient_Handle UpnpInstanceWrapper::handle() const
947 return handle_;
950 int UpnpInstanceWrapper::Callback(Upnp_EventType event_type, void *p_event, void *p_user_data)
952 UpnpInstanceWrapper* self = static_cast<UpnpInstanceWrapper*>( p_user_data );
953 vlc_mutex_locker lock( &self->s_lock );
954 if ( !self->callback_ )
955 return 0;
956 self->callback_( event_type, p_event, self->opaque_ );
957 return 0;