diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-02-26 16:31:47 -0500 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-02-26 16:31:47 -0500 |
| commit | ec131ba0dc25b29ca522cf4555bfad29ad501406 (patch) | |
| tree | d7b0d0a179c64762354d032df58b8f46546a1ac7 | |
| parent | 96d3c35ba0e181c41728fd7bbdb3d56903aca90d (diff) | |
added first play to classes
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("<", "<").Replace(">", ">"); + + return String.Format(BaseDidl, escapedData.Replace("\r\n", "")); + } + + private const string BaseDidl = "<DIDL-Lite xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\">{0}</DIDL-Lite>"; + + public async Task<bool> SetNextAvTransport(string value, string header, string metaData) + { + var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetNextAVTransportURI"); + if (command == null) + return false; + + var dictionary = new Dictionary<string, string>(); + dictionary.Add("NextURI", value); + dictionary.Add("NextURIMetaData", CreateDidlMeta(metaData)); + + var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId); + var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, value, dictionary), header); + await Task.Delay(100); + return true; + } + + public async Task<bool> SetPlay() + { + var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play"); + if (command == null) + return false; + + var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId); + + var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1)); + _count = 5; + return true; + } + + public async Task<bool> SetStop() + { + var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop"); + if (command == null) + return false; + + var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId); + + var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1)); + await Task.Delay(50); + _count = 4; + return true; + } + + public async Task<bool> SetPause() + { + var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause"); + if (command == null) + return false; + + var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId); + + var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 0)); + await Task.Delay(50); + TransportState = "PAUSED_PLAYBACK"; + return true; + } + + #endregion + + #region Get data + + int _count = 5; + async void dt_Elapsed(object sender, ElapsedEventArgs e) + { + if (_disposed) + return; + + ((Timer)sender).Stop(); + var hasTrack = await GetPositionInfo(); + if (_count > 4) + { + + await GetTransportInfo(); + if (!hasTrack) + { + await GetMediaInfo(); + } + await GetVolume(); + _count = 0; + } + + _count++; + if (_disposed) + return; + ((Timer)sender).Start(); + } + + private async Task GetVolume() + { + var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume"); + if (command == null) + return; + + var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId); + try + { + var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType)); + if (result == null) + return; + var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").FirstOrDefault().Element("CurrentVolume").Value; + if (volume == null) + return; + Volume = Int32.Parse(volume); + + //Reset the Mute value if Volume is bigger than zero + if (Volume > 0 && _muteVol > 0) + { + _muteVol = 0; + } + } + catch { } + } + + private async Task GetTransportInfo() + { + var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo"); + if (command == null) + return; + + var service = this.Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId); + if (service == null) + return; + + var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType)); + try + { + var transportState = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").FirstOrDefault().Element("CurrentTransportState").Value; + if (transportState != null) + TransportState = transportState; + } + catch { } + + if (result != null) + UpdateTime = DateTime.UtcNow; + } + + private async Task GetMediaInfo() + { + var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo"); + if (command == null) + return; + + var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId); + + var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType)); + try + { + var track = result.Document.Descendants("CurrentURIMetaData").FirstOrDefault().Value; + if (String.IsNullOrEmpty(track)) + { + CurrentId = "0"; + return; + } + XElement uPnpResponse = XElement.Parse((String)track); + + var e = uPnpResponse.Element(uPnpNamespaces.items); + + if (e == null) + e = uPnpResponse; + + var uTrack = uParser.CreateObjectFromXML(new uParserObject { Type = e.Element(uPnpNamespaces.uClass).Value, Element = e }); + if (uTrack != null) + CurrentId = uTrack.Id; + + } + catch { } + } + + private async Task<bool> GetPositionInfo() + { + var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo"); + if (command == null) + return true; + + var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId); + + var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType)); + + try + { + var duration = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").FirstOrDefault().Element("TrackDuration").Value; + + if (duration != null) + { + Duration = TimeSpan.Parse(duration); + } + + var position = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").FirstOrDefault().Element("RelTime").Value; + + if (position != null) + { + Position = TimeSpan.Parse(position); + } + + var track = result.Document.Descendants("TrackMetaData").Select(i => i.Value) + .FirstOrDefault(); + + if (String.IsNullOrEmpty(track)) + { + //If track is null, some vendors do this, use GetMediaInfo instead + return false; + } + + var uPnpResponse = XElement.Parse(track); + + var e = uPnpResponse.Element(uPnpNamespaces.items) ?? uPnpResponse; + + var uTrack = uBaseObject.Create(e); + + if (uTrack == null) + return true; + + CurrentId = uTrack.Id; + + return true; + } + catch { return false; } + } + + #endregion + + #region From XML + + internal async Task GetAVProtocolAsync() + { + var avService = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId); + if (avService == null) + return; + + string url = avService.SCPDURL; + if (!url.Contains("/")) + url = "/dmr/" + url; + if (!url.StartsWith("/")) + url = "/" + url; + + var httpClient = new SsdpHttpClient(); + var stream = await httpClient.GetDataAsync(new Uri(Properties.BaseUrl + url)); + + if (stream == null) + return; + + XDocument document = httpClient.ParseStream(stream); + stream.Dispose(); + + AvCommands = TransportCommands.Create(document); + } + + internal async Task GetRenderingProtocolAsync() + { + var avService = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId); + + if (avService == null) + return; + string url = avService.SCPDURL; + if (!url.Contains("/")) + url = "/dmr/" + url; + if (!url.StartsWith("/")) + url = "/" + url; + + var httpClient = new SsdpHttpClient(); + var stream = await httpClient.GetDataAsync(new Uri(Properties.BaseUrl + url)); + + if (stream == null) + return; + + XDocument document = httpClient.ParseStream(stream); + stream.Dispose(); + + RendererCommands = TransportCommands.Create(document); + } + + internal TransportCommands AvCommands + { + get; + set; + } + + internal TransportCommands RendererCommands + { + get; + set; + } + + public static async Task<Device> CreateuPnpDeviceAsync(Uri url) + { + var httpClient = new SsdpHttpClient(); + var stream = await httpClient.GetDataAsync(url); + + if (stream == null) + return null; + + var document = httpClient.ParseStream(stream); + stream.Dispose(); + + var deviceProperties = new DeviceProperties(); + + var name = document.Descendants(uPnpNamespaces.ud.GetName("friendlyName")).FirstOrDefault(); + if (name != null) + deviceProperties.Name = name.Value; + + var name2 = document.Descendants(uPnpNamespaces.ud.GetName("roomName")).FirstOrDefault(); + if (name2 != null) + deviceProperties.Name = name2.Value; + + var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault(); + if (model != null) + deviceProperties.ModelName = model.Value; + + var modelNumber = document.Descendants(uPnpNamespaces.ud.GetName("modelNumber")).FirstOrDefault(); + if (modelNumber != null) + deviceProperties.ModelNumber = modelNumber.Value; + + var uuid = document.Descendants(uPnpNamespaces.ud.GetName("UDN")).FirstOrDefault(); + if (uuid != null) + deviceProperties.UUID = uuid.Value; + + var manufacturer = document.Descendants(uPnpNamespaces.ud.GetName("manufacturer")).FirstOrDefault(); + if (manufacturer != null) + deviceProperties.Manufacturer = manufacturer.Value; + + var manufacturerUrl = document.Descendants(uPnpNamespaces.ud.GetName("manufacturerURL")).FirstOrDefault(); + if (manufacturerUrl != null) + deviceProperties.ManufacturerUrl = manufacturerUrl.Value; + + var presentationUrl = document.Descendants(uPnpNamespaces.ud.GetName("presentationURL")).FirstOrDefault(); + if (presentationUrl != null) + deviceProperties.PresentationUrl = presentationUrl.Value; + + + deviceProperties.BaseUrl = String.Format("http://{0}:{1}", url.Host, url.Port); + + var icon = document.Descendants(uPnpNamespaces.ud.GetName("icon")).FirstOrDefault(); + + if (icon != null) + { + deviceProperties.Icon = uIcon.Create(icon); + } + + var isRenderer = false; + + foreach (var services in document.Descendants(uPnpNamespaces.ud.GetName("serviceList"))) + { + if (services == null) + return null; + + var servicesList = services.Descendants(uPnpNamespaces.ud.GetName("service")); + + if (servicesList == null) + return null; + + foreach (var element in servicesList) + { + var service = uService.Create(element); + + if (service != null) + { + deviceProperties.Services.Add(service); + if (service.ServiceId == ServiceAvtransportId) + { + isRenderer = true; + } + } + } + } + + if (isRenderer) + { + + var device = new Device(deviceProperties); + + await device.GetRenderingProtocolAsync(); + await device.GetAVProtocolAsync(); + return device; + } + + return null; + } + + #endregion + + #region Events + + public event EventHandler<TransportStateEventArgs> PlaybackChanged; + public event EventHandler<CurrentIdEventArgs> CurrentIdChanged; + + private void NotifyPlaybackChanged(bool value) + { + if (PlaybackChanged != null) + { + PlaybackChanged.Invoke(this, new TransportStateEventArgs + { + Stopped = IsStopped + }); + } + } + + private void NotifyCurrentIdChanged(string value) + { + if (CurrentIdChanged != null) + CurrentIdChanged.Invoke(this, new CurrentIdEventArgs(value)); + } + + #endregion + + #region IDisposable + + bool _disposed; + public void Dispose() + { + if (!_disposed) + { + _disposed = true; + _dt.Stop(); + } + } + + #endregion + + public override string ToString() + { + return String.Format("{0} - {1}", Properties.Name, Properties.BaseUrl); + } + + private XDocument ParseStream(Stream stream) + { + var reader = new StreamReader(stream, Encoding.UTF8); + try + { + var doc = XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace); + stream.Dispose(); + return doc; + } + catch + { + } + return null; + } + } +} 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 |
