import remaining dlna classes

This commit is contained in:
Luke Pulverenti 2014-02-26 21:44:00 -05:00
parent ec131ba0dc
commit dfb491fcc5
15 changed files with 1766 additions and 215 deletions

View file

@ -226,11 +226,11 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Saves the current configuration to the file system
/// </summary>
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);
}
/// <summary>
@ -247,7 +247,7 @@ namespace MediaBrowser.Controller.Entities
}
Configuration = config;
SaveConfiguration(serializer);
SaveConfiguration();
}
}
}

View file

@ -52,16 +52,28 @@
<Link>Properties\SharedVersion.cs</Link>
</Compile>
<Compile Include="PlayTo\Argument.cs" />
<Compile Include="PlayTo\Configuration\DlnaProfile.cs" />
<Compile Include="PlayTo\Configuration\PluginConfiguration.cs" />
<Compile Include="PlayTo\Configuration\TranscodeSetting.cs" />
<Compile Include="PlayTo\CurrentIdEventArgs.cs" />
<Compile Include="PlayTo\Device.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="PlayTo\DeviceProperties.cs" />
<Compile Include="PlayTo\DidlBuilder.cs" />
<Compile Include="PlayTo\DlnaController.cs" />
<Compile Include="PlayTo\DlnaControllerFactory.cs" />
<Compile Include="PlayTo\Extensions.cs" />
<Compile Include="PlayTo\PlaylistItem.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="PlayTo\PlayToManager.cs" />
<Compile Include="PlayTo\PlayToServerEntryPoint.cs" />
<Compile Include="PlayTo\ServiceAction.cs" />
<Compile Include="PlayTo\SsdpHelper.cs" />
<Compile Include="PlayTo\SsdpHttpClient.cs" />
<Compile Include="PlayTo\StateVariable.cs" />
<Compile Include="PlayTo\StreamHelper.cs" />
<Compile Include="PlayTo\TransportCommands.cs" />
<Compile Include="PlayTo\TransportStateEventArgs.cs" />
<Compile Include="PlayTo\uBaseObject.cs" />
@ -77,6 +89,10 @@
<Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
<Name>MediaBrowser.Common</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
<Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
<Name>MediaBrowser.Controller</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
<Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
<Name>MediaBrowser.Model</Name>

View file

@ -0,0 +1,53 @@
namespace MediaBrowser.Dlna.PlayTo.Configuration
{
public class DlnaProfile
{
/// <summary>
/// Gets or sets the name to be displayed.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the type of the client.
/// </summary>
/// <value>
/// The type of the client.
/// </value>
public string ClientType { get; set; }
/// <summary>
/// Gets or sets the name of the friendly.
/// </summary>
/// <value>
/// The name of the friendly.
/// </value>
public string FriendlyName { get; set; }
/// <summary>
/// Gets or sets the model number.
/// </summary>
/// <value>
/// The model number.
/// </value>
public string ModelNumber { get; set; }
/// <summary>
/// Gets or sets the name of the model.
/// </summary>
/// <value>
/// The name of the model.
/// </value>
public string ModelName { get; set; }
/// <summary>
/// Gets or sets the transcode settings.
/// </summary>
/// <value>
/// The transcode settings.
/// </value>
public TranscodeSettings[] TranscodeSettings { get; set; }
}
}

View file

@ -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
};
}
}
}

View file

@ -0,0 +1,76 @@
using System;
namespace MediaBrowser.Dlna.PlayTo.Configuration
{
public class TranscodeSettings
{
/// <summary>
/// Gets or sets the container.
/// </summary>
/// <value>
/// The container.
/// </value>
public string Container { get; set; }
/// <summary>
/// Gets or sets the target container.
/// </summary>
/// <value>
/// The target container.
/// </value>
public string TargetContainer { get; set; }
/// <summary>
/// The default transcoding settings
/// </summary>
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;
}
/// <summary>
/// Gets the profile settings.
/// </summary>
/// <param name="deviceProperties">The device properties.</param>
/// <returns>The TranscodeSettings for the device</returns>
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();
}
}
}

