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 "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")
65 struct services_discovery_sys_t
67 UpnpInstanceWrapper
* p_upnp
;
68 std::shared_ptr
<SD::MediaServerList
> p_server_list
;
74 UpnpInstanceWrapper
* p_upnp
;
78 * VLC callback prototypes
82 static int OpenSD( vlc_object_t
* );
83 static void CloseSD( vlc_object_t
* );
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
)
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 )
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
121 * Extracts the result document from a SOAP response
123 IXML_Document
* parseBrowseResult( IXML_Document
* 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" );
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.
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
,
157 p_result_doc
= ixmlParseBuffer( psz_xml_result_string
);
158 free( psz_xml_result_string
);
164 IXML_NodeList
*p_elems
= ixmlDocument_getElementsByTagName( p_result_doc
,
167 IXML_Node
*p_node
= ixmlNodeList_item( p_elems
, 0 );
168 ixmlNodeList_free( p_elems
);
170 return (IXML_Document
*)p_node
;
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
) );
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
) );
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
) )
211 p_sd
->description
= _("Universal Plug'n'Play");
213 p_sys
->p_upnp
= UpnpInstanceWrapper::get( p_this
);
214 if ( !p_sys
->p_upnp
)
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();
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();
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();
262 MediaServerDesc::MediaServerDesc( const std::string
& udn
, const std::string
& fName
,
263 const std::string
& loc
, const std::string
& iconUrl
)
265 , friendlyName( fName
)
273 MediaServerDesc::~MediaServerDesc()
276 input_item_Release( inputItem
);
280 * MediaServerList class
282 MediaServerList::MediaServerList( services_discovery_t
* 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
) )
298 msg_Dbg( m_sd
, "Adding server '%s' with uuid '%s'", desc
->friendlyName
.c_str(), desc
->UDN
.c_str() );
302 p_input_item
= input_item_NewDirectory( desc
->location
.c_str(),
303 desc
->friendlyName
.c_str(),
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
);
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 )
324 p_input_item
= input_item_NewDirectory( psz_mrl
,
325 desc
->friendlyName
.c_str(),
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
);
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
)
360 void MediaServerList::parseNewServer( IXML_Document
*doc
, const std::string
&location
)
364 msg_Err( m_sd
, "Null IXML_Document" );
368 if ( location
.empty() )
370 msg_Err( m_sd
, "Empty location" );
374 const char* psz_base_url
= location
.c_str();
376 /* Try to extract baseURL */
377 IXML_NodeList
* p_url_list
= ixmlDocument_getElementsByTagName( doc
, "URLBase" );
380 if ( IXML_Node
* p_url_node
= ixmlNodeList_item( p_url_list
, 0 ) )
382 IXML_Node
* p_text_node
= ixmlNode_getFirstChild( p_url_node
);
384 psz_base_url
= ixmlNode_getNodeValue( p_text_node
);
386 ixmlNodeList_free( p_url_list
);
390 IXML_NodeList
* p_device_list
= ixmlDocument_getElementsByTagName( doc
, "device" );
392 if ( !p_device_list
)
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
)
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!" );
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 ) )
416 const char* psz_udn
= xml_getChildElementValue( p_device_element
,
420 msg_Warn( m_sd
, "No UDN!" );
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
);
431 const char* psz_friendly_name
=
432 xml_getChildElementValue( p_device_element
,
435 if ( !psz_friendly_name
)
437 msg_Dbg( m_sd
, "No friendlyName!" );
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
;
452 vlc_UrlParse( &url
, psz_base_url
);
454 char *psz_satip_channellist
= config_GetPsz("satip-channelist");
455 if( !psz_satip_channellist
) {
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
) ) {
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
);
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" );
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
);
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
);
502 p_server
->satIpHost
= url
.psz_host
;
503 p_server
->isSatIp
= true;
504 if ( !addServer( p_server
) )
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
);
516 /* Normally, fetch a playlist from the web,
517 * which will be processed by a lua script a bit later */
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
);
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
) ) {
537 free( psz_satip_channellist
);
538 vlc_UrlClean( &url
);
543 /* Check for ContentDirectory service. */
544 IXML_NodeList
* p_service_list
= ixmlElement_getElementsByTagName( p_device_element
, "service" );
545 if ( !p_service_list
)
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." );
558 int k
= strlen( CONTENT_DIRECTORY_SERVICE_TYPE
) - 1;
559 if ( strncmp( CONTENT_DIRECTORY_SERVICE_TYPE
,
560 psz_service_type
, k
) )
563 const char* psz_control_url
= xml_getChildElementValue( p_service_element
,
565 if ( !psz_control_url
)
567 msg_Warn( m_sd
, "No control url found." );
571 /* Try to browse content directory. */
572 char* psz_url
= ( char* ) malloc( strlen( psz_base_url
) + strlen( psz_control_url
) + 1 );
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
);
580 if ( unlikely( !p_server
) )
583 if ( !addServer( p_server
) )
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
)
601 IXML_NodeList
* p_icon_lists
= ixmlElement_getElementsByTagName( p_device_elem
, "iconList" );
602 if ( p_icon_lists
== NULL
)
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
)
619 unsigned int width
= atoi( widthStr
);
620 unsigned int height
= atoi( heightStr
);
621 if ( width
<= maxWidth
|| height
<= maxHeight
)
623 const char* iconUrl
= xml_getChildElementValue( p_icon
, "url" );
624 if ( iconUrl
== NULL
)
630 ixmlNodeList_free( p_icons
);
633 ixmlNodeList_free( p_icon_lists
);
635 if ( res
.empty() == false )
638 vlc_UrlParse( &url
, psz_base_url
);
640 if ( asprintf( &psz_url
, "%s://%s:%u%s", url
.psz_protocol
, url
.psz_host
, url
.i_port
, res
.c_str() ) < 0 )
647 vlc_UrlClean( &url
);
652 void MediaServerList::removeServer( const std::string
& udn
)
654 MediaServerDesc
* p_server
= getServer( udn
);
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())
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
)
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
;
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
) );
698 parseNewServer( p_description_doc
, UpnpDiscovery_get_Location_cstr( p_discovery
) );
699 ixmlDocument_free( p_description_doc
);
703 case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE
:
705 const UpnpDiscovery
* p_discovery
= ( const UpnpDiscovery
* )p_event
;
706 removeServer( UpnpDiscovery_get_DeviceID_cstr( p_discovery
) );
710 case UPNP_EVENT_SUBSCRIBE_COMPLETE
:
712 msg_Warn( m_sd
, "subscription complete" );
716 case UPNP_DISCOVERY_SEARCH_TIMEOUT
:
718 msg_Warn( m_sd
, "search timeout" );
722 case UPNP_EVENT_RECEIVED
:
723 case UPNP_EVENT_AUTORENEWAL_FAILED
:
724 case UPNP_EVENT_SUBSCRIPTION_EXPIRED
:
725 // Those are for the access part
730 msg_Err( m_sd
, "Unhandled event, please report ( type=%d )", event_type
);
735 return UPNP_E_SUCCESS
;
745 class ItemDescriptionHolder
748 struct Slave
: std::string
752 Slave(std::string
const &url
, slave_type type
) :
753 std::string(url
), type(type
)
758 std::set
<Slave
> slaves
;
760 const char* objectID
,
779 MEDIA_TYPE media_type
;
781 ItemDescriptionHolder()
785 bool init(IXML_Element
*itemElement
)
787 objectID
= ixmlElement_getAttribute( itemElement
, "id" );
790 title
= xml_getChildElementValue( itemElement
, "dc:title" );
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)
808 else if (strncmp(psz_media_type
, "object.item.audioItem", 21) == 0)
810 else if (strncmp(psz_media_type
, "object.item.imageItem", 21) == 0)
812 else if (strncmp(psz_media_type
, "object.container", 16 ) == 0)
813 media_type
= CONTAINER
;
819 void addSlave(const char *psz_slave
, slave_type type
)
822 slaves
.insert(Slave(psz_slave
, type
));
825 void addSubtitleSlave(IXML_Element
* p_resource
)
828 addSlave(ixmlElement_getAttribute( p_resource
, "pv:subtitleFileUri" ),
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
);
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
)
868 const char* psz_duration
= ixmlElement_getAttribute( p_resource
, "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 +
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
)
886 if( asprintf( &psz_url
, "upnp://%s?ObjectID=%s", psz_root
, objectID
) < 0 )
889 input_item_t
* p_item
= input_item_NewDirectory( psz_url
, title
, ITEM_NET
);
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
)
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
);
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
);
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
);
950 MediaServer::MediaServer( stream_t
*p_access
, input_item_node_t
*node
)
951 : m_psz_objectId( NULL
)
952 , m_access( p_access
)
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()
972 bool MediaServer::addContainer( IXML_Element
* containerElement
)
974 ItemDescriptionHolder holder
;
976 if ( holder
.init( containerElement
) == false )
979 input_item_t
* p_item
= holder
.createNewContainerItem( m_psz_root
);
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
);
989 bool MediaServer::addItem( IXML_Element
* itemElement
)
991 ItemDescriptionHolder holder
;
993 if (!holder
.init(itemElement
))
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
)
999 int list_lenght
= ixmlNodeList_length( p_resource_list
);
1000 if (list_lenght
<= 0 ) {
1001 ixmlNodeList_free( p_resource_list
);
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
)
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
:
1022 p_item
= holder
.createNewItem(p_resource
);
1025 case ItemDescriptionHolder::VIDEO
:
1026 case ItemDescriptionHolder::AUDIO
:
1027 holder
.setArtworkURL(p_resource
);
1029 case ItemDescriptionHolder::CONTAINER
:
1030 msg_Warn( m_access
, "Unexpected object.container in item enumeration" );
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
)
1040 p_item
= holder
.createNewItem(p_resource
);
1043 holder
.addSlave(xml_getChildElementValue( p_resource
, "res" ),
1047 ixmlNodeList_free( p_resource_list
);
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
);
1057 int MediaServer::sendActionCb( Upnp_EventType eventType
,
1058 UpnpEventPtr p_event
, void *p_cookie
)
1060 if( eventType
!= UPNP_CONTROL_ACTION_COMPLETE
)
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
) );
1070 *pp_sendActionResult
= ixmlParseBuffer( tmpStr
);
1071 ixmlFreeDOMString( tmpStr
);
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
;
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(),
1156 CONTENT_DIRECTORY_SERVICE_TYPE
,
1157 NULL
, /* ignored in SDK, must be NULL */
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
);
1175 * Fetches and parses the UPNP response
1177 bool MediaServer::fetchContents()
1179 IXML_Document
* p_response
= _browseAction( m_psz_objectId
,
1180 "BrowseDirectChildren",
1182 // Some servers don't understand "0" as "no-limit"
1183 "5000", /* RequestedCount */
1184 "" /* SortCriteria */
1188 msg_Err( m_access
, "No response from browse() action" );
1192 IXML_Document
* p_result
= parseBrowseResult( p_response
);
1194 ixmlDocument_free( p_response
);
1198 msg_Err( m_access
, "browse() response parsing failed" );
1203 msg_Dbg( m_access
, "Got DIDL document: %s", ixmlPrintDocument( p_result
) );
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
,
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
);
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
;
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
) )
1245 p_access
->p_sys
= p_sys
;
1246 p_sys
->p_upnp
= UpnpInstanceWrapper::get( p_this
);
1247 if ( !p_sys
->p_upnp
)
1250 return VLC_EGENERIC
;
1253 p_access
->pf_readdir
= ReadDirectory
;
1254 p_access
->pf_control
= access_vaDirectoryControlHelper
;
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();