[vlc-devel] [PATCH v2 5/5] upnp: Add UPnP/DLNA MediaRenderer control plugin
Johan Gunnarsson
johan.gunnarsson at gmail.com
Sat Jul 13 11:37:46 CEST 2019
---
modules/control/dlna.cpp | 819 +++++++++++++++++++++++++
modules/control/dlna.hpp | 92 +++
modules/services_discovery/Makefile.am | 4 +-
modules/services_discovery/upnp.cpp | 7 +
modules/services_discovery/upnp.hpp | 1 +
5 files changed, 922 insertions(+), 1 deletion(-)
create mode 100644 modules/control/dlna.cpp
create mode 100644 modules/control/dlna.hpp
diff --git a/modules/control/dlna.cpp b/modules/control/dlna.cpp
new file mode 100644
index 0000000000..4aab7a1aea
--- /dev/null
+++ b/modules/control/dlna.cpp
@@ -0,0 +1,819 @@
+/*****************************************************************************
+ * dlna.cpp : DLNA MediaRenderer module
+ *****************************************************************************
+ * Copyright (C) 2019 VLC authors and VideoLAN
+ *
+ * Authors: Johan Gunnarsson <johan.gunnarsson at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <cstring>
+#include <ctime>
+#include <map>
+#include <iostream>
+#include <regex>
+
+#include "../services_discovery/upnp-wrapper.hpp"
+
+#include <vlc_plugin.h>
+#include <vlc_interface.h>
+#include <vlc_playlist.h>
+#include <vlc_player.h>
+#include <vlc_input.h>
+
+#include "dlna.hpp"
+
+#define SINK_PROTOCOL_INFO ("http-get:*:video/mpeg:*," \
+ "http-get:*:video/mp4:*," \
+ "http-get:*:video/vnd.dlna.mpeg-tts:*," \
+ "http-get:*:video/avi:*," \
+ "http-get:*:video/x-matroska:*," \
+ "http-get:*:video/x-ms-wmv:*," \
+ "http-get:*:video/wtv:*," \
+ "http-get:*:audio/mpeg:*," \
+ "http-get:*:audio/mp3:*," \
+ "http-get:*:audio/mp4:*," \
+ "http-get:*:audio/x-ms-wma*," \
+ "http-get:*:audio/wav:*," \
+ "http-get:*:audio/L16:*," \
+ "http-get:*image/jpeg:*," \
+ "http-get:*image/png:*," \
+ "http-get:*image/gif:*," \
+ "http-get:*image/tiff:*")
+
+struct intf_sys_t
+{
+ UpnpInstanceWrapper *p_upnp;
+ std::shared_ptr<DLNA::EventHandler> p_listener;
+ vlc_playlist_t *playlist;
+ vlc_player_t *player;
+ vlc_player_listener_id *p_player_listener;
+ vlc_player_aout_listener_id *p_player_aout_listener;
+};
+
+typedef std::map<std::string,std::string> parammap;
+
+typedef bool (*ActionRequestHandler)( parammap&, parammap&, intf_thread_t* );
+
+namespace DLNA
+{
+
+/**
+ * Convert ticks (in microseconds) to a string in the form H:MM:SS. Can't use
+ * secstotimestr since it will omit hours if time is less than 1 hour.
+ */
+static std::string
+time_to_string( vlc_tick_t ticks )
+{
+ unsigned int time_in_seconds = (unsigned int) SEC_FROM_VLC_TICK(ticks);
+ unsigned int s = time_in_seconds % 60;
+ time_in_seconds -= s;
+ unsigned int m = (time_in_seconds / 60) % 60;
+ time_in_seconds -= 60 * m;
+ unsigned int h = time_in_seconds / (60 * 60);
+ char str[16] = {};
+ if( snprintf( str, sizeof( str ) - 1, "%u:%02u:%02u", h, m, s ) < 0 )
+ return std::string( "0:00:00" );
+ return std::string( str );
+}
+
+static bool
+handle_AVT_SetAVTransportURI( parammap& in_params, parammap& out_params,
+ intf_thread_t *p_intf )
+{
+ VLC_UNUSED(out_params);
+
+ std::string s = in_params["CurrentURI"];
+
+ input_item_t *item = input_item_New(s.c_str(), NULL);
+ if( !item )
+ {
+ msg_Err( p_intf, "Failed to parse URL" );
+ return -1;
+ }
+
+ vlc_player_t *player = vlc_playlist_GetPlayer( p_intf->p_sys->playlist );
+
+ vlc_player_Lock( player );
+ vlc_player_SetCurrentMedia( player, item );
+ vlc_player_Start( player );
+ vlc_player_Unlock( player );
+
+ input_item_Release( item );
+
+ return true;
+}
+
+static bool
+handle_AVT_GetMediaInfo( parammap& in_params, parammap& out_params,
+ intf_thread_t *p_intf )
+{
+ VLC_UNUSED(in_params);
+
+ vlc_player_t *player = vlc_playlist_GetPlayer( p_intf->p_sys->playlist );
+
+ vlc_player_Lock( player );
+ vlc_tick_t length = vlc_player_GetLength( player );
+ vlc_player_Unlock( player );
+
+ out_params["MediaDuration"] = time_to_string( length );
+
+ return true;
+}
+
+
+static bool
+handle_AVT_GetTransportInfo( parammap& in_params, parammap& out_params,
+ intf_thread_t *p_intf )
+{
+ VLC_UNUSED(in_params);
+
+ vlc_player_t *player = vlc_playlist_GetPlayer( p_intf->p_sys->playlist );
+
+ vlc_player_Lock( player );
+ enum vlc_player_state state = vlc_player_GetState( player );
+ vlc_player_Unlock( player );
+
+ switch( state )
+ {
+ case VLC_PLAYER_STATE_STOPPED:
+ out_params["CurrentTransportState"] = "STOPPED";
+ break;
+ case VLC_PLAYER_STATE_PLAYING:
+ out_params["CurrentTransportState"] = "PLAYING";
+ break;
+ case VLC_PLAYER_STATE_PAUSED:
+ out_params["CurrentTransportState"] = "PAUSED_PLAYBACK";
+ break;
+ case VLC_PLAYER_STATE_STARTED: /* fall through */
+ case VLC_PLAYER_STATE_STOPPING:
+ out_params["CurrentTransportState"] = "TRANSITIONING";
+ break;
+ default:
+ out_params["CurrentTransportState"] = "UNKNOWN";
+ break;
+ }
+
+ // TODO: get real status and speed
+ out_params["CurrentTransportStatus"] = "";
+ out_params["CurrentSpeed"] = "";
+
+ return true;
+}
+
+static bool
+handle_AVT_GetPositionInfo( parammap& in_params, parammap& out_params,
+ intf_thread_t *p_intf )
+{
+ VLC_UNUSED(in_params);
+
+ vlc_player_t *player = vlc_playlist_GetPlayer( p_intf->p_sys->playlist );
+
+ vlc_player_Lock( player );
+ vlc_tick_t length = vlc_player_GetLength( player );
+ vlc_tick_t time = vlc_player_GetTime( player );
+ vlc_player_Unlock( player );
+
+ out_params["Track"] = "0";
+ out_params["TrackDuration"] = time_to_string( length );
+ out_params["TrackMetaData"] = "";
+ out_params["TrackURI"] = "";
+ out_params["RelTime"] = time_to_string( time );
+ out_params["AbsTime"] = time_to_string( time );
+ out_params["RelCount"] = std::to_string( time );
+ out_params["AbsCount"] = std::to_string( time );
+
+ return true;
+}
+
+static bool
+handle_AVT_Stop( parammap& in_params, parammap& out_params,
+ intf_thread_t *p_intf )
+{
+ VLC_UNUSED(in_params);
+ VLC_UNUSED(out_params);
+
+ vlc_player_t *player = vlc_playlist_GetPlayer( p_intf->p_sys->playlist );
+
+ vlc_player_Lock( player );
+ vlc_player_Stop( player );
+ vlc_player_Unlock( player );
+
+ return true;
+}
+
+static bool
+handle_AVT_Play( parammap& in_params, parammap& out_params,
+ intf_thread_t *p_intf )
+{
+ VLC_UNUSED(in_params);
+ VLC_UNUSED(out_params);
+
+ vlc_player_t *player = vlc_playlist_GetPlayer( p_intf->p_sys->playlist );
+
+ vlc_player_Lock( player );
+ vlc_player_Resume( player );
+ vlc_player_Unlock( player );
+
+ return true;
+}
+
+static bool
+handle_AVT_Pause( parammap& in_params, parammap& out_params,
+ intf_thread_t *p_intf )
+{
+ VLC_UNUSED(in_params);
+ VLC_UNUSED(out_params);
+
+ vlc_player_t *player = vlc_playlist_GetPlayer( p_intf->p_sys->playlist );
+
+ vlc_player_Lock( player );
+ vlc_player_Pause( player );
+ vlc_player_Unlock( player );
+
+ return true;
+}
+
+static bool
+handle_AVT_Seek( parammap& in_params, parammap& out_params,
+ intf_thread_t *p_intf )
+{
+ VLC_UNUSED(out_params);
+
+ vlc_player_t *player = vlc_playlist_GetPlayer( p_intf->p_sys->playlist );
+
+ if( in_params["Unit"] == "ABS_TIME" || in_params["Unit"] == "REL_TIME" )
+ {
+ struct tm tm = {};
+ if( !strptime(in_params["Target"].c_str(), "%H:%M:%S", &tm) )
+ return true;
+
+ vlc_player_Lock( player );
+ vlc_player_SeekByTime( player,
+ VLC_TICK_FROM_SEC(tm.tm_hour * 60 * 60 +
+ tm.tm_min * 60 +
+ tm.tm_sec),
+ VLC_PLAYER_SEEK_FAST,
+ VLC_PLAYER_WHENCE_ABSOLUTE );
+ vlc_player_Unlock( player );
+ }
+
+ return true;
+}
+
+static bool
+handle_CM_GetProtocolInfo( parammap& in_params, parammap& out_params,
+ intf_thread_t *p_intf )
+{
+ VLC_UNUSED(p_intf);
+ VLC_UNUSED(in_params);
+
+ out_params["Source"] = "";
+ out_params["Sink"] = SINK_PROTOCOL_INFO;
+
+ return true;
+}
+
+static bool
+handle_RC_GetVolume( parammap& in_params, parammap& out_params,
+ intf_thread_t *p_intf )
+{
+ VLC_UNUSED(in_params);
+
+ /* Volume as in range [0.0, 2.0] or -1.0 if no audio */
+ float volume = vlc_player_aout_GetVolume( p_intf->p_sys->player );
+ if( volume < 0.0 )
+ volume = 0.0;
+ else if( volume > 2.0 )
+ volume = 2.0;
+
+ out_params["CurrentVolume"] = std::to_string( std::lround( volume * 50 ) );
+
+ return true;
+}
+
+static bool
+handle_RC_SetVolume( parammap& in_params, parammap& out_params,
+ intf_thread_t *p_intf )
+{
+ VLC_UNUSED(out_params);
+
+ /* Volume in range [0, 100] */
+ unsigned long volume = std::stoul( in_params["DesiredVolume"] );
+ if( volume > 100 )
+ volume = 100;
+
+ vlc_player_aout_SetVolume( p_intf->p_sys->player, volume / 50.0 );
+
+ return true;
+}
+
+static bool
+handle_RC_GetMute( parammap& in_params, parammap& out_params,
+ intf_thread_t *p_intf )
+{
+ VLC_UNUSED(in_params);
+
+ bool muted = vlc_player_aout_IsMuted( p_intf->p_sys->player );
+
+ if( muted )
+ out_params["CurrentMute"] = "1";
+ else
+ out_params["CurrentMute"] = "0";
+
+ return true;
+}
+
+static bool
+handle_RC_SetMute( parammap& in_params, parammap& out_params,
+ intf_thread_t *p_intf )
+{
+ VLC_UNUSED(out_params);
+
+ std::string mute = in_params["DesiredMute"];
+
+ if( mute == "1" || mute == "true" || mute == "yes" )
+ vlc_player_aout_Mute( p_intf->p_sys->player, true );
+ else if( mute == "0" || mute == "false" || mute == "no" )
+ vlc_player_aout_Mute( p_intf->p_sys->player, false );
+
+ return true;
+}
+
+#define SRV_AVT "urn:upnp-org:serviceId:AVTransport"
+#define SRV_RC "urn:upnp-org:serviceId:RenderingControl"
+#define SRV_CM "urn:upnp-org:serviceId:ConnectionManager"
+
+static struct {
+ const char *service;
+ const char *action;
+ ActionRequestHandler handler;
+} actions[] = {
+ { SRV_AVT, "SetAVTransportURI", handle_AVT_SetAVTransportURI },
+ { SRV_AVT, "GetMediaInfo", handle_AVT_GetMediaInfo },
+ { SRV_AVT, "GetTransportInfo", handle_AVT_GetTransportInfo },
+ { SRV_AVT, "GetPositionInfo", handle_AVT_GetPositionInfo },
+ { SRV_AVT, "Stop", handle_AVT_Stop },
+ { SRV_AVT, "Play", handle_AVT_Play },
+ { SRV_AVT, "Pause", handle_AVT_Pause },
+ { SRV_AVT, "Seek", handle_AVT_Seek },
+ { SRV_CM, "GetProtocolInfo", handle_CM_GetProtocolInfo },
+ { SRV_RC, "GetVolume", handle_RC_GetVolume },
+ { SRV_RC, "SetVolume", handle_RC_SetVolume },
+ { SRV_RC, "GetMute", handle_RC_GetMute },
+ { SRV_RC, "SetMute", handle_RC_SetMute },
+ { NULL, NULL, NULL }
+};
+
+static parammap
+build_param_map( IXML_Node *p_node )
+{
+ parammap params;
+
+ for( IXML_Node *param = ixmlNode_getFirstChild( p_node );
+ param != NULL;
+ param = ixmlNode_getNextSibling( param ) )
+ {
+ const DOMString key = ixmlNode_getNodeName(param);
+ if( !key )
+ continue;
+
+ IXML_Node *vnode = ixmlNode_getFirstChild( param );
+ if( !vnode )
+ continue;
+
+ const DOMString value = ixmlNode_getNodeValue( vnode );
+ if( !value )
+ continue;
+
+ params[std::string(key)] = std::string(value);
+ }
+
+ return params;
+}
+
+static std::string
+build_event_xml( const char **keys, const char **values, int count )
+{
+ IXML_Document *doc = NULL;
+ IXML_Element *event = NULL;
+ IXML_Element *instance = NULL;
+ DOMString xmlbuf = NULL;
+ std::string xmlstr;
+
+ doc = ixmlDocument_createDocument();
+ if( !doc )
+ return xmlstr;
+
+ event = ixmlDocument_createElement( doc, "Event" );
+ if( !event )
+ goto err1;
+
+ if( ixmlNode_appendChild( (IXML_Node *)doc,
+ (IXML_Node *)event ) != IXML_SUCCESS )
+ {
+ ixmlElement_free( event );
+ goto err1;
+ }
+
+ instance = ixmlDocument_createElement( doc, "InstanceID" );
+ if( !instance )
+ goto err1;
+
+ if( ixmlNode_appendChild( (IXML_Node *)event,
+ (IXML_Node *)instance ) != IXML_SUCCESS )
+ {
+ ixmlElement_free( instance );
+ goto err1;
+ }
+
+ if( ixmlElement_setAttribute( instance, "val", "0") != IXML_SUCCESS )
+ goto err1;
+
+ for( int i = 0; i < count; i++ )
+ {
+ IXML_Element *arg = ixmlDocument_createElement( doc, keys[i] );
+ if( !arg )
+ goto err1;
+
+ if( ixmlNode_appendChild( (IXML_Node *)instance,
+ (IXML_Node *)arg ) != IXML_SUCCESS )
+ {
+ ixmlElement_free( arg );
+ goto err1;
+ }
+
+ if( ixmlElement_setAttribute( arg, "val", values[i] ) != IXML_SUCCESS )
+ goto err1;
+ }
+
+ xmlbuf = ixmlNodetoString( (IXML_Node *)doc );
+ if( !xmlbuf )
+ goto err1;
+
+ xmlstr = std::string( xmlbuf );
+
+ free( xmlbuf );
+
+ ixmlDocument_free( doc );
+
+ xmlstr = std::regex_replace( xmlstr, std::regex( "&" ), "&" );
+ xmlstr = std::regex_replace( xmlstr, std::regex( "\"" ), """ );
+ xmlstr = std::regex_replace( xmlstr, std::regex( "\'" ), "'" );
+ xmlstr = std::regex_replace( xmlstr, std::regex( "<" ), "<" );
+ xmlstr = std::regex_replace( xmlstr, std::regex( ">" ), ">" );
+
+ return xmlstr;
+
+err1:
+ ixmlDocument_free( doc );
+
+ return xmlstr;
+}
+
+
+static void
+emit_event( intf_thread_t *p_intf, const char *s_sid, std::string key,
+ std::string value )
+{
+ const char *event_keys[1] = { key.c_str() };
+ const char *event_values[1] = { value.c_str() };
+
+ std::string event_xml = build_event_xml( event_keys, event_values, 1 );
+
+ const char *var_keys[1] = { "LastChange" };
+ const char *var_values[1] = { event_xml.c_str() };
+
+ int ret = UpnpNotify( p_intf->p_sys->p_upnp->device_handle(),
+ UPNP_UDN,
+ s_sid,
+ (const char **) &var_keys,
+ (const char **) &var_values,
+ 1 );
+ if( ret != UPNP_E_SUCCESS )
+ msg_Dbg( p_intf, "UpnpNotify failed" );
+}
+
+int
+EventHandler::onActionRequest( UpnpActionRequest *p_event,
+ void *p_user_data )
+{
+ VLC_UNUSED(p_user_data);
+
+ /* For example urn:upnp-org:serviceId:AVTransport */
+ const char *service_id = UpnpActionRequest_get_ServiceID_cstr( p_event );
+
+ /* For example SetAVTransportURI */
+ const char *action_name = UpnpActionRequest_get_ActionName_cstr( p_event );
+
+ /* "Body" XML node in the request */
+ IXML_Document *body = UpnpActionRequest_get_ActionRequest( p_event );
+ if( !body )
+ return 0;
+
+ for( IXML_Node *action = ixmlNode_getFirstChild( (IXML_Node*) body );
+ action != NULL;
+ action = ixmlNode_getNextSibling( action ) )
+ {
+ parammap in_params = build_param_map( action );
+
+ for( size_t i = 0; actions[i].handler; i++ )
+ {
+ if( strcmp( actions[i].service, service_id ) ||
+ strcmp( actions[i].action, action_name ) )
+ continue;
+
+ parammap out_params;
+
+ int r = actions[i].handler( in_params, out_params, p_intf );
+ if( !r )
+ continue;
+
+ IXML_Document *d = UpnpMakeActionResponse( action_name,
+ service_id,
+ 0,
+ NULL );
+ if( !d )
+ continue;
+
+ UpnpActionRequest_set_ActionResult( p_event, d );
+
+ for( auto& x : out_params )
+ {
+ int r = UpnpAddToActionResponse( &d,
+ action_name,
+ service_id,
+ x.first.c_str(),
+ x.second.c_str() );
+ if( r != UPNP_E_SUCCESS )
+ {
+ if( d != NULL )
+ ixmlDocument_free( d );
+
+ UpnpActionRequest_set_ActionResult( p_event, NULL );
+ UpnpActionRequest_set_ErrCode( p_event, r );
+ return r;
+ }
+ }
+
+ UpnpActionRequest_set_ErrCode( p_event, UPNP_E_SUCCESS );
+ return UPNP_E_SUCCESS;
+ }
+ }
+
+ UpnpActionRequest_set_ErrCode( p_event, UPNP_E_INTERNAL_ERROR );
+ return UPNP_E_INTERNAL_ERROR;
+}
+
+int
+EventHandler::onGetVarRequest( UpnpStateVarRequest *p_event,
+ void *p_user_data )
+{
+ VLC_UNUSED(p_event);
+ VLC_UNUSED(p_user_data);
+
+ // TODO
+ return 0;
+}
+
+int
+EventHandler::onSubscriptionRequest( UpnpSubscriptionRequest *p_event,
+ void *p_user_data )
+{
+ VLC_UNUSED(p_user_data);
+
+ /* For example urn:upnp-org:serviceId:AVTransport */
+ const char *service_id =
+ UpnpSubscriptionRequest_get_ServiceId_cstr( p_event );
+
+ /* For example ... */
+ const char *udn = UpnpSubscriptionRequest_get_UDN_cstr( p_event );
+
+ /* For example ... */
+ const char *sid = UpnpSubscriptionRequest_get_SID_cstr( p_event );
+
+ std::string event_xml = build_event_xml( NULL, NULL, 0 );
+
+ const char *var_keys[1] = { "LastChange" };
+ const char *var_values[1] = { event_xml.c_str() };
+
+ int ret = UpnpAcceptSubscription( p_intf->p_sys->p_upnp->device_handle(),
+ udn,
+ service_id,
+ (const char **) &var_keys,
+ (const char **) &var_values,
+ 1,
+ sid );
+ if( ret != UPNP_E_SUCCESS )
+ msg_Dbg( p_intf, "UpnpAcceptSubscription failed" );
+
+ return ret;
+}
+
+int
+EventHandler::onEvent( Upnp_EventType event_type, UpnpEventPtr p_event,
+ void* p_user_data )
+{
+ switch( event_type )
+ {
+ case UPNP_CONTROL_ACTION_REQUEST:
+ return onActionRequest( (UpnpActionRequest *) p_event, p_user_data );
+ case UPNP_CONTROL_GET_VAR_REQUEST:
+ return onGetVarRequest( (UpnpStateVarRequest *) p_event, p_user_data );
+ case UPNP_EVENT_SUBSCRIPTION_REQUEST:
+ return onSubscriptionRequest( (UpnpSubscriptionRequest *) p_event,
+ p_user_data );
+ default:
+ return 0;
+ }
+}
+
+static void
+player_state_changed( vlc_player_t *player, enum vlc_player_state state,
+ void *data )
+{
+ VLC_UNUSED(player);
+
+ intf_thread_t *p_intf = (intf_thread_t *)data;
+
+ const char *new_state;
+
+ switch (state)
+ {
+ case VLC_PLAYER_STATE_STOPPED:
+ new_state = "STOPPED";
+ break;
+ case VLC_PLAYER_STATE_PLAYING:
+ new_state = "PLAYING";
+ break;
+ case VLC_PLAYER_STATE_PAUSED:
+ new_state = "PAUSED_PLAYBACK";
+ break;
+ case VLC_PLAYER_STATE_STARTED: /* fall through */
+ case VLC_PLAYER_STATE_STOPPING:
+ new_state = "TRANSITIONING";
+ break;
+ default:
+ new_state = "UNKNOWN";
+ break;
+ }
+
+ emit_event( p_intf, SRV_AVT, "TransportState", new_state );
+}
+
+static void
+player_aout_volume_changed( vlc_player_t *player, float new_volume,
+ void *data )
+{
+ VLC_UNUSED(player);
+
+ intf_thread_t *p_intf = (intf_thread_t *) data;
+
+ if( new_volume < 0.0 )
+ new_volume = 0.0;
+ else if( new_volume > 2.0 )
+ new_volume = 2.0;
+
+ /* Volume in range [0, 100] */
+ std::string v = std::to_string( std::lround( new_volume * 50 ) );
+
+ emit_event( p_intf, SRV_RC, "Volume", v );
+}
+
+static void
+player_aout_mute_changed( vlc_player_t *player, bool new_mute,
+ void *data )
+{
+ VLC_UNUSED(player);
+
+ intf_thread_t *p_intf = (intf_thread_t *) data;
+
+ std::string m = new_mute ? "1" : "0";
+
+ emit_event( p_intf, SRV_RC, "Mute", m );
+}
+
+int
+OpenControl( vlc_object_t *p_this )
+{
+ intf_thread_t *p_intf = (intf_thread_t *)p_this;
+
+ p_intf->p_sys = (intf_sys_t *)calloc ( 1, sizeof( intf_sys_t ) );
+ if( unlikely(p_intf->p_sys == NULL) )
+ return VLC_ENOMEM;
+
+ p_intf->p_sys->playlist = vlc_intf_GetMainPlaylist( p_intf );
+ if( !p_intf->p_sys->playlist )
+ goto error1;
+
+ p_intf->p_sys->p_upnp = UpnpInstanceWrapper::get( p_this );
+ if( !p_intf->p_sys->p_upnp )
+ goto error1;
+
+ /* Start the UPnP MediaRenderer service */
+ p_intf->p_sys->p_upnp->startMediaRenderer( p_this );
+ try
+ {
+ p_intf->p_sys->p_listener =
+ std::make_shared<DLNA::EventHandler>( p_intf );
+ }
+ catch ( const std::bad_alloc& )
+ {
+ msg_Err( p_this, "Failed to alloc DLNA::EventHandler" );
+ goto error2;
+ }
+
+ p_intf->p_sys->p_upnp->addListener( p_intf->p_sys->p_listener );
+
+ static struct vlc_player_cbs player_cbs = {};
+ player_cbs.on_state_changed = player_state_changed;
+
+ static struct vlc_player_aout_cbs player_aout_cbs = {};
+ player_aout_cbs.on_volume_changed = player_aout_volume_changed;
+ player_aout_cbs.on_mute_changed = player_aout_mute_changed;
+
+ p_intf->p_sys->player = vlc_playlist_GetPlayer( p_intf->p_sys->playlist );
+ if( !p_intf->p_sys->player )
+ goto error2;
+
+ vlc_playlist_Lock( p_intf->p_sys->playlist );
+
+ p_intf->p_sys->p_player_listener =
+ vlc_player_AddListener( p_intf->p_sys->player, &player_cbs, p_intf );
+ if ( !p_intf->p_sys->p_player_listener )
+ goto error3;
+
+ p_intf->p_sys->p_player_aout_listener =
+ vlc_player_aout_AddListener( p_intf->p_sys->player, &player_aout_cbs,
+ p_intf );
+ if ( !p_intf->p_sys->p_player_aout_listener )
+ goto error4;
+
+ vlc_playlist_Unlock( p_intf->p_sys->playlist );
+
+ msg_Info( p_this, "Started MediaRenderer service" );
+
+ return VLC_SUCCESS;
+
+error4:
+ vlc_playlist_Lock( p_intf->p_sys->playlist );
+ vlc_player_RemoveListener( p_intf->p_sys->player,
+ p_intf->p_sys->p_player_listener );
+ vlc_playlist_Unlock( p_intf->p_sys->playlist );
+
+error3:
+ p_intf->p_sys->p_upnp->removeListener( p_intf->p_sys->p_listener );
+
+error2:
+ p_intf->p_sys->p_upnp->release();
+
+error1:
+ free( p_intf->p_sys );
+
+ return VLC_EGENERIC;
+}
+
+void
+CloseControl( vlc_object_t *p_this )
+{
+ intf_thread_t *p_intf = (intf_thread_t *)p_this;
+
+ /* Remove player listeners */
+ vlc_playlist_Lock( p_intf->p_sys->playlist );
+ vlc_player_aout_RemoveListener( p_intf->p_sys->player,
+ p_intf->p_sys->p_player_aout_listener );
+ vlc_player_RemoveListener( p_intf->p_sys->player,
+ p_intf->p_sys->p_player_listener );
+ vlc_playlist_Unlock( p_intf->p_sys->playlist );
+
+ /* Remove UPnP listener */
+ p_intf->p_sys->p_upnp->removeListener( p_intf->p_sys->p_listener );
+
+ /* Stop the UPnP MediaRenderer service */
+ p_intf->p_sys->p_upnp->stopMediaRenderer( p_this );
+
+ p_intf->p_sys->p_upnp->release();
+
+ free( p_intf->p_sys );
+
+ msg_Info( p_this, "Stopped MediaRenderer service" );
+}
+
+} // namespace DLNA
diff --git a/modules/control/dlna.hpp b/modules/control/dlna.hpp
new file mode 100644
index 0000000000..87ce3749a6
--- /dev/null
+++ b/modules/control/dlna.hpp
@@ -0,0 +1,92 @@
+/*****************************************************************************
+ * dlna.cpp : DLNA MediaRenderer module
+ *****************************************************************************
+ * Copyright (C) 2019 VLC authors and VideoLAN
+ *
+ * Authors: Johan Gunnarsson <johan.gunnarsson at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *****************************************************************************/
+
+#ifndef CONTROL_DLNA_H
+#define CONTROL_DLNA_H
+
+#include "../services_discovery/upnp-wrapper.hpp"
+
+#include <vlc_plugin.h>
+#include <vlc_interface.h>
+
+#if UPNP_VERSION < 10623
+/* For compatibility with libupnp older than 1.6.23 */
+typedef struct Upnp_Action_Request UpnpActionRequest;
+#define UpnpActionRequest_get_ActionName_cstr(x) ((x)->ActionName)
+#define UpnpActionRequest_get_ServiceID_cstr(x) ((x)->ServiceID)
+#define UpnpActionRequest_get_DevUDN_cstr(x) ((x)->DevUDN)
+#define UpnpActionRequest_get_ActionRequest(x) ((x)->ActionRequest)
+typedef struct Upnp_State_Var_Request UpnpStateVarRequest;
+#define UpnpStateVarRequest_get_StateVarName_cstr(x) ((x)->StateVarName)
+#define UpnpStateVarRequest_get_ServiceID_cstr(x) ((x)->ServiceID)
+#define UpnpStateVarRequest_get_DevUDN_cstr(x) ((x)->DevUDN)
+typedef struct Upnp_Subscription_Request UpnpSubscriptionRequest;
+#define UpnpSubscriptionRequest_get_ServiceId_cstr(x) ((x)->ServiceId)
+#define UpnpSubscriptionRequest_get_SID_cstr(x) ((x)->Sid)
+#define UpnpSubscriptionRequest_get_UDN_cstr(x) ((x)->UDN)
+#endif
+
+#if UPNP_VERSION < 10800
+/* For compatibility with libupnp older than 1.8.0 */
+#define UpnpActionRequest_set_ErrCode(x, y) ((x)->ErrCode = (y))
+#define UpnpActionRequest_set_ErrStr_cstr(x, y) ((x)->ErrStr = (y))
+#define UpnpActionRequest_set_ActionResult(x, y) ((x)->ActionResult = (y))
+#endif
+
+namespace DLNA
+{
+
+int OpenControl( vlc_object_t *p_this );
+void CloseControl( vlc_object_t *p_this );
+
+class EventHandler : public UpnpInstanceWrapper::Listener
+{
+public:
+ EventHandler( intf_thread_t *_p_intf )
+ : p_intf( _p_intf )
+ {
+ }
+
+ ~EventHandler()
+ {
+ }
+
+ int onEvent( Upnp_EventType event_type,
+ UpnpEventPtr p_event,
+ void *p_user_data ) override;
+
+private:
+ intf_thread_t *p_intf = NULL;
+
+ int onActionRequest( UpnpActionRequest *p_event,
+ void *p_user_data );
+
+ int onGetVarRequest( UpnpStateVarRequest *p_event,
+ void *p_user_data );
+
+ int onSubscriptionRequest( UpnpSubscriptionRequest *p_event,
+ void *p_user_data );
+};
+
+} // namespace DLNA
+
+#endif
diff --git a/modules/services_discovery/Makefile.am b/modules/services_discovery/Makefile.am
index 72c21efb56..65af52edf8 100644
--- a/modules/services_discovery/Makefile.am
+++ b/modules/services_discovery/Makefile.am
@@ -31,7 +31,9 @@ libupnp_plugin_la_SOURCES = services_discovery/upnp.cpp services_discovery/upnp.
stream_out/dlna/profile_names.hpp \
stream_out/dlna/dlna_common.hpp \
stream_out/dlna/dlna.hpp \
- stream_out/dlna/dlna.cpp
+ stream_out/dlna/dlna.cpp \
+ control/dlna.hpp \
+ control/dlna.cpp
libupnp_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) $(UPNP_CFLAGS)
libupnp_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(sddir)'
libupnp_plugin_la_LIBADD = $(UPNP_LIBS)
diff --git a/modules/services_discovery/upnp.cpp b/modules/services_discovery/upnp.cpp
index 72644d0565..310969f908 100644
--- a/modules/services_discovery/upnp.cpp
+++ b/modules/services_discovery/upnp.cpp
@@ -180,6 +180,13 @@ vlc_module_begin()
add_string(SOUT_CFG_PREFIX "base_url", NULL, BASE_URL_TEXT, BASE_URL_LONGTEXT, false)
add_string(SOUT_CFG_PREFIX "url", NULL, URL_TEXT, URL_LONGTEXT, false)
add_renderer_opts(SOUT_CFG_PREFIX)
+
+ add_submodule()
+ set_description( N_("UPnP/DLNA MediaRenderer") )
+ set_category( CAT_INTERFACE )
+ set_subcategory( SUBCAT_INTERFACE_CONTROL )
+ set_callbacks( DLNA::OpenControl, DLNA::CloseControl )
+ set_capability( "interface", 0 )
vlc_module_end()
/*
diff --git a/modules/services_discovery/upnp.hpp b/modules/services_discovery/upnp.hpp
index 4eef534f91..ca527af250 100644
--- a/modules/services_discovery/upnp.hpp
+++ b/modules/services_discovery/upnp.hpp
@@ -29,6 +29,7 @@
#include "upnp-wrapper.hpp"
#include "../stream_out/dlna/dlna_common.hpp"
+#include "../control/dlna.hpp"
#include <vlc_url.h>
#include <vlc_interrupt.h>
--
2.17.1
More information about the vlc-devel
mailing list