View file

@ -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<bool> 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<Device> CreateuPnpDeviceAsync(Uri url)
public static async Task<Device> 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;
}
}
}

View file

@ -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 = @"<item id=""{0}"" parentID=""{1}"" restricted=""1"" xmlns=""urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"">" + CRLF;
internal const string DIDL_TITLE = @" <dc:title xmlns:dc=""http://purl.org/dc/elements/1.1/"">{0}</dc:title>" + CRLF;
internal const string DIDL_ARTIST = @"<upnp:artist xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:artist>" + CRLF;
internal const string DIDL_ALBUM = @"<upnp:album xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:album>" + CRLF;
internal const string DIDL_TRACKNUM = @"<upnp:originalTrackNumber xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">0</upnp:originalTrackNumber>" + CRLF;
internal const string DIDL_VIDEOCLASS = @" <upnp:class xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">object.item.videoItem</upnp:class>" + CRLF;
internal const string DIDL_AUDIOCLASS = @" <upnp:class xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">object.item.audioItem.musicTrack</upnp:class>" + CRLF;
internal const string DIDL_IMAGE = @" <upnp:albumArtURI dlna:profileID=""JPEG_TN"" xmlns:dlna=""urn:schemas-dlna-org:metadata-1-0/"" xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:albumArtURI>" + CRLF +
@" <upnp:icon xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:icon>" + CRLF;
internal const string DIDL_RELEASEDATE = @" <dc:date xmlns:dc=""http://purl.org/dc/elements/1.1/"">{0}</dc:date>" + CRLF;
internal const string DIDL_GENRE = @" <upnp:genre xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:genre>" + CRLF;
internal const string DESCRIPTION = @" <dc:description xmlns:dc=""http://purl.org/dc/elements/1.1/"">{0}</dc:description>" + CRLF;
internal const string DIDL_VIDEO_RES = @" <res bitrate=""{0}"" duration=""{1}"" protocolInfo=""http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01500000000000000000000000000000"" resolution=""{2}x{3}"" size=""0"">{4}</res>" + CRLF;
internal const string DIDL_AUDIO_RES = @" <res bitrate=""{0}"" duration=""{1}"" nrAudioChannels=""2"" protocolInfo=""http-get:*:audio/mp3:DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01500000000000000000000000000000"" sampleFrequency=""{2}"" size=""0"">{3}</res>" + CRLF;
internal const string DIDL_IMAGE_RES = @" <res protocolInfo=""http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_CI=1;DLNA.ORG_FLAGS=00D00000000000000000000000000000"" resolution=""212x320"">{0}</res>" + CRLF;
internal const string DIDL_ALBUMIMAGE_RES = @" <res protocolInfo=""http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_CI=1;DLNA.ORG_FLAGS=00D00000000000000000000000000000"" resolution=""320x320"">{0}</res>" + CRLF;
internal const string DIDL_RATING = @" <upnp:rating xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:rating>" + CRLF;
internal const string DIDL_END = "</item>";
#endregion
/// <summary>
/// Builds a Didl MetaData object for the specified dto.
/// </summary>
/// <param name="dto">The dto.</param>
/// <param name="userId">The user identifier.</param>
/// <param name="serverAddress">The server address.</param>
/// <param name="streamUrl">The stream URL.</param>
/// <param name="streams">The streams.</param>
/// <returns>System.String.</returns>
internal static string Build(BaseItem dto, string userId, string serverAddress, string streamUrl, IEnumerable<MediaStream> 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<MediaStream> 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<MediaStream> 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
}
}

View file

