Commit f7dfc8f1 authored by Hugo Beauzée-Luyssen's avatar Hugo Beauzée-Luyssen

UPnP: important rewrite

This is splitting the UPnP module in 2 parts:
- A service discovery module that is solely responsible for discovering
UPnP devices on the network
- An access module that will leverage the recently introduced
pf_readdir callback to list directories.

This removes the need for recursion and handling of all the items from
within the SD module.
parent f890621c
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
* Authors: Rémi Denis-Courmont <rem # videolan.org> (original plugin) * Authors: Rémi Denis-Courmont <rem # videolan.org> (original plugin)
* Christian Henz <henz # c-lab.de> * Christian Henz <henz # c-lab.de>
* Mirsal Ennaime <mirsal dot ennaime at gmail dot com> * Mirsal Ennaime <mirsal dot ennaime at gmail dot com>
* Hugo Beauzée-Luyssen <hugo@beauzee.fr>
* *
* UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink * UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink
* *
...@@ -32,13 +33,16 @@ ...@@ -32,13 +33,16 @@
# include "config.h" # include "config.h"
#endif #endif
#include "services_discovery/upnp.hpp" #include "upnp.hpp"
#include <vlc_access.h>
#include <vlc_plugin.h> #include <vlc_plugin.h>
#include <vlc_services_discovery.h> #include <vlc_services_discovery.h>
#include <vlc_url.h>
#include <assert.h> #include <assert.h>
#include <limits.h> #include <limits.h>
#include <algorithm>
/* /*
* Constants * Constants
...@@ -51,138 +55,55 @@ const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:Conte ...@@ -51,138 +55,55 @@ const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:Conte
*/ */
struct services_discovery_sys_t struct services_discovery_sys_t
{ {
UpnpClient_Handle client_handle; UpnpInstanceWrapper* p_upnp;
MediaServerList* p_server_list; SD::MediaServerList* p_server_list;
vlc_mutex_t callback_lock;
}; };
struct access_sys_t
{
UpnpInstanceWrapper* p_upnp;
};
UpnpInstanceWrapper* UpnpInstanceWrapper::s_instance;
vlc_mutex_t UpnpInstanceWrapper::s_lock = VLC_STATIC_MUTEX;
/* /*
* VLC callback prototypes * VLC callback prototypes
*/ */
static int Open( vlc_object_t* ); namespace SD
static void Close( vlc_object_t* ); {
static int Open( vlc_object_t* );
static void Close( vlc_object_t* );
}
namespace Access
{
static int Open( vlc_object_t* );
static void Close( vlc_object_t* );
}
VLC_SD_PROBE_HELPER( "upnp", "Universal Plug'n'Play", SD_CAT_LAN ) VLC_SD_PROBE_HELPER( "upnp", "Universal Plug'n'Play", SD_CAT_LAN )
/* /*
* Module descriptor * Module descriptor
*/ */
vlc_module_begin(); vlc_module_begin()
set_shortname( "UPnP" ); set_shortname( "UPnP" );
set_description( N_( "Universal Plug'n'Play" ) ); set_description( N_( "Universal Plug'n'Play" ) );
set_category( CAT_PLAYLIST ); set_category( CAT_PLAYLIST );
set_subcategory( SUBCAT_PLAYLIST_SD ); set_subcategory( SUBCAT_PLAYLIST_SD );
set_capability( "services_discovery", 0 ); set_capability( "services_discovery", 0 );
set_callbacks( Open, Close ); set_callbacks( SD::Open, SD::Close );
VLC_SD_PROBE_SUBMODULE
vlc_module_end();
/*
* Local prototypes
*/
static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data );
const char* xml_getChildElementValue( IXML_Element* p_parent,
const char* psz_tag_name );
const char* xml_getChildElementValue( IXML_Document* p_doc,
const char* psz_tag_name );
const char* xml_getChildElementAttributeValue( IXML_Element* p_parent,
const char* psz_tag_name,
const char* psz_attribute );
int xml_getNumber( IXML_Document* p_doc,
const char* psz_tag_name );
IXML_Document* parseBrowseResult( IXML_Document* p_doc );
/*
* Initializes UPNP instance.
*/
static int Open( vlc_object_t *p_this )
{
int i_res;
services_discovery_t *p_sd = ( services_discovery_t* )p_this;
services_discovery_sys_t *p_sys = ( services_discovery_sys_t * )
calloc( 1, sizeof( services_discovery_sys_t ) );
if( !( p_sd->p_sys = p_sys ) )
return VLC_ENOMEM;
#ifdef UPNP_ENABLE_IPV6
char* psz_miface;
psz_miface = var_InheritString( p_sd, "miface" );
msg_Info( p_sd, "Initializing libupnp on '%s' interface", psz_miface );
i_res = UpnpInit2( psz_miface, 0 );
free( psz_miface );
#else
/* If UpnpInit2 isnt available, initialize on first IPv4-capable interface */
i_res = UpnpInit( 0, 0 );
#endif
if( i_res != UPNP_E_SUCCESS )
{
msg_Err( p_sd, "Initialization failed: %s", UpnpGetErrorMessage( i_res ) );
free( p_sys );
return VLC_EGENERIC;
}
ixmlRelaxParser( 1 );
p_sys->p_server_list = new MediaServerList( p_sd );
vlc_mutex_init( &p_sys->callback_lock );
/* Register a control point */
i_res = UpnpRegisterClient( Callback, p_sd, &p_sys->client_handle );
if( i_res != UPNP_E_SUCCESS )
{
msg_Err( p_sd, "Client registration failed: %s", UpnpGetErrorMessage( i_res ) );
Close( (vlc_object_t*) p_sd );
return VLC_EGENERIC;
}
/* Search for media servers */
i_res = UpnpSearchAsync( p_sys->client_handle, 5,
MEDIA_SERVER_DEVICE_TYPE, p_sd );
if( i_res != UPNP_E_SUCCESS )
{
msg_Err( p_sd, "Error sending search request: %s", UpnpGetErrorMessage( i_res ) );
Close( (vlc_object_t*) p_sd );
return VLC_EGENERIC;
}
/* libupnp does not treat a maximum content length of 0 as unlimited
* until 64dedf (~ pupnp v1.6.7) and provides no sane way to discriminate
* between versions */
if( (i_res = UpnpSetMaxContentLength( INT_MAX )) != UPNP_E_SUCCESS )
{
msg_Err( p_sd, "Failed to set maximum content length: %s",
UpnpGetErrorMessage( i_res ));
Close( (vlc_object_t*) p_sd );
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
/*
* Releases resources.
*/
static void Close( vlc_object_t *p_this )
{
services_discovery_t *p_sd = ( services_discovery_t* )p_this;
UpnpUnRegisterClient( p_sd->p_sys->client_handle );
UpnpFinish();
delete p_sd->p_sys->p_server_list; add_submodule()
vlc_mutex_destroy( &p_sd->p_sys->callback_lock ); set_category( CAT_INPUT )
set_subcategory( SUBCAT_INPUT_ACCESS )
set_callbacks( Access::Open, Access::Close )
set_capability( "access", 0 )
free( p_sd->p_sys ); VLC_SD_PROBE_SUBMODULE
} vlc_module_end()
/* XML utility functions */
/* /*
* Returns the value of a child element, or NULL on error * Returns the value of a child element, or NULL on error
...@@ -207,51 +128,6 @@ const char* xml_getChildElementValue( IXML_Element* p_parent, ...@@ -207,51 +128,6 @@ const char* xml_getChildElementValue( IXML_Element* p_parent,
return ixmlNode_getNodeValue( p_text_node ); return ixmlNode_getNodeValue( p_text_node );
} }
/*
* Returns the value of a child element's attribute, or NULL on error
*/
const char* xml_getChildElementAttributeValue( IXML_Element* p_parent,
const char* psz_tag_name,
const char* psz_attribute )
{
assert( p_parent );
assert( psz_tag_name );
assert( psz_attribute );
IXML_NodeList* p_node_list;
p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name );
if ( !p_node_list ) return NULL;
IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
ixmlNodeList_free( p_node_list );
if ( !p_element ) return NULL;
return ixmlElement_getAttribute( (IXML_Element*) p_element, psz_attribute );
}
/*
* Returns the value of a child element, or NULL on error
*/
const char* xml_getChildElementValue( IXML_Document* p_doc,
const char* psz_tag_name )
{
assert( p_doc );
assert( psz_tag_name );
IXML_NodeList* p_node_list;
p_node_list = ixmlDocument_getElementsByTagName( p_doc, psz_tag_name );
if ( !p_node_list ) return NULL;
IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
ixmlNodeList_free( p_node_list );
if ( !p_element ) return NULL;
IXML_Node* p_text_node = ixmlNode_getFirstChild( p_element );
if ( !p_text_node ) return NULL;
return ixmlNode_getNodeValue( p_text_node );
}
/* /*
* Extracts the result document from a SOAP response * Extracts the result document from a SOAP response
*/ */
...@@ -259,7 +135,10 @@ IXML_Document* parseBrowseResult( IXML_Document* p_doc ) ...@@ -259,7 +135,10 @@ IXML_Document* parseBrowseResult( IXML_Document* p_doc )
{ {
assert( p_doc ); assert( p_doc );
const char* psz_raw_didl = xml_getChildElementValue( p_doc, "Result" ); // ixml*_getElementsByTagName will ultimately only case the pointer to a Node
// pointer, and pass it to a private function. Don't bother have a IXML_Document
// version of getChildElementValue
const char* psz_raw_didl = xml_getChildElementValue( (IXML_Element*)p_doc, "Result" );
if( !psz_raw_didl ) if( !psz_raw_didl )
return NULL; return NULL;
...@@ -302,561 +181,532 @@ IXML_Document* parseBrowseResult( IXML_Document* p_doc ) ...@@ -302,561 +181,532 @@ IXML_Document* parseBrowseResult( IXML_Document* p_doc )
return (IXML_Document*)p_node; return (IXML_Document*)p_node;
} }
namespace SD
{
/* /*
* Get the number value from a SOAP response * Initializes UPNP instance.
*/ */
int xml_getNumber( IXML_Document* p_doc, static int Open( vlc_object_t *p_this )
const char* psz_tag_name )
{ {
assert( p_doc ); services_discovery_t *p_sd = ( services_discovery_t* )p_this;
assert( psz_tag_name ); services_discovery_sys_t *p_sys = ( services_discovery_sys_t * )
calloc( 1, sizeof( services_discovery_sys_t ) );
const char* psz = xml_getChildElementValue( p_doc, psz_tag_name ); if( !( p_sd->p_sys = p_sys ) )
return VLC_ENOMEM;
if( !psz ) p_sys->p_server_list = new(std::nothrow) SD::MediaServerList( p_sd );
return 0; if ( unlikely( p_sys->p_server_list == NULL ) )
{
return VLC_ENOMEM;
}
char *psz_end; p_sys->p_upnp = UpnpInstanceWrapper::get( p_this, SD::MediaServerList::Callback, p_sys->p_server_list );
long l = strtol( psz, &psz_end, 10 ); if ( !p_sys->p_upnp )
{
Close( p_this );
return VLC_EGENERIC;
}
if( *psz_end || l < 0 || l > INT_MAX ) /* Search for media servers */
return 0; int i_res = UpnpSearchAsync( p_sys->p_upnp->handle(), 5,
MEDIA_SERVER_DEVICE_TYPE, p_sys->p_upnp );
if( i_res != UPNP_E_SUCCESS )
{
msg_Err( p_sd, "Error sending search request: %s", UpnpGetErrorMessage( i_res ) );
Close( p_this );
return VLC_EGENERIC;
}
return (int)l; return VLC_SUCCESS;
} }
/* /*
* Handles all UPnP events * Releases resources.
*/ */
static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data ) static void Close( vlc_object_t *p_this )
{ {
services_discovery_t* p_sd = ( services_discovery_t* ) p_user_data; services_discovery_t *p_sd = ( services_discovery_t* )p_this;
services_discovery_sys_t* p_sys = p_sd->p_sys; services_discovery_sys_t *p_sys = p_sd->p_sys;
vlc_mutex_locker locker( &p_sys->callback_lock );
switch( event_type ) if (p_sys->p_upnp)
{ p_sys->p_upnp->release( true );
case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE: delete p_sys->p_server_list;
case UPNP_DISCOVERY_SEARCH_RESULT: free( p_sys );
{ }
struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
IXML_Document *p_description_doc = 0; MediaServerDesc::MediaServerDesc(const std::string& udn, const std::string& fName, const std::string& loc)
: UDN( udn )
, friendlyName( fName )
, location( loc )
, inputItem( NULL )
{
}
int i_res; MediaServerDesc::~MediaServerDesc()
i_res = UpnpDownloadXmlDoc( p_discovery->Location, &p_description_doc ); {
if ( i_res != UPNP_E_SUCCESS ) if (inputItem)
{ vlc_gc_decref( inputItem );
msg_Warn( p_sd, "Could not download device description! " }
"Fetching data from %s failed: %s",
p_discovery->Location, UpnpGetErrorMessage( i_res ) );
return i_res;
}
MediaServer::parseDeviceDescription( p_description_doc, /*
p_discovery->Location, p_sd ); * MediaServerList class
*/
MediaServerList::MediaServerList( services_discovery_t* p_sd )
: p_sd_( p_sd )
{
vlc_mutex_init( &lock_ );
}
ixmlDocument_free( p_description_doc ); MediaServerList::~MediaServerList()
} {
break; vlc_delete_all(list_);
vlc_mutex_destroy( &lock_ );
}
case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE: bool MediaServerList::addServer( MediaServerDesc* desc )
{ {
struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event; vlc_mutex_locker lock( &lock_ );
input_item_t* p_input_item = NULL;
if ( getServer( desc->UDN ) )
return false;
p_sys->p_server_list->removeServer( p_discovery->DeviceId ); msg_Dbg( p_sd_, "Adding server '%s' with uuid '%s'", desc->friendlyName.c_str(), desc->UDN.c_str() );
} char* psz_mrl;
break; if( asprintf(&psz_mrl, "upnp://%s?ObjectID=%s", desc->location.c_str(), desc->UDN.c_str() ) < 0 )
return false;
case UPNP_EVENT_RECEIVED: p_input_item = input_item_NewWithType( psz_mrl, desc->friendlyName.c_str(), 0,
{ NULL, 0, -1, ITEM_TYPE_NODE );
Upnp_Event* p_e = ( Upnp_Event* )p_event; free( psz_mrl );
if ( !p_input_item )
return false;
desc->inputItem = p_input_item;
input_item_SetDescription( p_input_item, desc->UDN.c_str() );
services_discovery_AddItem( p_sd_, p_input_item, NULL );
list_.push_back( desc );
return true;
}
MediaServer* p_server = p_sys->p_server_list->getServerBySID( p_e->Sid ); MediaServerDesc* MediaServerList::getServer( const std::string& udn )
if ( p_server ) p_server->fetchContents(); {
} std::vector<MediaServerDesc*>::const_iterator it = list_.begin();
break; std::vector<MediaServerDesc*>::const_iterator ite = list_.end();
case UPNP_EVENT_AUTORENEWAL_FAILED: for ( ; it != ite; ++it )
case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
{ {
/* Re-subscribe. */ if( udn == (*it)->UDN )
{
Upnp_Event_Subscribe* p_s = ( Upnp_Event_Subscribe* )p_event; return *it;
}
MediaServer* p_server = p_sys->p_server_list->getServerBySID( p_s->Sid );
if ( p_server ) p_server->subscribeToContentDirectory();
}
break;
case UPNP_EVENT_SUBSCRIBE_COMPLETE:
msg_Warn( p_sd, "subscription complete" );
break;
case UPNP_DISCOVERY_SEARCH_TIMEOUT:
msg_Warn( p_sd, "search timeout" );
break;
default:
msg_Err( p_sd, "Unhandled event, please report ( type=%d )", event_type );
break;
} }
return NULL;
return UPNP_E_SUCCESS;
} }
void MediaServerList::parseNewServer( IXML_Document *doc, const std::string &location )
/*
* Local class implementations.
*/
/*
* MediaServer
*/
void MediaServer::parseDeviceDescription( IXML_Document* p_doc,
const char* p_location,
services_discovery_t* p_sd )
{ {
if ( !p_doc ) if ( !doc )
{ {
msg_Err( p_sd, "Null IXML_Document" ); msg_Err( p_sd_, "Null IXML_Document" );
return; return;
} }
if ( !p_location ) if ( location.empty() )
{ {
msg_Err( p_sd, "Null location" ); msg_Err( p_sd_, "Empty location" );
return; return;
} }
const char* psz_base_url = p_location; const char* psz_base_url = location.c_str();
/* Try to extract baseURL */ /* Try to extract baseURL */
IXML_NodeList* p_url_list = ixmlDocument_getElementsByTagName( p_doc, "URLBase" ); IXML_NodeList* p_url_list = ixmlDocument_getElementsByTagName( doc, "URLBase" );
if ( p_url_list ) if ( p_url_list )
{ {
if ( IXML_Node* p_url_node = ixmlNodeList_item( p_url_list, 0 ) ) if ( IXML_Node* p_url_node = ixmlNodeList_item( p_url_list, 0 ) )
{ {
IXML_Node* p_text_node = ixmlNode_getFirstChild( p_url_node ); IXML_Node* p_text_node = ixmlNode_getFirstChild( p_url_node );
if ( p_text_node ) psz_base_url = ixmlNode_getNodeValue( p_text_node ); if ( p_text_node )
psz_base_url = ixmlNode_getNodeValue( p_text_node );
} }
ixmlNodeList_free( p_url_list ); ixmlNodeList_free( p_url_list );
} }
/* Get devices */ /* Get devices */
IXML_NodeList* p_device_list = IXML_NodeList* p_device_list = ixmlDocument_getElementsByTagName( doc, "device" );
ixmlDocument_getElementsByTagName( p_doc, "device" );
if ( p_device_list ) if ( !p_device_list )
return;
for ( unsigned int i = 0; i < ixmlNodeList_length( p_device_list ); i++ )
{ {
for ( unsigned int i = 0; i < ixmlNodeList_length( p_device_list ); i++ ) IXML_Element* p_device_element = ( IXML_Element* ) ixmlNodeList_item( p_device_list, i );
if( !p_device_element )
continue;
const char* psz_device_type = xml_getChildElementValue( p_device_element, "deviceType" );
if ( !psz_device_type )
{ {
IXML_Element* p_device_element = msg_Warn( p_sd_, "No deviceType found!" );
( IXML_Element* ) ixmlNodeList_item( p_device_list, i ); continue;
}
if( !p_device_element ) if ( strncmp( MEDIA_SERVER_DEVICE_TYPE, psz_device_type,
continue; strlen( MEDIA_SERVER_DEVICE_TYPE ) - 1 ) )
continue;
const char* psz_device_type = const char* psz_udn = xml_getChildElementValue( p_device_element,
xml_getChildElementValue( p_device_element, "deviceType" ); "UDN" );
if ( !psz_udn )
{
msg_Warn( p_sd_, "No UDN!" );
continue;
}
if ( !psz_device_type ) /* Check if server is already added */
{ if ( p_sd_->p_sys->p_server_list->getServer( psz_udn ) )
msg_Warn( p_sd, "No deviceType found!" ); {
continue; msg_Warn( p_sd_, "Server with uuid '%s' already exists.", psz_udn );
} continue;
}
if ( strncmp( MEDIA_SERVER_DEVICE_TYPE, psz_device_type, const char* psz_friendly_name =
strlen( MEDIA_SERVER_DEVICE_TYPE ) - 1 ) != 0 ) xml_getChildElementValue( p_device_element,
continue; "friendlyName" );
const char* psz_udn = xml_getChildElementValue( p_device_element, if ( !psz_friendly_name )
"UDN" ); {
if ( !psz_udn ) msg_Dbg( p_sd_, "No friendlyName!" );
{ continue;
msg_Warn( p_sd, "No UDN!" ); }
continue;
}
/* Check if server is already added */ // We now have basic info, we need to get the content browsing url
if ( p_sd->p_sys->p_server_list->getServer( psz_udn ) != 0 ) // so the access module can browse without fetching the manifest again
{
msg_Warn( p_sd, "Server with uuid '%s' already exists.", psz_udn );
continue;
}
const char* psz_friendly_name = /* Check for ContentDirectory service. */
xml_getChildElementValue( p_device_element, IXML_NodeList* p_service_list = ixmlElement_getElementsByTagName( p_device_element, "service" );
"friendlyName" ); if ( !p_service_list )
continue;
for ( unsigned int j = 0; j < ixmlNodeList_length( p_service_list ); j++ )
{
IXML_Element* p_service_element = (IXML_Element*)ixmlNodeList_item( p_service_list, j );
if ( !psz_friendly_name ) const char* psz_service_type = xml_getChildElementValue( p_service_element, "serviceType" );
if ( !psz_service_type )
{ {
msg_Dbg( p_sd, "No friendlyName!" ); msg_Warn( p_sd_, "No service type found." );
continue; continue;
} }
MediaServer* p_server = new MediaServer( psz_udn, int k = strlen( CONTENT_DIRECTORY_SERVICE_TYPE ) - 1;
psz_friendly_name, p_sd ); if ( strncmp( CONTENT_DIRECTORY_SERVICE_TYPE,
psz_service_type, k ) )
continue;
if ( !p_sd->p_sys->p_server_list->addServer( p_server ) ) const char* psz_control_url = xml_getChildElementValue( p_service_element,
"controlURL" );
if ( !psz_control_url )
{ {
delete p_server; msg_Warn( p_sd_, "No control url found." );
p_server = 0;
continue; continue;
} }
/* Check for ContentDirectory service. */ /* Try to browse content directory. */
IXML_NodeList* p_service_list = char* psz_url = ( char* ) malloc( strlen( psz_base_url ) + strlen( psz_control_url ) + 1 );
ixmlElement_getElementsByTagName( p_device_element, if ( psz_url )
"service" );
if ( p_service_list )
{ {
for ( unsigned int j = 0; if ( UpnpResolveURL( psz_base_url, psz_control_url, psz_url ) == UPNP_E_SUCCESS )
j < ixmlNodeList_length( p_service_list ); j++ )
{ {
IXML_Element* p_service_element = SD::MediaServerDesc* p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn,
( IXML_Element* ) ixmlNodeList_item( p_service_list, j ); psz_friendly_name, psz_url );
free( psz_url );
if ( unlikely( !p_server ) )
break;
const char* psz_service_type = if ( !addServer( p_server ) )
xml_getChildElementValue( p_service_element,
"serviceType" );
if ( !psz_service_type )
{ {
msg_Warn( p_sd, "No service type found." ); delete p_server;
continue; continue;
} }
}
else
free( psz_url );
}
}
ixmlNodeList_free( p_service_list );
}
ixmlNodeList_free( p_device_list );
}
int k = strlen( CONTENT_DIRECTORY_SERVICE_TYPE ) - 1; void MediaServerList::removeServer( const std::string& udn )
if ( strncmp( CONTENT_DIRECTORY_SERVICE_TYPE, {
psz_service_type, k ) != 0 ) vlc_mutex_locker lock( &lock_ );
continue;
p_server->_i_content_directory_service_version = MediaServerDesc* p_server = getServer( udn );
psz_service_type[k]; if ( !p_server )
return;
const char* psz_event_sub_url = msg_Dbg( p_sd_, "Removing server '%s'", p_server->friendlyName.c_str() );
xml_getChildElementValue( p_service_element,
"eventSubURL" );
if ( !psz_event_sub_url )
{
msg_Warn( p_sd, "No event subscription url found." );
continue;
}
const char* psz_control_url = assert(p_server->inputItem);
xml_getChildElementValue( p_service_element, services_discovery_RemoveItem( p_sd_, p_server->inputItem );
"controlURL" );
if ( !psz_control_url )
{
msg_Warn( p_sd, "No control url found." );
continue;
}
/* Try to subscribe to ContentDirectory service */ std::vector<MediaServerDesc*>::iterator it = std::find(list_.begin(), list_.end(), p_server);
if (it != list_.end())
{
list_.erase( it );
}
delete p_server;
}
char* psz_url = ( char* ) malloc( strlen( psz_base_url ) + /*
strlen( psz_event_sub_url ) + 1 ); * Handles servers listing UPnP events
if ( psz_url ) */
{ int MediaServerList::Callback( Upnp_EventType event_type, void* p_event, void* p_user_data )
if ( UpnpResolveURL( psz_base_url, psz_event_sub_url, psz_url ) == {
UPNP_E_SUCCESS ) MediaServerList* self = static_cast<MediaServerList*>( p_user_data );
{ services_discovery_t* p_sd = self->p_sd_;
p_server->setContentDirectoryEventURL( psz_url );
p_server->subscribeToContentDirectory(); switch( event_type )
} {
case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
free( psz_url ); case UPNP_DISCOVERY_SEARCH_RESULT:
} {
struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
IXML_Document *p_description_doc = NULL;
int i_res;
i_res = UpnpDownloadXmlDoc( p_discovery->Location, &p_description_doc );
if ( i_res != UPNP_E_SUCCESS )
{
msg_Warn( p_sd, "Could not download device description! "
"Fetching data from %s failed: %s",
p_discovery->Location, UpnpGetErrorMessage( i_res ) );
return i_res;
}
self->parseNewServer( p_description_doc, p_discovery->Location );
ixmlDocument_free( p_description_doc );
}
break;
/* Try to browse content directory. */ case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
{
struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
psz_url = ( char* ) malloc( strlen( psz_base_url ) + self->removeServer( p_discovery->DeviceId );
strlen( psz_control_url ) + 1 );
if ( psz_url )
{
if ( UpnpResolveURL( psz_base_url, psz_control_url, psz_url ) ==
UPNP_E_SUCCESS )
{
p_server->setContentDirectoryControlURL( psz_url );
p_server->fetchContents();
}
free( psz_url );
}
}
ixmlNodeList_free( p_service_list );
}
}
ixmlNodeList_free( p_device_list );
} }
} break;
MediaServer::MediaServer( const char* psz_udn,
const char* psz_friendly_name,
services_discovery_t* p_sd )
{
_p_sd = p_sd;
_UDN = psz_udn; case UPNP_EVENT_SUBSCRIBE_COMPLETE:
_friendly_name = psz_friendly_name; msg_Warn( p_sd, "subscription complete" );
break;
_p_contents = NULL; case UPNP_DISCOVERY_SEARCH_TIMEOUT:
_p_input_item = NULL; msg_Warn( p_sd, "search timeout" );
_i_content_directory_service_version = 1; break;
}
MediaServer::~MediaServer() case UPNP_EVENT_RECEIVED:
{ case UPNP_EVENT_AUTORENEWAL_FAILED:
delete _p_contents; case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
} // Those are for the access part
break;
const char* MediaServer::getUDN() const default:
{ msg_Err( p_sd, "Unhandled event, please report ( type=%d )", event_type );
return _UDN.c_str(); break;
} }
const char* MediaServer::getFriendlyName() const return UPNP_E_SUCCESS;
{
return _friendly_name.c_str();
} }
void MediaServer::setContentDirectoryEventURL( const char* psz_url )
{
_content_directory_event_url = psz_url;
} }
const char* MediaServer::getContentDirectoryEventURL() const namespace Access
{ {
return _content_directory_event_url.c_str();
}
void MediaServer::setContentDirectoryControlURL( const char* psz_url ) MediaServer::MediaServer(const char *psz_url, access_t *p_access, input_item_node_t *node)
: url_( psz_url )
, access_( p_access )
, node_( node )
{ {
_content_directory_control_url = psz_url;
} }
const char* MediaServer::getContentDirectoryControlURL() const void MediaServer::addItem(const char *objectID, const char *title )
{ {
return _content_directory_control_url.c_str(); vlc_url_t url;
} vlc_UrlParse( &url, url_.c_str(), '?' );
char* psz_url;
/** if (asprintf( &psz_url, "upnp://%s://%s:%u%s?ObjectID=%s", url.psz_protocol,
* Subscribes current client handle to Content Directory Service. url.psz_host, url.i_port ? url.i_port : 80, url.psz_path, objectID ) < 0 )
* CDS exports the server shares to clients.
*/
void MediaServer::subscribeToContentDirectory()
{
const char* psz_url = getContentDirectoryEventURL();
if ( !psz_url )
{ {
msg_Dbg( _p_sd, "No subscription url set!" ); vlc_UrlClean( &url );
return; return ;
} }
vlc_UrlClean( &url );
int i_timeout = 1810; input_item_t* p_item = input_item_NewWithType( psz_url, title, 0, NULL,
Upnp_SID sid; 0, -1, ITEM_TYPE_NODE );
free( psz_url);
int i_res = UpnpSubscribe( _p_sd->p_sys->client_handle, psz_url, &i_timeout, sid ); if ( !p_item )
return;
input_item_CopyOptions( node_->p_item, p_item );
input_item_node_AppendItem( node_, p_item );
input_item_Release( p_item );
}
if ( i_res == UPNP_E_SUCCESS ) void MediaServer::addItem(const char* title, const char*, const char*,
{ mtime_t duration, const char* psz_url)
_i_subscription_timeout = i_timeout; {
memcpy( _subscription_id, sid, sizeof( Upnp_SID ) ); input_item_t* p_item = input_item_NewExt( psz_url, title, 0, NULL, 0, duration );
} input_item_node_AppendItem( node_, p_item );
else input_item_Release( p_item );
{
msg_Dbg( _p_sd, "Subscribe failed: '%s': %s",
getFriendlyName(), UpnpGetErrorMessage( i_res ) );
}
} }
/*
* Constructs UpnpAction to browse available content. /* Access part */
*/
IXML_Document* MediaServer::_browseAction( const char* psz_object_id_, IXML_Document* MediaServer::_browseAction( const char* psz_object_id_,
const char* psz_browser_flag_, const char* psz_browser_flag_,
const char* psz_filter_, const char* psz_filter_,
const char* psz_starting_index_,
const char* psz_requested_count_, const char* psz_requested_count_,
const char* psz_sort_criteria_ ) const char* psz_sort_criteria_ )
{ {
IXML_Document* p_action = 0; IXML_Document* p_action = NULL;
IXML_Document* p_response = 0; IXML_Document* p_response = NULL;
const char* psz_url = getContentDirectoryControlURL(); const char* psz_url = url_.c_str();
if ( !psz_url ) if ( url_.empty() )
{ {
msg_Dbg( _p_sd, "No subscription url set!" ); msg_Dbg( access_, "No subscription url set!" );
return 0; return NULL;
} }
char* psz_service_type = strdup( CONTENT_DIRECTORY_SERVICE_TYPE );
psz_service_type[strlen( psz_service_type ) - 1] =
_i_content_directory_service_version;
int i_res; int i_res;
i_res = UpnpAddToAction( &p_action, "Browse", i_res = UpnpAddToAction( &p_action, "Browse",
psz_service_type, "ObjectID", psz_object_id_ ); CONTENT_DIRECTORY_SERVICE_TYPE, "ObjectID", psz_object_id_ );
if ( i_res != UPNP_E_SUCCESS ) if ( i_res != UPNP_E_SUCCESS )
{ {
msg_Dbg( _p_sd, "AddToAction 'ObjectID' failed: %s", msg_Dbg( access_, "AddToAction 'ObjectID' failed: %s",
UpnpGetErrorMessage( i_res ) ); UpnpGetErrorMessage( i_res ) );
goto browseActionCleanup; goto browseActionCleanup;
} }
i_res = UpnpAddToAction( &p_action, "Browse", i_res = UpnpAddToAction( &p_action, "Browse",
psz_service_type, "BrowseFlag", psz_browser_flag_ ); CONTENT_DIRECTORY_SERVICE_TYPE, "StartingIndex", "0" );
if ( i_res != UPNP_E_SUCCESS ) if ( i_res != UPNP_E_SUCCESS )
{ {
msg_Dbg( _p_sd, "AddToAction 'BrowseFlag' failed: %s", msg_Dbg( access_, "AddToAction 'StartingIndex' failed: %s",
UpnpGetErrorMessage( i_res ) ); UpnpGetErrorMessage( i_res ) );
goto browseActionCleanup; goto browseActionCleanup;
} }
i_res = UpnpAddToAction( &p_action, "Browse", i_res = UpnpAddToAction( &p_action, "Browse",
psz_service_type, "Filter", psz_filter_ ); CONTENT_DIRECTORY_SERVICE_TYPE, "BrowseFlag", psz_browser_flag_ );
if ( i_res != UPNP_E_SUCCESS ) if ( i_res != UPNP_E_SUCCESS )
{ {
msg_Dbg( _p_sd, "AddToAction 'Filter' failed: %s", msg_Dbg( access_, "AddToAction 'BrowseFlag' failed: %s",
UpnpGetErrorMessage( i_res ) ); UpnpGetErrorMessage( i_res ) );
goto browseActionCleanup; goto browseActionCleanup;
} }
i_res = UpnpAddToAction( &p_action, "Browse", i_res = UpnpAddToAction( &p_action, "Browse",
psz_service_type, "StartingIndex", psz_starting_index_ ); CONTENT_DIRECTORY_SERVICE_TYPE, "Filter", psz_filter_ );
if ( i_res != UPNP_E_SUCCESS ) if ( i_res != UPNP_E_SUCCESS )
{ {
msg_Dbg( _p_sd, "AddToAction 'StartingIndex' failed: %s", msg_Dbg( access_, "AddToAction 'Filter' failed: %s",
UpnpGetErrorMessage( i_res ) ); UpnpGetErrorMessage( i_res ) );
goto browseActionCleanup; goto browseActionCleanup;
} }
i_res = UpnpAddToAction( &p_action, "Browse", i_res = UpnpAddToAction( &p_action, "Browse",
psz_service_type, "RequestedCount", psz_requested_count_ ); CONTENT_DIRECTORY_SERVICE_TYPE, "RequestedCount", psz_requested_count_ );
if ( i_res != UPNP_E_SUCCESS ) if ( i_res != UPNP_E_SUCCESS )
{ {
msg_Dbg( _p_sd, "AddToAction 'RequestedCount' failed: %s", msg_Dbg( access_, "AddToAction 'RequestedCount' failed: %s",
UpnpGetErrorMessage( i_res ) ); UpnpGetErrorMessage( i_res ) );
goto browseActionCleanup; goto browseActionCleanup;
} }
i_res = UpnpAddToAction( &p_action, "Browse", i_res = UpnpAddToAction( &p_action, "Browse",
psz_service_type, "SortCriteria", psz_sort_criteria_ ); CONTENT_DIRECTORY_SERVICE_TYPE, "SortCriteria", psz_sort_criteria_ );
if ( i_res != UPNP_E_SUCCESS ) if ( i_res != UPNP_E_SUCCESS )
{ {
msg_Dbg( _p_sd, "AddToAction 'SortCriteria' failed: %s", msg_Dbg( access_, "AddToAction 'SortCriteria' failed: %s",
UpnpGetErrorMessage( i_res ) ); UpnpGetErrorMessage( i_res ) );
goto browseActionCleanup; goto browseActionCleanup;
} }
i_res = UpnpSendAction( _p_sd->p_sys->client_handle, i_res = UpnpSendAction( access_->p_sys->p_upnp->handle(),
psz_url, psz_url,
psz_service_type, CONTENT_DIRECTORY_SERVICE_TYPE,
0, /* ignored in SDK, must be NULL */ NULL, /* ignored in SDK, must be NULL */
p_action, p_action,
&p_response ); &p_response );
if ( i_res != UPNP_E_SUCCESS ) if ( i_res != UPNP_E_SUCCESS )
{ {
msg_Err( _p_sd, "%s when trying the send() action with URL: %s", msg_Err( access_, "%s when trying the send() action with URL: %s",
UpnpGetErrorMessage( i_res ), psz_url ); UpnpGetErrorMessage( i_res ), psz_url );
ixmlDocument_free( p_response ); ixmlDocument_free( p_response );
p_response = 0; p_response = NULL;
} }
browseActionCleanup: browseActionCleanup:
free( psz_service_type );
ixmlDocument_free( p_action ); ixmlDocument_free( p_action );
return p_response; return p_response;
} }
void MediaServer::fetchContents()
{
/* Delete previous contents to prevent duplicate entries */
if ( _p_contents )
{
delete _p_contents;
services_discovery_RemoveItem( _p_sd, _p_input_item );
services_discovery_AddItem( _p_sd, _p_input_item, NULL );
}
Container* root = new Container( 0, "0", getFriendlyName() );
_fetchContents( root, 0 );
_p_contents = root;
_p_contents->setInputItem( _p_input_item );
_buildPlaylist( _p_contents, NULL );
}
/* /*
* Fetches and parses the UPNP response * Fetches and parses the UPNP response
*/ */
bool MediaServer::_fetchContents( Container* p_parent, int i_offset ) bool MediaServer::fetchContents()
{ {
if (!p_parent) const char* objectID = "";
{ vlc_url_t url;
msg_Err( _p_sd, "No parent" ); vlc_UrlParse( &url, access_->psz_location, '?');
return false;
}
char* psz_starting_index; if ( url.psz_option && !strncmp( url.psz_option, "ObjectID=", strlen( "ObjectID=" ) ) )
if( asprintf( &psz_starting_index, "%d", i_offset ) < 0 )
{ {
msg_Err( _p_sd, "asprintf error:%d", i_offset ); objectID = &url.psz_option[strlen( "ObjectID=" )];
return false;
} }
IXML_Document* p_response = _browseAction( p_parent->getObjectID(), IXML_Document* p_response = _browseAction( objectID,
"BrowseDirectChildren", "BrowseDirectChildren",
"id,dc:title,res," /* Filter */ "id,dc:title,res," /* Filter */
"sec:CaptionInfo,sec:CaptionInfoEx," "sec:CaptionInfo,sec:CaptionInfoEx,"
"pv:subtitlefile", "pv:subtitlefile",
psz_starting_index, /* StartingIndex */
"0", /* RequestedCount */ "0", /* RequestedCount */
"" /* SortCriteria */ "" /* SortCriteria */
); );
free( psz_starting_index ); vlc_UrlClean( &url );
if ( !p_response ) if ( !p_response )
{ {
msg_Err( _p_sd, "No response from browse() action" ); msg_Err( access_, "No response from browse() action" );
return false; return false;
} }
IXML_Document* p_result = parseBrowseResult( p_response ); IXML_Document* p_result = parseBrowseResult( p_response );
int i_number_returned = xml_getNumber( p_response, "NumberReturned" );
int i_total_matches = xml_getNumber( p_response , "TotalMatches" );
#ifndef NDEBUG
msg_Dbg( _p_sd, "i_offset[%d]i_number_returned[%d]_total_matches[%d]\n",
i_offset, i_number_returned, i_total_matches );
#endif
ixmlDocument_free( p_response ); ixmlDocument_free( p_response );
if ( !p_result ) if ( !p_result )
{ {
msg_Err( _p_sd, "browse() response parsing failed" ); msg_Err( access_, "browse() response parsing failed" );
return false; return false;
} }
#ifndef NDEBUG #ifndef NDEBUG
msg_Dbg( _p_sd, "Got DIDL document: %s", ixmlPrintDocument( p_result ) ); msg_Dbg( access_, "Got DIDL document: %s", ixmlPrintDocument( p_result ) );
#endif #endif
IXML_NodeList* containerNodeList = IXML_NodeList* containerNodeList =
...@@ -864,11 +714,9 @@ bool MediaServer::_fetchContents( Container* p_parent, int i_offset ) ...@@ -864,11 +714,9 @@ bool MediaServer::_fetchContents( Container* p_parent, int i_offset )
if ( containerNodeList ) if ( containerNodeList )
{ {
for ( unsigned int i = 0; for ( unsigned int i = 0; i < ixmlNodeList_length( containerNodeList ); i++ )
i < ixmlNodeList_length( containerNodeList ); i++ )
{ {
IXML_Element* containerElement = IXML_Element* containerElement = (IXML_Element*)ixmlNodeList_item( containerNodeList, i );
( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
const char* objectID = ixmlElement_getAttribute( containerElement, const char* objectID = ixmlElement_getAttribute( containerElement,
"id" ); "id" );
...@@ -877,13 +725,9 @@ bool MediaServer::_fetchContents( Container* p_parent, int i_offset ) ...@@ -877,13 +725,9 @@ bool MediaServer::_fetchContents( Container* p_parent, int i_offset )
const char* title = xml_getChildElementValue( containerElement, const char* title = xml_getChildElementValue( containerElement,
"dc:title" ); "dc:title" );
if ( !title ) if ( !title )
continue; continue;
addItem(objectID, title);
Container* container = new Container( p_parent, objectID, title );
p_parent->addContainer( container );
_fetchContents( container, 0 );
} }
ixmlNodeList_free( containerNodeList ); ixmlNodeList_free( containerNodeList );
} }
...@@ -944,443 +788,188 @@ bool MediaServer::_fetchContents( Container* p_parent, int i_offset ) ...@@ -944,443 +788,188 @@ bool MediaServer::_fetchContents( Container* p_parent, int i_offset )
i_seconds ); i_seconds );
} }
Item* item = new Item( p_parent, objectID, title, psz_resource_url, psz_subtitles, i_duration ); addItem( title, objectID, psz_subtitles, i_duration, psz_resource_url );
p_parent->addItem( item );
} }
ixmlNodeList_free( p_resource_list ); ixmlNodeList_free( p_resource_list );
} }
else continue; else
continue;
} }
ixmlNodeList_free( itemNodeList ); ixmlNodeList_free( itemNodeList );
} }
ixmlDocument_free( p_result ); ixmlDocument_free( p_result );
if( i_offset + i_number_returned < i_total_matches )
return _fetchContents( p_parent, i_offset + i_number_returned );
return true; return true;
} }
// TODO: Create a permanent fix for the item duplication bug. The current fix static int ReadDirectory( access_t *p_access, input_item_node_t* p_node )
// is essentially only a small hack. Although it fixes the problem, it introduces
// annoying cosmetic issues with the playlist. For example, when the UPnP Server
// rebroadcasts it's directory structure, the VLC Client deletes the old directory
// structure, causing the user to go back to the root node of the directory. The
// directory is then rebuilt, and the user is forced to traverse through the directory
// to find the item they were looking for. Some servers may not push the directory
// structure too often, but we cannot rely on this fix.
//
// I have thought up another fix, but this would require certain features to
// be present within the VLC services discovery. Currently, services_discovery_AddItem
// does not allow the programmer to nest items. It only allows a "2 deep" scope.
// An example of the limitation is below:
//
// Root Directory
// + Item 1
// + Item 2
//
// services_discovery_AddItem will not let the programmer specify a child-node to
// insert items into, so we would not be able to do the following:
//
// Root Directory
// + Item 1
// + Sub Item 1
// + Item 2
// + Sub Item 1 of Item 2
// + Sub-Sub Item 1 of Sub Item 1
//
// This creates a HUGE limitation on what we are able to do. If we were able to do
// the above, we could simply preserve the old directory listing, and compare what items
// do not exist in the new directory listing, then remove them from the shown listing using
// services_discovery_RemoveItem. If new files were introduced within an already existing
// container, we could simply do so with services_discovery_AddItem.
/*
* Builds playlist based on available input items.
*/
void MediaServer::_buildPlaylist( Container* p_parent, input_item_node_t *p_input_node )
{ {
bool b_send = p_input_node == NULL; MediaServer server( p_access->psz_location, p_access, p_node );
if( b_send )
p_input_node = input_item_node_Create( p_parent->getInputItem() );
for ( unsigned int i = 0; i < p_parent->getNumContainers(); i++ )
{
Container* p_container = p_parent->getContainer( i );
input_item_t* p_input_item = input_item_New( "vlc://nop",
p_container->getTitle() );
input_item_node_t *p_new_node =
input_item_node_AppendItem( p_input_node, p_input_item );
p_container->setInputItem( p_input_item ); if ( !server.fetchContents() )
_buildPlaylist( p_container, p_new_node ); return VLC_EGENERIC;
} return VLC_SUCCESS;
}
for ( unsigned int i = 0; i < p_parent->getNumItems(); i++ ) static int Control( access_t *, int i_query, va_list args )
{
switch ( i_query )
{ {
Item* p_item = p_parent->getItem( i ); case ACCESS_CAN_SEEK:
case ACCESS_CAN_FASTSEEK:
char **ppsz_opts = NULL; case ACCESS_CAN_PAUSE:
char *psz_input_slave = p_item->buildInputSlaveOption(); case ACCESS_CAN_CONTROL_PACE:
if( psz_input_slave ) *va_arg( args, bool* ) = false;
{ break;
ppsz_opts = (char**)malloc( 2 * sizeof( char* ) );
ppsz_opts[0] = psz_input_slave;
ppsz_opts[1] = p_item->buildSubTrackIdOption();
}
input_item_t* p_input_item = input_item_NewExt( p_item->getResource(),
p_item->getTitle(),
psz_input_slave ? 2 : 0,
psz_input_slave ? ppsz_opts : NULL,
VLC_INPUT_OPTION_TRUSTED, /* XXX */
p_item->getDuration() );
assert( p_input_item ); case ACCESS_GET_SIZE:
if( ppsz_opts ) {
{ *va_arg( args, uint64_t * ) = 0;
free( ppsz_opts[0] ); break;
free( ppsz_opts[1] ); }
free( ppsz_opts ); case ACCESS_GET_PTS_DELAY:
*va_arg( args, int64_t * ) = 0;
break;
psz_input_slave = NULL; case ACCESS_SET_PAUSE_STATE:
} /* Nothing to do */
break;
input_item_node_AppendItem( p_input_node, p_input_item ); default:
p_item->setInputItem( p_input_item ); return VLC_EGENERIC;
} }
return VLC_SUCCESS;
if( b_send )
input_item_node_PostAndDelete( p_input_node );
} }
void MediaServer::setInputItem( input_item_t* p_input_item ) static int Open( vlc_object_t *p_this )
{ {
if( _p_input_item == p_input_item ) access_t* p_access = (access_t*)p_this;
return; access_sys_t* p_sys = new(std::nothrow) access_sys_t;
if ( unlikely( !p_sys ) )
return VLC_ENOMEM;
if( _p_input_item ) p_access->p_sys = p_sys;
vlc_gc_decref( _p_input_item ); p_sys->p_upnp = UpnpInstanceWrapper::get( p_this, NULL, NULL );
if ( !p_sys->p_upnp )
{
delete p_sys;
return VLC_EGENERIC;
}
vlc_gc_incref( p_input_item ); p_access->pf_readdir = ReadDirectory;
_p_input_item = p_input_item; ACCESS_SET_CALLBACKS( NULL, NULL, Control, NULL );
}
input_item_t* MediaServer::getInputItem() const return VLC_SUCCESS;
{
return _p_input_item;
} }
bool MediaServer::compareSID( const char* psz_sid ) static void Close( vlc_object_t* p_this )
{ {
return ( strncmp( _subscription_id, psz_sid, sizeof( Upnp_SID ) ) == 0 ); access_t* p_access = (access_t*)p_this;
p_access->p_sys->p_upnp->release( false );
delete p_access->p_sys;
} }
/*
* MediaServerList class
*/
MediaServerList::MediaServerList( services_discovery_t* p_sd )
{
_p_sd = p_sd;
} }
MediaServerList::~MediaServerList() UpnpInstanceWrapper::UpnpInstanceWrapper()
: handle_( -1 )
, opaque_( NULL )
, callback_( NULL )
, refcount_( 0 )
{ {
for ( unsigned int i = 0; i < _list.size(); i++ )
{
delete _list[i];
}
} }
bool MediaServerList::addServer( MediaServer* p_server ) UpnpInstanceWrapper::~UpnpInstanceWrapper()
{ {
input_item_t* p_input_item = NULL; UpnpUnRegisterClient( handle_ );
if ( getServer( p_server->getUDN() ) != 0 ) return false; UpnpFinish();
msg_Dbg( _p_sd, "Adding server '%s' with uuid '%s'", p_server->getFriendlyName(), p_server->getUDN() );
p_input_item = input_item_New( "vlc://nop", p_server->getFriendlyName() );
input_item_SetDescription( p_input_item, p_server->getUDN() );
p_server->setInputItem( p_input_item );
services_discovery_AddItem( _p_sd, p_input_item, NULL );
_list.push_back( p_server );
return true;
} }
MediaServer* MediaServerList::getServer( const char* psz_udn ) UpnpInstanceWrapper *UpnpInstanceWrapper::get(vlc_object_t *p_obj, Upnp_FunPtr callback, SD::MediaServerList *opaque)
{ {
MediaServer* p_result = 0; vlc_mutex_locker lock( &s_lock );
if ( s_instance == NULL )
for ( unsigned int i = 0; i < _list.size(); i++ )
{ {
if( strcmp( psz_udn, _list[i]->getUDN() ) == 0 ) UpnpInstanceWrapper* instance = new(std::nothrow) UpnpInstanceWrapper;
if ( unlikely( !instance ) )
return NULL;
#ifdef UPNP_ENABLE_IPV6
char* psz_miface = var_InheritString( p_obj, "miface" );
msg_Info( p_obj, "Initializing libupnp on '%s' interface", psz_miface );
int i_res = UpnpInit2( psz_miface, 0 );
free( psz_miface );
#else
/* If UpnpInit2 isnt available, initialize on first IPv4-capable interface */
int i_res = UpnpInit( 0, 0 );
#endif
if( i_res != UPNP_E_SUCCESS )
{ {
p_result = _list[i]; msg_Err( p_obj, "Initialization failed: %s", UpnpGetErrorMessage( i_res ) );
break; delete instance;
return NULL;
} }
}
return p_result; ixmlRelaxParser( 1 );
}
MediaServer* MediaServerList::getServerBySID( const char* psz_sid )
{
MediaServer* p_server = 0;
for ( unsigned int i = 0; i < _list.size(); i++ ) /* Register a control point */
{ i_res = UpnpRegisterClient( Callback, instance, &instance->handle_ );
if ( _list[i]->compareSID( psz_sid ) ) if( i_res != UPNP_E_SUCCESS )
{ {
p_server = _list[i]; msg_Err( p_obj, "Client registration failed: %s", UpnpGetErrorMessage( i_res ) );
break; delete instance;
return NULL;
} }
}
return p_server;
}
void MediaServerList::removeServer( const char* psz_udn ) /* libupnp does not treat a maximum content length of 0 as unlimited
{ * until 64dedf (~ pupnp v1.6.7) and provides no sane way to discriminate
MediaServer* p_server = getServer( psz_udn ); * between versions */
if ( !p_server ) return; if( (i_res = UpnpSetMaxContentLength( INT_MAX )) != UPNP_E_SUCCESS )
msg_Dbg( _p_sd, "Removing server '%s'", p_server->getFriendlyName() );
services_discovery_RemoveItem( _p_sd, p_server->getInputItem() );
std::vector<MediaServer*>::iterator it;
for ( it = _list.begin(); it != _list.end(); ++it )
{
if ( *it == p_server )
{ {
_list.erase( it ); msg_Err( p_obj, "Failed to set maximum content length: %s",
delete p_server; UpnpGetErrorMessage( i_res ));
break; delete instance;
return NULL;
} }
s_instance = instance;
} }
} s_instance->refcount_++;
// This assumes a single UPNP SD instance
if (callback && opaque)
/*
* Item class
*/
Item::Item( Container* p_parent,
const char* psz_object_id, const char* psz_title,
const char* psz_resource, const char* psz_subtitles,
mtime_t i_duration )
{
_parent = p_parent;
_objectID = psz_object_id;
_title = psz_title;
_resource = psz_resource;
_subtitles = psz_subtitles ? psz_subtitles : "";
_duration = i_duration;
_p_input_item = NULL;
}
Item::~Item()
{
if( _p_input_item )
vlc_gc_decref( _p_input_item );
}
const char* Item::getObjectID() const
{
return _objectID.c_str();
}
const char* Item::getTitle() const
{
return _title.c_str();
}
const char* Item::getResource() const
{
return _resource.c_str();
}
const char* Item::getSubtitles() const
{
if( !_subtitles.size() )
return NULL;
return _subtitles.c_str();
}
mtime_t Item::getDuration() const
{
return _duration;
}
char* Item::buildInputSlaveOption() const
{
const char *psz_subtitles = getSubtitles();
const char *psz_scheme_delim = "://";
const char *psz_sub_opt_fmt = ":input-slave=%s/%s://%s";
const char *psz_demux = "subtitle";
char *psz_uri_scheme = NULL;
const char *psz_scheme_end = NULL;
const char *psz_uri_location = NULL;
char *psz_input_slave = NULL;
size_t i_scheme_len;
if( !psz_subtitles )
return NULL;
psz_scheme_end = strstr( psz_subtitles, psz_scheme_delim );
/* subtitles not being an URI would make no sense */
if( !psz_scheme_end )
return NULL;
i_scheme_len = psz_scheme_end - psz_subtitles;
psz_uri_scheme = (char*)malloc( i_scheme_len + 1 );
if( !psz_uri_scheme )
return NULL;
memcpy( psz_uri_scheme, psz_subtitles, i_scheme_len );
psz_uri_scheme[i_scheme_len] = '\0';
/* If the subtitles try to force a vlc demux,
* then something is very wrong */
if( strchr( psz_uri_scheme, '/' ) )
{ {
free( psz_uri_scheme ); assert(!s_instance->callback_ && !s_instance->opaque_);
return NULL; s_instance->opaque_ = opaque;
s_instance->callback_ = callback;
} }
return s_instance;
psz_uri_location = psz_scheme_end + strlen( psz_scheme_delim );
if( -1 == asprintf( &psz_input_slave, psz_sub_opt_fmt,
psz_uri_scheme, psz_demux, psz_uri_location ) )
psz_input_slave = NULL;
free( psz_uri_scheme );
return psz_input_slave;
}
char* Item::buildSubTrackIdOption() const
{
return strdup( ":sub-track-id=2" );
}
void Item::setInputItem( input_item_t* p_input_item )
{
if( _p_input_item == p_input_item )
return;
if( _p_input_item )
vlc_gc_decref( _p_input_item );
vlc_gc_incref( p_input_item );
_p_input_item = p_input_item;
}
/*
* Container class
*/
Container::Container( Container* p_parent,
const char* psz_object_id,
const char* psz_title )
{
_parent = p_parent;
_objectID = psz_object_id;
_title = psz_title;
_p_input_item = NULL;
} }
Container::~Container() void UpnpInstanceWrapper::release(bool isSd)
{ {
for ( unsigned int i = 0; i < _containers.size(); i++ ) vlc_mutex_locker lock( &s_lock );
if ( isSd )
{ {
delete _containers[i]; callback_ = NULL;
opaque_ = NULL;
} }
if (--s_instance->refcount_ == 0)
for ( unsigned int i = 0; i < _items.size(); i++ )
{ {
delete _items[i]; delete s_instance;
s_instance = NULL;
} }
if( _p_input_item )
vlc_gc_decref( _p_input_item );
} }
void Container::addItem( Item* item ) UpnpClient_Handle UpnpInstanceWrapper::handle() const
{ {
_items.push_back( item ); return handle_;
} }
void Container::addContainer( Container* p_container ) int UpnpInstanceWrapper::Callback(Upnp_EventType event_type, void *p_event, void *p_user_data)
{ {
_containers.push_back( p_container ); UpnpInstanceWrapper* self = static_cast<UpnpInstanceWrapper*>( p_user_data );
} vlc_mutex_locker lock( &self->s_lock );
if ( !self->callback_ )
const char* Container::getObjectID() const return 0;
{ self->callback_( event_type, p_event, self->opaque_ );
return _objectID.c_str();
}
const char* Container::getTitle() const
{
return _title.c_str();
}
unsigned int Container::getNumItems() const
{
return _items.size();
}
unsigned int Container::getNumContainers() const
{
return _containers.size();
}
Item* Container::getItem( unsigned int i_index ) const
{
if ( i_index < _items.size() ) return _items[i_index];
return 0;
}
Container* Container::getContainer( unsigned int i_index ) const
{
if ( i_index < _containers.size() ) return _containers[i_index];
return 0; return 0;
} }
Container* Container::getParent()
{
return _parent;
}
void Container::setInputItem( input_item_t* p_input_item )
{
if( _p_input_item == p_input_item )
return;
if( _p_input_item )
vlc_gc_decref( _p_input_item );
vlc_gc_incref( p_input_item );
_p_input_item = p_input_item;
}
input_item_t* Container::getInputItem() const
{
return _p_input_item;
}
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
* Authors: Rémi Denis-Courmont <rem # videolan.org> (original plugin) * Authors: Rémi Denis-Courmont <rem # videolan.org> (original plugin)
* Christian Henz <henz # c-lab.de> * Christian Henz <henz # c-lab.de>
* Mirsal Ennaime <mirsal dot ennaime at gmail dot com> * Mirsal Ennaime <mirsal dot ennaime at gmail dot com>
* Hugo Beauzée-Luyssen <hugo@beauzee.fr>
* *
* UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink * UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink
* *
...@@ -33,62 +34,55 @@ ...@@ -33,62 +34,55 @@
#include <vlc_common.h> #include <vlc_common.h>
// Classes namespace SD
class Container;
class MediaServer
{ {
public: class MediaServerList;
}
static void parseDeviceDescription( IXML_Document* p_doc,
const char* psz_location, /*
services_discovery_t* p_sd ); * libUpnp allows only one instance per process, so we have to share one for
* both SD & Access module
MediaServer( const char* psz_udn, * Since the callback is bound to the UpnpClient_Handle, we have to register
const char* psz_friendly_name, * a wrapper callback, in order for the access module to be able to initialize
services_discovery_t* p_sd ); * libUpnp first.
* When a SD wishes to use libUpnp, it will provide its own callback, that the
~MediaServer(); * wrapper will forward.
* This way, we always have a register callback & a client handle.
const char* getUDN() const; */
const char* getFriendlyName() const; class UpnpInstanceWrapper
{
void setContentDirectoryEventURL( const char* psz_url ); public:
const char* getContentDirectoryEventURL() const; // This increases the refcount before returning the instance
static UpnpInstanceWrapper* get(vlc_object_t* p_obj, Upnp_FunPtr callback, SD::MediaServerList *opaque);
void setContentDirectoryControlURL( const char* psz_url ); void release(bool isSd);
const char* getContentDirectoryControlURL() const; UpnpClient_Handle handle() const;
void subscribeToContentDirectory();
void fetchContents();
void setInputItem( input_item_t* p_input_item );
input_item_t* getInputItem() const;
bool compareSID( const char* psz_sid );
private: private:
static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data );
bool _fetchContents( Container* p_parent, int i_starting_index ); UpnpInstanceWrapper();
void _buildPlaylist( Container* p_container, input_item_node_t *p_item_node ); ~UpnpInstanceWrapper();
IXML_Document* _browseAction( const char*, const char*,
const char*, const char*, const char*, const char* );
services_discovery_t* _p_sd;
Container* _p_contents;
input_item_t* _p_input_item;
std::string _UDN; private:
std::string _friendly_name; static UpnpInstanceWrapper* s_instance;
static vlc_mutex_t s_lock;
UpnpClient_Handle handle_;
SD::MediaServerList* opaque_;
Upnp_FunPtr callback_;
int refcount_;
};
std::string _content_directory_event_url; namespace SD
std::string _content_directory_control_url; {
int _i_subscription_timeout; struct MediaServerDesc
int _i_content_directory_service_version; {
Upnp_SID _subscription_id; MediaServerDesc(const std::string& udn, const std::string& fName, const std::string& loc);
~MediaServerDesc();
std::string UDN;
std::string friendlyName;
std::string location;
input_item_t* inputItem;
}; };
...@@ -99,87 +93,45 @@ public: ...@@ -99,87 +93,45 @@ public:
MediaServerList( services_discovery_t* p_sd ); MediaServerList( services_discovery_t* p_sd );
~MediaServerList(); ~MediaServerList();
bool addServer( MediaServer* p_server ); bool addServer(MediaServerDesc *desc );
void removeServer( const char* psz_udn ); void removeServer(const std::string &udn );
MediaServerDesc* getServer( const std::string& udn );
MediaServer* getServer( const char* psz_udn ); static int Callback( Upnp_EventType event_type, void* p_event, void* p_user_data );
MediaServer* getServerBySID( const char* psz_sid );
private: private:
void parseNewServer( IXML_Document* doc, const std::string& location );
services_discovery_t* _p_sd; private:
services_discovery_t* p_sd_;
std::vector<MediaServer*> _list; std::vector<MediaServerDesc*> list_;
vlc_mutex_t lock_;
}; };
}
class Item namespace Access
{ {
public:
Item( Container* parent,
const char* objectID,
const char* title,
const char* subtitles,
const char* resource,
mtime_t duration );
~Item();
const char* getObjectID() const;
const char* getTitle() const;
const char* getResource() const;
const char* getSubtitles() const;
char* buildInputSlaveOption() const;
char* buildSubTrackIdOption() const;
mtime_t getDuration() const;
void setInputItem( input_item_t* p_input_item );
private:
input_item_t* _p_input_item;
Container* _parent;
std::string _objectID;
std::string _title;
std::string _resource;
std::string _subtitles;
mtime_t _duration;
};
class Container class MediaServer
{ {
public: public:
MediaServer( const char* psz_url, access_t* p_access, input_item_node_t* node );
Container( Container* parent, const char* objectID, const char* title ); bool fetchContents();
~Container();
void addItem( Item* item );
void addContainer( Container* container );
const char* getObjectID() const;
const char* getTitle() const;
unsigned int getNumItems() const;
unsigned int getNumContainers() const;
Item* getItem( unsigned int i ) const;
Container* getContainer( unsigned int i ) const;
Container* getParent();
void setInputItem( input_item_t* p_input_item );
input_item_t* getInputItem() const;
private: private:
MediaServer(const MediaServer&);
MediaServer& operator=(const MediaServer&);
input_item_t* _p_input_item; void addItem(const char* objectID, const char* title);
void addItem(const char* title, const char* psz_objectID, const char* psz_subtitles, mtime_t duration, const char* psz_url );
Container* _parent; IXML_Document* _browseAction(const char*, const char*,
const char*, const char*, const char* );
std::string _objectID; private:
std::string _title; const std::string url_;
std::vector<Item*> _items; access_t* access_;
std::vector<Container*> _containers; input_item_node_t* node_;
}; };
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment