import remaining dlna classes
This commit is contained in:
parent
ec131ba0dc
commit
dfb491fcc5
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
53
MediaBrowser.Dlna/PlayTo/Configuration/DlnaProfile.cs
Normal file
53
MediaBrowser.Dlna/PlayTo/Configuration/DlnaProfile.cs
Normal 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; }
|
||||
}
|
||||
}
|
119
MediaBrowser.Dlna/PlayTo/Configuration/PluginConfiguration.cs
Normal file
119
MediaBrowser.Dlna/PlayTo/Configuration/PluginConfiguration.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
76
MediaBrowser.Dlna/PlayTo/Configuration/TranscodeSetting.cs
Normal file
76
MediaBrowser.Dlna/PlayTo/Configuration/TranscodeSetting.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
154
MediaBrowser.Dlna/PlayTo/DidlBuilder.cs
Normal file
154
MediaBrowser.Dlna/PlayTo/DidlBuilder.cs
Normal 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
|
||||
}
|
||||
}
|
481
MediaBrowser.Dlna/PlayTo/DlnaController.cs
Normal file
481
MediaBrowser.Dlna/PlayTo/DlnaController.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
MediaBrowser.Dlna/PlayTo/DlnaControllerFactory.cs
Normal file
31
MediaBrowser.Dlna/PlayTo/DlnaControllerFactory.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
271
MediaBrowser.Dlna/PlayTo/PlayToManager.cs
Normal file
271
MediaBrowser.Dlna/PlayTo/PlayToManager.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
69
MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs
Normal file
69
MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
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)
|
||||
// {
|
||||
//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)
|
||||
{
|
||||
|
||||
// //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 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 = "ts";
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// foreach (string supported in PlayToConfiguration.SupportedStaticFormats)
|
||||
// {
|
||||
// if (path.EndsWith(supported))
|
||||
// {
|
||||
// playlistItem.Transcode = false;
|
||||
// playlistItem.FileFormat = supported;
|
||||
// return playlistItem;
|
||||
// }
|
||||
// }
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// playlistItem.Transcode = true;
|
||||
// playlistItem.FileFormat = "mp3";
|
||||
// }
|
||||
playlistItem.Transcode = true;
|
||||
playlistItem.FileFormat = "mp3";
|
||||
}
|
||||
|
||||
// return playlistItem;
|
||||
//}
|
||||
return playlistItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
188
MediaBrowser.Dlna/PlayTo/StreamHelper.cs
Normal file
188
MediaBrowser.Dlna/PlayTo/StreamHelper.cs
Normal 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
|
||||
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user