@ -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);
}
}
}
/// <summary>
/// Handles the Elapsed event of the updateTimer control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="ElapsedEventArgs"/> instance containing the event data.</param>
async void updateTimer_Elapsed(object sender, ElapsedEventArgs e)
{
if (_disposed)
return;
((Timer)sender).Stop();
await ReportProgress().ConfigureAwait(false);
if (!_disposed && IsSessionActive)
((Timer)sender).Start();
}
/// <summary>
/// Reports the playback progress.
/// </summary>
/// <returns></returns>
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<string> { "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<BaseItem>();
foreach (string id in command.ItemIds)
{
AddItemFromId(Guid.Parse(id), items);
}
var playlist = new List<PlaylistItem>();
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<PlaylistItem> _playlist = new List<PlaylistItem>();
private List<PlaylistItem> Playlist
{
get
{
return _playlist;
}
set
{
_playlist = value;
}
}
private void AddItemFromId(Guid id, List<BaseItem> 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;
}
/// <summary>
/// Plays the items.
/// </summary>
/// <param name="items">The items.</param>
/// <returns></returns>
private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items)
{
Playlist.Clear();
Playlist.AddRange(items);
await SetNext();
return true;
}
/// <summary>
/// Adds the items to playlist.
/// </summary>
/// <param name="items">The items.</param>
private void AddItemsToPlaylist(IEnumerable<PlaylistItem> items)
{
Playlist.AddRange(items);
}
private async Task<bool> 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<bool> 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");
}
}
}
}

View file

@ -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;
}
}
}

View file

@ -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<string, DateTime> _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<string, DateTime>();
_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<string, DateTime>();
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()
{
}
/// <summary>
/// Creates a socket for the interface and listends for data.
/// </summary>
/// <param name="localIp">The local ip.</param>
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);
}
/// <summary>
/// Gets a socket configured for SDDP multicasting.
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// Creates a new DlnaSessionController.
/// and logs the session in SessionManager
/// </summary>
/// <param name="uri">The URI.</param>
/// <returns></returns>
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);
}
}
/// <summary>
/// Determines if the Uri is valid for further inspection or not.
/// (the limit for reinspection is 5 minutes)
/// </summary>
/// <param name="uri">The URI.</param>
/// <returns>Returns <b>True</b> if the Uri is valid for further inspection</returns>
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();
}
}
}
}

View file

@ -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);
}
/// <summary>
/// Creates the defaultuser if needed.
/// </summary>
private async Task<User> 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
}
}

View file

@ -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;
}
}
}

View file

@ -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
{
/// <summary>
/// Gets the dlna headers.
/// </summary>
/// <param name="item">The item.</param>
/// <returns></returns>
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
/// <summary>
/// Gets the audio URL.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="serverAddress">The server address.</param>
/// <returns>System.String.</returns>
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
/// <summary>
/// Gets the video URL.
/// </summary>
/// <param name="deviceProperties">The device properties.</param>
/// <param name="item">The item.</param>
/// <param name="streams">The streams.</param>
/// <param name="serverAddress">The server address.</param>
/// <returns>The url to send to the device</returns>
internal static string GetVideoUrl(DeviceProperties deviceProperties, PlaylistItem item, List<MediaStream> 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);
}
/// <summary>
/// Gets the video codec.
/// </summary>
/// <param name="videoStream">The video stream.</param>
/// <returns></returns>
private static VideoCodecs GetVideoCodec(MediaStream videoStream)
{
switch (videoStream.Codec.ToLower())
{
case "h264":
case "mpeg4":
return VideoCodecs.Copy;
}
return VideoCodecs.H264;
}
/// <summary>
/// Gets the audio codec.
/// </summary>
/// <param name="audioStream">The audio stream.</param>
/// <returns></returns>
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;
}
/// <summary>
/// Builds the dlna URL.
/// </summary>
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
}
}

View file

@ -6,7 +6,7 @@ namespace MediaBrowser.Dlna.PlayTo
{
public class TransportCommands
{
List<StateVariable> _stateVariables = new List<StateVariable>();
private List<StateVariable> _stateVariables = new List<StateVariable>();
public List<StateVariable> StateVariables
{
get
@ -19,7 +19,7 @@ namespace MediaBrowser.Dlna.PlayTo
}
}
List<ServiceAction> _serviceActions = new List<ServiceAction>();
private List<ServiceAction> _serviceActions = new List<ServiceAction>();
public List<ServiceAction> ServiceActions
{
get

View file

@ -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