added first play to classes

This commit is contained in:
Luke Pulverenti 2014-02-26 16:31:47 -05:00
parent 96d3c35ba0
commit ec131ba0dc
22 changed files with 1797 additions and 4 deletions

View File

@ -1,4 +1,5 @@
using MediaBrowser.Common.Configuration; using System.Collections.Specialized;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
@ -367,7 +368,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
ContentType = httpResponse.ContentType, ContentType = httpResponse.ContentType,
Headers = httpResponse.Headers, Headers = new NameValueCollection(httpResponse.Headers),
ContentLength = contentLength ContentLength = contentLength
}; };

View File

@ -55,7 +55,9 @@ namespace MediaBrowser.Common.Implementations.IO
if (string.Equals(Path.GetExtension(filename), ".mblink", StringComparison.OrdinalIgnoreCase)) if (string.Equals(Path.GetExtension(filename), ".mblink", StringComparison.OrdinalIgnoreCase))
{ {
return File.ReadAllText(filename); var path = File.ReadAllText(filename);
return NormalizePath(path);
} }
return null; return null;

View File

@ -51,8 +51,37 @@
<Compile Include="..\SharedVersion.cs"> <Compile Include="..\SharedVersion.cs">
<Link>Properties\SharedVersion.cs</Link> <Link>Properties\SharedVersion.cs</Link>
</Compile> </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" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </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" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- 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. Other similar extension points exist, see Microsoft.Common.targets.

View 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")
};
}
}
}

View 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);
}
}
}
}

View 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("<", "&lt;").Replace(">", "&gt;");
return String.Format(BaseDidl, escapedData.Replace("\r\n", ""));
}
private const string BaseDidl = "&lt;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/\"&gt;{0}&lt;/DIDL-Lite&gt;";
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;
}
}
}

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

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

View 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;
//}
}
}

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

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

View 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);
}
}
}

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

View 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>";
}
}

View File

@ -0,0 +1,9 @@
using System;
namespace MediaBrowser.Dlna.PlayTo
{
public class TransportStateEventArgs : EventArgs
{
public bool Stopped { get; set; }
}
}

View 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];
}
}
}

View 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)
};
}
}
}

View 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);
}
}
}

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

View 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";
}
}

View 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);
}
}
}

View File

@ -1,5 +1,4 @@
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following