mirror of
https://code.videolan.org/videolan/vlc
synced 2024-09-04 09:11:33 +02:00
![Rafaël Carré](/assets/img/avatar_default.png)
Setting that argument to NULL will make playlist_CreateNode() behave like previously. That way we can create only one input per pair of node (for local playlist, media library, and service discovery) Previous behaviour was to create 2 inputs with the same i_id member, but we would store both input in a binary search tree (sorted by i_id), and that kind of tree MUST NOT have 2 items with the same key, else we will get some bad memory corruption when the wrong input is removed from the tree (the other being left in the tree while the memory referred by it was freed). Note that this breaks ABI
1093 lines
29 KiB
C++
1093 lines
29 KiB
C++
/*****************************************************************************
|
|
* Upnp_intell.cpp : UPnP discovery module (Intel SDK)
|
|
*****************************************************************************
|
|
* Copyright (C) 2004-2006 the VideoLAN team
|
|
* $Id$
|
|
*
|
|
* Authors: Rémi Denis-Courmont <rem # videolan.org> (original plugin)
|
|
* Christian Henz <henz # c-lab.de>
|
|
*
|
|
* UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
/*
|
|
\TODO: Debug messages: "__FILE__, __LINE__" ok ???, Wrn/Err ???
|
|
\TODO: Change names to VLC standard ???
|
|
*/
|
|
|
|
|
|
#include <vector>
|
|
#include <string>
|
|
|
|
#include <upnp/upnp.h>
|
|
#include <upnp/upnptools.h>
|
|
|
|
#undef PACKAGE_NAME
|
|
#include <vlc/vlc.h>
|
|
#include <vlc_playlist.h>
|
|
#include "vlc_strings.h"
|
|
|
|
|
|
// VLC handle
|
|
|
|
struct services_discovery_sys_t
|
|
{
|
|
playlist_item_t *p_node_cat;
|
|
playlist_item_t *p_node_one;
|
|
};
|
|
|
|
|
|
// Constants
|
|
|
|
const char* MEDIA_SERVER_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaServer:1";
|
|
const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:ContentDirectory:1";
|
|
|
|
|
|
// Classes
|
|
|
|
class MediaServer;
|
|
class MediaServerList;
|
|
class Item;
|
|
class Container;
|
|
|
|
// Cookie that is passed to the callback
|
|
|
|
typedef struct
|
|
{
|
|
services_discovery_t* serviceDiscovery;
|
|
UpnpClient_Handle clientHandle;
|
|
MediaServerList* serverList;
|
|
} Cookie;
|
|
|
|
|
|
// Class definitions...
|
|
|
|
class Lockable
|
|
{
|
|
public:
|
|
|
|
Lockable( Cookie* c )
|
|
{
|
|
vlc_mutex_init( c->serviceDiscovery, &_mutex );
|
|
}
|
|
|
|
~Lockable()
|
|
{
|
|
vlc_mutex_destroy( &_mutex );
|
|
}
|
|
|
|
void lock() { vlc_mutex_lock( &_mutex ); }
|
|
void unlock() { vlc_mutex_unlock( &_mutex ); }
|
|
|
|
private:
|
|
|
|
vlc_mutex_t _mutex;
|
|
};
|
|
|
|
|
|
class Locker
|
|
{
|
|
public:
|
|
Locker( Lockable* l )
|
|
{
|
|
_lockable = l;
|
|
_lockable->lock();
|
|
}
|
|
|
|
~Locker()
|
|
{
|
|
_lockable->unlock();
|
|
}
|
|
|
|
private:
|
|
Lockable* _lockable;
|
|
};
|
|
|
|
|
|
class MediaServer
|
|
{
|
|
public:
|
|
|
|
static void parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie );
|
|
|
|
MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie );
|
|
~MediaServer();
|
|
|
|
const char* getUDN() const;
|
|
const char* getFriendlyName() const;
|
|
|
|
void setContentDirectoryEventURL( const char* url );
|
|
const char* getContentDirectoryEventURL() const;
|
|
|
|
void setContentDirectoryControlURL( const char* url );
|
|
const char* getContentDirectoryControlURL() const;
|
|
|
|
void subscribeToContentDirectory();
|
|
void fetchContents();
|
|
|
|
void setPlaylistNode( playlist_item_t* node );
|
|
|
|
bool compareSID( const char* sid );
|
|
|
|
private:
|
|
|
|
bool _fetchContents( Container* parent );
|
|
void _buildPlaylist( Container* container );
|
|
IXML_Document* _browseAction( const char*, const char*, const char*, const char*, const char*, const char* );
|
|
|
|
Cookie* _cookie;
|
|
|
|
Container* _contents;
|
|
playlist_item_t* _playlistNode;
|
|
|
|
std::string _UDN;
|
|
std::string _friendlyName;
|
|
|
|
std::string _contentDirectoryEventURL;
|
|
std::string _contentDirectoryControlURL;
|
|
|
|
int _subscriptionTimeOut;
|
|
Upnp_SID _subscriptionID;
|
|
};
|
|
|
|
|
|
class MediaServerList
|
|
{
|
|
public:
|
|
|
|
MediaServerList( Cookie* cookie );
|
|
~MediaServerList();
|
|
|
|
bool addServer( MediaServer* s );
|
|
void removeServer( const char* UDN );
|
|
|
|
MediaServer* getServer( const char* UDN );
|
|
MediaServer* getServerBySID( const char* );
|
|
|
|
private:
|
|
|
|
Cookie* _cookie;
|
|
|
|
std::vector<MediaServer*> _list;
|
|
};
|
|
|
|
|
|
class Item
|
|
{
|
|
public:
|
|
|
|
Item( Container* parent, const char* objectID, const char* title, const char* resource );
|
|
|
|
const char* getObjectID() const;
|
|
const char* getTitle() const;
|
|
const char* getResource() const;
|
|
|
|
void setPlaylistNode( playlist_item_t* node );
|
|
playlist_item_t* getPlaylistNode() const ;
|
|
|
|
private:
|
|
|
|
playlist_item_t* _playlistNode;
|
|
|
|
Container* _parent;
|
|
std::string _objectID;
|
|
std::string _title;
|
|
std::string _resource;
|
|
};
|
|
|
|
|
|
class Container
|
|
{
|
|
public:
|
|
|
|
Container( Container* parent, const char* objectID, const char* title );
|
|
~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;
|
|
|
|
void setPlaylistNode( playlist_item_t* node );
|
|
playlist_item_t* getPlaylistNode() const;
|
|
|
|
private:
|
|
|
|
playlist_item_t* _playlistNode;
|
|
|
|
Container* _parent;
|
|
|
|
std::string _objectID;
|
|
std::string _title;
|
|
std::vector<Item*> _items;
|
|
std::vector<Container*> _containers;
|
|
};
|
|
|
|
|
|
// VLC callback prototypes
|
|
|
|
static int Open( vlc_object_t* );
|
|
static void Close( vlc_object_t* );
|
|
static void Run( services_discovery_t *p_sd );
|
|
|
|
// Module descriptor
|
|
|
|
vlc_module_begin();
|
|
set_shortname( "UPnP" );
|
|
set_description( _( "Universal Plug'n'Play discovery ( Intel SDK )" ) );
|
|
set_category( CAT_PLAYLIST );
|
|
set_subcategory( SUBCAT_PLAYLIST_SD );
|
|
set_capability( "services_discovery", 0 );
|
|
set_callbacks( Open, Close );
|
|
vlc_module_end();
|
|
|
|
|
|
// More prototypes...
|
|
|
|
static Lockable* CallbackLock;
|
|
static int Callback( Upnp_EventType eventType, void* event, void* pCookie );
|
|
|
|
const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName );
|
|
IXML_Document* parseBrowseResult( IXML_Document* doc );
|
|
|
|
|
|
// VLC callbacks...
|
|
|
|
static int Open( vlc_object_t *p_this )
|
|
{
|
|
services_discovery_t *p_sd = ( services_discovery_t* )p_this;
|
|
services_discovery_sys_t *p_sys = ( services_discovery_sys_t * )
|
|
malloc( sizeof( services_discovery_sys_t ) );
|
|
|
|
p_sd->pf_run = Run;
|
|
p_sd->p_sys = p_sys;
|
|
|
|
/* Create our playlist node */
|
|
playlist_NodesPairCreate( pl_Get( p_sd ), _("Devices"),
|
|
&p_sys->p_node_cat, &p_sys->p_node_one,
|
|
VLC_TRUE );
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static void Close( vlc_object_t *p_this )
|
|
{
|
|
services_discovery_t *p_sd = ( services_discovery_t* )p_this;
|
|
services_discovery_sys_t *p_sys = p_sd->p_sys;
|
|
|
|
playlist_NodeDelete( pl_Get( p_sd ), p_sys->p_node_one, VLC_TRUE,
|
|
VLC_TRUE );
|
|
playlist_NodeDelete( pl_Get( p_sd ), p_sys->p_node_cat, VLC_TRUE,
|
|
VLC_TRUE );
|
|
|
|
free( p_sys );
|
|
}
|
|
|
|
static void Run( services_discovery_t* p_sd )
|
|
{
|
|
int res;
|
|
|
|
res = UpnpInit( 0, 0 );
|
|
if( res != UPNP_E_SUCCESS )
|
|
{
|
|
msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
|
|
return;
|
|
}
|
|
|
|
Cookie cookie;
|
|
cookie.serviceDiscovery = p_sd;
|
|
cookie.serverList = new MediaServerList( &cookie );
|
|
|
|
CallbackLock = new Lockable( &cookie );
|
|
|
|
res = UpnpRegisterClient( Callback, &cookie, &cookie.clientHandle );
|
|
if( res != UPNP_E_SUCCESS )
|
|
{
|
|
msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
|
|
goto shutDown;
|
|
}
|
|
|
|
res = UpnpSearchAsync( cookie.clientHandle, 5, MEDIA_SERVER_DEVICE_TYPE, &cookie );
|
|
if( res != UPNP_E_SUCCESS )
|
|
{
|
|
msg_Err( p_sd, "%s", UpnpGetErrorMessage( res ) );
|
|
goto shutDown;
|
|
}
|
|
|
|
msg_Dbg( p_sd, "UPnP discovery started" );
|
|
while( !p_sd->b_die )
|
|
{
|
|
msleep( 500 );
|
|
}
|
|
|
|
msg_Dbg( p_sd, "UPnP discovery stopped" );
|
|
|
|
shutDown:
|
|
UpnpFinish();
|
|
delete cookie.serverList;
|
|
delete CallbackLock;
|
|
}
|
|
|
|
|
|
// XML utility functions:
|
|
|
|
// Returns the value of a child element, or 0 on error
|
|
const char* xml_getChildElementValue( IXML_Element* parent, const char* tagName )
|
|
{
|
|
if ( !parent ) return 0;
|
|
if ( !tagName ) return 0;
|
|
|
|
char* s = strdup( tagName );
|
|
IXML_NodeList* nodeList = ixmlElement_getElementsByTagName( parent, s );
|
|
free( s );
|
|
if ( !nodeList ) return 0;
|
|
|
|
IXML_Node* element = ixmlNodeList_item( nodeList, 0 );
|
|
ixmlNodeList_free( nodeList );
|
|
if ( !element ) return 0;
|
|
|
|
IXML_Node* textNode = ixmlNode_getFirstChild( element );
|
|
if ( !textNode ) return 0;
|
|
|
|
return ixmlNode_getNodeValue( textNode );
|
|
}
|
|
|
|
// Extracts the result document from a SOAP response
|
|
IXML_Document* parseBrowseResult( IXML_Document* doc )
|
|
{
|
|
if ( !doc ) return 0;
|
|
|
|
IXML_NodeList* resultList = ixmlDocument_getElementsByTagName( doc, "Result" );
|
|
if ( !resultList ) return 0;
|
|
|
|
IXML_Node* resultNode = ixmlNodeList_item( resultList, 0 );
|
|
|
|
ixmlNodeList_free( resultList );
|
|
|
|
if ( !resultNode ) return 0;
|
|
|
|
IXML_Node* textNode = ixmlNode_getFirstChild( resultNode );
|
|
if ( !textNode ) return 0;
|
|
|
|
const char* resultString = ixmlNode_getNodeValue( textNode );
|
|
char* resultXML = strdup( resultString );
|
|
|
|
resolve_xml_special_chars( resultXML );
|
|
|
|
IXML_Document* browseDoc = ixmlParseBuffer( resultXML );
|
|
|
|
free( resultXML );
|
|
|
|
return browseDoc;
|
|
}
|
|
|
|
|
|
// Handles all UPnP events
|
|
static int Callback( Upnp_EventType eventType, void* event, void* pCookie )
|
|
{
|
|
Locker locker( CallbackLock );
|
|
|
|
Cookie* cookie = ( Cookie* )pCookie;
|
|
|
|
switch( eventType ) {
|
|
|
|
case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
|
|
case UPNP_DISCOVERY_SEARCH_RESULT:
|
|
{
|
|
struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
|
|
|
|
IXML_Document *descriptionDoc = 0;
|
|
|
|
int res;
|
|
res = UpnpDownloadXmlDoc( discovery->Location, &descriptionDoc );
|
|
if ( res != UPNP_E_SUCCESS )
|
|
{
|
|
msg_Dbg( cookie->serviceDiscovery, "%s:%d: Could not download device description!", __FILE__, __LINE__ );
|
|
return res;
|
|
}
|
|
|
|
MediaServer::parseDeviceDescription( descriptionDoc, discovery->Location, cookie );
|
|
|
|
ixmlDocument_free( descriptionDoc );
|
|
}
|
|
break;
|
|
|
|
case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
|
|
{
|
|
struct Upnp_Discovery* discovery = ( struct Upnp_Discovery* )event;
|
|
|
|
cookie->serverList->removeServer( discovery->DeviceId );
|
|
}
|
|
break;
|
|
|
|
case UPNP_EVENT_RECEIVED:
|
|
{
|
|
Upnp_Event* e = ( Upnp_Event* )event;
|
|
|
|
MediaServer* server = cookie->serverList->getServerBySID( e->Sid );
|
|
if ( server ) server->fetchContents();
|
|
}
|
|
break;
|
|
|
|
case UPNP_EVENT_AUTORENEWAL_FAILED:
|
|
case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
|
|
{
|
|
// Re-subscribe...
|
|
|
|
Upnp_Event_Subscribe* s = ( Upnp_Event_Subscribe* )event;
|
|
|
|
MediaServer* server = cookie->serverList->getServerBySID( s->Sid );
|
|
if ( server ) server->subscribeToContentDirectory();
|
|
}
|
|
break;
|
|
|
|
case UPNP_EVENT_SUBSCRIBE_COMPLETE:
|
|
msg_Warn( cookie->serviceDiscovery, "subscription complete" );
|
|
break;
|
|
|
|
case UPNP_DISCOVERY_SEARCH_TIMEOUT:
|
|
msg_Warn( cookie->serviceDiscovery, "search timeout" );
|
|
break;
|
|
|
|
default:
|
|
msg_Dbg( cookie->serviceDiscovery, "%s:%d: DEBUG: UNHANDLED EVENT ( TYPE=%d )", __FILE__, __LINE__, eventType );
|
|
break;
|
|
}
|
|
|
|
return UPNP_E_SUCCESS;
|
|
}
|
|
|
|
|
|
// Class implementations...
|
|
|
|
// MediaServer...
|
|
|
|
void MediaServer::parseDeviceDescription( IXML_Document* doc, const char* location, Cookie* cookie )
|
|
{
|
|
if ( !doc ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: NULL", __FILE__, __LINE__ ); return; }
|
|
if ( !location ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: NULL", __FILE__, __LINE__ ); return; }
|
|
|
|
const char* baseURL = location;
|
|
|
|
// Try to extract baseURL
|
|
|
|
IXML_NodeList* urlList = ixmlDocument_getElementsByTagName( doc, "baseURL" );
|
|
if ( urlList )
|
|
{
|
|
if ( IXML_Node* urlNode = ixmlNodeList_item( urlList, 0 ) )
|
|
{
|
|
IXML_Node* textNode = ixmlNode_getFirstChild( urlNode );
|
|
if ( textNode ) baseURL = ixmlNode_getNodeValue( textNode );
|
|
}
|
|
|
|
ixmlNodeList_free( urlList );
|
|
}
|
|
|
|
// Get devices
|
|
|
|
IXML_NodeList* deviceList = ixmlDocument_getElementsByTagName( doc, "device" );
|
|
if ( deviceList )
|
|
{
|
|
|
|
for ( unsigned int i = 0; i < ixmlNodeList_length( deviceList ); i++ )
|
|
{
|
|
IXML_Element* deviceElement = ( IXML_Element* )ixmlNodeList_item( deviceList, i );
|
|
|
|
const char* deviceType = xml_getChildElementValue( deviceElement, "deviceType" );
|
|
if ( !deviceType ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no deviceType!", __FILE__, __LINE__ ); continue; }
|
|
if ( strcmp( MEDIA_SERVER_DEVICE_TYPE, deviceType ) != 0 ) continue;
|
|
|
|
const char* UDN = xml_getChildElementValue( deviceElement, "UDN" );
|
|
if ( !UDN ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no UDN!", __FILE__, __LINE__ ); continue; }
|
|
if ( cookie->serverList->getServer( UDN ) != 0 ) continue;
|
|
|
|
const char* friendlyName = xml_getChildElementValue( deviceElement, "friendlyName" );
|
|
if ( !friendlyName ) { msg_Dbg( cookie->serviceDiscovery, "%s:%d: no friendlyName!", __FILE__, __LINE__ ); continue; }
|
|
|
|
MediaServer* server = new MediaServer( UDN, friendlyName, cookie );
|
|
if ( !cookie->serverList->addServer( server ) ) {
|
|
|
|
delete server;
|
|
server = 0;
|
|
continue;
|
|
}
|
|
|
|
// Check for ContentDirectory service...
|
|
|
|
IXML_NodeList* serviceList = ixmlElement_getElementsByTagName( deviceElement, "service" );
|
|
if ( serviceList )
|
|
{
|
|
for ( unsigned int j = 0; j < ixmlNodeList_length( serviceList ); j++ )
|
|
{
|
|
IXML_Element* serviceElement = ( IXML_Element* )ixmlNodeList_item( serviceList, j );
|
|
|
|
const char* serviceType = xml_getChildElementValue( serviceElement, "serviceType" );
|
|
if ( !serviceType ) continue;
|
|
if ( strcmp( CONTENT_DIRECTORY_SERVICE_TYPE, serviceType ) != 0 ) continue;
|
|
|
|
const char* eventSubURL = xml_getChildElementValue( serviceElement, "eventSubURL" );
|
|
if ( !eventSubURL ) continue;
|
|
|
|
const char* controlURL = xml_getChildElementValue( serviceElement, "controlURL" );
|
|
if ( !controlURL ) continue;
|
|
|
|
// Try to subscribe to ContentDirectory service
|
|
|
|
char* url = ( char* )malloc( strlen( baseURL ) + strlen( eventSubURL ) + 1 );
|
|
if ( url )
|
|
{
|
|
char* s1 = strdup( baseURL );
|
|
char* s2 = strdup( eventSubURL );
|
|
|
|
if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS )
|
|
{
|
|
// msg_Dbg( cookie->serviceDiscovery, "CDS EVENT URL: %s", url );
|
|
|
|
server->setContentDirectoryEventURL( url );
|
|
server->subscribeToContentDirectory();
|
|
}
|
|
|
|
free( s1 );
|
|
free( s2 );
|
|
free( url );
|
|
}
|
|
|
|
// Try to browse content directory...
|
|
|
|
url = ( char* )malloc( strlen( baseURL ) + strlen( controlURL ) + 1 );
|
|
if ( url )
|
|
{
|
|
char* s1 = strdup( baseURL );
|
|
char* s2 = strdup( controlURL );
|
|
|
|
if ( UpnpResolveURL( s1, s2, url ) == UPNP_E_SUCCESS )
|
|
{
|
|
// msg_Dbg( cookie->serviceDiscovery, "CDS CTRL URL: %s", url );
|
|
|
|
server->setContentDirectoryControlURL( url );
|
|
server->fetchContents();
|
|
}
|
|
|
|
free( s1 );
|
|
free( s2 );
|
|
free( url );
|
|
}
|
|
}
|
|
|
|
ixmlNodeList_free( serviceList );
|
|
}
|
|
}
|
|
|
|
ixmlNodeList_free( deviceList );
|
|
}
|
|
}
|
|
|
|
MediaServer::MediaServer( const char* UDN, const char* friendlyName, Cookie* cookie )
|
|
{
|
|
_cookie = cookie;
|
|
|
|
_UDN = UDN;
|
|
_friendlyName = friendlyName;
|
|
|
|
_contents = 0;
|
|
_playlistNode = 0;
|
|
}
|
|
|
|
MediaServer::~MediaServer()
|
|
{
|
|
if ( _contents )
|
|
{
|
|
playlist_NodeDelete( pl_Get( _cookie->serviceDiscovery ) ,
|
|
_playlistNode, VLC_TRUE, VLC_TRUE );
|
|
}
|
|
|
|
delete _contents;
|
|
}
|
|
|
|
const char* MediaServer::getUDN() const
|
|
{
|
|
const char* s = _UDN.c_str();
|
|
return s;
|
|
}
|
|
|
|
const char* MediaServer::getFriendlyName() const
|
|
{
|
|
const char* s = _friendlyName.c_str();
|
|
return s;
|
|
}
|
|
|
|
void MediaServer::setContentDirectoryEventURL( const char* url )
|
|
{
|
|
_contentDirectoryEventURL = url;
|
|
}
|
|
|
|
const char* MediaServer::getContentDirectoryEventURL() const
|
|
{
|
|
const char* s = _contentDirectoryEventURL.c_str();
|
|
return s;
|
|
}
|
|
|
|
void MediaServer::setContentDirectoryControlURL( const char* url )
|
|
{
|
|
_contentDirectoryControlURL = url;
|
|
}
|
|
|
|
const char* MediaServer::getContentDirectoryControlURL() const
|
|
{
|
|
return _contentDirectoryControlURL.c_str();
|
|
}
|
|
|
|
void MediaServer::subscribeToContentDirectory()
|
|
{
|
|
const char* url = getContentDirectoryEventURL();
|
|
if ( !url || strcmp( url, "" ) == 0 )
|
|
{
|
|
msg_Dbg( _cookie->serviceDiscovery, "No subscription url set!" );
|
|
return;
|
|
}
|
|
|
|
int timeOut = 1810;
|
|
Upnp_SID sid;
|
|
|
|
int res = UpnpSubscribe( _cookie->clientHandle, url, &timeOut, sid );
|
|
|
|
if ( res == UPNP_E_SUCCESS )
|
|
{
|
|
_subscriptionTimeOut = timeOut;
|
|
memcpy( _subscriptionID, sid, sizeof( Upnp_SID ) );
|
|
}
|
|
else
|
|
{
|
|
msg_Dbg( _cookie->serviceDiscovery, "%s:%d: WARNING: '%s': %s", __FILE__, __LINE__, getFriendlyName(), UpnpGetErrorMessage( res ) );
|
|
}
|
|
}
|
|
|
|
IXML_Document* MediaServer::_browseAction( const char* pObjectID, const char* pBrowseFlag, const char* pFilter,
|
|
const char* pStartingIndex, const char* pRequestedCount, const char* pSortCriteria )
|
|
{
|
|
IXML_Document* action = 0;
|
|
IXML_Document* response = 0;
|
|
|
|
const char* url = getContentDirectoryControlURL();
|
|
if ( !url || strcmp( url, "" ) == 0 ) { msg_Dbg( _cookie->serviceDiscovery, "No subscription url set!" ); return 0; }
|
|
|
|
char* ObjectID = strdup( pObjectID );
|
|
char* BrowseFlag = strdup( pBrowseFlag );
|
|
char* Filter = strdup( pFilter );
|
|
char* StartingIndex = strdup( pStartingIndex );
|
|
char* RequestedCount = strdup( pRequestedCount );
|
|
char* SortCriteria = strdup( pSortCriteria );
|
|
|
|
char* serviceType = strdup( CONTENT_DIRECTORY_SERVICE_TYPE );
|
|
|
|
int res;
|
|
|
|
res = UpnpAddToAction( &action, "Browse", serviceType, "ObjectID", ObjectID );
|
|
if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
|
|
|
|
res = UpnpAddToAction( &action, "Browse", serviceType, "BrowseFlag", BrowseFlag );
|
|
if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
|
|
|
|
res = UpnpAddToAction( &action, "Browse", serviceType, "Filter", Filter );
|
|
if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
|
|
|
|
res = UpnpAddToAction( &action, "Browse", serviceType, "StartingIndex", StartingIndex );
|
|
if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
|
|
|
|
res = UpnpAddToAction( &action, "Browse", serviceType, "RequestedCount", RequestedCount );
|
|
if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
|
|
|
|
res = UpnpAddToAction( &action, "Browse", serviceType, "SortCriteria", SortCriteria );
|
|
if ( res != UPNP_E_SUCCESS ) { /* msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) ); */ goto browseActionCleanup; }
|
|
|
|
res = UpnpSendAction( _cookie->clientHandle,
|
|
url,
|
|
CONTENT_DIRECTORY_SERVICE_TYPE,
|
|
0,
|
|
action,
|
|
&response );
|
|
if ( res != UPNP_E_SUCCESS )
|
|
{
|
|
msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR: %s", __FILE__, __LINE__, UpnpGetErrorMessage( res ) );
|
|
ixmlDocument_free( response );
|
|
response = 0;
|
|
}
|
|
|
|
browseActionCleanup:
|
|
|
|
free( ObjectID );
|
|
free( BrowseFlag );
|
|
free( Filter );
|
|
free( StartingIndex );
|
|
free( RequestedCount );
|
|
free( SortCriteria );
|
|
|
|
free( serviceType );
|
|
|
|
ixmlDocument_free( action );
|
|
return response;
|
|
}
|
|
|
|
void MediaServer::fetchContents()
|
|
{
|
|
Container* root = new Container( 0, "0", getFriendlyName() );
|
|
playlist_t * p_playlist = pl_Get( _cookie->serviceDiscovery );
|
|
_fetchContents( root );
|
|
|
|
if ( _contents )
|
|
{
|
|
PL_LOCK;
|
|
playlist_NodeEmpty( p_playlist, _playlistNode, VLC_TRUE );
|
|
PL_UNLOCK;
|
|
delete _contents;
|
|
}
|
|
|
|
_contents = root;
|
|
_contents->setPlaylistNode( _playlistNode );
|
|
|
|
_buildPlaylist( _contents );
|
|
}
|
|
|
|
bool MediaServer::_fetchContents( Container* parent )
|
|
{
|
|
if (!parent) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: parent==NULL", __FILE__, __LINE__ ); return false; }
|
|
|
|
IXML_Document* response = _browseAction( parent->getObjectID(), "BrowseDirectChildren", "*", "0", "0", "" );
|
|
if ( !response ) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR!", __FILE__, __LINE__ ); return false; }
|
|
|
|
IXML_Document* result = parseBrowseResult( response );
|
|
ixmlDocument_free( response );
|
|
if ( !result ) { msg_Dbg( _cookie->serviceDiscovery, "%s:%d: ERROR!", __FILE__, __LINE__ ); return false; }
|
|
|
|
IXML_NodeList* containerNodeList = ixmlDocument_getElementsByTagName( result, "container" );
|
|
if ( containerNodeList )
|
|
{
|
|
for ( unsigned int i = 0; i < ixmlNodeList_length( containerNodeList ); i++ )
|
|
{
|
|
IXML_Element* containerElement = ( IXML_Element* )ixmlNodeList_item( containerNodeList, i );
|
|
|
|
const char* objectID = ixmlElement_getAttribute( containerElement, "id" );
|
|
if ( !objectID ) continue;
|
|
|
|
const char* childCountStr = ixmlElement_getAttribute( containerElement, "childCount" );
|
|
if ( !childCountStr ) continue;
|
|
int childCount = atoi( childCountStr );
|
|
|
|
const char* title = xml_getChildElementValue( containerElement, "dc:title" );
|
|
if ( !title ) continue;
|
|
|
|
const char* resource = xml_getChildElementValue( containerElement, "res" );
|
|
|
|
if ( resource && childCount < 1 )
|
|
{
|
|
Item* item = new Item( parent, objectID, title, resource );
|
|
parent->addItem( item );
|
|
}
|
|
else
|
|
{
|
|
Container* container = new Container( parent, objectID, title );
|
|
parent->addContainer( container );
|
|
|
|
if ( childCount > 0 ) _fetchContents( container );
|
|
}
|
|
}
|
|
|
|
ixmlNodeList_free( containerNodeList );
|
|
}
|
|
|
|
IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( result, "item" );
|
|
if ( itemNodeList )
|
|
{
|
|
for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
|
|
{
|
|
IXML_Element* itemElement = ( IXML_Element* )ixmlNodeList_item( itemNodeList, i );
|
|
|
|
const char* objectID = ixmlElement_getAttribute( itemElement, "id" );
|
|
if ( !objectID ) continue;
|
|
|
|
const char* title = xml_getChildElementValue( itemElement, "dc:title" );
|
|
if ( !title ) continue;
|
|
|
|
const char* resource = xml_getChildElementValue( itemElement, "res" );
|
|
if ( !resource ) continue;
|
|
|
|
Item* item = new Item( parent, objectID, title, resource );
|
|
parent->addItem( item );
|
|
}
|
|
|
|
ixmlNodeList_free( itemNodeList );
|
|
}
|
|
|
|
ixmlDocument_free( result );
|
|
|
|
return true;
|
|
}
|
|
|
|
void MediaServer::_buildPlaylist( Container* parent )
|
|
{
|
|
playlist_t *p_playlist = pl_Get( _cookie->serviceDiscovery );
|
|
for ( unsigned int i = 0; i < parent->getNumContainers(); i++ )
|
|
{
|
|
Container* container = parent->getContainer( i );
|
|
playlist_item_t* parentNode = parent->getPlaylistNode();
|
|
|
|
char* title = strdup( container->getTitle() );
|
|
playlist_item_t* node = playlist_NodeCreate( p_playlist, title, parentNode, 0, NULL );
|
|
free( title );
|
|
|
|
container->setPlaylistNode( node );
|
|
_buildPlaylist( container );
|
|
}
|
|
|
|
for ( unsigned int i = 0; i < parent->getNumItems(); i++ )
|
|
{
|
|
Item* item = parent->getItem( i );
|
|
playlist_item_t* parentNode = parent->getPlaylistNode();
|
|
|
|
input_item_t* p_input = input_ItemNew( _cookie->serviceDiscovery,
|
|
item->getResource(),
|
|
item->getTitle() );
|
|
int i_cat;
|
|
playlist_BothAddInput( p_playlist, p_input, parentNode,
|
|
PLAYLIST_APPEND, PLAYLIST_END, &i_cat, NULL,
|
|
VLC_FALSE );
|
|
/* TODO: do this better by storing ids */
|
|
playlist_item_t *p_node = playlist_ItemGetById( p_playlist, i_cat, VLC_FALSE );
|
|
assert( p_node );
|
|
item->setPlaylistNode( p_node );
|
|
}
|
|
}
|
|
|
|
void MediaServer::setPlaylistNode( playlist_item_t* playlistNode )
|
|
{
|
|
_playlistNode = playlistNode;
|
|
}
|
|
|
|
bool MediaServer::compareSID( const char* sid )
|
|
{
|
|
return ( strncmp( _subscriptionID, sid, sizeof( Upnp_SID ) ) == 0 );
|
|
}
|
|
|
|
|
|
// MediaServerList...
|
|
|
|
MediaServerList::MediaServerList( Cookie* cookie )
|
|
{
|
|
_cookie = cookie;
|
|
}
|
|
|
|
MediaServerList::~MediaServerList()
|
|
{
|
|
for ( unsigned int i = 0; i < _list.size(); i++ )
|
|
{
|
|
delete _list[i];
|
|
}
|
|
}
|
|
|
|
bool MediaServerList::addServer( MediaServer* s )
|
|
{
|
|
if ( getServer( s->getUDN() ) != 0 ) return false;
|
|
|
|
msg_Dbg( _cookie->serviceDiscovery, "Adding server '%s'", s->getFriendlyName() );
|
|
|
|
_list.push_back( s );
|
|
|
|
char* name = strdup( s->getFriendlyName() );
|
|
playlist_item_t* node = playlist_NodeCreate( pl_Get( _cookie->serviceDiscovery ),
|
|
name,
|
|
_cookie->serviceDiscovery->p_sys->p_node_cat, 0, NULL );
|
|
free( name );
|
|
s->setPlaylistNode( node );
|
|
|
|
return true;
|
|
}
|
|
|
|
MediaServer* MediaServerList::getServer( const char* UDN )
|
|
{
|
|
MediaServer* result = 0;
|
|
|
|
for ( unsigned int i = 0; i < _list.size(); i++ )
|
|
{
|
|
if( strcmp( UDN, _list[i]->getUDN() ) == 0 )
|
|
{
|
|
result = _list[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
MediaServer* MediaServerList::getServerBySID( const char* sid )
|
|
{
|
|
MediaServer* server = 0;
|
|
|
|
for ( unsigned int i = 0; i < _list.size(); i++ )
|
|
{
|
|
if ( _list[i]->compareSID( sid ) )
|
|
{
|
|
server = _list[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return server;
|
|
}
|
|
|
|
void MediaServerList::removeServer( const char* UDN )
|
|
{
|
|
MediaServer* server = getServer( UDN );
|
|
if ( !server ) return;
|
|
|
|
msg_Dbg( _cookie->serviceDiscovery, "Removing server '%s'", server->getFriendlyName() );
|
|
|
|
std::vector<MediaServer*>::iterator it;
|
|
for ( it = _list.begin(); it != _list.end(); it++ )
|
|
{
|
|
if ( *it == server )
|
|
{
|
|
_list.erase( it );
|
|
delete server;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Item...
|
|
|
|
Item::Item( Container* parent, const char* objectID, const char* title, const char* resource )
|
|
{
|
|
_parent = parent;
|
|
|
|
_objectID = objectID;
|
|
_title = title;
|
|
_resource = resource;
|
|
|
|
_playlistNode = 0;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
void Item::setPlaylistNode( playlist_item_t* node )
|
|
{
|
|
_playlistNode = node;
|
|
}
|
|
|
|
playlist_item_t* Item::getPlaylistNode() const
|
|
{
|
|
return _playlistNode;
|
|
}
|
|
|
|
|
|
// Container...
|
|
|
|
Container::Container( Container* parent, const char* objectID, const char* title )
|
|
{
|
|
_parent = parent;
|
|
|
|
_objectID = objectID;
|
|
_title = title;
|
|
|
|
_playlistNode = 0;
|
|
}
|
|
|
|
Container::~Container()
|
|
{
|
|
for ( unsigned int i = 0; i < _containers.size(); i++ )
|
|
{
|
|
delete _containers[i];
|
|
}
|
|
|
|
for ( unsigned int i = 0; i < _items.size(); i++ )
|
|
{
|
|
delete _items[i];
|
|
}
|
|
}
|
|
|
|
void Container::addItem( Item* item )
|
|
{
|
|
_items.push_back( item );
|
|
}
|
|
|
|
void Container::addContainer( Container* container )
|
|
{
|
|
_containers.push_back( container );
|
|
}
|
|
|
|
const char* Container::getObjectID() const
|
|
{
|
|
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 ) const
|
|
{
|
|
if ( i < _items.size() ) return _items[i];
|
|
return 0;
|
|
}
|
|
|
|
Container* Container::getContainer( unsigned int i ) const
|
|
{
|
|
if ( i < _containers.size() ) return _containers[i];
|
|
return 0;
|
|
}
|
|
|
|
void Container::setPlaylistNode( playlist_item_t* node )
|
|
{
|
|
_playlistNode = node;
|
|
}
|
|
|
|
playlist_item_t* Container::getPlaylistNode() const
|
|
{
|
|
return _playlistNode;
|
|
}
|