diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs
index d5fc3172c..e6a62c181 100644
--- a/MediaBrowser.Controller/Entities/User.cs
+++ b/MediaBrowser.Controller/Entities/User.cs
@@ -226,11 +226,11 @@ namespace MediaBrowser.Controller.Entities
///
/// Saves the current configuration to the file system
///
- public void SaveConfiguration(IXmlSerializer serializer)
+ public void SaveConfiguration()
{
var xmlPath = ConfigurationFilePath;
Directory.CreateDirectory(System.IO.Path.GetDirectoryName(xmlPath));
- serializer.SerializeToFile(Configuration, xmlPath);
+ XmlSerializer.SerializeToFile(Configuration, xmlPath);
}
///
@@ -247,7 +247,7 @@ namespace MediaBrowser.Controller.Entities
}
Configuration = config;
- SaveConfiguration(serializer);
+ SaveConfiguration();
}
}
}
diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
index 937c9b4c9..59420c5a1 100644
--- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
+++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
@@ -52,16 +52,28 @@
Properties\SharedVersion.cs
+
+
+
+
+ Code
+
+
+
+
Code
+
+
+
@@ -77,6 +89,10 @@
{9142eefa-7570-41e1-bfcc-468bb571af2f}
MediaBrowser.Common
+
+ {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}
+ MediaBrowser.Controller
+
{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}
MediaBrowser.Model
diff --git a/MediaBrowser.Dlna/PlayTo/Configuration/DlnaProfile.cs b/MediaBrowser.Dlna/PlayTo/Configuration/DlnaProfile.cs
new file mode 100644
index 000000000..e75cea5a9
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/Configuration/DlnaProfile.cs
@@ -0,0 +1,53 @@
+namespace MediaBrowser.Dlna.PlayTo.Configuration
+{
+ public class DlnaProfile
+ {
+ ///
+ /// Gets or sets the name to be displayed.
+ ///
+ ///
+ /// The name.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Gets or sets the type of the client.
+ ///
+ ///
+ /// The type of the client.
+ ///
+ public string ClientType { get; set; }
+
+ ///
+ /// Gets or sets the name of the friendly.
+ ///
+ ///
+ /// The name of the friendly.
+ ///
+ public string FriendlyName { get; set; }
+
+ ///
+ /// Gets or sets the model number.
+ ///
+ ///
+ /// The model number.
+ ///
+ public string ModelNumber { get; set; }
+
+ ///
+ /// Gets or sets the name of the model.
+ ///
+ ///
+ /// The name of the model.
+ ///
+ public string ModelName { get; set; }
+
+ ///
+ /// Gets or sets the transcode settings.
+ ///
+ ///
+ /// The transcode settings.
+ ///
+ public TranscodeSettings[] TranscodeSettings { get; set; }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/Configuration/PluginConfiguration.cs b/MediaBrowser.Dlna/PlayTo/Configuration/PluginConfiguration.cs
new file mode 100644
index 000000000..1bd8781bb
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/Configuration/PluginConfiguration.cs
@@ -0,0 +1,119 @@
+namespace MediaBrowser.Dlna.PlayTo.Configuration
+{
+ public class PlayToConfiguration
+ {
+ private static readonly string[] _supportedStaticFormats = { "mp3", "flac", "m4a", "wma", "avi", "mp4", "mkv", "ts" };
+ public static string[] SupportedStaticFormats
+ {
+ get
+ {
+ return _supportedStaticFormats;
+ }
+ }
+
+ private static readonly DlnaProfile[] _profiles = GetDefaultProfiles();
+ public static DlnaProfile[] Profiles
+ {
+ get
+ {
+ return _profiles;
+ }
+ }
+
+ private static DlnaProfile[] GetDefaultProfiles()
+ {
+ var profile0 = new DlnaProfile
+ {
+ Name = "Samsung TV (B Series) [Profile]",
+ ClientType = "DLNA",
+ FriendlyName = "^TV$",
+ ModelNumber = @"1\.0",
+ ModelName = "Samsung DTV DMR",
+ TranscodeSettings = TranscodeSettings.GetDefaultTranscodingSettings()
+ };
+
+ var profile1 = new DlnaProfile
+ {
+ Name = "Samsung TV (E/F-series) [Profile]",
+ ClientType = "DLNA",
+ FriendlyName = @"(^\[TV\][A-Z]{2}\d{2}(E|F)[A-Z]?\d{3,4}.*)|^\[TV\] Samsung",
+ ModelNumber = @"(1\.0)|(AllShare1\.0)",
+ TranscodeSettings = TranscodeSettings.GetDefaultTranscodingSettings()
+ };
+
+ var profile2 = new DlnaProfile
+ {
+ Name = "Samsung TV (C/D-series) [Profile]",
+ ClientType = "DLNA",
+ FriendlyName = @"(^TV-\d{2}C\d{3}.*)|(^\[TV\][A-Z]{2}\d{2}(D)[A-Z]?\d{3,4}.*)|^\[TV\] Samsung",
+ ModelNumber = @"(1\.0)|(AllShare1\.0)",
+ TranscodeSettings = TranscodeSettings.GetDefaultTranscodingSettings()
+ };
+
+ var profile3 = new DlnaProfile
+ {
+ Name = "Xbox 360 [Profile]",
+ ClientType = "DLNA",
+ ModelName = "Xbox 360",
+ TranscodeSettings = new[]
+ {
+ new TranscodeSettings {Container = "mkv", TargetContainer = "ts"},
+ new TranscodeSettings {Container = "flac", TargetContainer = "mp3"},
+ new TranscodeSettings {Container = "m4a", TargetContainer = "mp3"}
+ }
+ };
+
+ var profile4 = new DlnaProfile
+ {
+ Name = "Xbox One [Profile]",
+ ModelName = "Xbox One",
+ ClientType = "DLNA",
+ FriendlyName = "Xbox-SystemOS",
+ TranscodeSettings = new[]
+ {
+ new TranscodeSettings {Container = "mkv", TargetContainer = "ts"},
+ new TranscodeSettings {Container = "flac", TargetContainer = "mp3"},
+ new TranscodeSettings {Container = "m4a", TargetContainer = "mp3"}
+ }
+ };
+
+ var profile5 = new DlnaProfile
+ {
+ Name = "Sony Bravia TV (2012)",
+ ClientType = "TV",
+ FriendlyName = @"BRAVIA KDL-\d{2}[A-Z]X\d5(\d|G).*",
+ TranscodeSettings = TranscodeSettings.GetDefaultTranscodingSettings()
+ };
+
+ //WDTV does not need any transcoding of the formats we support statically
+ var profile6 = new DlnaProfile
+ {
+ Name = "WDTV Live [Profile]",
+ ClientType = "DLNA",
+ ModelName = "WD TV HD Live",
+ TranscodeSettings = new TranscodeSettings[] { }
+ };
+
+ var profile7 = new DlnaProfile
+ {
+ //Linksys DMA2100us does not need any transcoding of the formats we support statically
+ Name = "Linksys DMA2100 [Profile]",
+ ClientType = "DLNA",
+ ModelName = "DMA2100us",
+ TranscodeSettings = new TranscodeSettings[] { }
+ };
+
+ return new[]
+ {
+ profile0,
+ profile1,
+ profile2,
+ profile3,
+ profile4,
+ profile5,
+ profile6,
+ profile7
+ };
+ }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/Configuration/TranscodeSetting.cs b/MediaBrowser.Dlna/PlayTo/Configuration/TranscodeSetting.cs
new file mode 100644
index 000000000..f5cceaaaa
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/Configuration/TranscodeSetting.cs
@@ -0,0 +1,76 @@
+using System;
+
+namespace MediaBrowser.Dlna.PlayTo.Configuration
+{
+ public class TranscodeSettings
+ {
+ ///
+ /// Gets or sets the container.
+ ///
+ ///
+ /// The container.
+ ///
+ public string Container { get; set; }
+
+ ///
+ /// Gets or sets the target container.
+ ///
+ ///
+ /// The target container.
+ ///
+ public string TargetContainer { get; set; }
+
+ ///
+ /// The default transcoding settings
+ ///
+ private static readonly TranscodeSettings[] DefaultTranscodingSettings =
+ {
+ new TranscodeSettings { Container = "mkv", TargetContainer = "ts" },
+ new TranscodeSettings { Container = "flac", TargetContainer = "mp3" },
+ new TranscodeSettings { Container = "m4a", TargetContainer = "mp3" }
+ };
+
+ public static TranscodeSettings[] GetDefaultTranscodingSettings()
+ {
+ return DefaultTranscodingSettings;
+ }
+
+ ///
+ /// Gets the profile settings.
+ ///
+ /// The device properties.
+ /// The TranscodeSettings for the device
+ public static TranscodeSettings[] GetProfileSettings(DeviceProperties deviceProperties)
+ {
+ foreach (var profile in PlayToConfiguration.Profiles)
+ {
+ if (!string.IsNullOrEmpty(profile.FriendlyName))
+ {
+ if (!string.Equals(deviceProperties.Name, profile.FriendlyName, StringComparison.OrdinalIgnoreCase))
+ continue;
+ }
+
+ if (!string.IsNullOrEmpty(profile.ModelNumber))
+ {
+ if (!string.Equals(deviceProperties.ModelNumber, profile.ModelNumber, StringComparison.OrdinalIgnoreCase))
+ continue;
+ }
+
+ if (!string.IsNullOrEmpty(profile.ModelName))
+ {
+ if (!string.Equals(deviceProperties.ModelName, profile.ModelName, StringComparison.OrdinalIgnoreCase))
+ continue;
+ }
+
+ deviceProperties.DisplayName = profile.Name;
+ deviceProperties.ClientType = profile.ClientType;
+ return profile.TranscodeSettings;
+
+ }
+
+ // Since we don't have alot of info about different devices we go down the safe
+ // route abd use the default transcoding settings if no profile exist
+ return GetDefaultTranscodingSettings();
+ }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/Device.cs b/MediaBrowser.Dlna/PlayTo/Device.cs
index 36e631c13..b6de29af6 100644
--- a/MediaBrowser.Dlna/PlayTo/Device.cs
+++ b/MediaBrowser.Dlna/PlayTo/Device.cs
@@ -1,12 +1,11 @@
using MediaBrowser.Common.Net;
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
-using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Xml.Linq;
+using MediaBrowser.Model.Logging;
namespace MediaBrowser.Dlna.PlayTo
{
@@ -122,12 +121,15 @@ namespace MediaBrowser.Dlna.PlayTo
#endregion
private readonly IHttpClient _httpClient;
-
+ private readonly ILogger _logger;
+
#region Constructor & Initializer
- public Device(DeviceProperties deviceProperties)
+ public Device(DeviceProperties deviceProperties, IHttpClient httpClient, ILogger logger)
{
Properties = deviceProperties;
+ _httpClient = httpClient;
+ _logger = logger;
}
internal void Start()
@@ -182,9 +184,15 @@ namespace MediaBrowser.Dlna.PlayTo
if (command == null)
return true;
- var service = this.Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId);
+ var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId);
- var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, value));
+ if (service == null)
+ {
+ throw new InvalidOperationException("Unable to find service");
+ }
+
+ var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, value))
+ .ConfigureAwait(false);
Volume = value;
return true;
}
@@ -197,7 +205,14 @@ namespace MediaBrowser.Dlna.PlayTo
var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
- var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, String.Format("{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"));
+ if (service == null)
+ {
+ throw new InvalidOperationException("Unable to find service");
+ }
+
+ var result = 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"))
+ .ConfigureAwait(false);
+
return value;
}
@@ -206,7 +221,9 @@ namespace MediaBrowser.Dlna.PlayTo
_dt.Stop();
TransportState = "STOPPED";
CurrentId = "0";
- await Task.Delay(50);
+
+ await Task.Delay(50).ConfigureAwait(false);
+
var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
if (command == null)
return false;
@@ -218,12 +235,21 @@ namespace MediaBrowser.Dlna.PlayTo
};
var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
- var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, url, dictionary), header);
+
+ if (service == null)
+ {
+ throw new InvalidOperationException("Unable to find service");
+ }
+
+ var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, url, dictionary), header)
+ .ConfigureAwait(false);
+
if (!IsPlaying)
{
- await Task.Delay(50);
- await SetPlay();
+ await Task.Delay(50).ConfigureAwait(false);
+ await SetPlay().ConfigureAwait(false);
}
+
_count = 5;
_dt.Start();
return true;
@@ -252,8 +278,17 @@ namespace MediaBrowser.Dlna.PlayTo
dictionary.Add("NextURIMetaData", CreateDidlMeta(metaData));
var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
- var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, value, dictionary), header);
- await Task.Delay(100);
+
+ if (service == null)
+ {
+ throw new InvalidOperationException("Unable to find service");
+ }
+
+ var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, value, dictionary), header)
+ .ConfigureAwait(false);
+
+ await Task.Delay(100).ConfigureAwait(false);
+
return true;
}
@@ -265,7 +300,14 @@ namespace MediaBrowser.Dlna.PlayTo
var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
- var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1));
+ if (service == null)
+ {
+ throw new InvalidOperationException("Unable to find service");
+ }
+
+ var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1))
+ .ConfigureAwait(false);
+
_count = 5;
return true;
}
@@ -278,8 +320,10 @@ namespace MediaBrowser.Dlna.PlayTo
var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
- var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1));
- await Task.Delay(50);
+ var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1))
+ .ConfigureAwait(false);
+
+ await Task.Delay(50).ConfigureAwait(false);
_count = 4;
return true;
}
@@ -292,8 +336,10 @@ namespace MediaBrowser.Dlna.PlayTo
var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
- var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 0));
- await Task.Delay(50);
+ var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 0))
+ .ConfigureAwait(false);
+
+ await Task.Delay(50).ConfigureAwait(false);
TransportState = "PAUSED_PLAYBACK";
return true;
}
@@ -302,23 +348,26 @@ namespace MediaBrowser.Dlna.PlayTo
#region Get data
+ // TODO: What is going on here
int _count = 5;
+
async void dt_Elapsed(object sender, ElapsedEventArgs e)
{
if (_disposed)
return;
((Timer)sender).Stop();
- var hasTrack = await GetPositionInfo();
+ var hasTrack = await GetPositionInfo().ConfigureAwait(false);
+
+ // TODO: Why make these requests if hasTrack==false?
if (_count > 4)
{
-
- await GetTransportInfo();
+ await GetTransportInfo().ConfigureAwait(false);
if (!hasTrack)
{
- await GetMediaInfo();
+ await GetMediaInfo().ConfigureAwait(false);
}
- await GetVolume();
+ await GetVolume().ConfigureAwait(false);
_count = 0;
}
@@ -335,23 +384,41 @@ namespace MediaBrowser.Dlna.PlayTo
return;
var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId);
+
+ if (service == null)
+ {
+ throw new InvalidOperationException("Unable to find service");
+ }
+
+ XDocument result;
+
try
{
- var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType));
- if (result == null)
- return;
- var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").FirstOrDefault().Element("CurrentVolume").Value;
- if (volume == null)
- return;
- Volume = Int32.Parse(volume);
-
- //Reset the Mute value if Volume is bigger than zero
- if (Volume > 0 && _muteVol > 0)
- {
- _muteVol = 0;
- }
+ result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
+ .ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting volume info", ex);
+ return;
+ }
+
+ if (result == null || result.Document == null)
+ return;
+
+ var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null);
+ var volumeValue = volume == null ? null : volume.Value;
+
+ if (volumeValue == null)
+ return;
+
+ Volume = Int32.Parse(volumeValue);
+
+ //Reset the Mute value if Volume is bigger than zero
+ if (Volume > 0 && _muteVol > 0)
+ {
+ _muteVol = 0;
}
- catch { }
}
private async Task GetTransportInfo()
@@ -360,21 +427,35 @@ namespace MediaBrowser.Dlna.PlayTo
if (command == null)
return;
- var service = this.Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
+ var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
if (service == null)
return;
- var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType));
+ XDocument result;
+
try
{
- var transportState = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").FirstOrDefault().Element("CurrentTransportState").Value;
- if (transportState != null)
- TransportState = transportState;
+ result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
+ .ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting transport info", ex);
+ return;
}
- catch { }
- if (result != null)
- UpdateTime = DateTime.UtcNow;
+ if (result == null || result.Document == null)
+ return;
+
+ var transportState =
+ result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null);
+
+ var transportStateValue = transportState == null ? null : transportState.Value;
+
+ if (transportStateValue != null)
+ TransportState = transportStateValue;
+
+ UpdateTime = DateTime.UtcNow;
}
private async Task GetMediaInfo()
@@ -385,28 +466,47 @@ namespace MediaBrowser.Dlna.PlayTo
var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
- var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType));
+ if (service == null)
+ {
+ throw new InvalidOperationException("Unable to find service");
+ }
+
+ XDocument result;
+
try
{
- var track = result.Document.Descendants("CurrentURIMetaData").FirstOrDefault().Value;
- if (String.IsNullOrEmpty(track))
- {
- CurrentId = "0";
- return;
- }
- XElement uPnpResponse = XElement.Parse((String)track);
-
- var e = uPnpResponse.Element(uPnpNamespaces.items);
-
- if (e == null)
- e = uPnpResponse;
-
- var uTrack = uParser.CreateObjectFromXML(new uParserObject { Type = e.Element(uPnpNamespaces.uClass).Value, Element = e });
- if (uTrack != null)
- CurrentId = uTrack.Id;
-
+ result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
+ .ConfigureAwait(false);
}
- catch { }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting media info", ex);
+ return;
+ }
+
+ if (result == null || result.Document == null)
+ return;
+
+ var track = result.Document.Descendants("CurrentURIMetaData").Select(i => i.Value).FirstOrDefault();
+
+ if (String.IsNullOrEmpty(track))
+ {
+ CurrentId = "0";
+ return;
+ }
+
+ var uPnpResponse = XElement.Parse(track);
+
+ var e = uPnpResponse.Element(uPnpNamespaces.items) ?? uPnpResponse;
+
+ var uTrack = uParser.CreateObjectFromXML(new uParserObject
+ {
+ Type = e.GetValue(uPnpNamespaces.uClass),
+ Element = e
+ });
+
+ if (uTrack != null)
+ CurrentId = uTrack.Id;
}
private async Task GetPositionInfo()
@@ -417,78 +517,89 @@ namespace MediaBrowser.Dlna.PlayTo
var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
- var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType));
-
+ if (service == null)
+ {
+ throw new InvalidOperationException("Unable to find service");
+ }
+
+ XDocument result;
+
try
{
- var duration = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").FirstOrDefault().Element("TrackDuration").Value;
-
- if (duration != null)
- {
- Duration = TimeSpan.Parse(duration);
- }
-
- var position = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").FirstOrDefault().Element("RelTime").Value;
-
- if (position != null)
- {
- Position = TimeSpan.Parse(position);
- }
-
- var track = result.Document.Descendants("TrackMetaData").Select(i => i.Value)
- .FirstOrDefault();
-
- if (String.IsNullOrEmpty(track))
- {
- //If track is null, some vendors do this, use GetMediaInfo instead
- return false;
- }
-
- var uPnpResponse = XElement.Parse(track);
-
- var e = uPnpResponse.Element(uPnpNamespaces.items) ?? uPnpResponse;
-
- var uTrack = uBaseObject.Create(e);
-
- if (uTrack == null)
- return true;
-
- CurrentId = uTrack.Id;
-
- return true;
+ result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
+ .ConfigureAwait(false);
}
- catch { return false; }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting position info", ex);
+ return false;
+ }
+
+ if (result == null || result.Document == null)
+ return true;
+
+ var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
+ var duration = durationElem == null ? null : durationElem.Value;
+
+ if (duration != null)
+ {
+ Duration = TimeSpan.Parse(duration);
+ }
+
+ var positionElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != null);
+ var position = positionElem == null ? null : positionElem.Value;
+
+ if (position != null)
+ {
+ Position = TimeSpan.Parse(position);
+ }
+
+ var track = result.Document.Descendants("TrackMetaData").Select(i => i.Value)
+ .FirstOrDefault();
+
+ if (String.IsNullOrEmpty(track))
+ {
+ //If track is null, some vendors do this, use GetMediaInfo instead
+ return false;
+ }
+
+ var uPnpResponse = XElement.Parse(track);
+
+ var e = uPnpResponse.Element(uPnpNamespaces.items) ?? uPnpResponse;
+
+ var uTrack = uBaseObject.Create(e);
+
+ if (uTrack == null)
+ return true;
+
+ CurrentId = uTrack.Id;
+
+ return true;
}
#endregion
#region From XML
- internal async Task GetAVProtocolAsync()
+ private async Task GetAVProtocolAsync()
{
var avService = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
if (avService == null)
return;
- string url = avService.SCPDURL;
+ var url = avService.SCPDURL;
if (!url.Contains("/"))
url = "/dmr/" + url;
if (!url.StartsWith("/"))
url = "/" + url;
- var httpClient = new SsdpHttpClient();
- var stream = await httpClient.GetDataAsync(new Uri(Properties.BaseUrl + url));
-
- if (stream == null)
- return;
-
- XDocument document = httpClient.ParseStream(stream);
- stream.Dispose();
+ var httpClient = new SsdpHttpClient(_httpClient);
+ var document = await httpClient.GetDataAsync(new Uri(Properties.BaseUrl + url));
AvCommands = TransportCommands.Create(document);
}
- internal async Task GetRenderingProtocolAsync()
+ private async Task GetRenderingProtocolAsync()
{
var avService = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId);
@@ -500,14 +611,8 @@ namespace MediaBrowser.Dlna.PlayTo
if (!url.StartsWith("/"))
url = "/" + url;
- var httpClient = new SsdpHttpClient();
- var stream = await httpClient.GetDataAsync(new Uri(Properties.BaseUrl + url));
-
- if (stream == null)
- return;
-
- XDocument document = httpClient.ParseStream(stream);
- stream.Dispose();
+ var httpClient = new SsdpHttpClient(_httpClient);
+ var document = await httpClient.GetDataAsync(new Uri(Properties.BaseUrl + url));
RendererCommands = TransportCommands.Create(document);
}
@@ -524,16 +629,11 @@ namespace MediaBrowser.Dlna.PlayTo
set;
}
- public static async Task CreateuPnpDeviceAsync(Uri url)
+ public static async Task CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, ILogger logger)
{
- var httpClient = new SsdpHttpClient();
- var stream = await httpClient.GetDataAsync(url);
+ var ssdpHttpClient = new SsdpHttpClient(httpClient);
- if (stream == null)
- return null;
-
- var document = httpClient.ParseStream(stream);
- stream.Dispose();
+ var document = await ssdpHttpClient.GetDataAsync(url).ConfigureAwait(false);
var deviceProperties = new DeviceProperties();
@@ -587,14 +687,14 @@ namespace MediaBrowser.Dlna.PlayTo
return null;
var servicesList = services.Descendants(uPnpNamespaces.ud.GetName("service"));
-
+
if (servicesList == null)
return null;
foreach (var element in servicesList)
{
var service = uService.Create(element);
-
+
if (service != null)
{
deviceProperties.Services.Add(service);
@@ -609,10 +709,11 @@ namespace MediaBrowser.Dlna.PlayTo
if (isRenderer)
{
- var device = new Device(deviceProperties);
+ var device = new Device(deviceProperties, httpClient, logger);
+
+ await device.GetRenderingProtocolAsync().ConfigureAwait(false);
+ await device.GetAVProtocolAsync().ConfigureAwait(false);
- await device.GetRenderingProtocolAsync();
- await device.GetAVProtocolAsync();
return device;
}
@@ -663,20 +764,5 @@ namespace MediaBrowser.Dlna.PlayTo
{
return String.Format("{0} - {1}", Properties.Name, Properties.BaseUrl);
}
-
- private XDocument ParseStream(Stream stream)
- {
- var reader = new StreamReader(stream, Encoding.UTF8);
- try
- {
- var doc = XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace);
- stream.Dispose();
- return doc;
- }
- catch
- {
- }
- return null;
- }
}
}
diff --git a/MediaBrowser.Dlna/PlayTo/DidlBuilder.cs b/MediaBrowser.Dlna/PlayTo/DidlBuilder.cs
new file mode 100644
index 000000000..04f9a4644
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/DidlBuilder.cs
@@ -0,0 +1,154 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Dlna.PlayTo
+{
+ internal class DidlBuilder
+ {
+ #region Constants
+
+ internal const string CRLF = "\r\n";
+ internal const string UNKNOWN = "Unknown";
+
+ internal const string DIDL_START = @"- " + CRLF;
+ internal const string DIDL_TITLE = @" {0}" + CRLF;
+ internal const string DIDL_ARTIST = @"{0}" + CRLF;
+ internal const string DIDL_ALBUM = @"{0}" + CRLF;
+ internal const string DIDL_TRACKNUM = @"0" + CRLF;
+ internal const string DIDL_VIDEOCLASS = @" object.item.videoItem" + CRLF;
+ internal const string DIDL_AUDIOCLASS = @" object.item.audioItem.musicTrack" + CRLF;
+ internal const string DIDL_IMAGE = @" {0}" + CRLF +
+ @" {0}" + CRLF;
+ internal const string DIDL_RELEASEDATE = @" {0}" + CRLF;
+ internal const string DIDL_GENRE = @" {0}" + CRLF;
+ internal const string DESCRIPTION = @" {0}" + CRLF;
+ internal const string DIDL_VIDEO_RES = @" {4}" + CRLF;
+ internal const string DIDL_AUDIO_RES = @" {3}" + CRLF;
+ internal const string DIDL_IMAGE_RES = @" {0}" + CRLF;
+ internal const string DIDL_ALBUMIMAGE_RES = @" {0}" + CRLF;
+ internal const string DIDL_RATING = @" {0}" + CRLF;
+ internal const string DIDL_END = "
";
+
+ #endregion
+
+ ///
+ /// Builds a Didl MetaData object for the specified dto.
+ ///
+ /// The dto.
+ /// The user identifier.
+ /// The server address.
+ /// The stream URL.
+ /// The streams.
+ /// System.String.
+ internal static string Build(BaseItem dto, string userId, string serverAddress, string streamUrl, IEnumerable streams)
+ {
+ string response = string.Format(DIDL_START, dto.Id, userId);
+ response += string.Format(DIDL_TITLE, dto.Name.Replace("&", "and"));
+ if (IsVideo(dto))
+ response += DIDL_VIDEOCLASS;
+ else
+ response += DIDL_AUDIOCLASS;
+
+ response += string.Format(DIDL_IMAGE, GetImageUrl(dto, serverAddress));
+ response += string.Format(DIDL_RELEASEDATE, GetDateString(dto.PremiereDate));
+
+ //TODO Add genres to didl;
+ response += string.Format(DIDL_GENRE, UNKNOWN);
+
+ if (IsVideo(dto))
+ {
+ response += string.Format(DESCRIPTION, UNKNOWN);
+ response += GetVideoDIDL(dto, streamUrl, streams);
+ response += string.Format(DIDL_IMAGE_RES, GetImageUrl(dto, serverAddress));
+ }
+ else
+ {
+ var audio = dto as Audio;
+
+ if (audio != null)
+ {
+ response += string.Format(DIDL_ARTIST, audio.Artists.FirstOrDefault() ?? UNKNOWN);
+ response += string.Format(DIDL_ALBUM, audio.Album);
+
+ // TODO: Bad format string?
+ response += string.Format(DIDL_TRACKNUM, audio.IndexNumber ?? 0);
+ }
+
+ response += GetAudioDIDL(dto, streamUrl, streams);
+ response += string.Format(DIDL_ALBUMIMAGE_RES, GetImageUrl(dto, serverAddress));
+ }
+
+ response += DIDL_END;
+
+ return response;
+
+ }
+
+ #region Private methods
+
+ private static string GetVideoDIDL(BaseItem dto, string streamUrl, IEnumerable streams)
+ {
+ var videostream = streams.Where(stream => stream.Type == Model.Entities.MediaStreamType.Video).OrderBy(s => s.IsDefault).FirstOrDefault();
+
+ if (videostream == null)
+ {
+ // TOOD: ???
+ return string.Empty;
+ }
+
+ return string.Format(DIDL_VIDEO_RES, videostream.BitRate.HasValue ? videostream.BitRate.Value / 10 : 0, GetDurationString(dto), videostream.Width ?? 0, videostream.Height ?? 0, streamUrl);
+ }
+
+ private static string GetAudioDIDL(BaseItem dto, string streamUrl, IEnumerable streams)
+ {
+ var audiostream = streams.Where(stream => stream.Type == MediaStreamType.Audio).OrderBy(s => s.IsDefault).FirstOrDefault();
+
+ if (audiostream == null)
+ {
+ // TOOD: ???
+ return string.Empty;
+ }
+
+ return string.Format(DIDL_AUDIO_RES, audiostream.BitRate.HasValue ? audiostream.BitRate.Value / 10 : 16000, GetDurationString(dto), audiostream.SampleRate ?? 0, streamUrl);
+ }
+
+ private static string GetImageUrl(BaseItem dto, string serverAddress)
+ {
+ var imageType = ImageType.Primary;
+
+ if (!dto.HasImage(ImageType.Primary))
+ {
+ dto = dto.Parents.FirstOrDefault(i => i.HasImage(ImageType.Primary));
+ }
+
+ return string.Format("{0}/Items/{1}/Images/{2}", serverAddress, dto.Id, imageType);
+ }
+
+ private static string GetDurationString(BaseItem dto)
+ {
+ var duration = TimeSpan.FromTicks(dto.RunTimeTicks.HasValue ? dto.RunTimeTicks.Value : 0);
+
+ // TODO: Bad format string?
+ return string.Format("{0}:{1:00}:2{00}.000", duration.Hours, duration.Minutes, duration.Seconds);
+ }
+
+ private static string GetDateString(DateTime? date)
+ {
+ if (!date.HasValue)
+ return UNKNOWN;
+
+ return string.Format("{0}-{1:00}-{2:00}", date.Value.Year, date.Value.Month, date.Value.Day);
+ }
+
+ private static bool IsVideo(BaseItem item)
+ {
+ return string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
+ }
+
+ #endregion
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/DlnaController.cs b/MediaBrowser.Dlna/PlayTo/DlnaController.cs
new file mode 100644
index 000000000..894e32599
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/DlnaController.cs
@@ -0,0 +1,481 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Dlna.PlayTo.Configuration;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Session;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Timers;
+using Timer = System.Timers.Timer;
+
+namespace MediaBrowser.Dlna.PlayTo
+{
+ public class PlayToController : ISessionController
+ {
+ private Device _device;
+ private BaseItem _currentItem = null;
+ private TranscodeSettings[] _transcodeSettings;
+ private readonly SessionInfo _session;
+ private readonly ISessionManager _sessionManager;
+ private readonly IItemRepository _itemRepository;
+ private readonly ILibraryManager _libraryManager;
+ private readonly INetworkManager _networkManager;
+ private readonly ILogger _logger;
+ private bool _playbackStarted = false;
+
+ public bool SupportsMediaRemoteControl
+ {
+ get { return true; }
+ }
+
+ public bool IsSessionActive
+ {
+ get
+ {
+ if (_device == null || _device.UpdateTime == default(DateTime))
+ return false;
+
+ return DateTime.UtcNow <= _device.UpdateTime.AddSeconds(30);
+ }
+ }
+
+ public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager)
+ {
+ _session = session;
+ _itemRepository = itemRepository;
+ _sessionManager = sessionManager;
+ _libraryManager = libraryManager;
+ _networkManager = networkManager;
+ _logger = logger;
+ }
+
+ public void Init(Device device, TranscodeSettings[] transcodeSettings)
+ {
+ _transcodeSettings = transcodeSettings;
+ _device = device;
+ _device.PlaybackChanged += Device_PlaybackChanged;
+ _device.CurrentIdChanged += Device_CurrentIdChanged;
+ _device.Start();
+
+ _updateTimer = new Timer(1000);
+ _updateTimer.Elapsed += updateTimer_Elapsed;
+ _updateTimer.Start();
+ }
+
+ #region Device EventHandlers & Update Timer
+
+ Timer _updateTimer;
+
+ async void Device_PlaybackChanged(object sender, TransportStateEventArgs e)
+ {
+ if (_currentItem == null)
+ return;
+
+ if (e.Stopped == false)
+ await ReportProgress().ConfigureAwait(false);
+
+ else if (e.Stopped && _playbackStarted)
+ {
+ _playbackStarted = false;
+
+ await _sessionManager.OnPlaybackStopped(new PlaybackStopInfo
+ {
+ Item = _currentItem,
+ SessionId = _session.Id,
+ PositionTicks = _device.Position.Ticks
+
+ }).ConfigureAwait(false);
+
+ await SetNext().ConfigureAwait(false);
+ }
+ }
+
+ async void Device_CurrentIdChanged(object sender, CurrentIdEventArgs e)
+ {
+ if (e.Id != Guid.Empty)
+ {
+ if (_currentItem != null && _currentItem.Id == e.Id)
+ {
+ return;
+ }
+
+ var item = _libraryManager.GetItemById(e.Id);
+
+ if (item != null)
+ {
+ _logger.Debug("{0} - CurrentId {1}", _session.DeviceName, item.Id);
+ _currentItem = item;
+ _playbackStarted = false;
+
+ await ReportProgress().ConfigureAwait(false);
+ }
+ }
+ }
+
+ ///
+ /// Handles the Elapsed event of the updateTimer control.
+ ///
+ /// The source of the event.
+ /// The instance containing the event data.
+ async void updateTimer_Elapsed(object sender, ElapsedEventArgs e)
+ {
+ if (_disposed)
+ return;
+
+ ((Timer)sender).Stop();
+
+ await ReportProgress().ConfigureAwait(false);
+
+ if (!_disposed && IsSessionActive)
+ ((Timer)sender).Start();
+ }
+
+ ///
+ /// Reports the playback progress.
+ ///
+ ///
+ private async Task ReportProgress()
+ {
+ if (_currentItem == null || _device.IsStopped)
+ return;
+
+ if (!_playbackStarted)
+ {
+ await _sessionManager.OnPlaybackStart(new PlaybackInfo { Item = _currentItem, SessionId = _session.Id, CanSeek = true, QueueableMediaTypes = new List { "Audio", "Video" } }).ConfigureAwait(false);
+ _playbackStarted = true;
+ }
+
+ if ((_device.IsPlaying || _device.IsPaused))
+ {
+ var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
+ if (playlistItem != null && playlistItem.Transcode)
+ {
+ await _sessionManager.OnPlaybackProgress(new PlaybackProgressInfo
+ {
+ Item = _currentItem,
+ SessionId = _session.Id,
+ PositionTicks = _device.Position.Ticks + playlistItem.StartPositionTicks,
+ IsMuted = _device.IsMuted,
+ IsPaused = _device.IsPaused
+
+ }).ConfigureAwait(false);
+ }
+ else if (_currentItem != null)
+ {
+ await _sessionManager.OnPlaybackProgress(new PlaybackProgressInfo
+ {
+ Item = _currentItem,
+ SessionId = _session.Id,
+ PositionTicks = _device.Position.Ticks,
+ IsMuted = _device.IsMuted,
+ IsPaused = _device.IsPaused
+
+ }).ConfigureAwait(false);
+ }
+ }
+ }
+
+ #endregion
+
+ #region SendCommands
+
+ public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
+ {
+ _logger.Debug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
+
+ var items = new List();
+ foreach (string id in command.ItemIds)
+ {
+ AddItemFromId(Guid.Parse(id), items);
+ }
+
+ var playlist = new List();
+ var isFirst = true;
+
+ var serverAddress = GetServerAddress();
+
+ foreach (var item in items)
+ {
+ if (isFirst && command.StartPositionTicks.HasValue)
+ {
+ playlist.Add(CreatePlaylistItem(item, command.StartPositionTicks.Value, serverAddress));
+ isFirst = false;
+ }
+ else
+ {
+ playlist.Add(CreatePlaylistItem(item, 0, serverAddress));
+ }
+ }
+
+ _logger.Debug("{0} - Playlist created", _session.DeviceName);
+
+ if (command.PlayCommand == PlayCommand.PlayLast)
+ {
+ AddItemsToPlaylist(playlist);
+ return Task.FromResult(true);
+ }
+ if (command.PlayCommand == PlayCommand.PlayNext)
+ {
+ AddItemsToPlaylist(playlist);
+ return Task.FromResult(true);
+ }
+
+ _logger.Debug("{0} - Playing {1} items", _session.DeviceName, playlist.Count);
+ return PlayItems(playlist);
+ }
+
+ public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
+ {
+ switch (command.Command)
+ {
+ case PlaystateCommand.Stop:
+ Playlist.Clear();
+ return _device.SetStop();
+
+ case PlaystateCommand.Pause:
+ return _device.SetPause();
+
+ case PlaystateCommand.Unpause:
+ return _device.SetPlay();
+
+ case PlaystateCommand.Seek:
+ var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
+ if (playlistItem != null && playlistItem.Transcode && playlistItem.IsVideo && _currentItem != null)
+ {
+ var newItem = CreatePlaylistItem(_currentItem, command.SeekPositionTicks ?? 0, GetServerAddress());
+ playlistItem.StartPositionTicks = newItem.StartPositionTicks;
+ playlistItem.StreamUrl = newItem.StreamUrl;
+ playlistItem.Didl = newItem.Didl;
+ return _device.SetAvTransport(playlistItem.StreamUrl, playlistItem.DlnaHeaders, playlistItem.Didl);
+
+ }
+ return _device.Seek(TimeSpan.FromTicks(command.SeekPositionTicks ?? 0));
+
+
+ case PlaystateCommand.NextTrack:
+ _currentItem = null;
+ return SetNext();
+
+ case PlaystateCommand.PreviousTrack:
+ _currentItem = null;
+ return SetPrevious();
+ }
+
+ return Task.FromResult(true);
+ }
+
+ public Task SendSystemCommand(SystemCommand command, CancellationToken cancellationToken)
+ {
+ switch (command)
+ {
+ case SystemCommand.VolumeDown:
+ return _device.VolumeDown();
+ case SystemCommand.VolumeUp:
+ return _device.VolumeUp();
+ case SystemCommand.Mute:
+ return _device.VolumeDown(true);
+ case SystemCommand.Unmute:
+ return _device.VolumeUp(true);
+ case SystemCommand.ToggleMute:
+ return _device.ToggleMute();
+ default:
+ return Task.FromResult(true);
+ }
+ }
+
+ public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(true);
+ }
+
+ public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
+ {
+ return Task.FromResult(true);
+ }
+
+ public Task SendServerRestartNotification(CancellationToken cancellationToken)
+ {
+ return Task.FromResult(true);
+ }
+
+ public Task SendServerShutdownNotification(CancellationToken cancellationToken)
+ {
+ return Task.FromResult(true);
+ }
+
+ public Task SendBrowseCommand(BrowseRequest command, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(true);
+ }
+
+ public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(true);
+ }
+
+ public Task SendMessageCommand(MessageCommand command, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(true);
+ }
+
+ #endregion
+
+ #region Playlist
+
+ private List _playlist = new List();
+
+ private List Playlist
+ {
+ get
+ {
+ return _playlist;
+ }
+ set
+ {
+ _playlist = value;
+ }
+ }
+
+ private void AddItemFromId(Guid id, List list)
+ {
+ var item = _libraryManager.GetItemById(id);
+ if (item.IsFolder)
+ {
+ foreach (var childId in _itemRepository.GetChildren(item.Id))
+ {
+ AddItemFromId(childId, list);
+ }
+ }
+ else
+ {
+ if (item.MediaType == MediaType.Audio || item.MediaType == MediaType.Video)
+ {
+ list.Add(item);
+ }
+ }
+ }
+
+ private string GetServerAddress()
+ {
+ return string.Format("{0}://{1}:{2}/mediabrowser",
+
+ "http",
+ _networkManager.GetLocalIpAddresses().FirstOrDefault() ?? "localhost",
+ "8096"
+ );
+ }
+
+ private PlaylistItem CreatePlaylistItem(BaseItem item, long startPostionTicks, string serverAddress)
+ {
+ var streams = _itemRepository.GetMediaStreams(new MediaStreamQuery { ItemId = item.Id }).ToList();
+
+ var playlistItem = PlaylistItem.GetBasicConfig(item, _transcodeSettings);
+ playlistItem.StartPositionTicks = startPostionTicks;
+
+ if (playlistItem.IsAudio)
+ playlistItem.StreamUrl = StreamHelper.GetAudioUrl(playlistItem, serverAddress);
+ else
+ {
+ playlistItem.StreamUrl = StreamHelper.GetVideoUrl(_device.Properties, playlistItem, streams, serverAddress);
+ }
+
+ var didl = DidlBuilder.Build(item, _session.UserId.ToString(), serverAddress, playlistItem.StreamUrl, streams);
+ playlistItem.Didl = didl;
+
+ var header = StreamHelper.GetDlnaHeaders(playlistItem);
+ playlistItem.DlnaHeaders = header;
+ return playlistItem;
+ }
+
+ ///
+ /// Plays the items.
+ ///
+ /// The items.
+ ///
+ private async Task PlayItems(IEnumerable items)
+ {
+ Playlist.Clear();
+ Playlist.AddRange(items);
+ await SetNext();
+ return true;
+ }
+
+ ///
+ /// Adds the items to playlist.
+ ///
+ /// The items.
+ private void AddItemsToPlaylist(IEnumerable items)
+ {
+ Playlist.AddRange(items);
+ }
+
+ private async Task SetNext()
+ {
+ if (!Playlist.Any() || Playlist.All(i => i.PlayState != 0))
+ {
+ return true;
+ }
+ var currentitem = Playlist.FirstOrDefault(i => i.PlayState == 1);
+
+ if (currentitem != null)
+ {
+ currentitem.PlayState = 2;
+ }
+
+ var nextTrack = Playlist.FirstOrDefault(i => i.PlayState == 0);
+ if (nextTrack == null)
+ {
+ await _device.SetStop();
+ return true;
+ }
+ nextTrack.PlayState = 1;
+ await _device.SetAvTransport(nextTrack.StreamUrl, nextTrack.DlnaHeaders, nextTrack.Didl);
+ if (nextTrack.StartPositionTicks > 0 && !nextTrack.Transcode)
+ await _device.Seek(TimeSpan.FromTicks(nextTrack.StartPositionTicks));
+ return true;
+ }
+
+ public Task SetPrevious()
+ {
+ if (!Playlist.Any() || Playlist.All(i => i.PlayState != 2))
+ return Task.FromResult(false);
+
+ var currentitem = Playlist.FirstOrDefault(i => i.PlayState == 1);
+
+ var prevTrack = Playlist.LastOrDefault(i => i.PlayState == 2);
+
+ if (currentitem != null)
+ {
+ currentitem.PlayState = 0;
+ }
+
+ if (prevTrack == null)
+ return Task.FromResult(false);
+
+ prevTrack.PlayState = 1;
+ return _device.SetAvTransport(prevTrack.StreamUrl, prevTrack.DlnaHeaders, prevTrack.Didl);
+ }
+
+ #endregion
+
+ private bool _disposed;
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ _updateTimer.Stop();
+ _disposed = true;
+ _device.Dispose();
+ _logger.Log(LogSeverity.Debug, "PlayTo - Controller disposed");
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/DlnaControllerFactory.cs b/MediaBrowser.Dlna/PlayTo/DlnaControllerFactory.cs
new file mode 100644
index 000000000..720dc200b
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/DlnaControllerFactory.cs
@@ -0,0 +1,31 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Logging;
+
+namespace MediaBrowser.Dlna.PlayTo
+{
+ public class PlayToControllerFactory : ISessionControllerFactory
+ {
+ private readonly ISessionManager _sessionManager;
+ private readonly IItemRepository _itemRepository;
+ private readonly ILibraryManager _libraryManager;
+ private readonly ILogger _logger;
+ private readonly INetworkManager _networkManager;
+
+ public PlayToControllerFactory(ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogManager logManager, INetworkManager networkManager)
+ {
+ _itemRepository = itemRepository;
+ _sessionManager = sessionManager;
+ _libraryManager = libraryManager;
+ _networkManager = networkManager;
+ _logger = logManager.GetLogger("PlayTo");
+ }
+
+ public ISessionController GetSessionController(SessionInfo session)
+ {
+ return null;
+ }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
new file mode 100644
index 000000000..1e81f32f8
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
@@ -0,0 +1,271 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Dlna.PlayTo.Configuration;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Collections.Concurrent;
+using System.Linq;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Dlna.PlayTo
+{
+ class PlayToManager : IDisposable
+ {
+ private bool _disposed = false;
+ private readonly ILogger _logger;
+ private readonly ISessionManager _sessionManager;
+ private readonly IHttpClient _httpClient;
+ private User _defualtUser;
+ private readonly CancellationTokenSource _tokenSource;
+ private ConcurrentDictionary _locations;
+
+ private readonly IItemRepository _itemRepository;
+ private readonly ILibraryManager _libraryManager;
+ private readonly INetworkManager _networkManager;
+
+ public PlayToManager(ILogger logger, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, INetworkManager networkManager)
+ {
+ _locations = new ConcurrentDictionary();
+ _tokenSource = new CancellationTokenSource();
+
+ _logger = logger;
+ _sessionManager = sessionManager;
+ _httpClient = httpClient;
+ _itemRepository = itemRepository;
+ _libraryManager = libraryManager;
+ _networkManager = networkManager;
+ }
+
+ public async void Start(User defaultUser)
+ {
+ _defualtUser = defaultUser;
+ _logger.Log(LogSeverity.Info, "PlayTo-Manager starting");
+
+ _locations = new ConcurrentDictionary();
+
+ foreach (var network in NetworkInterface.GetAllNetworkInterfaces())
+ {
+ _logger.Debug("Found interface: {0}. Type: {1}. Status: {2}", network.Name, network.NetworkInterfaceType, network.OperationalStatus);
+
+ if (!network.SupportsMulticast || OperationalStatus.Up != network.OperationalStatus || !network.GetIPProperties().MulticastAddresses.Any())
+ continue;
+
+ var ipV4 = network.GetIPProperties().GetIPv4Properties();
+ if (null == ipV4)
+ continue;
+
+ IPAddress localIp = null;
+
+ foreach (UnicastIPAddressInformation ipInfo in network.GetIPProperties().UnicastAddresses)
+ {
+ if (ipInfo.Address.AddressFamily == AddressFamily.InterNetwork)
+ {
+ localIp = ipInfo.Address;
+ break;
+ }
+ }
+
+ if (localIp == null)
+ {
+ continue;
+ }
+
+ try
+ {
+ CreateListener(localIp);
+ }
+ catch (Exception e)
+ {
+ _logger.ErrorException("Failed to Initilize Socket", e);
+ }
+
+ await Task.Delay(100).ConfigureAwait(false);
+ }
+ }
+
+ public void Stop()
+ {
+ }
+
+ ///
+ /// Creates a socket for the interface and listends for data.
+ ///
+ /// The local ip.
+ private void CreateListener(IPAddress localIp)
+ {
+ Task.Factory.StartNew(async (o) =>
+ {
+ try
+ {
+ var socket = GetMulticastSocket();
+
+ socket.Bind(new IPEndPoint(localIp, 0));
+
+ _logger.Info("Creating SSDP listener");
+
+ var receiveBuffer = new byte[64000];
+
+ CreateNotifier(socket);
+
+ while (!_tokenSource.IsCancellationRequested)
+ {
+ var receivedBytes = await socket.ReceiveAsync(receiveBuffer, 0, 64000);
+
+ if (receivedBytes > 0)
+ {
+ var rawData = Encoding.UTF8.GetString(receiveBuffer, 0, receivedBytes);
+ var uri = SsdpHelper.ParseSsdpResponse(rawData);
+
+ TryCreateController(uri);
+ }
+ }
+
+ _logger.Info("SSDP listener - Task completed");
+ }
+ catch (OperationCanceledException c)
+ {
+ }
+ catch (Exception e)
+ {
+ _logger.ErrorException("Error in listener", e);
+ }
+
+ }, _tokenSource.Token, TaskCreationOptions.LongRunning);
+ }
+
+ private void TryCreateController(Uri uri)
+ {
+ Task.Run(async () =>
+ {
+ try
+ {
+ await CreateController(uri).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException c)
+ {
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error creating play to controller", ex);
+ }
+ });
+ }
+
+ private void CreateNotifier(Socket socket)
+ {
+ Task.Factory.StartNew(async (o) =>
+ {
+ try
+ {
+ var request = SsdpHelper.CreateRendererSSDP(3);
+
+ while (true)
+ {
+ socket.SendTo(request, new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900));
+
+ await Task.Delay(10000).ConfigureAwait(false);
+ }
+ }
+ catch (OperationCanceledException c)
+ {
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error in notifier", ex);
+ }
+
+ }, _tokenSource.Token, TaskCreationOptions.LongRunning);
+
+ }
+
+ ///
+ /// Gets a socket configured for SDDP multicasting.
+ ///
+ ///
+ private Socket GetMulticastSocket()
+ {
+ var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+ socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250")));
+ //socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 3);
+ return socket;
+ }
+
+ ///
+ /// Creates a new DlnaSessionController.
+ /// and logs the session in SessionManager
+ ///
+ /// The URI.
+ ///
+ private async Task CreateController(Uri uri)
+ {
+ if (!IsUriValid(uri))
+ return;
+
+ var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _logger).ConfigureAwait(false);
+
+ if (device != null && device.RendererCommands != null && !_sessionManager.Sessions.Any(s => string.Equals(s.DeviceId, device.Properties.UUID) && s.IsActive))
+ {
+ var transcodeProfiles = TranscodeSettings.GetProfileSettings(device.Properties);
+
+ var sessionInfo = await _sessionManager.LogSessionActivity(device.Properties.ClientType, device.Properties.Name, device.Properties.UUID, device.Properties.DisplayName, uri.OriginalString, _defualtUser)
+ .ConfigureAwait(false);
+
+ var controller = sessionInfo.SessionController as PlayToController;
+
+ if (controller == null)
+ {
+ sessionInfo.SessionController = controller = new PlayToController(sessionInfo, _sessionManager, _itemRepository, _libraryManager, _logger, _networkManager);
+ }
+
+ controller.Init(device, transcodeProfiles);
+
+ _logger.Info("DLNA Session created for {0} - {1}", device.Properties.Name, device.Properties.ModelName);
+ }
+ }
+
+ ///
+ /// Determines if the Uri is valid for further inspection or not.
+ /// (the limit for reinspection is 5 minutes)
+ ///
+ /// The URI.
+ /// Returns True if the Uri is valid for further inspection
+ private bool IsUriValid(Uri uri)
+ {
+ if (uri == null)
+ return false;
+
+ if (!_locations.ContainsKey(uri.OriginalString))
+ {
+ _locations.AddOrUpdate(uri.OriginalString, DateTime.UtcNow, (key, existingVal) => existingVal);
+
+ return true;
+ }
+
+ var time = _locations[uri.OriginalString];
+
+ if ((DateTime.UtcNow - time).TotalMinutes <= 5)
+ {
+ return false;
+ }
+ return _locations.TryUpdate(uri.OriginalString, DateTime.UtcNow, time);
+ }
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+ _tokenSource.Cancel();
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs b/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs
new file mode 100644
index 000000000..e998d13c1
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs
@@ -0,0 +1,69 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Plugins;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Logging;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Dlna.PlayTo
+{
+ public class PlayToServerEntryPoint : IServerEntryPoint
+ {
+ const string DefaultUser = "Play To";
+
+ private bool _disposed;
+
+ private readonly IUserManager _userManager;
+ private readonly PlayToManager _manager;
+
+ public PlayToServerEntryPoint(ILogManager logManager, ISessionManager sessionManager, IUserManager userManager, IHttpClient httpClient, INetworkManager networkManager, IItemRepository itemRepository, ILibraryManager libraryManager)
+ {
+ _userManager = userManager;
+
+ _manager = new PlayToManager(logManager.GetLogger("PlayTo"), sessionManager, httpClient, itemRepository, libraryManager, networkManager);
+ }
+
+ ///
+ /// Creates the defaultuser if needed.
+ ///
+ private async Task CreateUserIfNeeded()
+ {
+ var user = _userManager.Users.FirstOrDefault(u => u.Name == DefaultUser);
+
+ if (user == null)
+ {
+ user = await _userManager.CreateUser(DefaultUser);
+
+ user.Configuration.IsHidden = true;
+ user.Configuration.IsAdministrator = false;
+ user.SaveConfiguration();
+ }
+
+ return user;
+ }
+
+ public async void Run()
+ {
+ //var defaultUser = await CreateUserIfNeeded().ConfigureAwait(false);
+
+ //_manager.Start(defaultUser);
+ }
+
+ #region Dispose
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+ _manager.Stop();
+ _manager.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs
index 591e39bef..d2d04c4a8 100644
--- a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs
+++ b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs
@@ -1,4 +1,7 @@
-
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Dlna.PlayTo.Configuration;
+using System;
+
namespace MediaBrowser.Dlna.PlayTo
{
public class PlaylistItem
@@ -23,73 +26,73 @@ namespace MediaBrowser.Dlna.PlayTo
public long StartPositionTicks { get; set; }
- //internal static PlaylistItem GetBasicConfig(BaseItem item, TranscodeSettings[] profileTranscodings)
- //{
+ public static PlaylistItem GetBasicConfig(BaseItem item, TranscodeSettings[] profileTranscodings)
+ {
- // var playlistItem = new PlaylistItem();
- // playlistItem.ItemId = item.Id.ToString();
+ var playlistItem = new PlaylistItem();
+ playlistItem.ItemId = item.Id.ToString();
- // if (string.Equals(item.MediaType, MediaBrowser.Model.Entities.MediaType.Video, StringComparison.OrdinalIgnoreCase))
- // {
- // playlistItem.IsVideo = true;
- // }
- // else
- // {
- // playlistItem.IsAudio = true;
- // }
+ if (string.Equals(item.MediaType, Model.Entities.MediaType.Video, StringComparison.OrdinalIgnoreCase))
+ {
+ playlistItem.IsVideo = true;
+ }
+ else
+ {
+ playlistItem.IsAudio = true;
+ }
-
- // var path = item.Path.ToLower();
- // //Check the DlnaProfile associated with the renderer
- // if (profileTranscodings != null)
- // {
- // foreach (TranscodeSettings transcodeSetting in profileTranscodings)
- // {
- // if (string.IsNullOrWhiteSpace(transcodeSetting.Container))
- // continue;
- // if (path.EndsWith(transcodeSetting.Container))
- // {
- // playlistItem.Transcode = true;
- // playlistItem.FileFormat = transcodeSetting.TargetContainer;
- // return playlistItem;
- // }
- // }
- // }
- // if (playlistItem.IsVideo)
- // {
+ var path = item.Path.ToLower();
- // //Check to see if we support serving the format statically
- // foreach (string supported in PlayToConfiguration.SupportedStaticFormats)
- // {
- // if (path.EndsWith(supported))
- // {
- // playlistItem.Transcode = false;
- // playlistItem.FileFormat = supported;
- // return playlistItem;
- // }
- // }
+ //Check the DlnaProfile associated with the renderer
+ if (profileTranscodings != null)
+ {
+ foreach (TranscodeSettings transcodeSetting in profileTranscodings)
+ {
+ if (string.IsNullOrWhiteSpace(transcodeSetting.Container))
+ continue;
+ if (path.EndsWith(transcodeSetting.Container))
+ {
+ playlistItem.Transcode = true;
+ playlistItem.FileFormat = transcodeSetting.TargetContainer;
+ return playlistItem;
+ }
+ }
+ }
+ if (playlistItem.IsVideo)
+ {
- // playlistItem.Transcode = true;
- // playlistItem.FileFormat = "ts";
- // }
- // else
- // {
- // foreach (string supported in PlayToConfiguration.SupportedStaticFormats)
- // {
- // if (path.EndsWith(supported))
- // {
- // playlistItem.Transcode = false;
- // playlistItem.FileFormat = supported;
- // return playlistItem;
- // }
- // }
+ //Check to see if we support serving the format statically
+ foreach (string supported in PlayToConfiguration.SupportedStaticFormats)
+ {
+ if (path.EndsWith(supported))
+ {
+ playlistItem.Transcode = false;
+ playlistItem.FileFormat = supported;
+ return playlistItem;
+ }
+ }
- // playlistItem.Transcode = true;
- // playlistItem.FileFormat = "mp3";
- // }
+ playlistItem.Transcode = true;
+ playlistItem.FileFormat = "ts";
+ }
+ else
+ {
+ foreach (string supported in PlayToConfiguration.SupportedStaticFormats)
+ {
+ if (path.EndsWith(supported))
+ {
+ playlistItem.Transcode = false;
+ playlistItem.FileFormat = supported;
+ return playlistItem;
+ }
+ }
- // return playlistItem;
- //}
+ playlistItem.Transcode = true;
+ playlistItem.FileFormat = "mp3";
+ }
+
+ return playlistItem;
+ }
}
}
diff --git a/MediaBrowser.Dlna/PlayTo/StreamHelper.cs b/MediaBrowser.Dlna/PlayTo/StreamHelper.cs
new file mode 100644
index 000000000..eed0bb7d7
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/StreamHelper.cs
@@ -0,0 +1,188 @@
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+
+namespace MediaBrowser.Dlna.PlayTo
+{
+ class StreamHelper
+ {
+ ///
+ /// Gets the dlna headers.
+ ///
+ /// The item.
+ ///
+ internal static string GetDlnaHeaders(PlaylistItem item)
+ {
+ var orgOp = item.Transcode ? ";DLNA.ORG_OP=00" : ";DLNA.ORG_OP=01";
+
+ var orgCi = item.Transcode ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
+
+ const string dlnaflags = ";DLNA.ORG_FLAGS=01500000000000000000000000000000";
+
+ var contentFeatures = string.Empty;
+
+ if (string.Equals(item.FileFormat, "mp3", StringComparison.OrdinalIgnoreCase))
+ {
+ contentFeatures = "DLNA.ORG_PN=MP3";
+ }
+ else if (string.Equals(item.FileFormat, "wma", StringComparison.OrdinalIgnoreCase))
+ {
+ contentFeatures = "DLNA.ORG_PN=WMABASE";
+ }
+ else if (string.Equals(item.FileFormat, "avi", StringComparison.OrdinalIgnoreCase))
+ {
+ contentFeatures = "DLNA.ORG_PN=AVI";
+ }
+ else if (string.Equals(item.FileFormat, "mkv", StringComparison.OrdinalIgnoreCase))
+ {
+ contentFeatures = "DLNA.ORG_PN=MATROSKA";
+ }
+ else if (string.Equals(item.FileFormat, "mp4", StringComparison.OrdinalIgnoreCase))
+ {
+ contentFeatures = "DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC";
+ }
+ else if (string.Equals(item.FileFormat, "mpeg", StringComparison.OrdinalIgnoreCase))
+ {
+ contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL";
+ }
+ else if (string.Equals(item.FileFormat, "ts", StringComparison.OrdinalIgnoreCase))
+ {
+ contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL";
+ }
+ else if (item.IsVideo)
+ {
+ //Default to AVI for video
+ contentFeatures = "DLNA.ORG_PN=AVI";
+ }
+ else
+ {
+ //Default to MP3 for audio
+ contentFeatures = "DLNA.ORG_PN=MP3";
+ }
+
+ return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
+ }
+
+ #region Audio
+
+ ///
+ /// Gets the audio URL.
+ ///
+ /// The item.
+ /// The server address.
+ /// System.String.
+ internal static string GetAudioUrl(PlaylistItem item, string serverAddress)
+ {
+ if (!item.Transcode)
+ return string.Format("{0}/audio/{1}/stream.{2}?Static=True", serverAddress, item.ItemId, item.FileFormat);
+
+ return string.Format("{0}/audio/{1}/stream.mp3?AudioCodec=Mp3", serverAddress, item.ItemId);
+ }
+
+ #endregion
+
+ #region Video
+
+ ///
+ /// Gets the video URL.
+ ///
+ /// The device properties.
+ /// The item.
+ /// The streams.
+ /// The server address.
+ /// The url to send to the device
+ internal static string GetVideoUrl(DeviceProperties deviceProperties, PlaylistItem item, List streams, string serverAddress)
+ {
+ if (!item.Transcode)
+ return string.Format("{0}/Videos/{1}/stream.{2}?Static=True", serverAddress, item.ItemId, item.FileFormat);
+
+ var videostream = streams.Where(m => m.Type == MediaStreamType.Video).OrderBy(m => m.IsDefault).FirstOrDefault();
+ var audiostream = streams.Where(m => m.Type == MediaStreamType.Audio).OrderBy(m => m.IsDefault).FirstOrDefault();
+
+ var videoCodec = GetVideoCodec(videostream);
+ var audioCodec = GetAudioCodec(audiostream);
+ int? videoBitrate = null;
+ int? audioBitrate = null;
+ int? audioChannels = null;
+
+ if (videoCodec != VideoCodecs.Copy)
+ videoBitrate = 2000000;
+
+ if (audioCodec != AudioCodecs.Copy)
+ {
+ audioBitrate = 128000;
+ audioChannels = 2;
+ }
+
+ string dlnaCommand = BuildDlnaUrl(deviceProperties.UUID, videoCodec, audioCodec, null, null, videoBitrate, audioChannels, audioBitrate, item.StartPositionTicks, "baseline", "3");
+ return string.Format("{0}/Videos/{1}/stream.{2}?{3}", serverAddress, item.ItemId, item.FileFormat, dlnaCommand);
+ }
+
+ ///
+ /// Gets the video codec.
+ ///
+ /// The video stream.
+ ///
+ private static VideoCodecs GetVideoCodec(MediaStream videoStream)
+ {
+ switch (videoStream.Codec.ToLower())
+ {
+ case "h264":
+ case "mpeg4":
+ return VideoCodecs.Copy;
+
+ }
+ return VideoCodecs.H264;
+ }
+
+ ///
+ /// Gets the audio codec.
+ ///
+ /// The audio stream.
+ ///
+ private static AudioCodecs GetAudioCodec(MediaStream audioStream)
+ {
+ if (audioStream != null)
+ {
+ switch (audioStream.Codec.ToLower())
+ {
+ case "aac":
+ case "mp3":
+ case "wma":
+ return AudioCodecs.Copy;
+
+ }
+ }
+ return AudioCodecs.Aac;
+ }
+
+ ///
+ /// Builds the dlna URL.
+ ///
+ private static string BuildDlnaUrl(string deviceID, VideoCodecs? videoCodec, AudioCodecs? audioCodec, int? subtitleIndex, int? audiostreamIndex, int? videoBitrate, int? audiochannels, int? audioBitrate, long? startPositionTicks, string profile, string videoLevel)
+ {
+ var usCulture = new CultureInfo("en-US");
+
+ var dlnaparam = string.Format("Params={0};", deviceID);
+
+ dlnaparam += videoCodec.HasValue ? videoCodec.Value + ";" : ";";
+ dlnaparam += audioCodec.HasValue ? audioCodec.Value + ";" : ";";
+ dlnaparam += audiostreamIndex.HasValue ? audiostreamIndex.Value.ToString(usCulture) + ";" : ";";
+ dlnaparam += subtitleIndex.HasValue ? subtitleIndex.Value.ToString(usCulture) + ";" : ";";
+ dlnaparam += videoBitrate.HasValue ? videoBitrate.Value.ToString(usCulture) + ";" : ";";
+ dlnaparam += audioBitrate.HasValue ? audioBitrate.Value.ToString(usCulture) + ";" : ";";
+ dlnaparam += audiochannels.HasValue ? audiochannels.Value.ToString(usCulture) + ";" : ";";
+ dlnaparam += startPositionTicks.HasValue ? startPositionTicks.Value.ToString(usCulture) + ";" : ";";
+ dlnaparam += profile + ";";
+ dlnaparam += videoLevel + ";";
+
+ return dlnaparam;
+ }
+
+ #endregion
+
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/TransportCommands.cs b/MediaBrowser.Dlna/PlayTo/TransportCommands.cs
index c0332642f..5aba8ba94 100644
--- a/MediaBrowser.Dlna/PlayTo/TransportCommands.cs
+++ b/MediaBrowser.Dlna/PlayTo/TransportCommands.cs
@@ -6,7 +6,7 @@ namespace MediaBrowser.Dlna.PlayTo
{
public class TransportCommands
{
- List _stateVariables = new List();
+ private List _stateVariables = new List();
public List StateVariables
{
get
@@ -19,7 +19,7 @@ namespace MediaBrowser.Dlna.PlayTo
}
}
- List _serviceActions = new List();
+ private List _serviceActions = new List();
public List ServiceActions
{
get
diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs
index 495b07646..97fd02e0d 100644
--- a/MediaBrowser.ServerApplication/ApplicationHost.cs
+++ b/MediaBrowser.ServerApplication/ApplicationHost.cs
@@ -27,6 +27,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Controller.Session;
using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Dlna.PlayTo;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.System;
@@ -744,6 +745,9 @@ namespace MediaBrowser.ServerApplication
// Server implementations
list.Add(typeof(ServerApplicationPaths).Assembly);
+ // Dlna implementations
+ list.Add(typeof(PlayToServerEntryPoint).Assembly);
+
list.AddRange(Assemblies.GetAssembliesWithParts());
// Include composable parts in the running assembly