added first play to classes
This commit is contained in:
parent
96d3c35ba0
commit
ec131ba0dc
|
@ -1,4 +1,5 @@
|
|||
using MediaBrowser.Common.Configuration;
|
||||
using System.Collections.Specialized;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Logging;
|
||||
|
@ -367,7 +368,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
|||
|
||||
ContentType = httpResponse.ContentType,
|
||||
|
||||
Headers = httpResponse.Headers,
|
||||
Headers = new NameValueCollection(httpResponse.Headers),
|
||||
|
||||
ContentLength = contentLength
|
||||
};
|
||||
|
|
|
@ -55,7 +55,9 @@ namespace MediaBrowser.Common.Implementations.IO
|
|||
|
||||
if (string.Equals(Path.GetExtension(filename), ".mblink", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return File.ReadAllText(filename);
|
||||
var path = File.ReadAllText(filename);
|
||||
|
||||
return NormalizePath(path);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -51,8 +51,37 @@
|
|||
<Compile Include="..\SharedVersion.cs">
|
||||
<Link>Properties\SharedVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="PlayTo\Argument.cs" />
|
||||
<Compile Include="PlayTo\CurrentIdEventArgs.cs" />
|
||||
<Compile Include="PlayTo\DeviceProperties.cs" />
|
||||
<Compile Include="PlayTo\Extensions.cs" />
|
||||
<Compile Include="PlayTo\PlaylistItem.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="PlayTo\ServiceAction.cs" />
|
||||
<Compile Include="PlayTo\SsdpHelper.cs" />
|
||||
<Compile Include="PlayTo\SsdpHttpClient.cs" />
|
||||
<Compile Include="PlayTo\StateVariable.cs" />
|
||||
<Compile Include="PlayTo\TransportCommands.cs" />
|
||||
<Compile Include="PlayTo\TransportStateEventArgs.cs" />
|
||||
<Compile Include="PlayTo\uBaseObject.cs" />
|
||||
<Compile Include="PlayTo\uContainer.cs" />
|
||||
<Compile Include="PlayTo\uIcon.cs" />
|
||||
<Compile Include="PlayTo\uParser.cs" />
|
||||
<Compile Include="PlayTo\uPnpNamespaces.cs" />
|
||||
<Compile Include="PlayTo\uService.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
|
||||
<Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
|
||||
<Name>MediaBrowser.Common</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
|
||||
<Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
|
||||
<Name>MediaBrowser.Model</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
|
|
29
MediaBrowser.Dlna/PlayTo/Argument.cs
Normal file
29
MediaBrowser.Dlna/PlayTo/Argument.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class Argument
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Direction { get; set; }
|
||||
|
||||
public string RelatedStateVariable { get; set; }
|
||||
|
||||
public static Argument FromXml(XElement container)
|
||||
{
|
||||
if (container == null)
|
||||
{
|
||||
throw new ArgumentNullException("container");
|
||||
}
|
||||
|
||||
return new Argument
|
||||
{
|
||||
Name = container.GetValue(uPnpNamespaces.svc + "name"),
|
||||
Direction = container.GetValue(uPnpNamespaces.svc + "direction"),
|
||||
RelatedStateVariable = container.GetValue(uPnpNamespaces.svc + "relatedStateVariable")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
21
MediaBrowser.Dlna/PlayTo/CurrentIdEventArgs.cs
Normal file
21
MediaBrowser.Dlna/PlayTo/CurrentIdEventArgs.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class CurrentIdEventArgs : EventArgs
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public CurrentIdEventArgs(string id)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(id) || id == "0")
|
||||
{
|
||||
Id = Guid.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
Id = new Guid(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
682
MediaBrowser.Dlna/PlayTo/Device.cs
Normal file
682
MediaBrowser.Dlna/PlayTo/Device.cs
Normal file
|
@ -0,0 +1,682 @@
|
|||
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;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public sealed class Device : IDisposable
|
||||
{
|
||||
const string ServiceAvtransportId = "urn:upnp-org:serviceId:AVTransport";
|
||||
const string ServiceRenderingId = "urn:upnp-org:serviceId:RenderingControl";
|
||||
|
||||
#region Fields & Properties
|
||||
|
||||
private Timer _dt;
|
||||
|
||||
public DeviceProperties Properties { get; set; }
|
||||
|
||||
private int _muteVol;
|
||||
public bool IsMuted
|
||||
{
|
||||
get
|
||||
{
|
||||
return _muteVol > 0;
|
||||
}
|
||||
}
|
||||
|
||||
string _currentId = String.Empty;
|
||||
public string CurrentId
|
||||
{
|
||||
get
|
||||
{
|
||||
return _currentId;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_currentId == value)
|
||||
return;
|
||||
_currentId = value;
|
||||
NotifyCurrentIdChanged(value);
|
||||
}
|
||||
}
|
||||
|
||||
public int Volume { get; set; }
|
||||
|
||||
public TimeSpan Duration { get; set; }
|
||||
|
||||
private TimeSpan _position = TimeSpan.FromSeconds(0);
|
||||
public TimeSpan Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return _position;
|
||||
}
|
||||
set
|
||||
{
|
||||
_position = value;
|
||||
}
|
||||
}
|
||||
|
||||
private string _transportState = String.Empty;
|
||||
public string TransportState
|
||||
{
|
||||
get
|
||||
{
|
||||
return _transportState;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_transportState == value)
|
||||
return;
|
||||
|
||||
_transportState = value;
|
||||
|
||||
if (value == "PLAYING" || value == "STOPPED")
|
||||
NotifyPlaybackChanged(value == "STOPPED");
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPlaying
|
||||
{
|
||||
get
|
||||
{
|
||||
return TransportState == "PLAYING";
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsTransitioning
|
||||
{
|
||||
get
|
||||
{
|
||||
return (TransportState == "TRANSITIONING");
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPaused
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TransportState == "PAUSED" || TransportState == "PAUSED_PLAYBACK")
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsStopped
|
||||
{
|
||||
get
|
||||
{
|
||||
return (TransportState == "STOPPED");
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime UpdateTime
|
||||
{ get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
private readonly IHttpClient _httpClient;
|
||||
|
||||
#region Constructor & Initializer
|
||||
|
||||
public Device(DeviceProperties deviceProperties)
|
||||
{
|
||||
Properties = deviceProperties;
|
||||
}
|
||||
|
||||
internal void Start()
|
||||
{
|
||||
UpdateTime = DateTime.UtcNow;
|
||||
_dt = new Timer(1000);
|
||||
_dt.Elapsed += dt_Elapsed;
|
||||
_dt.Start();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Commanding
|
||||
|
||||
public Task<bool> VolumeDown(bool mute = false)
|
||||
{
|
||||
var sendVolume = (Volume - 5) > 0 ? Volume - 5 : 0;
|
||||
if (mute && _muteVol == 0)
|
||||
{
|
||||
sendVolume = 0;
|
||||
_muteVol = Volume;
|
||||
}
|
||||
return SetVolume(sendVolume);
|
||||
}
|
||||
|
||||
public Task<bool> VolumeUp(bool unmute = false)
|
||||
{
|
||||
var sendVolume = (Volume + 5) < 100 ? Volume + 5 : 100;
|
||||
if (unmute && _muteVol > 0)
|
||||
sendVolume = _muteVol;
|
||||
_muteVol = 0;
|
||||
return SetVolume(sendVolume);
|
||||
}
|
||||
|
||||
public Task ToggleMute()
|
||||
{
|
||||
if (_muteVol == 0)
|
||||
{
|
||||
_muteVol = Volume;
|
||||
return SetVolume(0);
|
||||
}
|
||||
|
||||
int tmp = _muteVol;
|
||||
_muteVol = 0;
|
||||
return SetVolume(tmp);
|
||||
|
||||
}
|
||||
|
||||
public async Task<bool> SetVolume(int value)
|
||||
{
|
||||
var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
|
||||
if (command == null)
|
||||
return true;
|
||||
|
||||
var service = this.Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId);
|
||||
|
||||
var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, value));
|
||||
Volume = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<TimeSpan> Seek(TimeSpan value)
|
||||
{
|
||||
var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
|
||||
if (command == null)
|
||||
return value;
|
||||
|
||||
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"));
|
||||
return value;
|
||||
}
|
||||
|
||||
public async Task<bool> SetAvTransport(string url, string header, string metaData)
|
||||
{
|
||||
_dt.Stop();
|
||||
TransportState = "STOPPED";
|
||||
CurrentId = "0";
|
||||
await Task.Delay(50);
|
||||
var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
|
||||
if (command == null)
|
||||
return false;
|
||||
|
||||
var dictionary = new Dictionary<string, string>
|
||||
{
|
||||
{"CurrentURI", url},
|
||||
{"CurrentURIMetaData", 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, url, dictionary), header);
|
||||
if (!IsPlaying)
|
||||
{
|
||||
await Task.Delay(50);
|
||||
await SetPlay();
|
||||
}
|
||||
_count = 5;
|
||||
_dt.Start();
|
||||
return true;
|
||||
}
|
||||
|
||||
private string CreateDidlMeta(string value)
|
||||
{
|
||||
if (value == null)
|
||||
return String.Empty;
|
||||
|
||||
var escapedData = value.Replace("<", "<").Replace(">", ">");
|
||||
|
||||
return String.Format(BaseDidl, escapedData.Replace("\r\n", ""));
|
||||
}
|
||||
|
||||
private const string BaseDidl = "<DIDL-Lite xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\">{0}</DIDL-Lite>";
|
||||
|
||||
public async Task<bool> SetNextAvTransport(string value, string header, string metaData)
|
||||
{
|
||||
var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetNextAVTransportURI");
|
||||
if (command == null)
|
||||
return false;
|
||||
|
||||
var dictionary = new Dictionary<string, string>();
|
||||
dictionary.Add("NextURI", value);
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> SetPlay()
|
||||
{
|
||||
var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play");
|
||||
if (command == null)
|
||||
return false;
|
||||
|
||||
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));
|
||||
_count = 5;
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> SetStop()
|
||||
{
|
||||
var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
|
||||
if (command == null)
|
||||
return false;
|
||||
|
||||
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);
|
||||
_count = 4;
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> SetPause()
|
||||
{
|
||||
var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
|
||||
if (command == null)
|
||||
return false;
|
||||
|
||||
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);
|
||||
TransportState = "PAUSED_PLAYBACK";
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Get data
|
||||
|
||||
int _count = 5;
|
||||
async void dt_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
((Timer)sender).Stop();
|
||||
var hasTrack = await GetPositionInfo();
|
||||
if (_count > 4)
|
||||
{
|
||||
|
||||
await GetTransportInfo();
|
||||
if (!hasTrack)
|
||||
{
|
||||
await GetMediaInfo();
|
||||
}
|
||||
await GetVolume();
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
_count++;
|
||||
if (_disposed)
|
||||
return;
|
||||
((Timer)sender).Start();
|
||||
}
|
||||
|
||||
private async Task GetVolume()
|
||||
{
|
||||
var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
|
||||
if (command == null)
|
||||
return;
|
||||
|
||||
var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId);
|
||||
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;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private async Task GetTransportInfo()
|
||||
{
|
||||
var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo");
|
||||
if (command == null)
|
||||
return;
|
||||
|
||||
var service = this.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));
|
||||
try
|
||||
{
|
||||
var transportState = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").FirstOrDefault().Element("CurrentTransportState").Value;
|
||||
if (transportState != null)
|
||||
TransportState = transportState;
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (result != null)
|
||||
UpdateTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
private async Task GetMediaInfo()
|
||||
{
|
||||
var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo");
|
||||
if (command == null)
|
||||
return;
|
||||
|
||||
var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
|
||||
|
||||
var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType));
|
||||
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;
|
||||
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private async Task<bool> GetPositionInfo()
|
||||
{
|
||||
var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
|
||||
if (command == null)
|
||||
return true;
|
||||
|
||||
var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
|
||||
|
||||
var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType));
|
||||
|
||||
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;
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region From XML
|
||||
|
||||
internal async Task GetAVProtocolAsync()
|
||||
{
|
||||
var avService = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
|
||||
if (avService == null)
|
||||
return;
|
||||
|
||||
string 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();
|
||||
|
||||
AvCommands = TransportCommands.Create(document);
|
||||
}
|
||||
|
||||
internal async Task GetRenderingProtocolAsync()
|
||||
{
|
||||
var avService = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId);
|
||||
|
||||
if (avService == null)
|
||||
return;
|
||||
string 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();
|
||||
|
||||
RendererCommands = TransportCommands.Create(document);
|
||||
}
|
||||
|
||||
internal TransportCommands AvCommands
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
internal TransportCommands RendererCommands
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public static async Task<Device> CreateuPnpDeviceAsync(Uri url)
|
||||
{
|
||||
var httpClient = new SsdpHttpClient();
|
||||
var stream = await httpClient.GetDataAsync(url);
|
||||
|
||||
if (stream == null)
|
||||
return null;
|
||||
|
||||
var document = httpClient.ParseStream(stream);
|
||||
stream.Dispose();
|
||||
|
||||
var deviceProperties = new DeviceProperties();
|
||||
|
||||
var name = document.Descendants(uPnpNamespaces.ud.GetName("friendlyName")).FirstOrDefault();
|
||||
if (name != null)
|
||||
deviceProperties.Name = name.Value;
|
||||
|
||||
var name2 = document.Descendants(uPnpNamespaces.ud.GetName("roomName")).FirstOrDefault();
|
||||
if (name2 != null)
|
||||
deviceProperties.Name = name2.Value;
|
||||
|
||||
var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault();
|
||||
if (model != null)
|
||||
deviceProperties.ModelName = model.Value;
|
||||
|
||||
var modelNumber = document.Descendants(uPnpNamespaces.ud.GetName("modelNumber")).FirstOrDefault();
|
||||
if (modelNumber != null)
|
||||
deviceProperties.ModelNumber = modelNumber.Value;
|
||||
|
||||
var uuid = document.Descendants(uPnpNamespaces.ud.GetName("UDN")).FirstOrDefault();
|
||||
if (uuid != null)
|
||||
deviceProperties.UUID = uuid.Value;
|
||||
|
||||
var manufacturer = document.Descendants(uPnpNamespaces.ud.GetName("manufacturer")).FirstOrDefault();
|
||||
if (manufacturer != null)
|
||||
deviceProperties.Manufacturer = manufacturer.Value;
|
||||
|
||||
var manufacturerUrl = document.Descendants(uPnpNamespaces.ud.GetName("manufacturerURL")).FirstOrDefault();
|
||||
if (manufacturerUrl != null)
|
||||
deviceProperties.ManufacturerUrl = manufacturerUrl.Value;
|
||||
|
||||
var presentationUrl = document.Descendants(uPnpNamespaces.ud.GetName("presentationURL")).FirstOrDefault();
|
||||
if (presentationUrl != null)
|
||||
deviceProperties.PresentationUrl = presentationUrl.Value;
|
||||
|
||||
|
||||
deviceProperties.BaseUrl = String.Format("http://{0}:{1}", url.Host, url.Port);
|
||||
|
||||
var icon = document.Descendants(uPnpNamespaces.ud.GetName("icon")).FirstOrDefault();
|
||||
|
||||
if (icon != null)
|
||||
{
|
||||
deviceProperties.Icon = uIcon.Create(icon);
|
||||
}
|
||||
|
||||
var isRenderer = false;
|
||||
|
||||
foreach (var services in document.Descendants(uPnpNamespaces.ud.GetName("serviceList")))
|
||||
{
|
||||
if (services == null)
|
||||
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);
|
||||
if (service.ServiceId == ServiceAvtransportId)
|
||||
{
|
||||
isRenderer = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isRenderer)
|
||||
{
|
||||
|
||||
var device = new Device(deviceProperties);
|
||||
|
||||
await device.GetRenderingProtocolAsync();
|
||||
await device.GetAVProtocolAsync();
|
||||
return device;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
public event EventHandler<TransportStateEventArgs> PlaybackChanged;
|
||||
public event EventHandler<CurrentIdEventArgs> CurrentIdChanged;
|
||||
|
||||
private void NotifyPlaybackChanged(bool value)
|
||||
{
|
||||
if (PlaybackChanged != null)
|
||||
{
|
||||
PlaybackChanged.Invoke(this, new TransportStateEventArgs
|
||||
{
|
||||
Stopped = IsStopped
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void NotifyCurrentIdChanged(string value)
|
||||
{
|
||||
if (CurrentIdChanged != null)
|
||||
CurrentIdChanged.Invoke(this, new CurrentIdEventArgs(value));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
bool _disposed;
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_disposed = true;
|
||||
_dt.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
176
MediaBrowser.Dlna/PlayTo/DeviceProperties.cs
Normal file
176
MediaBrowser.Dlna/PlayTo/DeviceProperties.cs
Normal file
|
@ -0,0 +1,176 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class DeviceProperties
|
||||
{
|
||||
private string _uuid = string.Empty;
|
||||
public string UUID
|
||||
{
|
||||
get
|
||||
{
|
||||
return _uuid;
|
||||
}
|
||||
set
|
||||
{
|
||||
_uuid = value;
|
||||
}
|
||||
}
|
||||
|
||||
private string _name = "PlayTo 1.0.0.0";
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
set
|
||||
{
|
||||
_name = value;
|
||||
}
|
||||
}
|
||||
|
||||
private string _clientType = "DLNA";
|
||||
public string ClientType
|
||||
{
|
||||
get
|
||||
{
|
||||
return _clientType;
|
||||
}
|
||||
set
|
||||
{
|
||||
_clientType = value;
|
||||
}
|
||||
}
|
||||
|
||||
private string _displayName = string.Empty;
|
||||
public string DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
return string.IsNullOrEmpty(_displayName) ? _name : _displayName;
|
||||
}
|
||||
set
|
||||
{
|
||||
_displayName = value;
|
||||
}
|
||||
}
|
||||
|
||||
private string _modelName = string.Empty;
|
||||
public string ModelName
|
||||
{
|
||||
get
|
||||
{
|
||||
return _modelName;
|
||||
}
|
||||
set
|
||||
{
|
||||
_modelName = value;
|
||||
}
|
||||
}
|
||||
|
||||
private string _modelNumber = string.Empty;
|
||||
public string ModelNumber
|
||||
{
|
||||
get
|
||||
{
|
||||
return _modelNumber;
|
||||
}
|
||||
set
|
||||
{
|
||||
_modelNumber = value;
|
||||
}
|
||||
}
|
||||
|
||||
private string _manufacturer = string.Empty;
|
||||
public string Manufacturer
|
||||
{
|
||||
get
|
||||
{
|
||||
return _manufacturer;
|
||||
}
|
||||
set
|
||||
{
|
||||
_manufacturer = value;
|
||||
}
|
||||
}
|
||||
|
||||
private string _manufacturerUrl = string.Empty;
|
||||
public string ManufacturerUrl
|
||||
{
|
||||
get
|
||||
{
|
||||
return _manufacturerUrl;
|
||||
}
|
||||
set
|
||||
{
|
||||
_manufacturerUrl = value;
|
||||
}
|
||||
}
|
||||
|
||||
private string _presentationUrl = string.Empty;
|
||||
public string PresentationUrl
|
||||
{
|
||||
get
|
||||
{
|
||||
return _presentationUrl;
|
||||
}
|
||||
set
|
||||
{
|
||||
_presentationUrl = value;
|
||||
}
|
||||
}
|
||||
|
||||
private string _baseUrl = string.Empty;
|
||||
public string BaseUrl
|
||||
{
|
||||
get
|
||||
{
|
||||
return _baseUrl;
|
||||
}
|
||||
set
|
||||
{
|
||||
_baseUrl = value;
|
||||
}
|
||||
}
|
||||
|
||||
private uIcon _icon;
|
||||
public uIcon Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
return _icon;
|
||||
}
|
||||
set
|
||||
{
|
||||
_icon = value;
|
||||
}
|
||||
}
|
||||
|
||||
private string _iconUrl;
|
||||
public string IconUrl
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_iconUrl) && _icon != null)
|
||||
{
|
||||
if (!_icon.Url.StartsWith("/"))
|
||||
_iconUrl = _baseUrl + "/" + _icon.Url;
|
||||
else
|
||||
_iconUrl = _baseUrl + _icon.Url;
|
||||
}
|
||||
|
||||
return _iconUrl;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<uService> _services = new List<uService>();
|
||||
public List<uService> Services
|
||||
{
|
||||
get
|
||||
{
|
||||
return _services;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
51
MediaBrowser.Dlna/PlayTo/Extensions.cs
Normal file
51
MediaBrowser.Dlna/PlayTo/Extensions.cs
Normal file
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static Task<int> ReceiveAsync(this Socket socket, byte[] buffer, int offset, int size)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<int>(socket);
|
||||
var remoteip = new IPEndPoint(IPAddress.Any, 0);
|
||||
var endpoint = (EndPoint)remoteip;
|
||||
|
||||
socket.BeginReceiveFrom(buffer, offset, size, SocketFlags.None, ref endpoint, iar =>
|
||||
{
|
||||
var result = (TaskCompletionSource<int>)iar.AsyncState;
|
||||
var iarSocket = (Socket)result.Task.AsyncState;
|
||||
|
||||
try
|
||||
{
|
||||
result.TrySetResult(iarSocket.EndReceive(iar));
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
result.TrySetException(exc);
|
||||
}
|
||||
}, tcs);
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public static string GetValue(this XElement container, XName name)
|
||||
{
|
||||
var node = container.Element(name);
|
||||
|
||||
return node == null ? null : node.Value;
|
||||
}
|
||||
|
||||
public static string GetDescendantValue(this XElement container, XName name)
|
||||
{
|
||||
var node = container.Descendants(name)
|
||||
.FirstOrDefault();
|
||||
|
||||
return node == null ? null : node.Value;
|
||||
}
|
||||
}
|
||||
}
|
95
MediaBrowser.Dlna/PlayTo/PlaylistItem.cs
Normal file
95
MediaBrowser.Dlna/PlayTo/PlaylistItem.cs
Normal file
|
@ -0,0 +1,95 @@
|
|||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class PlaylistItem
|
||||
{
|
||||
public string ItemId { get; set; }
|
||||
|
||||
public bool Transcode { get; set; }
|
||||
|
||||
public bool IsVideo { get; set; }
|
||||
|
||||
public bool IsAudio { get; set; }
|
||||
|
||||
public string FileFormat { get; set; }
|
||||
|
||||
public int PlayState { get; set; }
|
||||
|
||||
public string StreamUrl { get; set; }
|
||||
|
||||
public string DlnaHeaders { get; set; }
|
||||
|
||||
public string Didl { get; set; }
|
||||
|
||||
public long StartPositionTicks { get; set; }
|
||||
|
||||
//internal static PlaylistItem GetBasicConfig(BaseItem item, TranscodeSettings[] profileTranscodings)
|
||||
//{
|
||||
|
||||
// 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;
|
||||
// }
|
||||
|
||||
|
||||
// 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 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 = "mp3";
|
||||
// }
|
||||
|
||||
// return playlistItem;
|
||||
//}
|
||||
}
|
||||
}
|
34
MediaBrowser.Dlna/PlayTo/ServiceAction.cs
Normal file
34
MediaBrowser.Dlna/PlayTo/ServiceAction.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class ServiceAction
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public List<Argument> ArgumentList { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public static ServiceAction FromXml(XElement container)
|
||||
{
|
||||
var argumentList = new List<Argument>();
|
||||
|
||||
foreach (var arg in container.Descendants(uPnpNamespaces.svc + "argument"))
|
||||
{
|
||||
argumentList.Add(Argument.FromXml(arg));
|
||||
}
|
||||
|
||||
return new ServiceAction
|
||||
{
|
||||
Name = container.GetValue(uPnpNamespaces.svc + "name"),
|
||||
|
||||
ArgumentList = argumentList
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
56
MediaBrowser.Dlna/PlayTo/SsdpHelper.cs
Normal file
56
MediaBrowser.Dlna/PlayTo/SsdpHelper.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class SsdpHelper
|
||||
{
|
||||
private const string SsdpRenderer = "M-SEARCH * HTTP/1.1\r\n" +
|
||||
"HOST: 239.255.255.250:1900\r\n" +
|
||||
"User-Agent: UPnP/1.0 DLNADOC/1.50 Platinum/0.6.9.1\r\n" +
|
||||
"ST: urn:schemas-upnp-org:device:MediaRenderer:1\r\n" +
|
||||
"MAN: \"ssdp:discover\"\r\n" +
|
||||
"MX: {0}\r\n" +
|
||||
"\r\n";
|
||||
|
||||
/// <summary>
|
||||
/// Creates a SSDP MSearch packet for DlnaRenderers.
|
||||
/// </summary>
|
||||
/// <param name="mx">The mx. (Delaytime for device before responding)</param>
|
||||
/// <returns></returns>
|
||||
public static byte[] CreateRendererSSDP(int mx)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(string.Format(SsdpRenderer, mx));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the socket response into a location Uri for the DeviceDescription.xml.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns></returns>
|
||||
public static Uri ParseSsdpResponse(string data)
|
||||
{
|
||||
var res = (from line in data.Split(new[] { '\r', '\n' })
|
||||
where line.ToLowerInvariant().StartsWith("location:")
|
||||
select line).FirstOrDefault();
|
||||
|
||||
return !string.IsNullOrEmpty(res) ? new Uri(res.Substring(9).Trim()) : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses data into SSDP event.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Not yet used", true)]
|
||||
public static string ParseSsdpEvent(string data)
|
||||
{
|
||||
var sid = (from line in data.Split(new[] { '\r', '\n' })
|
||||
where line.ToLowerInvariant().StartsWith("sid:")
|
||||
select line).FirstOrDefault();
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
127
MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs
Normal file
127
MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs
Normal file
|
@ -0,0 +1,127 @@
|
|||
using MediaBrowser.Common.Net;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class SsdpHttpClient
|
||||
{
|
||||
private const string USERAGENT = "Microsoft-Windows/6.2 UPnP/1.0 Microsoft-DLNA DLNADOC/1.50";
|
||||
private const string FriendlyName = "MediaBrowser";
|
||||
|
||||
private static readonly CookieContainer Container = new CookieContainer();
|
||||
|
||||
private readonly IHttpClient _httpClient;
|
||||
|
||||
public SsdpHttpClient(IHttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public async Task<XDocument> SendCommandAsync(string baseUrl, uService service, string command, string postData, string header = null)
|
||||
{
|
||||
var serviceUrl = service.ControlURL;
|
||||
if (!serviceUrl.StartsWith("/"))
|
||||
serviceUrl = "/" + serviceUrl;
|
||||
|
||||
var response = await PostSoapDataAsync(new Uri(baseUrl + serviceUrl), "\"" + service.ServiceType + "#" + command + "\"", postData, header)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
using (var stream = response.Content)
|
||||
{
|
||||
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
||||
{
|
||||
return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SubscribeAsync(Uri url, string ip, int port, string localIp, int eventport, int timeOut = 3600)
|
||||
{
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
Url = url.ToString()
|
||||
};
|
||||
|
||||
options.RequestHeaders["UserAgent"] = USERAGENT;
|
||||
options.RequestHeaders["HOST"] = ip + ":" + port;
|
||||
options.RequestHeaders["CALLBACK"] = "<" + localIp + ":" + eventport + ">";
|
||||
options.RequestHeaders["NT"] = "upnp:event";
|
||||
options.RequestHeaders["TIMEOUT"] = "Second - " + timeOut;
|
||||
//request.CookieContainer = Container;
|
||||
|
||||
using (await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RespondAsync(Uri url, string ip, int port, string localIp, int eventport, int timeOut = 20000)
|
||||
{
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
Url = url.ToString()
|
||||
};
|
||||
|
||||
options.RequestHeaders["UserAgent"] = USERAGENT;
|
||||
options.RequestHeaders["HOST"] = ip + ":" + port;
|
||||
options.RequestHeaders["CALLBACK"] = "<" + localIp + ":" + eventport + ">";
|
||||
options.RequestHeaders["NT"] = "upnp:event";
|
||||
options.RequestHeaders["TIMEOUT"] = "Second - 3600";
|
||||
//request.CookieContainer = Container;
|
||||
|
||||
using (await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<XDocument> GetDataAsync(Uri url)
|
||||
{
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
Url = url.ToString()
|
||||
};
|
||||
|
||||
options.RequestHeaders["UserAgent"] = USERAGENT;
|
||||
options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
|
||||
//request.CookieContainer = Container;
|
||||
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
||||
{
|
||||
return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<HttpResponseInfo> PostSoapDataAsync(Uri url, string soapAction, string postData, string header = null, int timeOut = 20000)
|
||||
{
|
||||
if (!soapAction.StartsWith("\""))
|
||||
soapAction = "\"" + soapAction + "\"";
|
||||
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
Url = url.ToString()
|
||||
};
|
||||
|
||||
options.RequestHeaders["SOAPAction"] = soapAction;
|
||||
options.RequestHeaders["Pragma"] = "no-cache";
|
||||
options.RequestHeaders["UserAgent"] = USERAGENT;
|
||||
options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(header))
|
||||
{
|
||||
options.RequestHeaders["contentFeatures.dlna.org"] = header;
|
||||
}
|
||||
|
||||
options.RequestContentType = "text/xml; charset=\"utf-8\"";
|
||||
options.RequestContent = postData;
|
||||
|
||||
return _httpClient.Post(options);
|
||||
}
|
||||
}
|
||||
}
|
52
MediaBrowser.Dlna/PlayTo/StateVariable.cs
Normal file
52
MediaBrowser.Dlna/PlayTo/StateVariable.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class StateVariable
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string DataType { get; set; }
|
||||
|
||||
private List<string> _allowedValues = new List<string>();
|
||||
public List<string> AllowedValues
|
||||
{
|
||||
get
|
||||
{
|
||||
return _allowedValues;
|
||||
}
|
||||
set
|
||||
{
|
||||
_allowedValues = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public static StateVariable FromXml(XElement container)
|
||||
{
|
||||
var allowedValues = new List<string>();
|
||||
var element = container.Descendants(uPnpNamespaces.svc + "allowedValueList")
|
||||
.FirstOrDefault();
|
||||
|
||||
if (element != null)
|
||||
{
|
||||
var values = element.Descendants(uPnpNamespaces.svc + "allowedValue");
|
||||
|
||||
allowedValues.AddRange(values.Select(child => child.Value));
|
||||
}
|
||||
|
||||
return new StateVariable
|
||||
{
|
||||
Name = container.GetValue(uPnpNamespaces.svc + "name"),
|
||||
DataType = container.GetValue(uPnpNamespaces.svc + "dataType"),
|
||||
AllowedValues = allowedValues
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
157
MediaBrowser.Dlna/PlayTo/TransportCommands.cs
Normal file
157
MediaBrowser.Dlna/PlayTo/TransportCommands.cs
Normal file
|
@ -0,0 +1,157 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class TransportCommands
|
||||
{
|
||||
List<StateVariable> _stateVariables = new List<StateVariable>();
|
||||
public List<StateVariable> StateVariables
|
||||
{
|
||||
get
|
||||
{
|
||||
return _stateVariables;
|
||||
}
|
||||
set
|
||||
{
|
||||
_stateVariables = value;
|
||||
}
|
||||
}
|
||||
|
||||
List<ServiceAction> _serviceActions = new List<ServiceAction>();
|
||||
public List<ServiceAction> ServiceActions
|
||||
{
|
||||
get
|
||||
{
|
||||
return _serviceActions;
|
||||
}
|
||||
set
|
||||
{
|
||||
_serviceActions = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static TransportCommands Create(XDocument document)
|
||||
{
|
||||
var command = new TransportCommands();
|
||||
|
||||
var actionList = document.Descendants(uPnpNamespaces.svc + "actionList");
|
||||
|
||||
foreach (var container in actionList.Descendants(uPnpNamespaces.svc + "action"))
|
||||
{
|
||||
command.ServiceActions.Add(ServiceAction.FromXml(container));
|
||||
}
|
||||
|
||||
var stateValues = document.Descendants(uPnpNamespaces.ServiceStateTable).FirstOrDefault();
|
||||
|
||||
if (stateValues != null)
|
||||
{
|
||||
foreach (var container in stateValues.Elements(uPnpNamespaces.svc + "stateVariable"))
|
||||
{
|
||||
command.StateVariables.Add(StateVariable.FromXml(container));
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
public string BuildPost(ServiceAction action, string xmlNamespace)
|
||||
{
|
||||
var stateString = string.Empty;
|
||||
|
||||
foreach (var arg in action.ArgumentList)
|
||||
{
|
||||
if (arg.Direction == "out")
|
||||
continue;
|
||||
|
||||
if (arg.Name == "InstanceID")
|
||||
stateString += BuildArgumentXml(arg, "0");
|
||||
else
|
||||
stateString += BuildArgumentXml(arg, null);
|
||||
}
|
||||
|
||||
return string.Format(CommandBase, action.Name, xmlNamespace, stateString);
|
||||
}
|
||||
|
||||
public string BuildPost(ServiceAction action, string xmlNamesapce, object value, string commandParameter = "")
|
||||
{
|
||||
var stateString = string.Empty;
|
||||
|
||||
foreach (var arg in action.ArgumentList)
|
||||
{
|
||||
if (arg.Direction == "out")
|
||||
continue;
|
||||
if (arg.Name == "InstanceID")
|
||||
stateString += BuildArgumentXml(arg, "0");
|
||||
else
|
||||
stateString += BuildArgumentXml(arg, value.ToString(), commandParameter);
|
||||
}
|
||||
|
||||
return string.Format(CommandBase, action.Name, xmlNamesapce, stateString);
|
||||
}
|
||||
|
||||
public string BuildSearchPost(ServiceAction action, string xmlNamesapce, object value, string commandParameter = "")
|
||||
{
|
||||
var stateString = string.Empty;
|
||||
|
||||
foreach (var arg in action.ArgumentList)
|
||||
{
|
||||
if (arg.Direction == "out")
|
||||
continue;
|
||||
|
||||
if (arg.Name == "ObjectID")
|
||||
stateString += BuildArgumentXml(arg, value.ToString());
|
||||
else if (arg.Name == "Filter")
|
||||
stateString += BuildArgumentXml(arg, "*");
|
||||
else if (arg.Name == "StartingIndex")
|
||||
stateString += BuildArgumentXml(arg, "0");
|
||||
else if (arg.Name == "RequestedCount")
|
||||
stateString += BuildArgumentXml(arg, "200");
|
||||
else if (arg.Name == "BrowseFlag")
|
||||
stateString += BuildArgumentXml(arg, null, "BrowseDirectChildren");
|
||||
else if (arg.Name == "SortCriteria")
|
||||
stateString += BuildArgumentXml(arg, "");
|
||||
else
|
||||
stateString += BuildArgumentXml(arg, value.ToString(), commandParameter);
|
||||
}
|
||||
|
||||
return string.Format(CommandBase, action.Name, xmlNamesapce, stateString);
|
||||
}
|
||||
|
||||
public string BuildPost(ServiceAction action, string xmlNamesapce, object value, Dictionary<string, string> dictionary)
|
||||
{
|
||||
var stateString = string.Empty;
|
||||
|
||||
foreach (var arg in action.ArgumentList)
|
||||
{
|
||||
if (arg.Name == "InstanceID")
|
||||
stateString += BuildArgumentXml(arg, "0");
|
||||
else if (dictionary.ContainsKey(arg.Name))
|
||||
stateString += BuildArgumentXml(arg, dictionary[arg.Name]);
|
||||
else
|
||||
stateString += BuildArgumentXml(arg, value.ToString());
|
||||
}
|
||||
|
||||
return string.Format(CommandBase, action.Name, xmlNamesapce, stateString);
|
||||
}
|
||||
|
||||
private string BuildArgumentXml(Argument argument, string value, string commandParameter = "")
|
||||
{
|
||||
var state = StateVariables.FirstOrDefault(a => a.Name == argument.RelatedStateVariable);
|
||||
|
||||
if (state != null)
|
||||
{
|
||||
var sendValue = (state.AllowedValues.FirstOrDefault(a => a == commandParameter) ??
|
||||
state.AllowedValues.FirstOrDefault()) ??
|
||||
value;
|
||||
|
||||
return string.Format("<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", argument.Name, state.DataType ?? "string", sendValue);
|
||||
}
|
||||
|
||||
return string.Format("<{0}>{1}</{0}>", argument.Name, value);
|
||||
}
|
||||
|
||||
private const string CommandBase = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:{0} xmlns:m=\"{1}\">" + "{2}" + "</m:{0}>" + "</SOAP-ENV:Body></SOAP-ENV:Envelope>";
|
||||
}
|
||||
}
|
9
MediaBrowser.Dlna/PlayTo/TransportStateEventArgs.cs
Normal file
9
MediaBrowser.Dlna/PlayTo/TransportStateEventArgs.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class TransportStateEventArgs : EventArgs
|
||||
{
|
||||
public bool Stopped { get; set; }
|
||||
}
|
||||
}
|
66
MediaBrowser.Dlna/PlayTo/uBaseObject.cs
Normal file
66
MediaBrowser.Dlna/PlayTo/uBaseObject.cs
Normal file
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class uBaseObject
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public string ParentId { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public string SecondText { get; set; }
|
||||
|
||||
public string IconUrl { get; set; }
|
||||
|
||||
public string MetaData { get; set; }
|
||||
|
||||
public string Url { get; set; }
|
||||
|
||||
public string[] ProtocolInfo { get; set; }
|
||||
|
||||
public static uBaseObject Create(XElement container)
|
||||
{
|
||||
if (container == null)
|
||||
{
|
||||
throw new ArgumentNullException("container");
|
||||
}
|
||||
|
||||
return new uBaseObject
|
||||
{
|
||||
Id = container.Attribute(uPnpNamespaces.Id).Value,
|
||||
ParentId = container.Attribute(uPnpNamespaces.ParentId).Value,
|
||||
Title = container.GetValue(uPnpNamespaces.title),
|
||||
IconUrl = container.GetValue(uPnpNamespaces.Artwork),
|
||||
SecondText = "",
|
||||
Url = container.GetValue(uPnpNamespaces.Res),
|
||||
ProtocolInfo = GetProtocolInfo(container),
|
||||
MetaData = container.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
private static string[] GetProtocolInfo(XElement container)
|
||||
{
|
||||
if (container == null)
|
||||
{
|
||||
throw new ArgumentNullException("container");
|
||||
}
|
||||
|
||||
var resElement = container.Element(uPnpNamespaces.Res);
|
||||
|
||||
if (resElement != null)
|
||||
{
|
||||
var info = resElement.Attribute(uPnpNamespaces.ProtocolInfo);
|
||||
|
||||
if (info != null && !string.IsNullOrWhiteSpace(info.Value))
|
||||
{
|
||||
return info.Value.Split(':');
|
||||
}
|
||||
}
|
||||
|
||||
return new string[4];
|
||||
}
|
||||
}
|
||||
}
|
24
MediaBrowser.Dlna/PlayTo/uContainer.cs
Normal file
24
MediaBrowser.Dlna/PlayTo/uContainer.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class uContainer : uBaseObject
|
||||
{
|
||||
new public static uBaseObject Create(XElement container)
|
||||
{
|
||||
if (container == null)
|
||||
{
|
||||
throw new ArgumentNullException("container");
|
||||
}
|
||||
|
||||
return new uBaseObject
|
||||
{
|
||||
Id = (string)container.Attribute(uPnpNamespaces.Id),
|
||||
ParentId = (string)container.Attribute(uPnpNamespaces.ParentId),
|
||||
Title = (string)container.Element(uPnpNamespaces.title),
|
||||
IconUrl = container.GetValue(uPnpNamespaces.Artwork)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
48
MediaBrowser.Dlna/PlayTo/uIcon.cs
Normal file
48
MediaBrowser.Dlna/PlayTo/uIcon.cs
Normal file
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class uIcon
|
||||
{
|
||||
public string Url { get; private set; }
|
||||
|
||||
public string MimeType { get; private set; }
|
||||
|
||||
public int Width { get; private set; }
|
||||
|
||||
public int Height { get; private set; }
|
||||
|
||||
public string Depth { get; private set; }
|
||||
|
||||
public uIcon(string mimeType, string width, string height, string depth, string url)
|
||||
{
|
||||
MimeType = mimeType;
|
||||
Width = (!string.IsNullOrEmpty(width)) ? int.Parse(width) : 0;
|
||||
Height = (!string.IsNullOrEmpty(height)) ? int.Parse(height) : 0;
|
||||
Depth = depth;
|
||||
Url = url;
|
||||
}
|
||||
|
||||
public static uIcon Create(XElement element)
|
||||
{
|
||||
if (element == null)
|
||||
{
|
||||
throw new ArgumentNullException("element");
|
||||
}
|
||||
|
||||
var mimeType = element.GetDescendantValue(uPnpNamespaces.ud.GetName("mimetype"));
|
||||
var width = element.GetDescendantValue(uPnpNamespaces.ud.GetName("width"));
|
||||
var height = element.GetDescendantValue(uPnpNamespaces.ud.GetName("height"));
|
||||
var depth = element.GetDescendantValue(uPnpNamespaces.ud.GetName("depth"));
|
||||
var url = element.GetDescendantValue(uPnpNamespaces.ud.GetName("url"));
|
||||
|
||||
return new uIcon(mimeType, width, height, depth, url);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}x{1}", Height, Width);
|
||||
}
|
||||
}
|
||||
}
|
54
MediaBrowser.Dlna/PlayTo/uParser.cs
Normal file
54
MediaBrowser.Dlna/PlayTo/uParser.cs
Normal file
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class uParser
|
||||
{
|
||||
public static IList<uBaseObject> ParseBrowseXml(XDocument doc)
|
||||
{
|
||||
if (doc == null)
|
||||
{
|
||||
throw new ArgumentException("doc");
|
||||
}
|
||||
|
||||
var list = new List<uBaseObject>();
|
||||
|
||||
var document = doc.Document;
|
||||
|
||||
if (document == null)
|
||||
return list;
|
||||
|
||||
var item = (from result in document.Descendants("Result") select result).FirstOrDefault();
|
||||
|
||||
if (item == null)
|
||||
return list;
|
||||
|
||||
var uPnpResponse = XElement.Parse((String)item);
|
||||
|
||||
var uObjects = from container in uPnpResponse.Elements(uPnpNamespaces.containers)
|
||||
select new uParserObject { Type = (string)container.Element(uPnpNamespaces.uClass), Element = container };
|
||||
|
||||
var uObjects2 = from container in uPnpResponse.Elements(uPnpNamespaces.items)
|
||||
select new uParserObject { Type = (string)container.Element(uPnpNamespaces.uClass), Element = container };
|
||||
|
||||
list.AddRange(uObjects.Concat(uObjects2).Select(CreateObjectFromXML).Where(uObject => uObject != null));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static uBaseObject CreateObjectFromXML(uParserObject uItem)
|
||||
{
|
||||
return uContainer.Create(uItem.Element);
|
||||
}
|
||||
}
|
||||
|
||||
public class uParserObject
|
||||
{
|
||||
public string Type { get; set; }
|
||||
|
||||
public XElement Element { get; set; }
|
||||
}
|
||||
}
|
39
MediaBrowser.Dlna/PlayTo/uPnpNamespaces.cs
Normal file
39
MediaBrowser.Dlna/PlayTo/uPnpNamespaces.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using System.Xml.Linq;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class uPnpNamespaces
|
||||
{
|
||||
public static XNamespace dc = "http://purl.org/dc/elements/1.1/";
|
||||
public static XNamespace ns = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
|
||||
public static XNamespace svc = "urn:schemas-upnp-org:service-1-0";
|
||||
public static XNamespace ud = "urn:schemas-upnp-org:device-1-0";
|
||||
public static XNamespace upnp = "urn:schemas-upnp-org:metadata-1-0/upnp/";
|
||||
public static XNamespace RenderingControl = "urn:schemas-upnp-org:service:RenderingControl:1";
|
||||
public static XNamespace AvTransport = "urn:schemas-upnp-org:service:AVTransport:1";
|
||||
public static XNamespace ContentDirectory = "urn:schemas-upnp-org:service:ContentDirectory:1";
|
||||
|
||||
public static XName containers = ns + "container";
|
||||
public static XName items = ns + "item";
|
||||
public static XName title = dc + "title";
|
||||
public static XName creator = dc + "creator";
|
||||
public static XName artist = upnp + "artist";
|
||||
public static XName Id = "id";
|
||||
public static XName ParentId = "parentID";
|
||||
public static XName uClass = upnp + "class";
|
||||
public static XName Artwork = upnp + "albumArtURI";
|
||||
public static XName Description = dc + "description";
|
||||
public static XName LongDescription = upnp + "longDescription";
|
||||
public static XName Album = upnp + "album";
|
||||
public static XName Author = upnp + "author";
|
||||
public static XName Director = upnp + "director";
|
||||
public static XName PlayCount = upnp + "playbackCount";
|
||||
public static XName Tracknumber = upnp + "originalTrackNumber";
|
||||
public static XName Res = ns + "res";
|
||||
public static XName Duration = "duration";
|
||||
public static XName ProtocolInfo = "protocolInfo";
|
||||
|
||||
public static XName ServiceStateTable = svc + "serviceStateTable";
|
||||
public static XName StateVariable = svc + "stateVariable";
|
||||
}
|
||||
}
|
42
MediaBrowser.Dlna/PlayTo/uService.cs
Normal file
42
MediaBrowser.Dlna/PlayTo/uService.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using System.Xml.Linq;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class uService
|
||||
{
|
||||
public string ServiceType { get; set; }
|
||||
|
||||
public string ServiceId { get; set; }
|
||||
|
||||
public string SCPDURL { get; set; }
|
||||
|
||||
public string ControlURL { get; set; }
|
||||
|
||||
public string EventSubURL { get; set; }
|
||||
|
||||
public uService(string serviceType, string serviceId, string scpdUrl, string controlUrl, string eventSubUrl)
|
||||
{
|
||||
ServiceType = serviceType;
|
||||
ServiceId = serviceId;
|
||||
SCPDURL = scpdUrl;
|
||||
ControlURL = controlUrl;
|
||||
EventSubURL = eventSubUrl;
|
||||
}
|
||||
|
||||
public static uService Create(XElement element)
|
||||
{
|
||||
var type = element.GetDescendantValue(uPnpNamespaces.ud.GetName("serviceType"));
|
||||
var id = element.GetDescendantValue(uPnpNamespaces.ud.GetName("serviceId"));
|
||||
var scpdUrl = element.GetDescendantValue(uPnpNamespaces.ud.GetName("SCPDURL"));
|
||||
var controlURL = element.GetDescendantValue(uPnpNamespaces.ud.GetName("controlURL"));
|
||||
var eventSubURL = element.GetDescendantValue(uPnpNamespaces.ud.GetName("eventSubURL"));
|
||||
|
||||
return new uService(type, id, scpdUrl, controlURL, eventSubURL);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}", ServiceId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
|
|
Loading…
Reference in New Issue
Block a user