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 *****************************************************************************/
31 #include <vlc_common.h>
35 #include <vlc_access.h>
36 #include <vlc_plugin.h>
37 #include <vlc_interrupt.h>
38 #include <vlc_services_discovery.h>
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 "Auto", "ASTRA_19_2E", "ASTRA_28_2E", "ASTRA_23_5E", "MasterList", "ServerList", "CustomList"
58 static const char *const ppsz_readible_satip_channel_lists
[] = {
59 N_("Auto"), "Astra 19.2°E", "Astra 28.2°E", "Astra 23.5°E", N_("SAT>IP Main List"), N_("Device List"), N_("Custom List")
67 struct services_discovery_sys_t
69 UpnpInstanceWrapper
* p_upnp
;
70 std::shared_ptr
<SD::MediaServerList
> p_server_list
;
76 UpnpInstanceWrapper
* p_upnp
;
82 * VLC callback prototypes
86 static int OpenSD( vlc_object_t
* );
87 static void CloseSD( vlc_object_t
* );
92 static int OpenAccess( vlc_object_t
* );
93 static void CloseAccess( vlc_object_t
* );
96 VLC_SD_PROBE_HELPER( "upnp", N_("Universal Plug'n'Play"), SD_CAT_LAN
)
102 set_shortname( "UPnP" );
103 set_description( N_( "Universal Plug'n'Play" ) );
104 set_category( CAT_PLAYLIST
);
105 set_subcategory( SUBCAT_PLAYLIST_SD
);
106 set_capability( "services_discovery", 0 );
107 set_callbacks( SD::OpenSD
, SD::CloseSD
);
109 add_string( "satip-channelist", "auto", SATIP_CHANNEL_LIST
,
110 SATIP_CHANNEL_LIST
, false )
111 change_string_list( ppsz_satip_channel_lists
, ppsz_readible_satip_channel_lists
)
112 add_string( "satip-channellist-url", NULL
, SATIP_CHANNEL_LIST_URL
,
113 SATIP_CHANNEL_LIST_URL
, false )
116 set_category( CAT_INPUT
)
117 set_subcategory( SUBCAT_INPUT_ACCESS
)
118 set_callbacks( Access::OpenAccess
, Access::CloseAccess
)
119 set_capability( "access", 0 )
121 VLC_SD_PROBE_SUBMODULE
125 * Extracts the result document from a SOAP response
127 IXML_Document
* parseBrowseResult( IXML_Document
* p_doc
)
131 // ixml*_getElementsByTagName will ultimately only case the pointer to a Node
132 // pointer, and pass it to a private function. Don't bother have a IXML_Document
133 // version of getChildElementValue
134 const char* psz_raw_didl
= xml_getChildElementValue( (IXML_Element
*)p_doc
, "Result" );
139 /* First, try parsing the buffer as is */
140 IXML_Document
* p_result_doc
= ixmlParseBuffer( psz_raw_didl
);
141 if( !p_result_doc
) {
142 /* Missing namespaces confuse the ixml parser. This is a very ugly
143 * hack but it is needeed until devices start sending valid XML.
147 * The DIDL document is extracted from the Result tag, then wrapped into
148 * a valid XML header and a new root tag which contains missing namespace
149 * definitions so the ixml parser understands it.
151 * If you know of a better workaround, please oh please fix it */
152 const char* psz_xml_result_fmt
= "<?xml version=\"1.0\" ?>"
153 "<Result xmlns:sec=\"urn:samsung:metadata:2009\">%s</Result>";
155 char* psz_xml_result_string
= NULL
;
156 if( -1 == asprintf( &psz_xml_result_string
,
161 p_result_doc
= ixmlParseBuffer( psz_xml_result_string
);
162 free( psz_xml_result_string
);
168 IXML_NodeList
*p_elems
= ixmlDocument_getElementsByTagName( p_result_doc
,
171 IXML_Node
*p_node
= ixmlNodeList_item( p_elems
, 0 );
172 ixmlNodeList_free( p_elems
);
174 return (IXML_Document
*)p_node
;
181 SearchThread( void *p_data
)
183 services_discovery_t
*p_sd
= ( services_discovery_t
* )p_data
;
184 services_discovery_sys_t
*p_sys
= reinterpret_cast<services_discovery_sys_t
*>( p_sd
->p_sys
);
186 /* Search for media servers */
187 int i_res
= UpnpSearchAsync( p_sys
->p_upnp
->handle(), 5,
188 MEDIA_SERVER_DEVICE_TYPE
, MEDIA_SERVER_DEVICE_TYPE
);
189 if( i_res
!= UPNP_E_SUCCESS
)
191 msg_Err( p_sd
, "Error sending search request: %s", UpnpGetErrorMessage( i_res
) );
195 /* Search for Sat Ip servers*/
196 i_res
= UpnpSearchAsync( p_sys
->p_upnp
->handle(), 5,
197 SATIP_SERVER_DEVICE_TYPE
, MEDIA_SERVER_DEVICE_TYPE
);
198 if( i_res
!= UPNP_E_SUCCESS
)
199 msg_Err( p_sd
, "Error sending search request: %s", UpnpGetErrorMessage( i_res
) );
204 * Initializes UPNP instance.
206 static int OpenSD( vlc_object_t
*p_this
)
208 services_discovery_t
*p_sd
= ( services_discovery_t
* )p_this
;
209 services_discovery_sys_t
*p_sys
= new (std::nothrow
) services_discovery_sys_t();
211 if( !( p_sd
->p_sys
= p_sys
) )
214 p_sd
->description
= _("Universal Plug'n'Play");
216 p_sys
->p_upnp
= UpnpInstanceWrapper::get( p_this
);
217 if ( !p_sys
->p_upnp
)
225 p_sys
->p_server_list
= std::make_shared
<SD::MediaServerList
>( p_sd
);
227 catch ( const std::bad_alloc
& )
229 msg_Err( p_sd
, "Failed to create a MediaServerList");
230 p_sys
->p_upnp
->release();
234 p_sys
->p_upnp
->addListener( p_sys
->p_server_list
);
236 /* XXX: Contrary to what the libupnp doc states, UpnpSearchAsync is
237 * blocking (select() and send() are called). Therefore, Call
238 * UpnpSearchAsync from an other thread. */
239 if ( vlc_clone( &p_sys
->thread
, SearchThread
, p_this
,
240 VLC_THREAD_PRIORITY_LOW
) )
242 p_sys
->p_upnp
->removeListener( p_sys
->p_server_list
);
243 p_sys
->p_upnp
->release();
252 * Releases resources.
254 static void CloseSD( vlc_object_t
*p_this
)
256 services_discovery_t
*p_sd
= ( services_discovery_t
* )p_this
;
257 services_discovery_sys_t
*p_sys
= reinterpret_cast<services_discovery_sys_t
*>( p_sd
->p_sys
);
259 vlc_join( p_sys
->thread
, NULL
);
260 p_sys
->p_upnp
->removeListener( p_sys
->p_server_list
);
261 p_sys
->p_upnp
->release();
265 MediaServerDesc::MediaServerDesc( const std::string
& udn
, const std::string
& fName
,
266 const std::string
& loc
, const std::string
& iconUrl
)
268 , friendlyName( fName
)
276 MediaServerDesc::~MediaServerDesc()
279 input_item_Release( inputItem
);
283 * MediaServerList class
285 MediaServerList::MediaServerList( services_discovery_t
* p_sd
)
290 MediaServerList::~MediaServerList()
292 vlc_delete_all(m_list
);
295 bool MediaServerList::addServer( MediaServerDesc
* desc
)
297 input_item_t
* p_input_item
= NULL
;
298 if ( getServer( desc
->UDN
) )
301 msg_Dbg( m_sd
, "Adding server '%s' with uuid '%s'", desc
->friendlyName
.c_str(), desc
->UDN
.c_str() );
305 p_input_item
= input_item_NewDirectory( desc
->location
.c_str(),
306 desc
->friendlyName
.c_str(),
311 input_item_SetSetting( p_input_item
, SATIP_SERVER_DEVICE_TYPE
);
313 char *psz_playlist_option
;
315 if (asprintf( &psz_playlist_option
, "satip-host=%s",
316 desc
->satIpHost
.c_str() ) >= 0 ) {
317 input_item_AddOption( p_input_item
, psz_playlist_option
, 0 );
318 free( psz_playlist_option
);
322 // We might already have some options specified in the location.
323 char opt_delim
= desc
->location
.find( '?' ) == 0 ? '?' : '&';
324 if( asprintf( &psz_mrl
, "upnp://%s%cObjectID=0", desc
->location
.c_str(), opt_delim
) < 0 )
327 p_input_item
= input_item_NewDirectory( psz_mrl
,
328 desc
->friendlyName
.c_str(),
335 input_item_SetSetting( p_input_item
, MEDIA_SERVER_DEVICE_TYPE
);
338 if ( desc
->iconUrl
.empty() == false )
339 input_item_SetArtworkURL( p_input_item
, desc
->iconUrl
.c_str() );
340 desc
->inputItem
= p_input_item
;
341 input_item_SetDescription( p_input_item
, desc
->UDN
.c_str() );
342 services_discovery_AddItem( m_sd
, p_input_item
);
343 m_list
.push_back( desc
);
348 MediaServerDesc
* MediaServerList::getServer( const std::string
& udn
)
350 std::vector
<MediaServerDesc
*>::const_iterator it
= m_list
.begin();
351 std::vector
<MediaServerDesc
*>::const_iterator ite
= m_list
.end();
353 for ( ; it
!= ite
; ++it
)
355 if( udn
== (*it
)->UDN
)
363 void MediaServerList::parseNewServer( IXML_Document
*doc
, const std::string
&location
)
367 msg_Err( m_sd
, "Null IXML_Document" );
371 if ( location
.empty() )
373 msg_Err( m_sd
, "Empty location" );
377 const char* psz_base_url
= location
.c_str();
379 /* Try to extract baseURL */
380 IXML_NodeList
* p_url_list
= ixmlDocument_getElementsByTagName( doc
, "URLBase" );
383 if ( IXML_Node
* p_url_node
= ixmlNodeList_item( p_url_list
, 0 ) )
385 IXML_Node
* p_text_node
= ixmlNode_getFirstChild( p_url_node
);
387 psz_base_url
= ixmlNode_getNodeValue( p_text_node
);
389 ixmlNodeList_free( p_url_list
);
393 IXML_NodeList
* p_device_list
= ixmlDocument_getElementsByTagName( doc
, "device" );
395 if ( !p_device_list
)
398 for ( unsigned int i
= 0; i
< ixmlNodeList_length( p_device_list
); i
++ )
400 IXML_Element
* p_device_element
= ( IXML_Element
* ) ixmlNodeList_item( p_device_list
, i
);
402 if( !p_device_element
)
405 const char* psz_device_type
= xml_getChildElementValue( p_device_element
, "deviceType" );
407 if ( !psz_device_type
)
409 msg_Warn( m_sd
, "No deviceType found!" );
413 if ( strncmp( MEDIA_SERVER_DEVICE_TYPE
, psz_device_type
,
414 strlen( MEDIA_SERVER_DEVICE_TYPE
) - 1 )
415 && strncmp( SATIP_SERVER_DEVICE_TYPE
, psz_device_type
,
416 strlen( SATIP_SERVER_DEVICE_TYPE
) - 1 ) )
419 const char* psz_udn
= xml_getChildElementValue( p_device_element
,
423 msg_Warn( m_sd
, "No UDN!" );
427 /* Check if server is already added */
428 if ( getServer( psz_udn
) )
430 msg_Warn( m_sd
, "Server with uuid '%s' already exists.", psz_udn
);
434 const char* psz_friendly_name
=
435 xml_getChildElementValue( p_device_element
,
438 if ( !psz_friendly_name
)
440 msg_Dbg( m_sd
, "No friendlyName!" );
444 std::string iconUrl
= getIconURL( p_device_element
, psz_base_url
);
446 // We now have basic info, we need to get the content browsing url
447 // so the access module can browse without fetching the manifest again
448 if ( !strncmp( SATIP_SERVER_DEVICE_TYPE
, psz_device_type
,
449 strlen( SATIP_SERVER_DEVICE_TYPE
) - 1 ) ) {
450 parseSatipServer( p_device_element
, psz_base_url
, psz_udn
, psz_friendly_name
, iconUrl
);
453 /* Check for ContentDirectory service. */
454 IXML_NodeList
* p_service_list
= ixmlElement_getElementsByTagName( p_device_element
, "service" );
455 if ( !p_service_list
)
457 for ( unsigned int j
= 0; j
< ixmlNodeList_length( p_service_list
); j
++ )
459 IXML_Element
* p_service_element
= (IXML_Element
*)ixmlNodeList_item( p_service_list
, j
);
461 const char* psz_service_type
= xml_getChildElementValue( p_service_element
, "serviceType" );
462 if ( !psz_service_type
)
464 msg_Warn( m_sd
, "No service type found." );
468 int k
= strlen( CONTENT_DIRECTORY_SERVICE_TYPE
) - 1;
469 if ( strncmp( CONTENT_DIRECTORY_SERVICE_TYPE
,
470 psz_service_type
, k
) )
473 const char* psz_control_url
= xml_getChildElementValue( p_service_element
,
475 if ( !psz_control_url
)
477 msg_Warn( m_sd
, "No control url found." );
481 /* Try to browse content directory. */
482 char* psz_url
= ( char* ) malloc( strlen( psz_base_url
) + strlen( psz_control_url
) + 1 );
485 if ( UpnpResolveURL( psz_base_url
, psz_control_url
, psz_url
) == UPNP_E_SUCCESS
)
487 SD::MediaServerDesc
* p_server
= new(std::nothrow
) SD::MediaServerDesc( psz_udn
,
488 psz_friendly_name
, psz_url
, iconUrl
);
490 if ( unlikely( !p_server
) )
493 if ( !addServer( p_server
) )
503 ixmlNodeList_free( p_service_list
);
505 ixmlNodeList_free( p_device_list
);
508 std::string
MediaServerList::getIconURL( IXML_Element
* p_device_elem
, const char* psz_base_url
)
511 IXML_NodeList
* p_icon_lists
= ixmlElement_getElementsByTagName( p_device_elem
, "iconList" );
512 if ( p_icon_lists
== NULL
)
514 IXML_Element
* p_icon_list
= (IXML_Element
*)ixmlNodeList_item( p_icon_lists
, 0 );
515 if ( p_icon_list
!= NULL
)
517 IXML_NodeList
* p_icons
= ixmlElement_getElementsByTagName( p_icon_list
, "icon" );
518 if ( p_icons
!= NULL
)
520 unsigned int maxWidth
= 0;
521 unsigned int maxHeight
= 0;
522 for ( unsigned int i
= 0; i
< ixmlNodeList_length( p_icons
); ++i
)
524 IXML_Element
* p_icon
= (IXML_Element
*)ixmlNodeList_item( p_icons
, i
);
525 const char* widthStr
= xml_getChildElementValue( p_icon
, "width" );
526 const char* heightStr
= xml_getChildElementValue( p_icon
, "height" );
527 if ( widthStr
== NULL
|| heightStr
== NULL
)
529 unsigned int width
= atoi( widthStr
);
530 unsigned int height
= atoi( heightStr
);
531 if ( width
<= maxWidth
|| height
<= maxHeight
)
533 const char* iconUrl
= xml_getChildElementValue( p_icon
, "url" );
534 if ( iconUrl
== NULL
)
540 ixmlNodeList_free( p_icons
);
543 ixmlNodeList_free( p_icon_lists
);
545 if ( res
.empty() == false )
548 vlc_UrlParse( &url
, psz_base_url
);
550 if ( asprintf( &psz_url
, "%s://%s:%u%s", url
.psz_protocol
, url
.psz_host
, url
.i_port
, res
.c_str() ) < 0 )
557 vlc_UrlClean( &url
);
563 MediaServerList::parseSatipServer( IXML_Element
* p_device_element
, const char *psz_base_url
, const char *psz_udn
, const char *psz_friendly_name
, std::string iconUrl
)
565 SD::MediaServerDesc
* p_server
= NULL
;
568 vlc_UrlParse( &url
, psz_base_url
);
570 char *psz_satip_channellist
= config_GetPsz("satip-channelist");
571 if( !psz_satip_channellist
) {
572 psz_satip_channellist
= strdup("Auto");
575 /* Part 1: a user may have provided a custom playlist url */
576 if (strncmp(psz_satip_channellist
, "CustomList", 10) == 0) {
577 char *psz_satip_playlist_url
= config_GetPsz( "satip-channellist-url" );
578 if ( psz_satip_playlist_url
) {
579 p_server
= new(std::nothrow
) SD::MediaServerDesc( psz_udn
, psz_friendly_name
, psz_satip_playlist_url
, iconUrl
);
581 if( likely( p_server
) ) {
582 p_server
->satIpHost
= url
.psz_host
;
583 p_server
->isSatIp
= true;
584 if( !addServer( p_server
) ) {
589 /* to comply with the SAT>IP specification, we don't fall back on another channel list if this path failed */
590 free( psz_satip_playlist_url
);
591 vlc_UrlClean( &url
);
596 /* Part 2: device playlist
597 * In Automatic mode, or if requested by the user, check for a SAT>IP m3u list on the device */
598 if (strncmp(psz_satip_channellist
, "ServerList", 10) == 0 ||
599 strncmp(psz_satip_channellist
, "Auto", strlen ("Auto") == 0 )) {
600 const char* psz_m3u_url
= xml_getChildElementValue( p_device_element
, "satip:X_SATIPM3U" );
602 if ( strncmp( "http", psz_m3u_url
, 4) )
604 char* psz_url
= NULL
;
605 if ( UpnpResolveURL2( psz_base_url
, psz_m3u_url
, &psz_url
) == UPNP_E_SUCCESS
)
607 p_server
= new(std::nothrow
) SD::MediaServerDesc( psz_udn
, psz_friendly_name
, psz_url
, iconUrl
);
611 p_server
= new(std::nothrow
) SD::MediaServerDesc( psz_udn
, psz_friendly_name
, psz_m3u_url
, iconUrl
);
614 if ( unlikely( !p_server
) )
616 free( psz_satip_channellist
);
617 vlc_UrlClean( &url
);
621 p_server
->satIpHost
= url
.psz_host
;
622 p_server
->isSatIp
= true;
623 if ( !addServer( p_server
) )
626 msg_Dbg( m_sd
, "SAT>IP server '%s' did not provide a playlist", url
.psz_host
);
629 if(strncmp(psz_satip_channellist
, "ServerList", 10) == 0) {
630 /* to comply with the SAT>IP specifications, we don't fallback on another channel list if this path failed,
631 * but in Automatic mode, we continue */
632 free(psz_satip_channellist
);
633 vlc_UrlClean( &url
);
638 /* Part 3: satip.info playlist
639 * In the normal case, fetch a playlist from the satip website,
640 * which will be processed by a lua script a bit later, to make it work sanely
641 * MasterList is a list of usual Satellites */
643 /* In Auto mode, default to MasterList list from satip.info */
644 if( strncmp(psz_satip_channellist
, "Auto", strlen ("Auto") == 0 ) ) {
645 psz_satip_channellist
= strdup( "MasterList" );
649 if (asprintf( &psz_url
, "http://www.satip.info/Playlists/%s.m3u",
650 psz_satip_channellist
) < 0 ) {
651 vlc_UrlClean( &url
);
652 free( psz_satip_channellist
);
656 p_server
= new(std::nothrow
) SD::MediaServerDesc( psz_udn
,
657 psz_friendly_name
, psz_url
, iconUrl
);
659 if( likely( p_server
) ) {
660 p_server
->satIpHost
= url
.psz_host
;
661 p_server
->isSatIp
= true;
662 if( !addServer( p_server
) ) {
667 free( psz_satip_channellist
);
668 vlc_UrlClean( &url
);
671 void MediaServerList::removeServer( const std::string
& udn
)
673 MediaServerDesc
* p_server
= getServer( udn
);
677 msg_Dbg( m_sd
, "Removing server '%s'", p_server
->friendlyName
.c_str() );
679 assert(p_server
->inputItem
);
680 services_discovery_RemoveItem( m_sd
, p_server
->inputItem
);
682 std::vector
<MediaServerDesc
*>::iterator it
= std::find(m_list
.begin(), m_list
.end(), p_server
);
683 if (it
!= m_list
.end())
691 * Handles servers listing UPnP events
693 int MediaServerList::onEvent( Upnp_EventType event_type
, UpnpEventPtr p_event
, void* p_user_data
)
695 if (p_user_data
!= MEDIA_SERVER_DEVICE_TYPE
)
700 case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE
:
701 case UPNP_DISCOVERY_SEARCH_RESULT
:
703 const UpnpDiscovery
* p_discovery
= ( const UpnpDiscovery
* )p_event
;
705 IXML_Document
*p_description_doc
= NULL
;
708 i_res
= UpnpDownloadXmlDoc( UpnpDiscovery_get_Location_cstr( p_discovery
), &p_description_doc
);
710 if ( i_res
!= UPNP_E_SUCCESS
)
712 msg_Warn( m_sd
, "Could not download device description! "
713 "Fetching data from %s failed: %s",
714 UpnpDiscovery_get_Location_cstr( p_discovery
), UpnpGetErrorMessage( i_res
) );
717 parseNewServer( p_description_doc
, UpnpDiscovery_get_Location_cstr( p_discovery
) );
718 ixmlDocument_free( p_description_doc
);
722 case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE
:
724 const UpnpDiscovery
* p_discovery
= ( const UpnpDiscovery
* )p_event
;
725 removeServer( UpnpDiscovery_get_DeviceID_cstr( p_discovery
) );
729 case UPNP_EVENT_SUBSCRIBE_COMPLETE
:
731 msg_Warn( m_sd
, "subscription complete" );
735 case UPNP_DISCOVERY_SEARCH_TIMEOUT
:
737 msg_Warn( m_sd
, "search timeout" );
741 case UPNP_EVENT_RECEIVED
:
742 case UPNP_EVENT_AUTORENEWAL_FAILED
:
743 case UPNP_EVENT_SUBSCRIPTION_EXPIRED
:
744 // Those are for the access part
749 msg_Err( m_sd
, "Unhandled event, please report ( type=%d )", event_type
);
754 return UPNP_E_SUCCESS
;
764 class ItemDescriptionHolder
767 struct Slave
: std::string
771 Slave(std::string
const &url
, slave_type type
) :
772 std::string(url
), type(type
)
777 std::set
<Slave
> slaves
;
779 const char* objectID
,
798 MEDIA_TYPE media_type
;
800 ItemDescriptionHolder()
804 bool init(IXML_Element
*itemElement
)
806 objectID
= ixmlElement_getAttribute( itemElement
, "id" );
809 title
= xml_getChildElementValue( itemElement
, "dc:title" );
812 const char *psz_subtitles
= xml_getChildElementValue( itemElement
, "sec:CaptionInfo" );
813 if ( !psz_subtitles
&&
814 !(psz_subtitles
= xml_getChildElementValue( itemElement
, "sec:CaptionInfoEx" )) )
815 psz_subtitles
= xml_getChildElementValue( itemElement
, "pv:subtitlefile" );
816 addSlave(psz_subtitles
, SLAVE_TYPE_SPU
);
817 psz_artist
= xml_getChildElementValue( itemElement
, "upnp:artist" );
818 psz_genre
= xml_getChildElementValue( itemElement
, "upnp:genre" );
819 psz_album
= xml_getChildElementValue( itemElement
, "upnp:album" );
820 psz_date
= xml_getChildElementValue( itemElement
, "dc:date" );
821 psz_orig_track_nb
= xml_getChildElementValue( itemElement
, "upnp:originalTrackNumber" );
822 psz_album_artist
= xml_getChildElementValue( itemElement
, "upnp:albumArtist" );
823 psz_albumArt
= xml_getChildElementValue( itemElement
, "upnp:albumArtURI" );
824 const char *psz_media_type
= xml_getChildElementValue( itemElement
, "upnp:class" );
825 if (strncmp(psz_media_type
, "object.item.videoItem", 21) == 0)
827 else if (strncmp(psz_media_type
, "object.item.audioItem", 21) == 0)
829 else if (strncmp(psz_media_type
, "object.item.imageItem", 21) == 0)
831 else if (strncmp(psz_media_type
, "object.container", 16 ) == 0)
832 media_type
= CONTAINER
;
838 void addSlave(const char *psz_slave
, slave_type type
)
841 slaves
.insert(Slave(psz_slave
, type
));
844 void addSubtitleSlave(IXML_Element
* p_resource
)
847 addSlave(ixmlElement_getAttribute( p_resource
, "pv:subtitleFileUri" ),
851 void setArtworkURL(IXML_Element
* p_resource
)
853 psz_albumArt
= xml_getChildElementValue( p_resource
, "res" );
856 void apply(input_item_t
*p_item
)
858 if ( psz_artist
!= NULL
)
859 input_item_SetArtist( p_item
, psz_artist
);
860 if ( psz_genre
!= NULL
)
861 input_item_SetGenre( p_item
, psz_genre
);
862 if ( psz_album
!= NULL
)
863 input_item_SetAlbum( p_item
, psz_album
);
864 if ( psz_date
!= NULL
)
865 input_item_SetDate( p_item
, psz_date
);
866 if ( psz_orig_track_nb
!= NULL
)
867 input_item_SetTrackNumber( p_item
, psz_orig_track_nb
);
868 if ( psz_album_artist
!= NULL
)
869 input_item_SetAlbumArtist( p_item
, psz_album_artist
);
870 if ( psz_albumArt
!= NULL
)
871 input_item_SetArtworkURL( p_item
, psz_albumArt
);
872 for (std::set
<Slave
>::iterator it
= slaves
.begin(); it
!= slaves
.end(); ++it
)
874 input_item_slave
*p_slave
= input_item_slave_New( it
->c_str(), it
->type
,
875 SLAVE_PRIORITY_MATCH_ALL
);
877 input_item_AddSlave( p_item
, p_slave
);
881 input_item_t
*createNewItem(IXML_Element
*p_resource
)
883 vlc_tick_t i_duration
= INPUT_DURATION_UNKNOWN
;
884 const char* psz_resource_url
= xml_getChildElementValue( p_resource
, "res" );
885 if( !psz_resource_url
)
887 const char* psz_duration
= ixmlElement_getAttribute( p_resource
, "duration" );
890 int i_hours
, i_minutes
, i_seconds
;
891 if( sscanf( psz_duration
, "%d:%02d:%02d", &i_hours
, &i_minutes
, &i_seconds
) )
892 i_duration
= vlc_tick_from_sec( i_hours
* 3600 + i_minutes
* 60 +
895 return input_item_NewExt( psz_resource_url
, title
, i_duration
,
896 ITEM_TYPE_FILE
, ITEM_NET
);
899 input_item_t
*createNewContainerItem( const char* psz_root
)
901 if ( objectID
== NULL
|| title
== NULL
)
905 if( asprintf( &psz_url
, "upnp://%s?ObjectID=%s", psz_root
, objectID
) < 0 )
908 input_item_t
* p_item
= input_item_NewDirectory( psz_url
, title
, ITEM_NET
);
915 Upnp_i11e_cb::Upnp_i11e_cb( Upnp_FunPtr callback
, void *cookie
)
916 : m_refCount( 2 ) /* 2: owned by the caller, and the Upnp Async function */
917 , m_callback( callback
)
921 vlc_mutex_init( &m_lock
);
922 vlc_sem_init( &m_sem
, 0 );
925 Upnp_i11e_cb::~Upnp_i11e_cb()
927 vlc_mutex_destroy( &m_lock
);
928 vlc_sem_destroy( &m_sem
);
931 void Upnp_i11e_cb::waitAndRelease( void )
933 vlc_sem_wait_i11e( &m_sem
);
935 vlc_mutex_lock( &m_lock
);
936 if ( --m_refCount
== 0 )
938 /* The run callback is processed, we can destroy this object */
939 vlc_mutex_unlock( &m_lock
);
943 /* Interrupted, let the run callback destroy this object */
944 vlc_mutex_unlock( &m_lock
);
948 int Upnp_i11e_cb::run( Upnp_EventType eventType
, UpnpEventPtr p_event
, void *p_cookie
)
950 Upnp_i11e_cb
*self
= static_cast<Upnp_i11e_cb
*>( p_cookie
);
952 vlc_mutex_lock( &self
->m_lock
);
953 if ( --self
->m_refCount
== 0 )
955 /* Interrupted, we can destroy self */
956 vlc_mutex_unlock( &self
->m_lock
);
960 /* Process the user callback_ */
961 self
->m_callback( eventType
, p_event
, self
->m_cookie
);
962 vlc_mutex_unlock( &self
->m_lock
);
964 /* Signal that the callback is processed */
965 vlc_sem_post( &self
->m_sem
);
969 MediaServer::MediaServer( stream_t
*p_access
, input_item_node_t
*node
)
970 : m_psz_objectId( NULL
)
971 , m_access( p_access
)
975 m_psz_root
= strdup( p_access
->psz_location
);
976 char* psz_objectid
= strstr( m_psz_root
, "ObjectID=" );
977 if ( psz_objectid
!= NULL
)
979 // Remove this parameter from the URL, since it might cause some servers to fail
980 // Keep in mind that we added a '&' or a '?' to the URL, so remove it as well
981 *( psz_objectid
- 1) = 0;
982 m_psz_objectId
= &psz_objectid
[strlen( "ObjectID=" )];
986 MediaServer::~MediaServer()
991 bool MediaServer::addContainer( IXML_Element
* containerElement
)
993 ItemDescriptionHolder holder
;
995 if ( holder
.init( containerElement
) == false )
998 input_item_t
* p_item
= holder
.createNewContainerItem( m_psz_root
);
1001 holder
.apply( p_item
);
1002 input_item_CopyOptions( p_item
, m_node
->p_item
);
1003 input_item_node_AppendItem( m_node
, p_item
);
1004 input_item_Release( p_item
);
1008 bool MediaServer::addItem( IXML_Element
* itemElement
)
1010 ItemDescriptionHolder holder
;
1012 if (!holder
.init(itemElement
))
1014 /* Try to extract all resources in DIDL */
1015 IXML_NodeList
* p_resource_list
= ixmlDocument_getElementsByTagName( (IXML_Document
*) itemElement
, "res" );
1016 if ( !p_resource_list
)
1018 int list_lenght
= ixmlNodeList_length( p_resource_list
);
1019 if (list_lenght
<= 0 ) {
1020 ixmlNodeList_free( p_resource_list
);
1023 input_item_t
*p_item
= NULL
;
1025 for (int index
= 0; index
< list_lenght
; index
++)
1027 IXML_Element
* p_resource
= ( IXML_Element
* ) ixmlNodeList_item( p_resource_list
, index
);
1028 const char* rez_type
= ixmlElement_getAttribute( p_resource
, "protocolInfo" );
1030 if (strncmp(rez_type
, "http-get:*:video/", 17) == 0 && holder
.media_type
== ItemDescriptionHolder::VIDEO
)
1033 p_item
= holder
.createNewItem(p_resource
);
1034 holder
.addSubtitleSlave(p_resource
);
1036 else if (strncmp(rez_type
, "http-get:*:image/", 17) == 0)
1037 switch (holder
.media_type
)
1039 case ItemDescriptionHolder::IMAGE
:
1041 p_item
= holder
.createNewItem(p_resource
);
1044 case ItemDescriptionHolder::VIDEO
:
1045 case ItemDescriptionHolder::AUDIO
:
1046 holder
.setArtworkURL(p_resource
);
1048 case ItemDescriptionHolder::CONTAINER
:
1049 msg_Warn( m_access
, "Unexpected object.container in item enumeration" );
1052 else if (strncmp(rez_type
, "http-get:*:text/", 16) == 0)
1053 holder
.addSlave(xml_getChildElementValue( p_resource
, "res" ), SLAVE_TYPE_SPU
);
1054 else if (strncmp(rez_type
, "http-get:*:audio/", 17) == 0)
1056 if (holder
.media_type
== ItemDescriptionHolder::AUDIO
)
1059 p_item
= holder
.createNewItem(p_resource
);
1062 holder
.addSlave(xml_getChildElementValue( p_resource
, "res" ),
1066 ixmlNodeList_free( p_resource_list
);
1069 holder
.apply(p_item
);
1070 input_item_CopyOptions( p_item
, m_node
->p_item
);
1071 input_item_node_AppendItem( m_node
, p_item
);
1072 input_item_Release( p_item
);
1076 int MediaServer::sendActionCb( Upnp_EventType eventType
,
1077 UpnpEventPtr p_event
, void *p_cookie
)
1079 if( eventType
!= UPNP_CONTROL_ACTION_COMPLETE
)
1081 IXML_Document
** pp_sendActionResult
= (IXML_Document
** )p_cookie
;
1082 const UpnpActionComplete
*p_result
= (const UpnpActionComplete
*)p_event
;
1084 /* The only way to dup the result is to print it and parse it again */
1085 DOMString tmpStr
= ixmlPrintNode( ( IXML_Node
* ) UpnpActionComplete_get_ActionResult( p_result
) );
1089 *pp_sendActionResult
= ixmlParseBuffer( tmpStr
);
1090 ixmlFreeDOMString( tmpStr
);
1095 IXML_Document
* MediaServer::_browseAction( const char* psz_object_id_
,
1096 const char* psz_browser_flag_
,
1097 const char* psz_filter_
,
1098 const char* psz_requested_count_
,
1099 const char* psz_sort_criteria_
)
1101 IXML_Document
* p_action
= NULL
;
1102 IXML_Document
* p_response
= NULL
;
1103 Upnp_i11e_cb
*i11eCb
= NULL
;
1104 access_sys_t
*sys
= (access_sys_t
*)m_access
->p_sys
;
1111 i_res
= UpnpAddToAction( &p_action
, "Browse",
1112 CONTENT_DIRECTORY_SERVICE_TYPE
, "ObjectID", psz_object_id_
? psz_object_id_
: "0" );
1114 if ( i_res
!= UPNP_E_SUCCESS
)
1116 msg_Dbg( m_access
, "AddToAction 'ObjectID' failed: %s",
1117 UpnpGetErrorMessage( i_res
) );
1118 goto browseActionCleanup
;
1121 i_res
= UpnpAddToAction( &p_action
, "Browse",
1122 CONTENT_DIRECTORY_SERVICE_TYPE
, "BrowseFlag", psz_browser_flag_
);
1124 if ( i_res
!= UPNP_E_SUCCESS
)
1126 msg_Dbg( m_access
, "AddToAction 'BrowseFlag' failed: %s",
1127 UpnpGetErrorMessage( i_res
) );
1128 goto browseActionCleanup
;
1131 i_res
= UpnpAddToAction( &p_action
, "Browse",
1132 CONTENT_DIRECTORY_SERVICE_TYPE
, "Filter", psz_filter_
);
1134 if ( i_res
!= UPNP_E_SUCCESS
)
1136 msg_Dbg( m_access
, "AddToAction 'Filter' failed: %s",
1137 UpnpGetErrorMessage( i_res
) );
1138 goto browseActionCleanup
;
1141 i_res
= UpnpAddToAction( &p_action
, "Browse",
1142 CONTENT_DIRECTORY_SERVICE_TYPE
, "StartingIndex", "0" );
1143 if ( i_res
!= UPNP_E_SUCCESS
)
1145 msg_Dbg( m_access
, "AddToAction 'StartingIndex' failed: %s",
1146 UpnpGetErrorMessage( i_res
) );
1147 goto browseActionCleanup
;
1150 i_res
= UpnpAddToAction( &p_action
, "Browse",
1151 CONTENT_DIRECTORY_SERVICE_TYPE
, "RequestedCount", psz_requested_count_
);
1153 if ( i_res
!= UPNP_E_SUCCESS
)
1155 msg_Dbg( m_access
, "AddToAction 'RequestedCount' failed: %s",
1156 UpnpGetErrorMessage( i_res
) );
1157 goto browseActionCleanup
;
1160 i_res
= UpnpAddToAction( &p_action
, "Browse",
1161 CONTENT_DIRECTORY_SERVICE_TYPE
, "SortCriteria", psz_sort_criteria_
);
1163 if ( i_res
!= UPNP_E_SUCCESS
)
1165 msg_Dbg( m_access
, "AddToAction 'SortCriteria' failed: %s",
1166 UpnpGetErrorMessage( i_res
) );
1167 goto browseActionCleanup
;
1170 /* Setup an interruptible callback that will call sendActionCb if not
1171 * interrupted by vlc_interrupt_kill */
1172 i11eCb
= new Upnp_i11e_cb( sendActionCb
, &p_response
);
1173 i_res
= UpnpSendActionAsync( sys
->p_upnp
->handle(),
1175 CONTENT_DIRECTORY_SERVICE_TYPE
,
1176 NULL
, /* ignored in SDK, must be NULL */
1178 Upnp_i11e_cb::run
, i11eCb
);
1180 if ( i_res
!= UPNP_E_SUCCESS
)
1182 msg_Err( m_access
, "%s when trying the send() action with URL: %s",
1183 UpnpGetErrorMessage( i_res
), m_access
->psz_location
);
1185 /* Wait for the callback to fill p_response or wait for an interrupt */
1186 i11eCb
->waitAndRelease();
1188 browseActionCleanup
:
1189 ixmlDocument_free( p_action
);
1194 * Fetches and parses the UPNP response
1196 bool MediaServer::fetchContents()
1198 IXML_Document
* p_response
= _browseAction( m_psz_objectId
,
1199 "BrowseDirectChildren",
1201 // Some servers don't understand "0" as "no-limit"
1202 "5000", /* RequestedCount */
1203 "" /* SortCriteria */
1207 msg_Err( m_access
, "No response from browse() action" );
1211 IXML_Document
* p_result
= parseBrowseResult( p_response
);
1213 ixmlDocument_free( p_response
);
1217 msg_Err( m_access
, "browse() response parsing failed" );
1222 msg_Dbg( m_access
, "Got DIDL document: %s", ixmlPrintDocument( p_result
) );
1225 IXML_NodeList
* containerNodeList
=
1226 ixmlDocument_getElementsByTagName( p_result
, "container" );
1228 if ( containerNodeList
)
1230 for ( unsigned int i
= 0; i
< ixmlNodeList_length( containerNodeList
); i
++ )
1231 addContainer( (IXML_Element
*)ixmlNodeList_item( containerNodeList
, i
) );
1232 ixmlNodeList_free( containerNodeList
);
1235 IXML_NodeList
* itemNodeList
= ixmlDocument_getElementsByTagName( p_result
,
1239 for ( unsigned int i
= 0; i
< ixmlNodeList_length( itemNodeList
); i
++ )
1240 addItem( (IXML_Element
*)ixmlNodeList_item( itemNodeList
, i
) );
1241 ixmlNodeList_free( itemNodeList
);
1244 ixmlDocument_free( p_result
);
1248 static int ReadDirectory( stream_t
*p_access
, input_item_node_t
* p_node
)
1250 MediaServer
server( p_access
, p_node
);
1252 if ( !server
.fetchContents() )
1253 return VLC_EGENERIC
;
1257 static int OpenAccess( vlc_object_t
*p_this
)
1259 stream_t
* p_access
= (stream_t
*)p_this
;
1260 access_sys_t
* p_sys
= new(std::nothrow
) access_sys_t
;
1261 if ( unlikely( !p_sys
) )
1264 p_access
->p_sys
= p_sys
;
1265 p_sys
->p_upnp
= UpnpInstanceWrapper::get( p_this
);
1266 if ( !p_sys
->p_upnp
)
1269 return VLC_EGENERIC
;
1272 p_access
->pf_readdir
= ReadDirectory
;
1273 p_access
->pf_control
= access_vaDirectoryControlHelper
;
1278 static void CloseAccess( vlc_object_t
* p_this
)
1280 stream_t
* p_access
= (stream_t
*)p_this
;
1281 access_sys_t
*sys
= (access_sys_t
*)p_access
->p_sys
;
1283 sys
->p_upnp
->release();