Browse Source

added first play to classes

Luke Pulverenti 11 years ago
parent
commit
ec131ba0dc

+ 3 - 2
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
             };

+ 3 - 1
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;

+ 29 - 0
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.

+ 29 - 0
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")                
+            };
+        }
+    }
+}

+ 21 - 0
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);
+            }
+        }
+    }
+}

+ 682 - 0
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;
+        }
+    }
+}

+ 176 - 0
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;
+            }
+        }
+    }
+}

+ 51 - 0
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;
+        }
+    }
+}

+ 95 - 0
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;
+        //}
+    }
+}

+ 34 - 0
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
+            };
+        }
+    }
+}

+ 56 - 0
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;
+        }
+    }
+}

+ 127 - 0
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);
+        }
+    }
+}

+ 52 - 0
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
+            };
+        }
+    }
+}

+ 157 - 0
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>";
+    }
+}

+ 9 - 0
MediaBrowser.Dlna/PlayTo/TransportStateEventArgs.cs

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

+ 66 - 0
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];
+        }
+    }
+}

+ 24 - 0
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)
+            };
+        }
+    }
+}

+ 48 - 0
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);
+        }
+    }
+}

+ 54 - 0
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; }
+    }
+}

+ 39 - 0
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";
+    }
+}

+ 42 - 0
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);
+        }
+    }
+}

+ 0 - 1
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