aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Pulverenti <luke.pulverenti@gmail.com>2014-02-26 16:31:47 -0500
committerLuke Pulverenti <luke.pulverenti@gmail.com>2014-02-26 16:31:47 -0500
commitec131ba0dc25b29ca522cf4555bfad29ad501406 (patch)
treed7b0d0a179c64762354d032df58b8f46546a1ac7
parent96d3c35ba0e181c41728fd7bbdb3d56903aca90d (diff)
added first play to classes
-rw-r--r--MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs5
-rw-r--r--MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs4
-rw-r--r--MediaBrowser.Dlna/MediaBrowser.Dlna.csproj29
-rw-r--r--MediaBrowser.Dlna/PlayTo/Argument.cs29
-rw-r--r--MediaBrowser.Dlna/PlayTo/CurrentIdEventArgs.cs21
-rw-r--r--MediaBrowser.Dlna/PlayTo/Device.cs682
-rw-r--r--MediaBrowser.Dlna/PlayTo/DeviceProperties.cs176
-rw-r--r--MediaBrowser.Dlna/PlayTo/Extensions.cs51
-rw-r--r--MediaBrowser.Dlna/PlayTo/PlaylistItem.cs95
-rw-r--r--MediaBrowser.Dlna/PlayTo/ServiceAction.cs34
-rw-r--r--MediaBrowser.Dlna/PlayTo/SsdpHelper.cs56
-rw-r--r--MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs127
-rw-r--r--MediaBrowser.Dlna/PlayTo/StateVariable.cs52
-rw-r--r--MediaBrowser.Dlna/PlayTo/TransportCommands.cs157
-rw-r--r--MediaBrowser.Dlna/PlayTo/TransportStateEventArgs.cs9
-rw-r--r--MediaBrowser.Dlna/PlayTo/uBaseObject.cs66
-rw-r--r--MediaBrowser.Dlna/PlayTo/uContainer.cs24
-rw-r--r--MediaBrowser.Dlna/PlayTo/uIcon.cs48
-rw-r--r--MediaBrowser.Dlna/PlayTo/uParser.cs54
-rw-r--r--MediaBrowser.Dlna/PlayTo/uPnpNamespaces.cs39
-rw-r--r--MediaBrowser.Dlna/PlayTo/uService.cs42
-rw-r--r--MediaBrowser.Dlna/Properties/AssemblyInfo.cs1
22 files changed, 1797 insertions, 4 deletions
diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
index 8e5cab43c..3dcf044f7 100644
--- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
+++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
@@ -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
};
diff --git a/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs b/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs
index 98e81981d..24ac48e83 100644
--- a/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs
+++ b/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs
@@ -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;
diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
index 6f6fc3caa..937c9b4c9 100644
--- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
+++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
@@ -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.
diff --git a/MediaBrowser.Dlna/PlayTo/Argument.cs b/MediaBrowser.Dlna/PlayTo/Argument.cs
new file mode 100644
index 000000000..fd0468dff
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/Argument.cs
@@ -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")
+ };
+ }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/CurrentIdEventArgs.cs b/MediaBrowser.Dlna/PlayTo/CurrentIdEventArgs.cs
new file mode 100644
index 000000000..24158b890
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/CurrentIdEventArgs.cs
@@ -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);
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/Device.cs b/MediaBrowser.Dlna/PlayTo/Device.cs
new file mode 100644
index 000000000..36e631c13
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/Device.cs
@@ -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;
+ }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/DeviceProperties.cs b/MediaBrowser.Dlna/PlayTo/DeviceProperties.cs
new file mode 100644
index 000000000..395689575
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/DeviceProperties.cs
@@ -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;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/Extensions.cs b/MediaBrowser.Dlna/PlayTo/Extensions.cs
new file mode 100644
index 000000000..afa7eee72
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/Extensions.cs
@@ -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;
+ }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs
new file mode 100644
index 000000000..591e39bef
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs
@@ -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;
+ //}
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/ServiceAction.cs b/MediaBrowser.Dlna/PlayTo/ServiceAction.cs
new file mode 100644
index 000000000..4f1a321dd
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/ServiceAction.cs
@@ -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
+ };
+ }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/SsdpHelper.cs b/MediaBrowser.Dlna/PlayTo/SsdpHelper.cs
new file mode 100644
index 000000000..d07a7679f
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/SsdpHelper.cs
@@ -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;
+ }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs b/MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs
new file mode 100644
index 000000000..f440beed1
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs
@@ -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);
+ }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/StateVariable.cs b/MediaBrowser.Dlna/PlayTo/StateVariable.cs
new file mode 100644
index 000000000..0fb5d6f61
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/StateVariable.cs
@@ -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
+ };
+ }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/TransportCommands.cs b/MediaBrowser.Dlna/PlayTo/TransportCommands.cs
new file mode 100644
index 000000000..c0332642f
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/TransportCommands.cs
@@ -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>";
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/TransportStateEventArgs.cs b/MediaBrowser.Dlna/PlayTo/TransportStateEventArgs.cs
new file mode 100644
index 000000000..98844e3a6
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/TransportStateEventArgs.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace MediaBrowser.Dlna.PlayTo
+{
+ public class TransportStateEventArgs : EventArgs
+ {
+ public bool Stopped { get; set; }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/uBaseObject.cs b/MediaBrowser.Dlna/PlayTo/uBaseObject.cs
new file mode 100644
index 000000000..5831e7dab
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/uBaseObject.cs
@@ -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];
+ }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/uContainer.cs b/MediaBrowser.Dlna/PlayTo/uContainer.cs
new file mode 100644
index 000000000..ea35a9cbc
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/uContainer.cs
@@ -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)
+ };
+ }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/uIcon.cs b/MediaBrowser.Dlna/PlayTo/uIcon.cs
new file mode 100644
index 000000000..79bbbc1ef
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/uIcon.cs
@@ -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);
+ }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/uParser.cs b/MediaBrowser.Dlna/PlayTo/uParser.cs
new file mode 100644
index 000000000..1f3bb53bd
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/uParser.cs
@@ -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; }
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/uPnpNamespaces.cs b/MediaBrowser.Dlna/PlayTo/uPnpNamespaces.cs
new file mode 100644
index 000000000..d44bdceaa
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/uPnpNamespaces.cs
@@ -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";
+ }
+}
diff --git a/MediaBrowser.Dlna/PlayTo/uService.cs b/MediaBrowser.Dlna/PlayTo/uService.cs
new file mode 100644
index 000000000..08bdf18fc
--- /dev/null
+++ b/MediaBrowser.Dlna/PlayTo/uService.cs
@@ -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);
+ }
+ }
+}
diff --git a/MediaBrowser.Dlna/Properties/AssemblyInfo.cs b/MediaBrowser.Dlna/Properties/AssemblyInfo.cs
index 1d9f7cd97..a8403e6a5 100644
--- a/MediaBrowser.Dlna/Properties/AssemblyInfo.cs
+++ b/MediaBrowser.Dlna/Properties/AssemblyInfo.cs
@@ -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