@ -4,12 +4,13 @@ using System;
using System.Collections.Generic ;
using System.Globalization ;
using System.Linq ;
using System.Net.Http ;
using System.Security ;
using System.Threading ;
using System.Threading.Tasks ;
using System.Xml ;
using System.Xml.Linq ;
using Emby.Dlna.Common ;
using Emby.Dlna.Server ;
using Emby.Dlna.Ssdp ;
using MediaBrowser.Common.Net ;
using MediaBrowser.Controller.Configuration ;
@ -19,24 +20,48 @@ namespace Emby.Dlna.PlayTo
{
public class Device : IDisposable
{
#region Fields & Properties
private static readonly CultureInfo UsCulture = new CultureInfo ( "en-US" ) ;
private readonly IHttpClientFactory _httpClientFactory ;
private readonly ILogger _logger ;
private readonly object _timerLock = new object ( ) ;
private Timer _timer ;
private int _muteVol ;
private int _volume ;
private DateTime _lastVolumeRefresh ;
private bool _volumeRefreshActive ;
private int _connectFailureCount ;
private bool _disposed ;
public Device ( DeviceInfo deviceProperties , IHttpClientFactory httpClientFactory , ILogger logger )
{
Properties = deviceProperties ;
_httpClientFactory = httpClientFactory ;
_logger = logger ;
}
public event EventHandler < PlaybackStartEventArgs > PlaybackStart ;
public event EventHandler < PlaybackProgressEventArgs > PlaybackProgress ;
public event EventHandler < PlaybackStoppedEventArgs > PlaybackStopped ;
public event EventHandler < MediaChangedEventArgs > MediaChanged ;
public DeviceInfo Properties { get ; set ; }
private int _muteVol ;
public bool IsMuted { get ; set ; }
private int _volume ;
public int Volume
{
get
{
RefreshVolumeIfNeeded ( ) ;
RefreshVolumeIfNeeded ( ) .GetAwaiter ( ) . GetResult ( ) ;
return _volume ;
}
set = > _volume = value ;
}
@ -44,29 +69,21 @@ namespace Emby.Dlna.PlayTo
public TimeSpan Position { get ; set ; } = TimeSpan . FromSeconds ( 0 ) ;
public T RANSPORTSTATE TransportState { get ; private set ; }
public T ransportState TransportState { get ; private set ; }
public bool IsPlaying = > TransportState = = T RANSPORTSTATE. PLAYING ;
public bool IsPlaying = > TransportState = = T ransportState. Playing ;
public bool IsPaused = > TransportState = = T RANSPORTSTATE. PAUSED | | TransportState = = TRANSPORTSTATE . PAUSED_PLAYBACK ;
public bool IsPaused = > TransportState = = T ransportState. Paused | | TransportState = = TransportState . PausedPlayback ;
public bool IsStopped = > TransportState = = T RANSPORTSTATE. STOPPED ;
public bool IsStopped = > TransportState = = T ransportState. Stopped ;
# endregion
public Action OnDeviceUnavailable { get ; set ; }
private readonly IHttpClient _httpClient ;
private readonly ILogger _logger ;
private readonly IServerConfigurationManager _config ;
private TransportCommands AvCommands { get ; set ; }
p ublic Action OnDeviceUnavailable { get ; set ; }
private TransportCommands RendererCommands { get ; set ; }
public Device ( DeviceInfo deviceProperties , IHttpClient httpClient , ILogger logger , IServerConfigurationManager config )
{
Properties = deviceProperties ;
_httpClient = httpClient ;
_logger = logger ;
_config = config ;
}
public UBaseObject CurrentMediaInfo { get ; private set ; }
public void Start ( )
{
@ -74,26 +91,24 @@ namespace Emby.Dlna.PlayTo
_timer = new Timer ( TimerCallback , null , 1000 , Timeout . Infinite ) ;
}
private DateTime _lastVolumeRefresh ;
private bool _volumeRefreshActive ;
private void RefreshVolumeIfNeeded ( )
private Task RefreshVolumeIfNeeded ( )
{
if ( ! _volumeRefreshActive )
{
return ;
}
if ( DateTime . UtcNow > = _lastVolumeRefresh . AddSeconds ( 5 ) )
if ( _volumeRefreshActive
& & DateTime . UtcNow > = _lastVolumeRefresh . AddSeconds ( 5 ) )
{
_lastVolumeRefresh = DateTime . UtcNow ;
RefreshVolume ( CancellationToken . None ) ;
return RefreshVolume ( ) ;
}
return Task . CompletedTask ;
}
private async void RefreshVolume ( CancellationToken cancellationToken )
private async Task RefreshVolume ( CancellationToken cancellationToken = default )
{
if ( _disposed )
{
return ;
}
try
{
@ -106,7 +121,6 @@ namespace Emby.Dlna.PlayTo
}
}
private readonly object _timerLock = new object ( ) ;
private void RestartTimer ( bool immediate = false )
{
lock ( _timerLock )
@ -141,8 +155,6 @@ namespace Emby.Dlna.PlayTo
}
}
#region Commanding
public Task VolumeDown ( CancellationToken cancellationToken )
{
var sendVolume = Math . Max ( Volume - 5 , 0 ) ;
@ -211,7 +223,9 @@ namespace Emby.Dlna.PlayTo
var command = rendererCommands . ServiceActions . FirstOrDefault ( c = > c . Name = = "SetMute" ) ;
if ( command = = null )
{
return false ;
}
var service = GetServiceRenderingControl ( ) ;
@ -223,7 +237,7 @@ namespace Emby.Dlna.PlayTo
_logger . LogDebug ( "Setting mute" ) ;
var value = mute ? 1 : 0 ;
await new SsdpHttpClient ( _httpClient ) . SendCommandAsync ( Properties . BaseUrl , service , command . Name , rendererCommands . BuildPost ( command , service . ServiceType , value ) )
await new SsdpHttpClient ( _httpClient Factory ) . SendCommandAsync ( Properties . BaseUrl , service , command . Name , rendererCommands . BuildPost ( command , service . ServiceType , value ) )
. ConfigureAwait ( false ) ;
IsMuted = mute ;
@ -232,15 +246,20 @@ namespace Emby.Dlna.PlayTo
}
/// <summary>
/// Sets volume on a scale of 0-100
/// Sets volume on a scale of 0-100 .
/// </summary>
/// <param name="value">The volume on a scale of 0-100.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task SetVolume ( int value , CancellationToken cancellationToken )
{
var rendererCommands = await GetRenderingProtocolAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
var command = rendererCommands . ServiceActions . FirstOrDefault ( c = > c . Name = = "SetVolume" ) ;
if ( command = = null )
{
return ;
}
var service = GetServiceRenderingControl ( ) ;
@ -253,7 +272,7 @@ namespace Emby.Dlna.PlayTo
// Remote control will perform better
Volume = value ;
await new SsdpHttpClient ( _httpClient ) . SendCommandAsync ( Properties . BaseUrl , service , command . Name , rendererCommands . BuildPost ( command , service . ServiceType , value ) )
await new SsdpHttpClient ( _httpClient Factory ) . SendCommandAsync ( Properties . BaseUrl , service , command . Name , rendererCommands . BuildPost ( command , service . ServiceType , value ) )
. ConfigureAwait ( false ) ;
}
@ -263,7 +282,9 @@ namespace Emby.Dlna.PlayTo
var command = avCommands . ServiceActions . FirstOrDefault ( c = > c . Name = = "Seek" ) ;
if ( command = = null )
{
return ;
}
var service = GetAvTransportService ( ) ;
@ -272,7 +293,7 @@ namespace Emby.Dlna.PlayTo
throw new InvalidOperationException ( "Unable to find service" ) ;
}
await new SsdpHttpClient ( _httpClient ) . SendCommandAsync ( Properties . BaseUrl , service , command . Name , avCommands . BuildPost ( command , service . ServiceType , string . Format ( "{0:hh}:{0:mm}:{0:ss}" , value ) , "REL_TIME" ) )
await new SsdpHttpClient ( _httpClient Factory ) . SendCommandAsync ( Properties . BaseUrl , service , command . Name , avCommands . BuildPost ( command , service . ServiceType , string . Format ( CultureInfo . InvariantCulture , "{0:hh}:{0:mm}:{0:ss}" , value ) , "REL_TIME" ) )
. ConfigureAwait ( false ) ;
RestartTimer ( true ) ;
@ -282,18 +303,20 @@ namespace Emby.Dlna.PlayTo
{
var avCommands = await GetAVProtocolAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
url = url . Replace ( "&" , "&" );
url = url . Replace ( "&" , "&" , StringComparison . Ordinal );
_logger . LogDebug ( "{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}" , Properties . Name , url , header ) ;
var command = avCommands . ServiceActions . FirstOrDefault ( c = > c . Name = = "SetAVTransportURI" ) ;
if ( command = = null )
{
return ;
}
var dictionary = new Dictionary < string , string >
{
{ "CurrentURI" , url } ,
{ "CurrentURIMetaData" , CreateDidlMeta ( metaData ) }
{ "CurrentURI" , url } ,
{ "CurrentURIMetaData" , CreateDidlMeta ( metaData ) }
} ;
var service = GetAvTransportService ( ) ;
@ -304,7 +327,7 @@ namespace Emby.Dlna.PlayTo
}
var post = avCommands . BuildPost ( command , service . ServiceType , url , dictionary ) ;
await new SsdpHttpClient ( _httpClient ) . SendCommandAsync ( Properties . BaseUrl , service , command . Name , post , header : header )
await new SsdpHttpClient ( _httpClient Factory ) . SendCommandAsync ( Properties . BaseUrl , service , command . Name , post , header : header )
. ConfigureAwait ( false ) ;
await Task . Delay ( 50 ) . ConfigureAwait ( false ) ;
@ -329,7 +352,7 @@ namespace Emby.Dlna.PlayTo
return string . Empty ;
}
return DescriptionXmlBuilder . Escape ( value ) ;
return SecurityElement . Escape ( value ) ;
}
private Task SetPlay ( TransportCommands avCommands , CancellationToken cancellationToken )
@ -346,7 +369,7 @@ namespace Emby.Dlna.PlayTo
throw new InvalidOperationException ( "Unable to find service" ) ;
}
return new SsdpHttpClient ( _httpClient ) . SendCommandAsync (
return new SsdpHttpClient ( _httpClient Factory ) . SendCommandAsync (
Properties . BaseUrl ,
service ,
command . Name ,
@ -375,7 +398,7 @@ namespace Emby.Dlna.PlayTo
var service = GetAvTransportService ( ) ;
await new SsdpHttpClient ( _httpClient ) . SendCommandAsync ( Properties . BaseUrl , service , command . Name , avCommands . BuildPost ( command , service . ServiceType , 1 ) )
await new SsdpHttpClient ( _httpClient Factory ) . SendCommandAsync ( Properties . BaseUrl , service , command . Name , avCommands . BuildPost ( command , service . ServiceType , 1 ) )
. ConfigureAwait ( false ) ;
RestartTimer ( true ) ;
@ -393,19 +416,14 @@ namespace Emby.Dlna.PlayTo
var service = GetAvTransportService ( ) ;
await new SsdpHttpClient ( _httpClient ) . SendCommandAsync ( Properties . BaseUrl , service , command . Name , avCommands . BuildPost ( command , service . ServiceType , 1 ) )
await new SsdpHttpClient ( _httpClient Factory ) . SendCommandAsync ( Properties . BaseUrl , service , command . Name , avCommands . BuildPost ( command , service . ServiceType , 1 ) )
. ConfigureAwait ( false ) ;
TransportState = T RANSPORTSTATE. PAUSED ;
TransportState = T ransportState. Paused ;
RestartTimer ( true ) ;
}
# endregion
#region Get data
private int _connectFailureCount ;
private async void TimerCallback ( object sender )
{
if ( _disposed )
@ -434,7 +452,7 @@ namespace Emby.Dlna.PlayTo
if ( transportState . HasValue )
{
// If we're not playing anything no need to get additional data
if ( transportState . Value = = T RANSPORTSTATE. STOPPED )
if ( transportState . Value = = T ransportState. Stopped )
{
UpdateMediaInfo ( null , transportState . Value ) ;
}
@ -458,10 +476,12 @@ namespace Emby.Dlna.PlayTo
_connectFailureCount = 0 ;
if ( _disposed )
{
return ;
}
// If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive
if ( transportState . Value = = T RANSPORTSTATE. STOPPED )
if ( transportState . Value = = T ransportState. Stopped )
{
RestartTimerInactive ( ) ;
}
@ -478,7 +498,9 @@ namespace Emby.Dlna.PlayTo
catch ( Exception ex )
{
if ( _disposed )
{
return ;
}
_logger . LogError ( ex , "Error updating device info for {DeviceName}" , Properties . Name ) ;
@ -494,6 +516,7 @@ namespace Emby.Dlna.PlayTo
return ;
}
}
RestartTimerInactive ( ) ;
}
}
@ -520,7 +543,7 @@ namespace Emby.Dlna.PlayTo
return ;
}
var result = await new SsdpHttpClient ( _httpClient ) . SendCommandAsync (
var result = await new SsdpHttpClient ( _httpClient Factory ) . SendCommandAsync (
Properties . BaseUrl ,
service ,
command . Name ,
@ -532,7 +555,7 @@ namespace Emby.Dlna.PlayTo
return ;
}
var volume = result . Document . Descendants ( u PnpNamespaces. RenderingControl + "GetVolumeResponse" ) . Select ( i = > i . Element ( "CurrentVolume" ) ) . FirstOrDefault ( i = > i ! = null ) ;
var volume = result . Document . Descendants ( U PnpNamespaces. RenderingControl + "GetVolumeResponse" ) . Select ( i = > i . Element ( "CurrentVolume" ) ) . FirstOrDefault ( i = > i ! = null ) ;
var volumeValue = volume ? . Value ;
if ( string . IsNullOrWhiteSpace ( volumeValue ) )
@ -570,7 +593,7 @@ namespace Emby.Dlna.PlayTo
return ;
}
var result = await new SsdpHttpClient ( _httpClient ) . SendCommandAsync (
var result = await new SsdpHttpClient ( _httpClient Factory ) . SendCommandAsync (
Properties . BaseUrl ,
service ,
command . Name ,
@ -578,16 +601,18 @@ namespace Emby.Dlna.PlayTo
cancellationToken : cancellationToken ) . ConfigureAwait ( false ) ;
if ( result = = null | | result . Document = = null )
{
return ;
}
var valueNode = result . Document . Descendants ( u PnpNamespaces. RenderingControl + "GetMuteResponse" )
var valueNode = result . Document . Descendants ( U PnpNamespaces. RenderingControl + "GetMuteResponse" )
. Select ( i = > i . Element ( "CurrentMute" ) )
. FirstOrDefault ( i = > i ! = null ) ;
IsMuted = string . Equals ( valueNode ? . Value , "1" , StringComparison . OrdinalIgnoreCase ) ;
}
private async Task < T RANSPORTSTATE ? > GetTransportInfo ( TransportCommands avCommands , CancellationToken cancellationToken )
private async Task < T ransportState ? > GetTransportInfo ( TransportCommands avCommands , CancellationToken cancellationToken )
{
var command = avCommands . ServiceActions . FirstOrDefault ( c = > c . Name = = "GetTransportInfo" ) ;
if ( command = = null )
@ -601,7 +626,7 @@ namespace Emby.Dlna.PlayTo
return null ;
}
var result = await new SsdpHttpClient ( _httpClient ) . SendCommandAsync (
var result = await new SsdpHttpClient ( _httpClient Factory ) . SendCommandAsync (
Properties . BaseUrl ,
service ,
command . Name ,
@ -614,12 +639,12 @@ namespace Emby.Dlna.PlayTo
}
var transportState =
result . Document . Descendants ( u PnpNamespaces. AvTransport + "GetTransportInfoResponse" ) . Select ( i = > i . Element ( "CurrentTransportState" ) ) . FirstOrDefault ( i = > i ! = null ) ;
result . Document . Descendants ( U PnpNamespaces. AvTransport + "GetTransportInfoResponse" ) . Select ( i = > i . Element ( "CurrentTransportState" ) ) . FirstOrDefault ( i = > i ! = null ) ;
var transportStateValue = transportState ? . Value ;
if ( transportStateValue ! = null
& & Enum . TryParse ( transportStateValue , true , out T RANSPORTSTATE state ) )
& & Enum . TryParse ( transportStateValue , true , out T ransportState state ) )
{
return state ;
}
@ -627,7 +652,7 @@ namespace Emby.Dlna.PlayTo
return null ;
}
private async Task < u BaseObject> GetMediaInfo ( TransportCommands avCommands , CancellationToken cancellationToken )
private async Task < U BaseObject> GetMediaInfo ( TransportCommands avCommands , CancellationToken cancellationToken )
{
var command = avCommands . ServiceActions . FirstOrDefault ( c = > c . Name = = "GetMediaInfo" ) ;
if ( command = = null )
@ -643,7 +668,7 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
var result = await new SsdpHttpClient ( _httpClient ) . SendCommandAsync (
var result = await new SsdpHttpClient ( _httpClient Factory ) . SendCommandAsync (
Properties . BaseUrl ,
service ,
command . Name ,
@ -662,7 +687,7 @@ namespace Emby.Dlna.PlayTo
return null ;
}
var e = track . Element ( uPnpNamespaces. i tems) ? ? track ;
var e = track . Element ( UPnpNamespaces. I tems) ? ? track ;
var elementString = ( string ) e ;
@ -678,13 +703,13 @@ namespace Emby.Dlna.PlayTo
return null ;
}
e = track . Element ( uPnpNamespaces. i tems) ? ? track ;
e = track . Element ( UPnpNamespaces. I tems) ? ? track ;
elementString = ( string ) e ;
if ( ! string . IsNullOrWhiteSpace ( elementString ) )
{
return new u BaseObject
return new U BaseObject
{
Url = elementString
} ;
@ -693,7 +718,7 @@ namespace Emby.Dlna.PlayTo
return null ;
}
private async Task < ( bool , u BaseObject) > GetPositionInfo ( TransportCommands avCommands , CancellationToken cancellationToken )
private async Task < ( bool , U BaseObject) > GetPositionInfo ( TransportCommands avCommands , CancellationToken cancellationToken )
{
var command = avCommands . ServiceActions . FirstOrDefault ( c = > c . Name = = "GetPositionInfo" ) ;
if ( command = = null )
@ -710,7 +735,7 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
var result = await new SsdpHttpClient ( _httpClient ) . SendCommandAsync (
var result = await new SsdpHttpClient ( _httpClient Factory ) . SendCommandAsync (
Properties . BaseUrl ,
service ,
command . Name ,
@ -722,11 +747,11 @@ namespace Emby.Dlna.PlayTo
return ( false , null ) ;
}
var trackUriElem = result . Document . Descendants ( u PnpNamespaces. AvTransport + "GetPositionInfoResponse" ) . Select ( i = > i . Element ( "TrackURI" ) ) . FirstOrDefault ( i = > i ! = null ) ;
var trackUri = trackUriElem = = null ? null : trackUriElem . Value ;
var trackUriElem = result . Document . Descendants ( U PnpNamespaces. AvTransport + "GetPositionInfoResponse" ) . Select ( i = > i . Element ( "TrackURI" ) ) . FirstOrDefault ( i = > i ! = null ) ;
var trackUri = trackUriElem ? . Value ;
var durationElem = result . Document . Descendants ( u PnpNamespaces. AvTransport + "GetPositionInfoResponse" ) . Select ( i = > i . Element ( "TrackDuration" ) ) . FirstOrDefault ( i = > i ! = null ) ;
var duration = durationElem = = null ? null : durationElem . Value ;
var durationElem = result . Document . Descendants ( U PnpNamespaces. AvTransport + "GetPositionInfoResponse" ) . Select ( i = > i . Element ( "TrackDuration" ) ) . FirstOrDefault ( i = > i ! = null ) ;
var duration = durationElem ? . Value ;
if ( ! string . IsNullOrWhiteSpace ( duration )
& & ! string . Equals ( duration , "NOT_IMPLEMENTED" , StringComparison . OrdinalIgnoreCase ) )
@ -738,8 +763,8 @@ namespace Emby.Dlna.PlayTo
Duration = null ;
}
var positionElem = result . Document . Descendants ( u PnpNamespaces. AvTransport + "GetPositionInfoResponse" ) . Select ( i = > i . Element ( "RelTime" ) ) . FirstOrDefault ( i = > i ! = null ) ;
var position = positionElem = = null ? null : positionElem . Value ;
var positionElem = result . Document . Descendants ( U PnpNamespaces. AvTransport + "GetPositionInfoResponse" ) . Select ( i = > i . Element ( "RelTime" ) ) . FirstOrDefault ( i = > i ! = null ) ;
var position = positionElem ? . Value ;
if ( ! string . IsNullOrWhiteSpace ( position ) & & ! string . Equals ( position , "NOT_IMPLEMENTED" , StringComparison . OrdinalIgnoreCase ) )
{
@ -750,7 +775,7 @@ namespace Emby.Dlna.PlayTo
if ( track = = null )
{
// If track is null, some vendors do this, use GetMediaInfo instead
// If track is null, some vendors do this, use GetMediaInfo instead
return ( true , null ) ;
}
@ -778,7 +803,7 @@ namespace Emby.Dlna.PlayTo
return ( true , null ) ;
}
var e = uPnpResponse . Element ( uPnpNamespaces. i tems) ;
var e = uPnpResponse . Element ( UPnpNamespaces. I tems) ;
var uTrack = CreateUBaseObject ( e , trackUri ) ;
@ -794,7 +819,6 @@ namespace Emby.Dlna.PlayTo
}
catch ( XmlException )
{
}
// first try to add a root node with a dlna namesapce
@ -806,43 +830,41 @@ namespace Emby.Dlna.PlayTo
}
catch ( XmlException )
{
}
// some devices send back invalid xml
try
{
return XElement . Parse ( xml . Replace ( "&" , "&" )) ;
return XElement . Parse ( xml . Replace ( "&" , "&" , StringComparison . Ordinal )) ;
}
catch ( XmlException )
{
}
return null ;
}
private static u BaseObject CreateUBaseObject ( XElement container , string trackUri )
private static U BaseObject CreateUBaseObject ( XElement container , string trackUri )
{
if ( container = = null )
{
throw new ArgumentNullException ( nameof ( container ) ) ;
}
var url = container . GetValue ( u PnpNamespaces. Res ) ;
var url = container . GetValue ( U PnpNamespaces. Res ) ;
if ( string . IsNullOrWhiteSpace ( url ) )
{
url = trackUri ;
}
return new u BaseObject
return new U BaseObject
{
Id = container . GetAttributeValue ( u PnpNamespaces. Id ) ,
ParentId = container . GetAttributeValue ( u PnpNamespaces. ParentId ) ,
Title = container . GetValue ( uPnpNamespaces. t itle) ,
IconUrl = container . GetValue ( u PnpNamespaces. Artwork ) ,
SecondText = "" ,
Id = container . GetAttributeValue ( U PnpNamespaces. Id ) ,
ParentId = container . GetAttributeValue ( U PnpNamespaces. ParentId ) ,
Title = container . GetValue ( UPnpNamespaces. T itle) ,
IconUrl = container . GetValue ( U PnpNamespaces. Artwork ) ,
SecondText = string . Empty ,
Url = url ,
ProtocolInfo = GetProtocolInfo ( container ) ,
MetaData = container . ToString ( )
@ -856,11 +878,11 @@ namespace Emby.Dlna.PlayTo
throw new ArgumentNullException ( nameof ( container ) ) ;
}
var resElement = container . Element ( u PnpNamespaces. Res ) ;
var resElement = container . Element ( U PnpNamespaces. Res ) ;
if ( resElement ! = null )
{
var info = resElement . Attribute ( u PnpNamespaces. ProtocolInfo ) ;
var info = resElement . Attribute ( U PnpNamespaces. ProtocolInfo ) ;
if ( info ! = null & & ! string . IsNullOrWhiteSpace ( info . Value ) )
{
@ -871,10 +893,6 @@ namespace Emby.Dlna.PlayTo
return new string [ 4 ] ;
}
# endregion
#region From XML
private async Task < TransportCommands > GetAVProtocolAsync ( CancellationToken cancellationToken )
{
if ( AvCommands ! = null )
@ -895,7 +913,7 @@ namespace Emby.Dlna.PlayTo
string url = NormalizeUrl ( Properties . BaseUrl , avService . ScpdUrl ) ;
var httpClient = new SsdpHttpClient ( _httpClient ) ;
var httpClient = new SsdpHttpClient ( _httpClient Factory ) ;
var document = await httpClient . GetDataAsync ( url , cancellationToken ) . ConfigureAwait ( false ) ;
@ -923,7 +941,7 @@ namespace Emby.Dlna.PlayTo
string url = NormalizeUrl ( Properties . BaseUrl , avService . ScpdUrl ) ;
var httpClient = new SsdpHttpClient ( _httpClient ) ;
var httpClient = new SsdpHttpClient ( _httpClient Factory ) ;
_logger . LogDebug ( "Dlna Device.GetRenderingProtocolAsync" ) ;
var document = await httpClient . GetDataAsync ( url , cancellationToken ) . ConfigureAwait ( false ) ;
@ -939,12 +957,12 @@ namespace Emby.Dlna.PlayTo
return url ;
}
if ( ! url . Contains ( "/" ) )
if ( ! url . Contains ( '/' , StringComparison . Ordinal ) )
{
url = "/dmr/" + url ;
}
if ( ! url . StartsWith ( "/" ))
if ( ! url . StartsWith ( "/" , StringComparison . Ordinal ))
{
url = "/" + url ;
}
@ -952,25 +970,21 @@ namespace Emby.Dlna.PlayTo
return baseUrl + url ;
}
private TransportCommands AvCommands { get ; set ; }
private TransportCommands RendererCommands { get ; set ; }
public static async Task < Device > CreateuPnpDeviceAsync ( Uri url , IHttpClient httpClient , IServerConfigurationManager config , ILogger logger , CancellationToken cancellationToken )
public static async Task < Device > CreateuPnpDeviceAsync ( Uri url , IHttpClientFactory httpClientFactory , ILogger logger , CancellationToken cancellationToken )
{
var ssdpHttpClient = new SsdpHttpClient ( httpClient ) ;
var ssdpHttpClient = new SsdpHttpClient ( httpClientFactory ) ;
var document = await ssdpHttpClient . GetDataAsync ( url . ToString ( ) , cancellationToken ) . ConfigureAwait ( false ) ;
var friendlyNames = new List < string > ( ) ;
var name = document . Descendants ( uPnpNamespaces. u d. GetName ( "friendlyName" ) ) . FirstOrDefault ( ) ;
var name = document . Descendants ( UPnpNamespaces. U d. GetName ( "friendlyName" ) ) . FirstOrDefault ( ) ;
if ( name ! = null & & ! string . IsNullOrWhiteSpace ( name . Value ) )
{
friendlyNames . Add ( name . Value ) ;
}
var room = document . Descendants ( uPnpNamespaces. u d. GetName ( "roomName" ) ) . FirstOrDefault ( ) ;
var room = document . Descendants ( UPnpNamespaces. U d. GetName ( "roomName" ) ) . FirstOrDefault ( ) ;
if ( room ! = null & & ! string . IsNullOrWhiteSpace ( room . Value ) )
{
friendlyNames . Add ( room . Value ) ;
@ -979,77 +993,77 @@ namespace Emby.Dlna.PlayTo
var deviceProperties = new DeviceInfo ( )
{
Name = string . Join ( " " , friendlyNames ) ,
BaseUrl = string . Format ( "http://{0}:{1}" , url . Host , url . Port )
BaseUrl = string . Format ( CultureInfo . InvariantCulture , "http://{0}:{1}" , url . Host , url . Port )
} ;
var model = document . Descendants ( uPnpNamespaces. u d. GetName ( "modelName" ) ) . FirstOrDefault ( ) ;
var model = document . Descendants ( UPnpNamespaces. U d. GetName ( "modelName" ) ) . FirstOrDefault ( ) ;
if ( model ! = null )
{
deviceProperties . ModelName = model . Value ;
}
var modelNumber = document . Descendants ( uPnpNamespaces. u d. GetName ( "modelNumber" ) ) . FirstOrDefault ( ) ;
var modelNumber = document . Descendants ( UPnpNamespaces. U d. GetName ( "modelNumber" ) ) . FirstOrDefault ( ) ;
if ( modelNumber ! = null )
{
deviceProperties . ModelNumber = modelNumber . Value ;
}
var uuid = document . Descendants ( uPnpNamespaces. u d. GetName ( "UDN" ) ) . FirstOrDefault ( ) ;
var uuid = document . Descendants ( UPnpNamespaces. U d. GetName ( "UDN" ) ) . FirstOrDefault ( ) ;
if ( uuid ! = null )
{
deviceProperties . UUID = uuid . Value ;
}
var manufacturer = document . Descendants ( uPnpNamespaces. u d. GetName ( "manufacturer" ) ) . FirstOrDefault ( ) ;
var manufacturer = document . Descendants ( UPnpNamespaces. U d. GetName ( "manufacturer" ) ) . FirstOrDefault ( ) ;
if ( manufacturer ! = null )
{
deviceProperties . Manufacturer = manufacturer . Value ;
}
var manufacturerUrl = document . Descendants ( uPnpNamespaces. u d. GetName ( "manufacturerURL" ) ) . FirstOrDefault ( ) ;
var manufacturerUrl = document . Descendants ( UPnpNamespaces. U d. GetName ( "manufacturerURL" ) ) . FirstOrDefault ( ) ;
if ( manufacturerUrl ! = null )
{
deviceProperties . ManufacturerUrl = manufacturerUrl . Value ;
}
var presentationUrl = document . Descendants ( uPnpNamespaces. u d. GetName ( "presentationURL" ) ) . FirstOrDefault ( ) ;
var presentationUrl = document . Descendants ( UPnpNamespaces. U d. GetName ( "presentationURL" ) ) . FirstOrDefault ( ) ;
if ( presentationUrl ! = null )
{
deviceProperties . PresentationUrl = presentationUrl . Value ;
}
var modelUrl = document . Descendants ( uPnpNamespaces. u d. GetName ( "modelURL" ) ) . FirstOrDefault ( ) ;
var modelUrl = document . Descendants ( UPnpNamespaces. U d. GetName ( "modelURL" ) ) . FirstOrDefault ( ) ;
if ( modelUrl ! = null )
{
deviceProperties . ModelUrl = modelUrl . Value ;
}
var serialNumber = document . Descendants ( uPnpNamespaces. u d. GetName ( "serialNumber" ) ) . FirstOrDefault ( ) ;
var serialNumber = document . Descendants ( UPnpNamespaces. U d. GetName ( "serialNumber" ) ) . FirstOrDefault ( ) ;
if ( serialNumber ! = null )
{
deviceProperties . SerialNumber = serialNumber . Value ;
}
var modelDescription = document . Descendants ( uPnpNamespaces. u d. GetName ( "modelDescription" ) ) . FirstOrDefault ( ) ;
var modelDescription = document . Descendants ( UPnpNamespaces. U d. GetName ( "modelDescription" ) ) . FirstOrDefault ( ) ;
if ( modelDescription ! = null )
{
deviceProperties . ModelDescription = modelDescription . Value ;
}
var icon = document . Descendants ( uPnpNamespaces. u d. GetName ( "icon" ) ) . FirstOrDefault ( ) ;
var icon = document . Descendants ( UPnpNamespaces. U d. GetName ( "icon" ) ) . FirstOrDefault ( ) ;
if ( icon ! = null )
{
deviceProperties . Icon = CreateIcon ( icon ) ;
}
foreach ( var services in document . Descendants ( uPnpNamespaces. u d. GetName ( "serviceList" ) ) )
foreach ( var services in document . Descendants ( UPnpNamespaces. U d. GetName ( "serviceList" ) ) )
{
if ( services = = null )
{
continue ;
}
var servicesList = services . Descendants ( uPnpNamespaces. u d. GetName ( "service" ) ) ;
var servicesList = services . Descendants ( UPnpNamespaces. U d. GetName ( "service" ) ) ;
if ( servicesList = = null )
{
continue ;
@ -1066,12 +1080,9 @@ namespace Emby.Dlna.PlayTo
}
}
return new Device ( deviceProperties , httpClient , logger , config ) ;
return new Device ( deviceProperties , httpClient Factory , logger ) ;
}
# endregion
private static readonly CultureInfo UsCulture = new CultureInfo ( "en-US" ) ;
private static DeviceIcon CreateIcon ( XElement element )
{
if ( element = = null )
@ -1079,11 +1090,11 @@ namespace Emby.Dlna.PlayTo
throw new ArgumentNullException ( nameof ( element ) ) ;
}
var mimeType = element . GetDescendantValue ( uPnpNamespaces. u d. GetName ( "mimetype" ) ) ;
var width = element . GetDescendantValue ( uPnpNamespaces. u d. GetName ( "width" ) ) ;
var height = element . GetDescendantValue ( uPnpNamespaces. u d. GetName ( "height" ) ) ;
var depth = element . GetDescendantValue ( uPnpNamespaces. u d. GetName ( "depth" ) ) ;
var url = element . GetDescendantValue ( uPnpNamespaces. u d. GetName ( "url" ) ) ;
var mimeType = element . GetDescendantValue ( UPnpNamespaces. U d. GetName ( "mimetype" ) ) ;
var width = element . GetDescendantValue ( UPnpNamespaces. U d. GetName ( "width" ) ) ;
var height = element . GetDescendantValue ( UPnpNamespaces. U d. GetName ( "height" ) ) ;
var depth = element . GetDescendantValue ( UPnpNamespaces. U d. GetName ( "depth" ) ) ;
var url = element . GetDescendantValue ( UPnpNamespaces. U d. GetName ( "url" ) ) ;
var widthValue = int . Parse ( width , NumberStyles . Integer , UsCulture ) ;
var heightValue = int . Parse ( height , NumberStyles . Integer , UsCulture ) ;
@ -1100,11 +1111,11 @@ namespace Emby.Dlna.PlayTo
private static DeviceService Create ( XElement element )
{
var type = element . GetDescendantValue ( uPnpNamespaces. u d. GetName ( "serviceType" ) ) ;
var id = element . GetDescendantValue ( uPnpNamespaces. u d. GetName ( "serviceId" ) ) ;
var scpdUrl = element . GetDescendantValue ( uPnpNamespaces. u d. GetName ( "SCPDURL" ) ) ;
var controlURL = element . GetDescendantValue ( uPnpNamespaces. u d. GetName ( "controlURL" ) ) ;
var eventSubURL = element . GetDescendantValue ( uPnpNamespaces. u d. GetName ( "eventSubURL" ) ) ;
var type = element . GetDescendantValue ( UPnpNamespaces. U d. GetName ( "serviceType" ) ) ;
var id = element . GetDescendantValue ( UPnpNamespaces. U d. GetName ( "serviceId" ) ) ;
var scpdUrl = element . GetDescendantValue ( UPnpNamespaces. U d. GetName ( "SCPDURL" ) ) ;
var controlURL = element . GetDescendantValue ( UPnpNamespaces. U d. GetName ( "controlURL" ) ) ;
var eventSubURL = element . GetDescendantValue ( UPnpNamespaces. U d. GetName ( "eventSubURL" ) ) ;
return new DeviceService
{
@ -1116,14 +1127,7 @@ namespace Emby.Dlna.PlayTo
} ;
}
public event EventHandler < PlaybackStartEventArgs > PlaybackStart ;
public event EventHandler < PlaybackProgressEventArgs > PlaybackProgress ;
public event EventHandler < PlaybackStoppedEventArgs > PlaybackStopped ;
public event EventHandler < MediaChangedEventArgs > MediaChanged ;
public uBaseObject CurrentMediaInfo { get ; private set ; }
private void UpdateMediaInfo ( uBaseObject mediaInfo , TRANSPORTSTATE state )
private void UpdateMediaInfo ( UBaseObject mediaInfo , TransportState state )
{
TransportState = state ;
@ -1132,7 +1136,7 @@ namespace Emby.Dlna.PlayTo
if ( previousMediaInfo = = null & & mediaInfo ! = null )
{
if ( state ! = T RANSPORTSTATE. STOPPED )
if ( state ! = T ransportState. Stopped )
{
OnPlaybackStart ( mediaInfo ) ;
}
@ -1151,7 +1155,7 @@ namespace Emby.Dlna.PlayTo
}
}
private void OnPlaybackStart ( u BaseObject mediaInfo )
private void OnPlaybackStart ( U BaseObject mediaInfo )
{
if ( string . IsNullOrWhiteSpace ( mediaInfo . Url ) )
{
@ -1164,7 +1168,7 @@ namespace Emby.Dlna.PlayTo
} ) ;
}
private void OnPlaybackProgress ( u BaseObject mediaInfo )
private void OnPlaybackProgress ( U BaseObject mediaInfo )
{
if ( string . IsNullOrWhiteSpace ( mediaInfo . Url ) )
{
@ -1177,7 +1181,7 @@ namespace Emby.Dlna.PlayTo
} ) ;
}
private void OnPlaybackStop ( u BaseObject mediaInfo )
private void OnPlaybackStop ( U BaseObject mediaInfo )
{
PlaybackStopped ? . Invoke ( this , new PlaybackStoppedEventArgs
{
@ -1185,7 +1189,7 @@ namespace Emby.Dlna.PlayTo
} ) ;
}
private void OnMediaChanged ( uBaseObject old , u BaseObject newMedia )
private void OnMediaChanged ( UBaseObject old , U BaseObject newMedia )
{
MediaChanged ? . Invoke ( this , new MediaChangedEventArgs
{
@ -1194,16 +1198,17 @@ namespace Emby.Dlna.PlayTo
} ) ;
}
#region IDisposable
bool _disposed ;
/// <inheritdoc />
public void Dispose ( )
{
Dispose ( true ) ;
GC . SuppressFinalize ( this ) ;
}
/// <summary>
/// Releases unmanaged and optionally managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose ( bool disposing )
{
if ( _disposed )
@ -1222,11 +1227,10 @@ namespace Emby.Dlna.PlayTo
_disposed = true ;
}
# endregion
/// <inheritdoc />
public override string ToString ( )
{
return string . Format ( "{0} - {1}" , Properties . Name , Properties . BaseUrl ) ;
return string . Format ( CultureInfo . InvariantCulture , "{0} - {1}" , Properties . Name , Properties . BaseUrl ) ;
}
}
}