upnp: include config.h from source files
[vlc.git] / modules / services_discovery / upnp.cpp
blob6708ca75f6e91912d837dc7829389b5b93e46014
1 /*****************************************************************************
2 * upnp.cpp : UPnP discovery module (libupnp)
3 *****************************************************************************
4 * Copyright (C) 2004-2018 VLC authors and VideoLAN
6 * Authors: Rémi Denis-Courmont <rem # videolan.org> (original plugin)
7 * Christian Henz <henz # c-lab.de>
8 * Mirsal Ennaime <mirsal dot ennaime at gmail dot com>
9 * Hugo Beauzée-Luyssen <hugo@beauzee.fr>
10 * Shaleen Jain <shaleen@jain.sh>
12 * This program is free software; you can redistribute it and/or modify it
13 * under the terms of the GNU Lesser General Public License as published by
14 * the Free Software Foundation; either version 2.1 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU Lesser General Public License for more details.
22 * You should have received a copy of the GNU Lesser General Public License
23 * along with this program; if not, write to the Free Software Foundation,
24 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25 *****************************************************************************/
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
31 #include <vlc_common.h>
33 #include "upnp.hpp"
35 #include <vlc_access.h>
36 #include <vlc_plugin.h>
37 #include <vlc_interrupt.h>
38 #include <vlc_services_discovery.h>
40 #include <assert.h>
41 #include <limits.h>
42 #include <algorithm>
43 #include <set>
44 #include <string>
47 * Constants
49 const char* MEDIA_SERVER_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaServer:1";
50 const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:ContentDirectory:1";
51 const char* SATIP_SERVER_DEVICE_TYPE = "urn:ses-com:device:SatIPServer:1";
53 #define SATIP_CHANNEL_LIST N_("SAT>IP channel list")
54 #define SATIP_CHANNEL_LIST_URL N_("Custom SAT>IP channel list URL")
55 static const char *const ppsz_satip_channel_lists[] = {
56 "ASTRA_19_2E", "ASTRA_28_2E", "ASTRA_23_5E", "MasterList", "ServerList", "CustomList"
58 static const char *const ppsz_readible_satip_channel_lists[] = {
59 "Astra 19.2°E", "Astra 28.2°E", "Astra 23.5°E", N_("Master List"), N_("Server List"), N_("Custom List")
63 * VLC handle
65 struct services_discovery_sys_t
67 UpnpInstanceWrapper* p_upnp;
68 std::shared_ptr<SD::MediaServerList> p_server_list;
69 vlc_thread_t thread;
72 struct access_sys_t
74 UpnpInstanceWrapper* p_upnp;
78 * VLC callback prototypes
80 namespace SD
82 static int OpenSD( vlc_object_t* );
83 static void CloseSD( vlc_object_t* );
86 namespace Access
88 static int OpenAccess( vlc_object_t* );
89 static void CloseAccess( vlc_object_t* );
92 VLC_SD_PROBE_HELPER( "upnp", N_("Universal Plug'n'Play"), SD_CAT_LAN )
95 * Module descriptor
97 vlc_module_begin()
98 set_shortname( "UPnP" );
99 set_description( N_( "Universal Plug'n'Play" ) );
100 set_category( CAT_PLAYLIST );
101 set_subcategory( SUBCAT_PLAYLIST_SD );
102 set_capability( "services_discovery", 0 );
103 set_callbacks( SD::OpenSD, SD::CloseSD );
105 add_string( "satip-channelist", "ASTRA_19_2E", SATIP_CHANNEL_LIST,
106 SATIP_CHANNEL_LIST, false )
107 change_string_list( ppsz_satip_channel_lists, ppsz_readible_satip_channel_lists )
108 add_string( "satip-channellist-url", NULL, SATIP_CHANNEL_LIST_URL,
109 SATIP_CHANNEL_LIST_URL, false )
111 add_submodule()
112 set_category( CAT_INPUT )
113 set_subcategory( SUBCAT_INPUT_ACCESS )
114 set_callbacks( Access::OpenAccess, Access::CloseAccess )
115 set_capability( "access", 0 )
117 VLC_SD_PROBE_SUBMODULE
118 vlc_module_end()
121 * Extracts the result document from a SOAP response
123 IXML_Document* parseBrowseResult( IXML_Document* p_doc )
125 assert( p_doc );
127 // ixml*_getElementsByTagName will ultimately only case the pointer to a Node
128 // pointer, and pass it to a private function. Don't bother have a IXML_Document
129 // version of getChildElementValue
130 const char* psz_raw_didl = xml_getChildElementValue( (IXML_Element*)p_doc, "Result" );
132 if( !psz_raw_didl )
133 return NULL;
135 /* First, try parsing the buffer as is */
136 IXML_Document* p_result_doc = ixmlParseBuffer( psz_raw_didl );
137 if( !p_result_doc ) {
138 /* Missing namespaces confuse the ixml parser. This is a very ugly
139 * hack but it is needeed until devices start sending valid XML.
141 * It works that way:
143 * The DIDL document is extracted from the Result tag, then wrapped into
144 * a valid XML header and a new root tag which contains missing namespace
145 * definitions so the ixml parser understands it.
147 * If you know of a better workaround, please oh please fix it */
148 const char* psz_xml_result_fmt = "<?xml version=\"1.0\" ?>"
149 "<Result xmlns:sec=\"urn:samsung:metadata:2009\">%s</Result>";
151 char* psz_xml_result_string = NULL;
152 if( -1 == asprintf( &psz_xml_result_string,
153 psz_xml_result_fmt,
154 psz_raw_didl) )
155 return NULL;
157 p_result_doc = ixmlParseBuffer( psz_xml_result_string );
158 free( psz_xml_result_string );
161 if( !p_result_doc )
162 return NULL;
164 IXML_NodeList *p_elems = ixmlDocument_getElementsByTagName( p_result_doc,
165 "DIDL-Lite" );
167 IXML_Node *p_node = ixmlNodeList_item( p_elems, 0 );
168 ixmlNodeList_free( p_elems );
170 return (IXML_Document*)p_node;
173 namespace SD
176 static void *
177 SearchThread( void *p_data )
179 services_discovery_t *p_sd = ( services_discovery_t* )p_data;
180 services_discovery_sys_t *p_sys = reinterpret_cast<services_discovery_sys_t *>( p_sd->p_sys );
182 /* Search for media servers */
183 int i_res = UpnpSearchAsync( p_sys->p_upnp->handle(), 5,
184 MEDIA_SERVER_DEVICE_TYPE, MEDIA_SERVER_DEVICE_TYPE );
185 if( i_res != UPNP_E_SUCCESS )
187 msg_Err( p_sd, "Error sending search request: %s", UpnpGetErrorMessage( i_res ) );
188 return NULL;
191 /* Search for Sat Ip servers*/
192 i_res = UpnpSearchAsync( p_sys->p_upnp->handle(), 5,
193 SATIP_SERVER_DEVICE_TYPE, MEDIA_SERVER_DEVICE_TYPE );
194 if( i_res != UPNP_E_SUCCESS )
195 msg_Err( p_sd, "Error sending search request: %s", UpnpGetErrorMessage( i_res ) );
196 return NULL;
200 * Initializes UPNP instance.
202 static int OpenSD( vlc_object_t *p_this )
204 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
205 services_discovery_sys_t *p_sys = ( services_discovery_sys_t * )
206 calloc( 1, sizeof( services_discovery_sys_t ) );
208 if( !( p_sd->p_sys = p_sys ) )
209 return VLC_ENOMEM;
211 p_sd->description = _("Universal Plug'n'Play");
213 p_sys->p_upnp = UpnpInstanceWrapper::get( p_this );
214 if ( !p_sys->p_upnp )
216 free(p_sys);
217 return VLC_EGENERIC;
222 p_sys->p_server_list = std::make_shared<SD::MediaServerList>( p_sd );
224 catch ( const std::bad_alloc& )
226 msg_Err( p_sd, "Failed to create a MediaServerList");
227 p_sys->p_upnp->release();
228 free(p_sys);
229 return VLC_EGENERIC;
231 p_sys->p_upnp->addListener( p_sys->p_server_list );
233 /* XXX: Contrary to what the libupnp doc states, UpnpSearchAsync is
234 * blocking (select() and send() are called). Therefore, Call
235 * UpnpSearchAsync from an other thread. */
236 if ( vlc_clone( &p_sys->thread, SearchThread, p_this,
237 VLC_THREAD_PRIORITY_LOW ) )
239 p_sys->p_upnp->removeListener( p_sys->p_server_list );
240 p_sys->p_upnp->release();
241 free(p_sys);
242 return VLC_EGENERIC;
245 return VLC_SUCCESS;
249 * Releases resources.
251 static void CloseSD( vlc_object_t *p_this )
253 services_discovery_t *p_sd = ( services_discovery_t* )p_this;
254 services_discovery_sys_t *p_sys = reinterpret_cast<services_discovery_sys_t *>( p_sd->p_sys );
256 vlc_join( p_sys->thread, NULL );
257 p_sys->p_upnp->removeListener( p_sys->p_server_list );
258 p_sys->p_upnp->release();
259 free( p_sys );
262 MediaServerDesc::MediaServerDesc( const std::string& udn, const std::string& fName,
263 const std::string& loc, const std::string& iconUrl )
264 : UDN( udn )
265 , friendlyName( fName )
266 , location( loc )
267 , iconUrl( iconUrl )
268 , inputItem( NULL )
269 , isSatIp( false )
273 MediaServerDesc::~MediaServerDesc()
275 if (inputItem)
276 input_item_Release( inputItem );
280 * MediaServerList class
282 MediaServerList::MediaServerList( services_discovery_t* p_sd )
283 : m_sd( p_sd )
287 MediaServerList::~MediaServerList()
289 vlc_delete_all(m_list);
292 bool MediaServerList::addServer( MediaServerDesc* desc )
294 input_item_t* p_input_item = NULL;
295 if ( getServer( desc->UDN ) )
296 return false;
298 msg_Dbg( m_sd, "Adding server '%s' with uuid '%s'", desc->friendlyName.c_str(), desc->UDN.c_str() );
300 if ( desc->isSatIp )
302 p_input_item = input_item_NewDirectory( desc->location.c_str(),
303 desc->friendlyName.c_str(),
304 ITEM_NET );
305 if ( !p_input_item )
306 return false;
308 input_item_SetSetting( p_input_item, SATIP_SERVER_DEVICE_TYPE );
310 char *psz_playlist_option;
312 if (asprintf( &psz_playlist_option, "satip-host=%s",
313 desc->satIpHost.c_str() ) >= 0 ) {
314 input_item_AddOption( p_input_item, psz_playlist_option, 0 );
315 free( psz_playlist_option );
317 } else {
318 char* psz_mrl;
319 // We might already have some options specified in the location.
320 char opt_delim = desc->location.find( '?' ) == 0 ? '?' : '&';
321 if( asprintf( &psz_mrl, "upnp://%s%cObjectID=0", desc->location.c_str(), opt_delim ) < 0 )
322 return false;
324 p_input_item = input_item_NewDirectory( psz_mrl,
325 desc->friendlyName.c_str(),
326 ITEM_NET );
327 free( psz_mrl );
329 if ( !p_input_item )
330 return false;
332 input_item_SetSetting( p_input_item, MEDIA_SERVER_DEVICE_TYPE );
335 if ( desc->iconUrl.empty() == false )
336 input_item_SetArtworkURL( p_input_item, desc->iconUrl.c_str() );
337 desc->inputItem = p_input_item;
338 input_item_SetDescription( p_input_item, desc->UDN.c_str() );
339 services_discovery_AddItem( m_sd, p_input_item );
340 m_list.push_back( desc );
342 return true;
345 MediaServerDesc* MediaServerList::getServer( const std::string& udn )
347 std::vector<MediaServerDesc*>::const_iterator it = m_list.begin();
348 std::vector<MediaServerDesc*>::const_iterator ite = m_list.end();
350 for ( ; it != ite; ++it )
352 if( udn == (*it)->UDN )
354 return *it;
357 return NULL;
360 void MediaServerList::parseNewServer( IXML_Document *doc, const std::string &location )
362 if ( !doc )
364 msg_Err( m_sd, "Null IXML_Document" );
365 return;
368 if ( location.empty() )
370 msg_Err( m_sd, "Empty location" );
371 return;
374 const char* psz_base_url = location.c_str();
376 /* Try to extract baseURL */
377 IXML_NodeList* p_url_list = ixmlDocument_getElementsByTagName( doc, "URLBase" );
378 if ( p_url_list )
380 if ( IXML_Node* p_url_node = ixmlNodeList_item( p_url_list, 0 ) )
382 IXML_Node* p_text_node = ixmlNode_getFirstChild( p_url_node );
383 if ( p_text_node )
384 psz_base_url = ixmlNode_getNodeValue( p_text_node );
386 ixmlNodeList_free( p_url_list );
389 /* Get devices */
390 IXML_NodeList* p_device_list = ixmlDocument_getElementsByTagName( doc, "device" );
392 if ( !p_device_list )
393 return;
395 for ( unsigned int i = 0; i < ixmlNodeList_length( p_device_list ); i++ )
397 IXML_Element* p_device_element = ( IXML_Element* ) ixmlNodeList_item( p_device_list, i );
399 if( !p_device_element )
400 continue;
402 const char* psz_device_type = xml_getChildElementValue( p_device_element, "deviceType" );
404 if ( !psz_device_type )
406 msg_Warn( m_sd, "No deviceType found!" );
407 continue;
410 if ( strncmp( MEDIA_SERVER_DEVICE_TYPE, psz_device_type,
411 strlen( MEDIA_SERVER_DEVICE_TYPE ) - 1 )
412 && strncmp( SATIP_SERVER_DEVICE_TYPE, psz_device_type,
413 strlen( SATIP_SERVER_DEVICE_TYPE ) - 1 ) )
414 continue;
416 const char* psz_udn = xml_getChildElementValue( p_device_element,
417 "UDN" );
418 if ( !psz_udn )
420 msg_Warn( m_sd, "No UDN!" );
421 continue;
424 /* Check if server is already added */
425 if ( getServer( psz_udn ) )
427 msg_Warn( m_sd, "Server with uuid '%s' already exists.", psz_udn );
428 continue;
431 const char* psz_friendly_name =
432 xml_getChildElementValue( p_device_element,
433 "friendlyName" );
435 if ( !psz_friendly_name )
437 msg_Dbg( m_sd, "No friendlyName!" );
438 continue;
441 std::string iconUrl = getIconURL( p_device_element, psz_base_url );
443 // We now have basic info, we need to get the content browsing url
444 // so the access module can browse without fetching the manifest again
446 if ( !strncmp( SATIP_SERVER_DEVICE_TYPE, psz_device_type,
447 strlen( SATIP_SERVER_DEVICE_TYPE ) - 1 ) )
449 SD::MediaServerDesc* p_server = NULL;
451 vlc_url_t url;
452 vlc_UrlParse( &url, psz_base_url );
454 char *psz_satip_channellist = config_GetPsz("satip-channelist");
455 if( !psz_satip_channellist ) {
456 break;
459 /* a user may have provided a custom playlist url */
460 if (strncmp(psz_satip_channellist, "CustomList", 10) == 0) {
461 char *psz_satip_playlist_url = config_GetPsz( "satip-channellist-url" );
462 if ( psz_satip_playlist_url ) {
463 p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn, psz_friendly_name, psz_satip_playlist_url, iconUrl );
465 if( likely( p_server ) ) {
466 p_server->satIpHost = url.psz_host;
467 p_server->isSatIp = true;
468 if( !addServer( p_server ) ) {
469 delete p_server;
473 /* to comply with the SAT>IP specification, we don't fall back on another channel list if this path failed */
474 free( psz_satip_playlist_url );
475 vlc_UrlClean( &url );
476 continue;
480 /* If requested by the user, check for a SAT>IP m3u list, which may be provided by some rare devices */
481 if (strncmp(psz_satip_channellist, "ServerList", 10) == 0) {
482 const char* psz_m3u_url = xml_getChildElementValue( p_device_element, "satip:X_SATIPM3U" );
483 if ( psz_m3u_url ) {
484 if ( strncmp( "http", psz_m3u_url, 4) )
486 char* psz_url = NULL;
487 if ( UpnpResolveURL2( psz_base_url, psz_m3u_url, &psz_url ) == UPNP_E_SUCCESS )
489 p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn, psz_friendly_name, psz_url, iconUrl );
490 free(psz_url);
492 } else {
493 p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn, psz_friendly_name, psz_m3u_url, iconUrl );
496 if ( unlikely( !p_server ) )
498 free( psz_satip_channellist );
499 break;
502 p_server->satIpHost = url.psz_host;
503 p_server->isSatIp = true;
504 if ( !addServer( p_server ) )
505 delete p_server;
506 } else {
507 msg_Warn( m_sd, "SAT>IP server '%s' did not provide a playlist", url.psz_host);
510 /* to comply with the SAT>IP specifications, we don't fallback on another channel list if this path failed */
511 free(psz_satip_channellist);
512 vlc_UrlClean( &url );
513 continue;
516 /* Normally, fetch a playlist from the web,
517 * which will be processed by a lua script a bit later */
518 char *psz_url;
519 if (asprintf( &psz_url, "http://www.satip.info/Playlists/%s.m3u",
520 psz_satip_channellist ) < 0 ) {
521 vlc_UrlClean( &url );
522 free( psz_satip_channellist );
523 continue;
526 p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn,
527 psz_friendly_name, psz_url, iconUrl );
529 if( likely( p_server ) ) {
530 p_server->satIpHost = url.psz_host;
531 p_server->isSatIp = true;
532 if( !addServer( p_server ) ) {
533 delete p_server;
536 free( psz_url );
537 free( psz_satip_channellist );
538 vlc_UrlClean( &url );
540 continue;
543 /* Check for ContentDirectory service. */
544 IXML_NodeList* p_service_list = ixmlElement_getElementsByTagName( p_device_element, "service" );
545 if ( !p_service_list )
546 continue;
547 for ( unsigned int j = 0; j < ixmlNodeList_length( p_service_list ); j++ )
549 IXML_Element* p_service_element = (IXML_Element*)ixmlNodeList_item( p_service_list, j );
551 const char* psz_service_type = xml_getChildElementValue( p_service_element, "serviceType" );
552 if ( !psz_service_type )
554 msg_Warn( m_sd, "No service type found." );
555 continue;
558 int k = strlen( CONTENT_DIRECTORY_SERVICE_TYPE ) - 1;
559 if ( strncmp( CONTENT_DIRECTORY_SERVICE_TYPE,
560 psz_service_type, k ) )
561 continue;
563 const char* psz_control_url = xml_getChildElementValue( p_service_element,
564 "controlURL" );
565 if ( !psz_control_url )
567 msg_Warn( m_sd, "No control url found." );
568 continue;
571 /* Try to browse content directory. */
572 char* psz_url = ( char* ) malloc( strlen( psz_base_url ) + strlen( psz_control_url ) + 1 );
573 if ( psz_url )
575 if ( UpnpResolveURL( psz_base_url, psz_control_url, psz_url ) == UPNP_E_SUCCESS )
577 SD::MediaServerDesc* p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn,
578 psz_friendly_name, psz_url, iconUrl );
579 free( psz_url );
580 if ( unlikely( !p_server ) )
581 break;
583 if ( !addServer( p_server ) )
585 delete p_server;
586 continue;
589 else
590 free( psz_url );
593 ixmlNodeList_free( p_service_list );
595 ixmlNodeList_free( p_device_list );
598 std::string MediaServerList::getIconURL( IXML_Element* p_device_elem, const char* psz_base_url )
600 std::string res;
601 IXML_NodeList* p_icon_lists = ixmlElement_getElementsByTagName( p_device_elem, "iconList" );
602 if ( p_icon_lists == NULL )
603 return res;
604 IXML_Element* p_icon_list = (IXML_Element*)ixmlNodeList_item( p_icon_lists, 0 );
605 if ( p_icon_list != NULL )
607 IXML_NodeList* p_icons = ixmlElement_getElementsByTagName( p_icon_list, "icon" );
608 if ( p_icons != NULL )
610 unsigned int maxWidth = 0;
611 unsigned int maxHeight = 0;
612 for ( unsigned int i = 0; i < ixmlNodeList_length( p_icons ); ++i )
614 IXML_Element* p_icon = (IXML_Element*)ixmlNodeList_item( p_icons, i );
615 const char* widthStr = xml_getChildElementValue( p_icon, "width" );
616 const char* heightStr = xml_getChildElementValue( p_icon, "height" );
617 if ( widthStr == NULL || heightStr == NULL )
618 continue;
619 unsigned int width = atoi( widthStr );
620 unsigned int height = atoi( heightStr );
621 if ( width <= maxWidth || height <= maxHeight )
622 continue;
623 const char* iconUrl = xml_getChildElementValue( p_icon, "url" );
624 if ( iconUrl == NULL )
625 continue;
626 maxWidth = width;
627 maxHeight = height;
628 res = iconUrl;
630 ixmlNodeList_free( p_icons );
633 ixmlNodeList_free( p_icon_lists );
635 if ( res.empty() == false )
637 vlc_url_t url;
638 vlc_UrlParse( &url, psz_base_url );
639 char* psz_url;
640 if ( asprintf( &psz_url, "%s://%s:%u%s", url.psz_protocol, url.psz_host, url.i_port, res.c_str() ) < 0 )
641 res.clear();
642 else
644 res = psz_url;
645 free( psz_url );
647 vlc_UrlClean( &url );
649 return res;
652 void MediaServerList::removeServer( const std::string& udn )
654 MediaServerDesc* p_server = getServer( udn );
655 if ( !p_server )
656 return;
658 msg_Dbg( m_sd, "Removing server '%s'", p_server->friendlyName.c_str() );
660 assert(p_server->inputItem);
661 services_discovery_RemoveItem( m_sd, p_server->inputItem );
663 std::vector<MediaServerDesc*>::iterator it = std::find(m_list.begin(), m_list.end(), p_server);
664 if (it != m_list.end())
666 m_list.erase( it );
668 delete p_server;
672 * Handles servers listing UPnP events
674 int MediaServerList::onEvent( Upnp_EventType event_type, UpnpEventPtr p_event, void* p_user_data )
676 if (p_user_data != MEDIA_SERVER_DEVICE_TYPE)
677 return 0;
679 switch( event_type )
681 case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
682 case UPNP_DISCOVERY_SEARCH_RESULT:
684 const UpnpDiscovery* p_discovery = ( const UpnpDiscovery* )p_event;
686 IXML_Document *p_description_doc = NULL;
688 int i_res;
689 i_res = UpnpDownloadXmlDoc( UpnpDiscovery_get_Location_cstr( p_discovery ), &p_description_doc );
691 if ( i_res != UPNP_E_SUCCESS )
693 msg_Warn( m_sd, "Could not download device description! "
694 "Fetching data from %s failed: %s",
695 UpnpDiscovery_get_Location_cstr( p_discovery ), UpnpGetErrorMessage( i_res ) );
696 return i_res;
698 parseNewServer( p_description_doc, UpnpDiscovery_get_Location_cstr( p_discovery ) );
699 ixmlDocument_free( p_description_doc );
701 break;
703 case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
705 const UpnpDiscovery* p_discovery = ( const UpnpDiscovery* )p_event;
706 removeServer( UpnpDiscovery_get_DeviceID_cstr( p_discovery ) );
708 break;
710 case UPNP_EVENT_SUBSCRIBE_COMPLETE:
712 msg_Warn( m_sd, "subscription complete" );
714 break;
716 case UPNP_DISCOVERY_SEARCH_TIMEOUT:
718 msg_Warn( m_sd, "search timeout" );
720 break;
722 case UPNP_EVENT_RECEIVED:
723 case UPNP_EVENT_AUTORENEWAL_FAILED:
724 case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
725 // Those are for the access part
726 break;
728 default:
730 msg_Err( m_sd, "Unhandled event, please report ( type=%d )", event_type );
732 break;
735 return UPNP_E_SUCCESS;
740 namespace Access
743 namespace
745 class ItemDescriptionHolder
747 private:
748 struct Slave : std::string
750 slave_type type;
752 Slave(std::string const &url, slave_type type) :
753 std::string(url), type(type)
758 std::set<Slave> slaves;
760 const char* objectID,
761 * title,
762 * psz_artist,
763 * psz_genre,
764 * psz_album,
765 * psz_date,
766 * psz_orig_track_nb,
767 * psz_album_artist,
768 * psz_albumArt;
770 public:
771 enum MEDIA_TYPE
773 VIDEO = 0,
774 AUDIO,
775 IMAGE,
776 CONTAINER
779 MEDIA_TYPE media_type;
781 ItemDescriptionHolder()
785 bool init(IXML_Element *itemElement)
787 objectID = ixmlElement_getAttribute( itemElement, "id" );
788 if ( !objectID )
789 return false;
790 title = xml_getChildElementValue( itemElement, "dc:title" );
791 if ( !title )
792 return false;
793 const char *psz_subtitles = xml_getChildElementValue( itemElement, "sec:CaptionInfo" );
794 if ( !psz_subtitles &&
795 !(psz_subtitles = xml_getChildElementValue( itemElement, "sec:CaptionInfoEx" )) )
796 psz_subtitles = xml_getChildElementValue( itemElement, "pv:subtitlefile" );
797 addSlave(psz_subtitles, SLAVE_TYPE_SPU);
798 psz_artist = xml_getChildElementValue( itemElement, "upnp:artist" );
799 psz_genre = xml_getChildElementValue( itemElement, "upnp:genre" );
800 psz_album = xml_getChildElementValue( itemElement, "upnp:album" );
801 psz_date = xml_getChildElementValue( itemElement, "dc:date" );
802 psz_orig_track_nb = xml_getChildElementValue( itemElement, "upnp:originalTrackNumber" );
803 psz_album_artist = xml_getChildElementValue( itemElement, "upnp:albumArtist" );
804 psz_albumArt = xml_getChildElementValue( itemElement, "upnp:albumArtURI" );
805 const char *psz_media_type = xml_getChildElementValue( itemElement, "upnp:class" );
806 if (strncmp(psz_media_type, "object.item.videoItem", 21) == 0)
807 media_type = VIDEO;
808 else if (strncmp(psz_media_type, "object.item.audioItem", 21) == 0)
809 media_type = AUDIO;
810 else if (strncmp(psz_media_type, "object.item.imageItem", 21) == 0)
811 media_type = IMAGE;
812 else if (strncmp(psz_media_type, "object.container", 16 ) == 0)
813 media_type = CONTAINER;
814 else
815 return false;
816 return true;
819 void addSlave(const char *psz_slave, slave_type type)
821 if (psz_slave)
822 slaves.insert(Slave(psz_slave, type));
825 void addSubtitleSlave(IXML_Element* p_resource)
827 if (slaves.empty())
828 addSlave(ixmlElement_getAttribute( p_resource, "pv:subtitleFileUri" ),
829 SLAVE_TYPE_SPU);
832 void setArtworkURL(IXML_Element* p_resource)
834 psz_albumArt = xml_getChildElementValue( p_resource, "res" );
837 void apply(input_item_t *p_item)
839 if ( psz_artist != NULL )
840 input_item_SetArtist( p_item, psz_artist );
841 if ( psz_genre != NULL )
842 input_item_SetGenre( p_item, psz_genre );
843 if ( psz_album != NULL )
844 input_item_SetAlbum( p_item, psz_album );
845 if ( psz_date != NULL )
846 input_item_SetDate( p_item, psz_date );
847 if ( psz_orig_track_nb != NULL )
848 input_item_SetTrackNumber( p_item, psz_orig_track_nb );
849 if ( psz_album_artist != NULL )
850 input_item_SetAlbumArtist( p_item, psz_album_artist );
851 if ( psz_albumArt != NULL )
852 input_item_SetArtworkURL( p_item, psz_albumArt );
853 for (std::set<Slave>::iterator it = slaves.begin(); it != slaves.end(); ++it)
855 input_item_slave *p_slave = input_item_slave_New( it->c_str(), it->type,
856 SLAVE_PRIORITY_MATCH_ALL );
857 if ( p_slave )
858 input_item_AddSlave( p_item, p_slave );
862 input_item_t *createNewItem(IXML_Element *p_resource)
864 vlc_tick_t i_duration = -1;
865 const char* psz_resource_url = xml_getChildElementValue( p_resource, "res" );
866 if( !psz_resource_url )
867 return NULL;
868 const char* psz_duration = ixmlElement_getAttribute( p_resource, "duration" );
869 if ( psz_duration )
871 int i_hours, i_minutes, i_seconds;
872 if( sscanf( psz_duration, "%d:%02d:%02d", &i_hours, &i_minutes, &i_seconds ) )
873 i_duration = CLOCK_FREQ * ( i_hours * 3600 + i_minutes * 60 +
874 i_seconds );
876 return input_item_NewExt( psz_resource_url, title, i_duration,
877 ITEM_TYPE_FILE, ITEM_NET );
880 input_item_t *createNewContainerItem( const char* psz_root )
882 if ( objectID == NULL || title == NULL )
883 return NULL;
885 char* psz_url;
886 if( asprintf( &psz_url, "upnp://%s?ObjectID=%s", psz_root, objectID ) < 0 )
887 return NULL;
889 input_item_t* p_item = input_item_NewDirectory( psz_url, title, ITEM_NET );
890 free( psz_url);
891 return p_item;
896 Upnp_i11e_cb::Upnp_i11e_cb( Upnp_FunPtr callback, void *cookie )
897 : m_refCount( 2 ) /* 2: owned by the caller, and the Upnp Async function */
898 , m_callback( callback )
899 , m_cookie( cookie )
902 vlc_mutex_init( &m_lock );
903 vlc_sem_init( &m_sem, 0 );
906 Upnp_i11e_cb::~Upnp_i11e_cb()
908 vlc_mutex_destroy( &m_lock );
909 vlc_sem_destroy( &m_sem );
912 void Upnp_i11e_cb::waitAndRelease( void )
914 vlc_sem_wait_i11e( &m_sem );
916 vlc_mutex_lock( &m_lock );
917 if ( --m_refCount == 0 )
919 /* The run callback is processed, we can destroy this object */
920 vlc_mutex_unlock( &m_lock );
921 delete this;
922 } else
924 /* Interrupted, let the run callback destroy this object */
925 vlc_mutex_unlock( &m_lock );
929 int Upnp_i11e_cb::run( Upnp_EventType eventType, UpnpEventPtr p_event, void *p_cookie )
931 Upnp_i11e_cb *self = static_cast<Upnp_i11e_cb*>( p_cookie );
933 vlc_mutex_lock( &self->m_lock );
934 if ( --self->m_refCount == 0 )
936 /* Interrupted, we can destroy self */
937 vlc_mutex_unlock( &self->m_lock );
938 delete self;
939 return 0;
941 /* Process the user callback_ */
942 self->m_callback( eventType, p_event, self->m_cookie);
943 vlc_mutex_unlock( &self->m_lock );
945 /* Signal that the callback is processed */
946 vlc_sem_post( &self->m_sem );
947 return 0;
950 MediaServer::MediaServer( stream_t *p_access, input_item_node_t *node )
951 : m_psz_objectId( NULL )
952 , m_access( p_access )
953 , m_node( node )
956 m_psz_root = strdup( p_access->psz_location );
957 char* psz_objectid = strstr( m_psz_root, "ObjectID=" );
958 if ( psz_objectid != NULL )
960 // Remove this parameter from the URL, since it might cause some servers to fail
961 // Keep in mind that we added a '&' or a '?' to the URL, so remove it as well
962 *( psz_objectid - 1) = 0;
963 m_psz_objectId = &psz_objectid[strlen( "ObjectID=" )];
967 MediaServer::~MediaServer()
969 free( m_psz_root );
972 bool MediaServer::addContainer( IXML_Element* containerElement )
974 ItemDescriptionHolder holder;
976 if ( holder.init( containerElement ) == false )
977 return false;
979 input_item_t* p_item = holder.createNewContainerItem( m_psz_root );
980 if ( !p_item )
981 return false;
982 holder.apply( p_item );
983 input_item_CopyOptions( p_item, m_node->p_item );
984 input_item_node_AppendItem( m_node, p_item );
985 input_item_Release( p_item );
986 return true;
989 bool MediaServer::addItem( IXML_Element* itemElement )
991 ItemDescriptionHolder holder;
993 if (!holder.init(itemElement))
994 return false;
995 /* Try to extract all resources in DIDL */
996 IXML_NodeList* p_resource_list = ixmlDocument_getElementsByTagName( (IXML_Document*) itemElement, "res" );
997 if ( !p_resource_list)
998 return false;
999 int list_lenght = ixmlNodeList_length( p_resource_list );
1000 if (list_lenght <= 0 ) {
1001 ixmlNodeList_free( p_resource_list );
1002 return false;
1004 input_item_t *p_item = NULL;
1006 for (int index = 0; index < list_lenght; index++)
1008 IXML_Element* p_resource = ( IXML_Element* ) ixmlNodeList_item( p_resource_list, index );
1009 const char* rez_type = ixmlElement_getAttribute( p_resource, "protocolInfo" );
1011 if (strncmp(rez_type, "http-get:*:video/", 17) == 0 && holder.media_type == ItemDescriptionHolder::VIDEO)
1013 if (!p_item)
1014 p_item = holder.createNewItem(p_resource);
1015 holder.addSubtitleSlave(p_resource);
1017 else if (strncmp(rez_type, "http-get:*:image/", 17) == 0)
1018 switch (holder.media_type)
1020 case ItemDescriptionHolder::IMAGE:
1021 if (!p_item) {
1022 p_item = holder.createNewItem(p_resource);
1023 break;
1025 case ItemDescriptionHolder::VIDEO:
1026 case ItemDescriptionHolder::AUDIO:
1027 holder.setArtworkURL(p_resource);
1028 break;
1029 case ItemDescriptionHolder::CONTAINER:
1030 msg_Warn( m_access, "Unexpected object.container in item enumeration" );
1031 continue;
1033 else if (strncmp(rez_type, "http-get:*:text/", 16) == 0)
1034 holder.addSlave(xml_getChildElementValue( p_resource, "res" ), SLAVE_TYPE_SPU);
1035 else if (strncmp(rez_type, "http-get:*:audio/", 17) == 0)
1037 if (holder.media_type == ItemDescriptionHolder::AUDIO)
1039 if (!p_item)
1040 p_item = holder.createNewItem(p_resource);
1042 else
1043 holder.addSlave(xml_getChildElementValue( p_resource, "res" ),
1044 SLAVE_TYPE_AUDIO);
1047 ixmlNodeList_free( p_resource_list );
1048 if (!p_item)
1049 return false;
1050 holder.apply(p_item);
1051 input_item_CopyOptions( p_item, m_node->p_item );
1052 input_item_node_AppendItem( m_node, p_item );
1053 input_item_Release( p_item );
1054 return true;
1057 int MediaServer::sendActionCb( Upnp_EventType eventType,
1058 UpnpEventPtr p_event, void *p_cookie )
1060 if( eventType != UPNP_CONTROL_ACTION_COMPLETE )
1061 return 0;
1062 IXML_Document** pp_sendActionResult = (IXML_Document** )p_cookie;
1063 const UpnpActionComplete *p_result = (const UpnpActionComplete *)p_event;
1065 /* The only way to dup the result is to print it and parse it again */
1066 DOMString tmpStr = ixmlPrintNode( ( IXML_Node * ) UpnpActionComplete_get_ActionResult( p_result ) );
1067 if (tmpStr == NULL)
1068 return 0;
1070 *pp_sendActionResult = ixmlParseBuffer( tmpStr );
1071 ixmlFreeDOMString( tmpStr );
1072 return 0;
1075 /* Access part */
1076 IXML_Document* MediaServer::_browseAction( const char* psz_object_id_,
1077 const char* psz_browser_flag_,
1078 const char* psz_filter_,
1079 const char* psz_requested_count_,
1080 const char* psz_sort_criteria_ )
1082 IXML_Document* p_action = NULL;
1083 IXML_Document* p_response = NULL;
1084 Upnp_i11e_cb *i11eCb = NULL;
1085 access_sys_t *sys = (access_sys_t *)m_access->p_sys;
1087 int i_res;
1089 if ( vlc_killed() )
1090 return NULL;
1092 i_res = UpnpAddToAction( &p_action, "Browse",
1093 CONTENT_DIRECTORY_SERVICE_TYPE, "ObjectID", psz_object_id_ ? psz_object_id_ : "0" );
1095 if ( i_res != UPNP_E_SUCCESS )
1097 msg_Dbg( m_access, "AddToAction 'ObjectID' failed: %s",
1098 UpnpGetErrorMessage( i_res ) );
1099 goto browseActionCleanup;
1102 i_res = UpnpAddToAction( &p_action, "Browse",
1103 CONTENT_DIRECTORY_SERVICE_TYPE, "BrowseFlag", psz_browser_flag_ );
1105 if ( i_res != UPNP_E_SUCCESS )
1107 msg_Dbg( m_access, "AddToAction 'BrowseFlag' failed: %s",
1108 UpnpGetErrorMessage( i_res ) );
1109 goto browseActionCleanup;
1112 i_res = UpnpAddToAction( &p_action, "Browse",
1113 CONTENT_DIRECTORY_SERVICE_TYPE, "Filter", psz_filter_ );
1115 if ( i_res != UPNP_E_SUCCESS )
1117 msg_Dbg( m_access, "AddToAction 'Filter' failed: %s",
1118 UpnpGetErrorMessage( i_res ) );
1119 goto browseActionCleanup;
1122 i_res = UpnpAddToAction( &p_action, "Browse",
1123 CONTENT_DIRECTORY_SERVICE_TYPE, "StartingIndex", "0" );
1124 if ( i_res != UPNP_E_SUCCESS )
1126 msg_Dbg( m_access, "AddToAction 'StartingIndex' failed: %s",
1127 UpnpGetErrorMessage( i_res ) );
1128 goto browseActionCleanup;
1131 i_res = UpnpAddToAction( &p_action, "Browse",
1132 CONTENT_DIRECTORY_SERVICE_TYPE, "RequestedCount", psz_requested_count_ );
1134 if ( i_res != UPNP_E_SUCCESS )
1136 msg_Dbg( m_access, "AddToAction 'RequestedCount' failed: %s",
1137 UpnpGetErrorMessage( i_res ) );
1138 goto browseActionCleanup;
1141 i_res = UpnpAddToAction( &p_action, "Browse",
1142 CONTENT_DIRECTORY_SERVICE_TYPE, "SortCriteria", psz_sort_criteria_ );
1144 if ( i_res != UPNP_E_SUCCESS )
1146 msg_Dbg( m_access, "AddToAction 'SortCriteria' failed: %s",
1147 UpnpGetErrorMessage( i_res ) );
1148 goto browseActionCleanup;
1151 /* Setup an interruptible callback that will call sendActionCb if not
1152 * interrupted by vlc_interrupt_kill */
1153 i11eCb = new Upnp_i11e_cb( sendActionCb, &p_response );
1154 i_res = UpnpSendActionAsync( sys->p_upnp->handle(),
1155 m_psz_root,
1156 CONTENT_DIRECTORY_SERVICE_TYPE,
1157 NULL, /* ignored in SDK, must be NULL */
1158 p_action,
1159 Upnp_i11e_cb::run, i11eCb );
1161 if ( i_res != UPNP_E_SUCCESS )
1163 msg_Err( m_access, "%s when trying the send() action with URL: %s",
1164 UpnpGetErrorMessage( i_res ), m_access->psz_location );
1166 /* Wait for the callback to fill p_response or wait for an interrupt */
1167 i11eCb->waitAndRelease();
1169 browseActionCleanup:
1170 ixmlDocument_free( p_action );
1171 return p_response;
1175 * Fetches and parses the UPNP response
1177 bool MediaServer::fetchContents()
1179 IXML_Document* p_response = _browseAction( m_psz_objectId,
1180 "BrowseDirectChildren",
1181 "*",
1182 // Some servers don't understand "0" as "no-limit"
1183 "5000", /* RequestedCount */
1184 "" /* SortCriteria */
1186 if ( !p_response )
1188 msg_Err( m_access, "No response from browse() action" );
1189 return false;
1192 IXML_Document* p_result = parseBrowseResult( p_response );
1194 ixmlDocument_free( p_response );
1196 if ( !p_result )
1198 msg_Err( m_access, "browse() response parsing failed" );
1199 return false;
1202 #ifndef NDEBUG
1203 msg_Dbg( m_access, "Got DIDL document: %s", ixmlPrintDocument( p_result ) );
1204 #endif
1206 IXML_NodeList* containerNodeList =
1207 ixmlDocument_getElementsByTagName( p_result, "container" );
1209 if ( containerNodeList )
1211 for ( unsigned int i = 0; i < ixmlNodeList_length( containerNodeList ); i++ )
1212 addContainer( (IXML_Element*)ixmlNodeList_item( containerNodeList, i ) );
1213 ixmlNodeList_free( containerNodeList );
1216 IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( p_result,
1217 "item" );
1218 if ( itemNodeList )
1220 for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
1221 addItem( (IXML_Element*)ixmlNodeList_item( itemNodeList, i ) );
1222 ixmlNodeList_free( itemNodeList );
1225 ixmlDocument_free( p_result );
1226 return true;
1229 static int ReadDirectory( stream_t *p_access, input_item_node_t* p_node )
1231 MediaServer server( p_access, p_node );
1233 if ( !server.fetchContents() )
1234 return VLC_EGENERIC;
1235 return VLC_SUCCESS;
1238 static int OpenAccess( vlc_object_t *p_this )
1240 stream_t* p_access = (stream_t*)p_this;
1241 access_sys_t* p_sys = new(std::nothrow) access_sys_t;
1242 if ( unlikely( !p_sys ) )
1243 return VLC_ENOMEM;
1245 p_access->p_sys = p_sys;
1246 p_sys->p_upnp = UpnpInstanceWrapper::get( p_this );
1247 if ( !p_sys->p_upnp )
1249 delete p_sys;
1250 return VLC_EGENERIC;
1253 p_access->pf_readdir = ReadDirectory;
1254 p_access->pf_control = access_vaDirectoryControlHelper;
1256 return VLC_SUCCESS;
1259 static void CloseAccess( vlc_object_t* p_this )
1261 stream_t* p_access = (stream_t*)p_this;
1262 access_sys_t *sys = (access_sys_t *)p_access->p_sys;
1264 sys->p_upnp->release();
1265 delete sys;