Bladeren bron

import remaining dlna classes

Luke Pulverenti 11 jaren geleden
bovenliggende
commit
dfb491fcc5

+ 3 - 3
MediaBrowser.Controller/Entities/User.cs

@@ -226,11 +226,11 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// Saves the current configuration to the file system
         /// </summary>
-        public void SaveConfiguration(IXmlSerializer serializer)
+        public void SaveConfiguration()
         {
             var xmlPath = ConfigurationFilePath;
             Directory.CreateDirectory(System.IO.Path.GetDirectoryName(xmlPath));
-            serializer.SerializeToFile(Configuration, xmlPath);
+            XmlSerializer.SerializeToFile(Configuration, xmlPath);
         }
 
         /// <summary>
@@ -247,7 +247,7 @@ namespace MediaBrowser.Controller.Entities
             }
 
             Configuration = config;
-            SaveConfiguration(serializer);
+            SaveConfiguration();
         }
     }
 }

+ 16 - 0
MediaBrowser.Dlna/MediaBrowser.Dlna.csproj

@@ -52,16 +52,28 @@
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>
     <Compile Include="PlayTo\Argument.cs" />
+    <Compile Include="PlayTo\Configuration\DlnaProfile.cs" />
+    <Compile Include="PlayTo\Configuration\PluginConfiguration.cs" />
+    <Compile Include="PlayTo\Configuration\TranscodeSetting.cs" />
     <Compile Include="PlayTo\CurrentIdEventArgs.cs" />
+    <Compile Include="PlayTo\Device.cs">
+      <SubType>Code</SubType>
+    </Compile>
     <Compile Include="PlayTo\DeviceProperties.cs" />
+    <Compile Include="PlayTo\DidlBuilder.cs" />
+    <Compile Include="PlayTo\DlnaController.cs" />
+    <Compile Include="PlayTo\DlnaControllerFactory.cs" />
     <Compile Include="PlayTo\Extensions.cs" />
     <Compile Include="PlayTo\PlaylistItem.cs">
       <SubType>Code</SubType>
     </Compile>
+    <Compile Include="PlayTo\PlayToManager.cs" />
+    <Compile Include="PlayTo\PlayToServerEntryPoint.cs" />
     <Compile Include="PlayTo\ServiceAction.cs" />
     <Compile Include="PlayTo\SsdpHelper.cs" />
     <Compile Include="PlayTo\SsdpHttpClient.cs" />
     <Compile Include="PlayTo\StateVariable.cs" />
+    <Compile Include="PlayTo\StreamHelper.cs" />
     <Compile Include="PlayTo\TransportCommands.cs" />
     <Compile Include="PlayTo\TransportStateEventArgs.cs" />
     <Compile Include="PlayTo\uBaseObject.cs" />
@@ -77,6 +89,10 @@
       <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
       <Name>MediaBrowser.Common</Name>
     </ProjectReference>
+    <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
+      <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
+      <Name>MediaBrowser.Controller</Name>
+    </ProjectReference>
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
       <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
       <Name>MediaBrowser.Model</Name>

+ 53 - 0
MediaBrowser.Dlna/PlayTo/Configuration/DlnaProfile.cs

@@ -0,0 +1,53 @@
+namespace MediaBrowser.Dlna.PlayTo.Configuration
+{
+    public class DlnaProfile
+    {
+        /// <summary>
+        /// Gets or sets the name to be displayed.
+        /// </summary>
+        /// <value>
+        /// The name.
+        /// </value>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Gets or sets the type of the client.
+        /// </summary>
+        /// <value>
+        /// The type of the client.
+        /// </value>
+        public string ClientType { get; set; }
+
+        /// <summary>
+        /// Gets or sets the name of the friendly.
+        /// </summary>
+        /// <value>
+        /// The name of the friendly.
+        /// </value>
+        public string FriendlyName { get; set; }
+
+        /// <summary>
+        /// Gets or sets the model number.
+        /// </summary>
+        /// <value>
+        /// The model number.
+        /// </value>
+        public string ModelNumber { get; set; }
+
+        /// <summary>
+        /// Gets or sets the name of the model.
+        /// </summary>
+        /// <value>
+        /// The name of the model.
+        /// </value>
+        public string ModelName { get; set; }
+
+        /// <summary>
+        /// Gets or sets the transcode settings.
+        /// </summary>
+        /// <value>
+        /// The transcode settings.
+        /// </value>
+        public TranscodeSettings[] TranscodeSettings { get; set; }
+    }
+}

+ 119 - 0
MediaBrowser.Dlna/PlayTo/Configuration/PluginConfiguration.cs

@@ -0,0 +1,119 @@
+namespace MediaBrowser.Dlna.PlayTo.Configuration
+{
+    public class PlayToConfiguration
+    {
+        private static readonly string[] _supportedStaticFormats = { "mp3", "flac", "m4a", "wma", "avi", "mp4", "mkv", "ts" };
+        public static string[] SupportedStaticFormats
+        {
+            get
+            {
+                return _supportedStaticFormats;
+            }
+        }
+
+        private static readonly DlnaProfile[] _profiles = GetDefaultProfiles();
+        public static DlnaProfile[] Profiles
+        {
+            get
+            {
+                return _profiles;
+            }
+        }
+
+        private static DlnaProfile[] GetDefaultProfiles()
+        {
+            var profile0 = new DlnaProfile
+            {
+                Name = "Samsung TV (B Series) [Profile]",
+                ClientType = "DLNA",
+                FriendlyName = "^TV$",
+                ModelNumber = @"1\.0",
+                ModelName = "Samsung DTV DMR",
+                TranscodeSettings = TranscodeSettings.GetDefaultTranscodingSettings()
+            };
+
+            var profile1 = new DlnaProfile
+            {
+                Name = "Samsung TV (E/F-series) [Profile]",
+                ClientType = "DLNA",
+                FriendlyName = @"(^\[TV\][A-Z]{2}\d{2}(E|F)[A-Z]?\d{3,4}.*)|^\[TV\] Samsung",
+                ModelNumber = @"(1\.0)|(AllShare1\.0)",
+                TranscodeSettings = TranscodeSettings.GetDefaultTranscodingSettings()
+            };
+
+            var profile2 = new DlnaProfile
+            {
+                Name = "Samsung TV (C/D-series) [Profile]",
+                ClientType = "DLNA",
+                FriendlyName = @"(^TV-\d{2}C\d{3}.*)|(^\[TV\][A-Z]{2}\d{2}(D)[A-Z]?\d{3,4}.*)|^\[TV\] Samsung",
+                ModelNumber = @"(1\.0)|(AllShare1\.0)",
+                TranscodeSettings = TranscodeSettings.GetDefaultTranscodingSettings()
+            };
+
+            var profile3 = new DlnaProfile
+            {
+                Name = "Xbox 360 [Profile]",
+                ClientType = "DLNA",
+                ModelName = "Xbox 360",
+                TranscodeSettings = new[]
+                {
+                    new TranscodeSettings {Container = "mkv", TargetContainer = "ts"},
+                    new TranscodeSettings {Container = "flac", TargetContainer = "mp3"},
+                    new TranscodeSettings {Container = "m4a", TargetContainer = "mp3"}
+                }
+            };
+
+            var profile4 = new DlnaProfile
+            {
+                Name = "Xbox One [Profile]",
+                ModelName = "Xbox One",
+                ClientType = "DLNA",
+                FriendlyName = "Xbox-SystemOS",
+                TranscodeSettings = new[]
+                {
+                    new TranscodeSettings {Container = "mkv", TargetContainer = "ts"},
+                    new TranscodeSettings {Container = "flac", TargetContainer = "mp3"},
+                    new TranscodeSettings {Container = "m4a", TargetContainer = "mp3"}
+                }
+            };
+
+            var profile5 = new DlnaProfile
+            {
+                Name = "Sony Bravia TV (2012)",
+                ClientType = "TV",
+                FriendlyName = @"BRAVIA KDL-\d{2}[A-Z]X\d5(\d|G).*",
+                TranscodeSettings = TranscodeSettings.GetDefaultTranscodingSettings()
+            };
+
+            //WDTV does not need any transcoding of the formats we support statically
+            var profile6 = new DlnaProfile
+            {
+                Name = "WDTV Live [Profile]",
+                ClientType = "DLNA",
+                ModelName = "WD TV HD Live",
+                TranscodeSettings = new TranscodeSettings[] { }
+            };
+
+            var profile7 = new DlnaProfile
+           {
+               //Linksys DMA2100us does not need any transcoding of the formats we support statically
+               Name = "Linksys DMA2100 [Profile]",
+               ClientType = "DLNA",
+               ModelName = "DMA2100us",
+               TranscodeSettings = new TranscodeSettings[] { }
+           };
+
+            return new[] 
+            {
+                profile0,
+                profile1,
+                profile2,
+                profile3,
+                profile4,
+                profile5,
+                profile6,
+                profile7
+            };
+        }
+    }
+}

+ 76 - 0
MediaBrowser.Dlna/PlayTo/Configuration/TranscodeSetting.cs

@@ -0,0 +1,76 @@
+using System;
+
+namespace MediaBrowser.Dlna.PlayTo.Configuration
+{
+    public class TranscodeSettings
+    {
+        /// <summary>
+        /// Gets or sets the container.
+        /// </summary>
+        /// <value>
+        /// The container.
+        /// </value>
+        public string Container { get; set; }
+
+        /// <summary>
+        /// Gets or sets the target container.
+        /// </summary>
+        /// <value>
+        /// The target container.
+        /// </value>
+        public string TargetContainer { get; set; }
+
+        /// <summary>
+        /// The default transcoding settings
+        /// </summary>
+        private static readonly TranscodeSettings[] DefaultTranscodingSettings =
+        { 
+            new TranscodeSettings { Container = "mkv", TargetContainer = "ts" }, 
+            new TranscodeSettings { Container = "flac", TargetContainer = "mp3" },
+            new TranscodeSettings { Container = "m4a", TargetContainer = "mp3" }
+        };
+
+        public static TranscodeSettings[] GetDefaultTranscodingSettings()
+        {
+            return DefaultTranscodingSettings;
+        }
+
+        /// <summary>
+        /// Gets the profile settings.
+        /// </summary>
+        /// <param name="deviceProperties">The device properties.</param>
+        /// <returns>The TranscodeSettings for the device</returns>
+        public static TranscodeSettings[] GetProfileSettings(DeviceProperties deviceProperties)
+        {
+            foreach (var profile in PlayToConfiguration.Profiles)
+            {
+                if (!string.IsNullOrEmpty(profile.FriendlyName))
+                {
+                    if (!string.Equals(deviceProperties.Name, profile.FriendlyName, StringComparison.OrdinalIgnoreCase))
+                        continue;
+                }
+
+                if (!string.IsNullOrEmpty(profile.ModelNumber))
+                {
+                    if (!string.Equals(deviceProperties.ModelNumber, profile.ModelNumber, StringComparison.OrdinalIgnoreCase))
+                        continue;
+                }
+
+                if (!string.IsNullOrEmpty(profile.ModelName))
+                {
+                    if (!string.Equals(deviceProperties.ModelName, profile.ModelName, StringComparison.OrdinalIgnoreCase))
+                        continue;
+                }
+
+                deviceProperties.DisplayName = profile.Name;
+                deviceProperties.ClientType = profile.ClientType;
+                return profile.TranscodeSettings;
+
+            }
+
+            // Since we don't have alot of info about different devices we go down the safe
+            // route abd use the default transcoding settings if no profile exist
+            return GetDefaultTranscodingSettings();
+        }
+    }
+}

+ 222 - 136
MediaBrowser.Dlna/PlayTo/Device.cs

@@ -1,12 +1,11 @@
 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;
+using MediaBrowser.Model.Logging;
 
 namespace MediaBrowser.Dlna.PlayTo
 {
@@ -122,12 +121,15 @@ namespace MediaBrowser.Dlna.PlayTo
         #endregion
 
         private readonly IHttpClient _httpClient;
-        
+        private readonly ILogger _logger;
+
         #region Constructor & Initializer
 
-        public Device(DeviceProperties deviceProperties)
+        public Device(DeviceProperties deviceProperties, IHttpClient httpClient, ILogger logger)
         {
             Properties = deviceProperties;
+            _httpClient = httpClient;
+            _logger = logger;
         }
 
         internal void Start()
@@ -182,9 +184,15 @@ namespace MediaBrowser.Dlna.PlayTo
             if (command == null)
                 return true;
 
-            var service = this.Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId);
+            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId);
 
-            var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, value));
+            if (service == null)
+            {
+                throw new InvalidOperationException("Unable to find service");
+            }
+
+            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, value))
+                .ConfigureAwait(false);
             Volume = value;
             return true;
         }
@@ -197,7 +205,14 @@ namespace MediaBrowser.Dlna.PlayTo
 
             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"));
+            if (service == null)
+            {
+                throw new InvalidOperationException("Unable to find service");
+            }
+
+            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, String.Format("{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
+                .ConfigureAwait(false);
+
             return value;
         }
 
@@ -206,7 +221,9 @@ namespace MediaBrowser.Dlna.PlayTo
             _dt.Stop();
             TransportState = "STOPPED";
             CurrentId = "0";
-            await Task.Delay(50);
+
+            await Task.Delay(50).ConfigureAwait(false);
+
             var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
             if (command == null)
                 return false;
@@ -218,12 +235,21 @@ namespace MediaBrowser.Dlna.PlayTo
             };
 
             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 (service == null)
+            {
+                throw new InvalidOperationException("Unable to find service");
+            }
+
+            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, url, dictionary), header)
+                .ConfigureAwait(false);
+
             if (!IsPlaying)
             {
-                await Task.Delay(50);
-                await SetPlay();
+                await Task.Delay(50).ConfigureAwait(false);
+                await SetPlay().ConfigureAwait(false);
             }
+
             _count = 5;
             _dt.Start();
             return true;
@@ -252,8 +278,17 @@ namespace MediaBrowser.Dlna.PlayTo
             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);
+
+            if (service == null)
+            {
+                throw new InvalidOperationException("Unable to find service");
+            }
+
+            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, value, dictionary), header)
+                .ConfigureAwait(false);
+
+            await Task.Delay(100).ConfigureAwait(false);
+
             return true;
         }
 
@@ -265,7 +300,14 @@ namespace MediaBrowser.Dlna.PlayTo
 
             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));
+            if (service == null)
+            {
+                throw new InvalidOperationException("Unable to find service");
+            }
+
+            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1))
+                .ConfigureAwait(false);
+
             _count = 5;
             return true;
         }
@@ -278,8 +320,10 @@ namespace MediaBrowser.Dlna.PlayTo
 
             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);
+            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1))
+                .ConfigureAwait(false);
+
+            await Task.Delay(50).ConfigureAwait(false);
             _count = 4;
             return true;
         }
@@ -292,8 +336,10 @@ namespace MediaBrowser.Dlna.PlayTo
 
             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);
+            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 0))
+                .ConfigureAwait(false);
+
+            await Task.Delay(50).ConfigureAwait(false);
             TransportState = "PAUSED_PLAYBACK";
             return true;
         }
@@ -302,23 +348,26 @@ namespace MediaBrowser.Dlna.PlayTo
 
         #region Get data
 
+        // TODO: What is going on here
         int _count = 5;
+
         async void dt_Elapsed(object sender, ElapsedEventArgs e)
         {
             if (_disposed)
                 return;
 
             ((Timer)sender).Stop();
-            var hasTrack = await GetPositionInfo();
+            var hasTrack = await GetPositionInfo().ConfigureAwait(false);
+
+            // TODO: Why make these requests if hasTrack==false?
             if (_count > 4)
             {
-
-                await GetTransportInfo();
+                await GetTransportInfo().ConfigureAwait(false);
                 if (!hasTrack)
                 {
-                    await GetMediaInfo();
+                    await GetMediaInfo().ConfigureAwait(false);
                 }
-                await GetVolume();
+                await GetVolume().ConfigureAwait(false);
                 _count = 0;
             }
 
@@ -335,23 +384,41 @@ namespace MediaBrowser.Dlna.PlayTo
                 return;
 
             var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId);
+
+            if (service == null)
+            {
+                throw new InvalidOperationException("Unable to find service");
+            }
+
+            XDocument result;
+
             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);
+                result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
+                   .ConfigureAwait(false);
+            }
+            catch (Exception ex)
+            {
+                _logger.ErrorException("Error getting volume info", ex);
+                return;
+            }
 
-                //Reset the Mute value if Volume is bigger than zero
-                if (Volume > 0 && _muteVol > 0)
-                {
-                    _muteVol = 0;
-                }
+            if (result == null || result.Document == null)
+                return;
+
+            var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null);
+            var volumeValue = volume == null ? null : volume.Value;
+
+            if (volumeValue == null)
+                return;
+
+            Volume = Int32.Parse(volumeValue);
+
+            //Reset the Mute value if Volume is bigger than zero
+            if (Volume > 0 && _muteVol > 0)
+            {
+                _muteVol = 0;
             }
-            catch { }
         }
 
         private async Task GetTransportInfo()
@@ -360,21 +427,35 @@ namespace MediaBrowser.Dlna.PlayTo
             if (command == null)
                 return;
 
-            var service = this.Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
+            var service = 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));
+            XDocument result;
+
             try
             {
-                var transportState = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").FirstOrDefault().Element("CurrentTransportState").Value;
-                if (transportState != null)
-                    TransportState = transportState;
+                result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
+                   .ConfigureAwait(false);
+            }
+            catch (Exception ex)
+            {
+                _logger.ErrorException("Error getting transport info", ex);
+                return;
             }
-            catch { }
 
-            if (result != null)
-                UpdateTime = DateTime.UtcNow;
+            if (result == null || result.Document == null)
+                return;
+
+            var transportState =
+                result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null);
+
+            var transportStateValue = transportState == null ? null : transportState.Value;
+
+            if (transportStateValue != null)
+                TransportState = transportStateValue;
+
+            UpdateTime = DateTime.UtcNow;
         }
 
         private async Task GetMediaInfo()
@@ -385,28 +466,47 @@ namespace MediaBrowser.Dlna.PlayTo
 
             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
+            if (service == null)
             {
-                var track = result.Document.Descendants("CurrentURIMetaData").FirstOrDefault().Value;
-                if (String.IsNullOrEmpty(track))
-                {
-                    CurrentId = "0";
-                    return;
-                }
-                XElement uPnpResponse = XElement.Parse((String)track);
+                throw new InvalidOperationException("Unable to find service");
+            }
+
+            XDocument result;
 
-                var e = uPnpResponse.Element(uPnpNamespaces.items);
+            try
+            {
+                result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
+                   .ConfigureAwait(false);
+            }
+            catch (Exception ex)
+            {
+                _logger.ErrorException("Error getting media info", ex);
+                return;
+            }
 
-                if (e == null)
-                    e = uPnpResponse;
+            if (result == null || result.Document == null)
+                return;
 
-                var uTrack = uParser.CreateObjectFromXML(new uParserObject { Type = e.Element(uPnpNamespaces.uClass).Value, Element = e });
-                if (uTrack != null)
-                    CurrentId = uTrack.Id;
+            var track = result.Document.Descendants("CurrentURIMetaData").Select(i => i.Value).FirstOrDefault();
 
+            if (String.IsNullOrEmpty(track))
+            {
+                CurrentId = "0";
+                return;
             }
-            catch { }
+
+            var uPnpResponse = XElement.Parse(track);
+
+            var e = uPnpResponse.Element(uPnpNamespaces.items) ?? uPnpResponse;
+
+            var uTrack = uParser.CreateObjectFromXML(new uParserObject
+            {
+                Type = e.GetValue(uPnpNamespaces.uClass),
+                Element = e
+            });
+
+            if (uTrack != null)
+                CurrentId = uTrack.Id;
         }
 
         private async Task<bool> GetPositionInfo()
@@ -417,78 +517,89 @@ namespace MediaBrowser.Dlna.PlayTo
 
             var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
 
-            var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType));
-            
+            if (service == null)
+            {
+                throw new InvalidOperationException("Unable to find service");
+            }
+
+            XDocument result;
+
             try
             {
-                var duration = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").FirstOrDefault().Element("TrackDuration").Value;
-                
-                if (duration != null)
-                {
-                    Duration = TimeSpan.Parse(duration);
-                }
+                result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
+                   .ConfigureAwait(false);
+            }
+            catch (Exception ex)
+            {
+                _logger.ErrorException("Error getting position info", ex);
+                return false;
+            }
 
-                var position = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").FirstOrDefault().Element("RelTime").Value;
-                
-                if (position != null)
-                {
-                    Position = TimeSpan.Parse(position);
-                }
+            if (result == null || result.Document == null)
+                return true;
 
-                var track = result.Document.Descendants("TrackMetaData").Select(i => i.Value)
-                    .FirstOrDefault();
+            var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
+            var duration = durationElem == null ? null : durationElem.Value;
 
-                if (String.IsNullOrEmpty(track))
-                {
-                    //If track is null, some vendors do this, use GetMediaInfo instead                    
-                    return false;
-                }
+            if (duration != null)
+            {
+                Duration = TimeSpan.Parse(duration);
+            }
 
-                var uPnpResponse = XElement.Parse(track);
+            var positionElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != null);
+            var position = positionElem == null ? null : positionElem.Value;
 
-                var e = uPnpResponse.Element(uPnpNamespaces.items) ?? uPnpResponse;
+            if (position != null)
+            {
+                Position = TimeSpan.Parse(position);
+            }
 
-                var uTrack = uBaseObject.Create(e);
+            var track = result.Document.Descendants("TrackMetaData").Select(i => i.Value)
+                .FirstOrDefault();
 
-                if (uTrack == null)
-                    return true;
+            if (String.IsNullOrEmpty(track))
+            {
+                //If track is null, some vendors do this, use GetMediaInfo instead                    
+                return false;
+            }
 
-                CurrentId = uTrack.Id;
+            var uPnpResponse = XElement.Parse(track);
 
+            var e = uPnpResponse.Element(uPnpNamespaces.items) ?? uPnpResponse;
+
+            var uTrack = uBaseObject.Create(e);
+
+            if (uTrack == null)
                 return true;
-            }
-            catch { return false; }
+
+            CurrentId = uTrack.Id;
+
+            return true;
         }
 
         #endregion
 
         #region From XML
 
-        internal async Task GetAVProtocolAsync()
+        private async Task GetAVProtocolAsync()
         {
             var avService = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
             if (avService == null)
                 return;
 
-            string url = avService.SCPDURL;
+            var 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();
+            var httpClient = new SsdpHttpClient(_httpClient);
+            var document = await httpClient.GetDataAsync(new Uri(Properties.BaseUrl + url));
 
             AvCommands = TransportCommands.Create(document);
         }
 
-        internal async Task GetRenderingProtocolAsync()
+        private async Task GetRenderingProtocolAsync()
         {
             var avService = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId);
 
@@ -500,14 +611,8 @@ namespace MediaBrowser.Dlna.PlayTo
             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();
+            var httpClient = new SsdpHttpClient(_httpClient);
+            var document = await httpClient.GetDataAsync(new Uri(Properties.BaseUrl + url));
 
             RendererCommands = TransportCommands.Create(document);
         }
@@ -524,16 +629,11 @@ namespace MediaBrowser.Dlna.PlayTo
             set;
         }
 
-        public static async Task<Device> CreateuPnpDeviceAsync(Uri url)
+        public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, ILogger logger)
         {
-            var httpClient = new SsdpHttpClient();
-            var stream = await httpClient.GetDataAsync(url);
-
-            if (stream == null)
-                return null;
+            var ssdpHttpClient = new SsdpHttpClient(httpClient);
 
-            var document = httpClient.ParseStream(stream);
-            stream.Dispose();
+            var document = await ssdpHttpClient.GetDataAsync(url).ConfigureAwait(false);
 
             var deviceProperties = new DeviceProperties();
 
@@ -587,14 +687,14 @@ namespace MediaBrowser.Dlna.PlayTo
                     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);
@@ -609,10 +709,11 @@ namespace MediaBrowser.Dlna.PlayTo
             if (isRenderer)
             {
 
-                var device = new Device(deviceProperties);
+                var device = new Device(deviceProperties, httpClient, logger);
+
+                await device.GetRenderingProtocolAsync().ConfigureAwait(false);
+                await device.GetAVProtocolAsync().ConfigureAwait(false);
 
-                await device.GetRenderingProtocolAsync();
-                await device.GetAVProtocolAsync();
                 return device;
             }
 
@@ -663,20 +764,5 @@ namespace MediaBrowser.Dlna.PlayTo
         {
             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;
-        }
     }
 }

+ 154 - 0
MediaBrowser.Dlna/PlayTo/DidlBuilder.cs

@@ -0,0 +1,154 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Dlna.PlayTo
+{
+    internal class DidlBuilder
+    {
+        #region Constants
+
+        internal const string CRLF = "\r\n";
+        internal const string UNKNOWN = "Unknown";
+
+        internal const string DIDL_START = @"<item id=""{0}"" parentID=""{1}"" restricted=""1"" xmlns=""urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"">" + CRLF;
+        internal const string DIDL_TITLE = @"  <dc:title xmlns:dc=""http://purl.org/dc/elements/1.1/"">{0}</dc:title>" + CRLF;
+        internal const string DIDL_ARTIST = @"<upnp:artist xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:artist>" + CRLF;
+        internal const string DIDL_ALBUM = @"<upnp:album xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:album>" + CRLF;
+        internal const string DIDL_TRACKNUM = @"<upnp:originalTrackNumber xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">0</upnp:originalTrackNumber>" + CRLF;
+        internal const string DIDL_VIDEOCLASS = @"  <upnp:class xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">object.item.videoItem</upnp:class>" + CRLF;
+        internal const string DIDL_AUDIOCLASS = @"  <upnp:class xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">object.item.audioItem.musicTrack</upnp:class>" + CRLF;
+        internal const string DIDL_IMAGE = @"  <upnp:albumArtURI dlna:profileID=""JPEG_TN"" xmlns:dlna=""urn:schemas-dlna-org:metadata-1-0/"" xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:albumArtURI>" + CRLF +
+                                                @"  <upnp:icon xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:icon>" + CRLF;
+        internal const string DIDL_RELEASEDATE = @"  <dc:date xmlns:dc=""http://purl.org/dc/elements/1.1/"">{0}</dc:date>" + CRLF;
+        internal const string DIDL_GENRE = @"  <upnp:genre xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:genre>" + CRLF;
+        internal const string DESCRIPTION = @"  <dc:description xmlns:dc=""http://purl.org/dc/elements/1.1/"">{0}</dc:description>" + CRLF;
+        internal const string DIDL_VIDEO_RES = @"  <res bitrate=""{0}"" duration=""{1}"" protocolInfo=""http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01500000000000000000000000000000"" resolution=""{2}x{3}"" size=""0"">{4}</res>" + CRLF;
+        internal const string DIDL_AUDIO_RES = @"  <res bitrate=""{0}"" duration=""{1}"" nrAudioChannels=""2"" protocolInfo=""http-get:*:audio/mp3:DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01500000000000000000000000000000"" sampleFrequency=""{2}"" size=""0"">{3}</res>" + CRLF;
+        internal const string DIDL_IMAGE_RES = @"  <res protocolInfo=""http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_CI=1;DLNA.ORG_FLAGS=00D00000000000000000000000000000"" resolution=""212x320"">{0}</res>" + CRLF;
+        internal const string DIDL_ALBUMIMAGE_RES = @"  <res protocolInfo=""http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_CI=1;DLNA.ORG_FLAGS=00D00000000000000000000000000000"" resolution=""320x320"">{0}</res>" + CRLF;
+        internal const string DIDL_RATING = @"  <upnp:rating xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:rating>" + CRLF;
+        internal const string DIDL_END = "</item>";
+
+        #endregion
+
+        /// <summary>
+        /// Builds a Didl MetaData object for the specified dto.
+        /// </summary>
+        /// <param name="dto">The dto.</param>
+        /// <param name="userId">The user identifier.</param>
+        /// <param name="serverAddress">The server address.</param>
+        /// <param name="streamUrl">The stream URL.</param>
+        /// <param name="streams">The streams.</param>
+        /// <returns>System.String.</returns>
+        internal static string Build(BaseItem dto, string userId, string serverAddress, string streamUrl, IEnumerable<MediaStream> streams)
+        {
+            string response = string.Format(DIDL_START, dto.Id, userId);
+            response += string.Format(DIDL_TITLE, dto.Name.Replace("&", "and"));
+            if (IsVideo(dto))
+                response += DIDL_VIDEOCLASS;
+            else
+                response += DIDL_AUDIOCLASS;
+
+            response += string.Format(DIDL_IMAGE, GetImageUrl(dto, serverAddress));
+            response += string.Format(DIDL_RELEASEDATE, GetDateString(dto.PremiereDate));
+
+            //TODO Add genres to didl;
+            response += string.Format(DIDL_GENRE, UNKNOWN);
+
+            if (IsVideo(dto))
+            {
+                response += string.Format(DESCRIPTION, UNKNOWN);
+                response += GetVideoDIDL(dto, streamUrl, streams);
+                response += string.Format(DIDL_IMAGE_RES, GetImageUrl(dto, serverAddress));
+            }
+            else
+            {
+                var audio = dto as Audio;
+
+                if (audio != null)
+                {
+                    response += string.Format(DIDL_ARTIST, audio.Artists.FirstOrDefault() ?? UNKNOWN);
+                    response += string.Format(DIDL_ALBUM, audio.Album);
+
+                    // TODO: Bad format string?
+                    response += string.Format(DIDL_TRACKNUM, audio.IndexNumber ?? 0);
+                }
+
+                response += GetAudioDIDL(dto, streamUrl, streams);
+                response += string.Format(DIDL_ALBUMIMAGE_RES, GetImageUrl(dto, serverAddress));
+            }
+
+            response += DIDL_END;
+
+            return response;
+
+        }
+
+        #region Private methods
+
+        private static string GetVideoDIDL(BaseItem dto, string streamUrl, IEnumerable<MediaStream> streams)
+        {
+            var videostream = streams.Where(stream => stream.Type == Model.Entities.MediaStreamType.Video).OrderBy(s => s.IsDefault).FirstOrDefault();
+
+            if (videostream == null)
+            {
+                // TOOD: ???
+                return string.Empty;
+            }
+
+            return string.Format(DIDL_VIDEO_RES, videostream.BitRate.HasValue ? videostream.BitRate.Value / 10 : 0, GetDurationString(dto), videostream.Width ?? 0, videostream.Height ?? 0, streamUrl);
+        }
+
+        private static string GetAudioDIDL(BaseItem dto, string streamUrl, IEnumerable<MediaStream> streams)
+        {
+            var audiostream = streams.Where(stream => stream.Type == MediaStreamType.Audio).OrderBy(s => s.IsDefault).FirstOrDefault();
+
+            if (audiostream == null)
+            {
+                // TOOD: ???
+                return string.Empty;
+            }
+
+            return string.Format(DIDL_AUDIO_RES, audiostream.BitRate.HasValue ? audiostream.BitRate.Value / 10 : 16000, GetDurationString(dto), audiostream.SampleRate ?? 0, streamUrl);
+        }
+
+        private static string GetImageUrl(BaseItem dto, string serverAddress)
+        {
+            var imageType = ImageType.Primary;
+
+            if (!dto.HasImage(ImageType.Primary))
+            {
+                dto = dto.Parents.FirstOrDefault(i => i.HasImage(ImageType.Primary));
+            }
+
+            return string.Format("{0}/Items/{1}/Images/{2}", serverAddress, dto.Id, imageType);
+        }
+
+        private static string GetDurationString(BaseItem dto)
+        {
+            var duration = TimeSpan.FromTicks(dto.RunTimeTicks.HasValue ? dto.RunTimeTicks.Value : 0);
+
+            // TODO: Bad format string?
+            return string.Format("{0}:{1:00}:2{00}.000", duration.Hours, duration.Minutes, duration.Seconds);
+        }
+
+        private static string GetDateString(DateTime? date)
+        {
+            if (!date.HasValue)
+                return UNKNOWN;
+
+            return string.Format("{0}-{1:00}-{2:00}", date.Value.Year, date.Value.Month, date.Value.Day);
+        }
+
+        private static bool IsVideo(BaseItem item)
+        {
+            return string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
+        }
+
+        #endregion
+    }
+}

+ 481 - 0
MediaBrowser.Dlna/PlayTo/DlnaController.cs

@@ -0,0 +1,481 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Dlna.PlayTo.Configuration;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Session;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Timers;
+using Timer = System.Timers.Timer;
+
+namespace MediaBrowser.Dlna.PlayTo
+{
+    public class PlayToController : ISessionController
+    {
+        private Device _device;
+        private BaseItem _currentItem = null;
+        private TranscodeSettings[] _transcodeSettings;
+        private readonly SessionInfo _session;
+        private readonly ISessionManager _sessionManager;
+        private readonly IItemRepository _itemRepository;
+        private readonly ILibraryManager _libraryManager;
+        private readonly INetworkManager _networkManager;
+        private readonly ILogger _logger;
+        private bool _playbackStarted = false;
+
+        public bool SupportsMediaRemoteControl
+        {
+            get { return true; }
+        }
+
+        public bool IsSessionActive
+        {
+            get
+            {
+                if (_device == null || _device.UpdateTime == default(DateTime))
+                    return false;
+
+                return DateTime.UtcNow <= _device.UpdateTime.AddSeconds(30);
+            }
+        }
+
+        public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager)
+        {
+            _session = session;
+            _itemRepository = itemRepository;
+            _sessionManager = sessionManager;
+            _libraryManager = libraryManager;
+            _networkManager = networkManager;
+            _logger = logger;
+        }
+
+        public void Init(Device device, TranscodeSettings[] transcodeSettings)
+        {
+            _transcodeSettings = transcodeSettings;
+            _device = device;
+            _device.PlaybackChanged += Device_PlaybackChanged;
+            _device.CurrentIdChanged += Device_CurrentIdChanged;
+            _device.Start();
+
+            _updateTimer = new Timer(1000);
+            _updateTimer.Elapsed += updateTimer_Elapsed;
+            _updateTimer.Start();
+        }
+
+        #region Device EventHandlers & Update Timer
+
+        Timer _updateTimer;
+
+        async void Device_PlaybackChanged(object sender, TransportStateEventArgs e)
+        {
+            if (_currentItem == null)
+                return;
+
+            if (e.Stopped == false)
+                await ReportProgress().ConfigureAwait(false);
+
+            else if (e.Stopped && _playbackStarted)
+            {
+                _playbackStarted = false;
+
+                await _sessionManager.OnPlaybackStopped(new PlaybackStopInfo
+                {
+                    Item = _currentItem,
+                    SessionId = _session.Id,
+                    PositionTicks = _device.Position.Ticks
+
+                }).ConfigureAwait(false);
+
+                await SetNext().ConfigureAwait(false);
+            }
+        }
+
+        async void Device_CurrentIdChanged(object sender, CurrentIdEventArgs e)
+        {
+            if (e.Id != Guid.Empty)
+            {
+                if (_currentItem != null && _currentItem.Id == e.Id)
+                {
+                    return;
+                }
+
+                var item = _libraryManager.GetItemById(e.Id);
+
+                if (item != null)
+                {
+                    _logger.Debug("{0} - CurrentId {1}", _session.DeviceName, item.Id);
+                    _currentItem = item;
+                    _playbackStarted = false;
+
+                    await ReportProgress().ConfigureAwait(false);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Handles the Elapsed event of the updateTimer control.
+        /// </summary>
+        /// <param name="sender">The source of the event.</param>
+        /// <param name="e">The <see cref="ElapsedEventArgs"/> instance containing the event data.</param>
+        async void updateTimer_Elapsed(object sender, ElapsedEventArgs e)
+        {
+            if (_disposed)
+                return;
+
+            ((Timer)sender).Stop();
+
+            await ReportProgress().ConfigureAwait(false);
+
+            if (!_disposed && IsSessionActive)
+                ((Timer)sender).Start();
+        }
+
+        /// <summary>
+        /// Reports the playback progress.
+        /// </summary>
+        /// <returns></returns>
+        private async Task ReportProgress()
+        {
+            if (_currentItem == null || _device.IsStopped)
+                return;
+
+            if (!_playbackStarted)
+            {
+                await _sessionManager.OnPlaybackStart(new PlaybackInfo { Item = _currentItem, SessionId = _session.Id, CanSeek = true, QueueableMediaTypes = new List<string> { "Audio", "Video" } }).ConfigureAwait(false);
+                _playbackStarted = true;
+            }
+
+            if ((_device.IsPlaying || _device.IsPaused))
+            {
+                var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
+                if (playlistItem != null && playlistItem.Transcode)
+                {
+                    await _sessionManager.OnPlaybackProgress(new PlaybackProgressInfo
+                    {
+                        Item = _currentItem,
+                        SessionId = _session.Id,
+                        PositionTicks = _device.Position.Ticks + playlistItem.StartPositionTicks,
+                        IsMuted = _device.IsMuted,
+                        IsPaused = _device.IsPaused
+
+                    }).ConfigureAwait(false);
+                }
+                else if (_currentItem != null)
+                {
+                    await _sessionManager.OnPlaybackProgress(new PlaybackProgressInfo
+                    {
+                        Item = _currentItem,
+                        SessionId = _session.Id,
+                        PositionTicks = _device.Position.Ticks,
+                        IsMuted = _device.IsMuted,
+                        IsPaused = _device.IsPaused
+
+                    }).ConfigureAwait(false);
+                }
+            }
+        }
+
+        #endregion
+
+        #region SendCommands
+
+        public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
+        {
+            _logger.Debug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
+
+            var items = new List<BaseItem>();
+            foreach (string id in command.ItemIds)
+            {
+                AddItemFromId(Guid.Parse(id), items);
+            }
+
+            var playlist = new List<PlaylistItem>();
+            var isFirst = true;
+
+            var serverAddress = GetServerAddress();
+
+            foreach (var item in items)
+            {
+                if (isFirst && command.StartPositionTicks.HasValue)
+                {
+                    playlist.Add(CreatePlaylistItem(item, command.StartPositionTicks.Value, serverAddress));
+                    isFirst = false;
+                }
+                else
+                {
+                    playlist.Add(CreatePlaylistItem(item, 0, serverAddress));
+                }
+            }
+
+            _logger.Debug("{0} - Playlist created", _session.DeviceName);
+
+            if (command.PlayCommand == PlayCommand.PlayLast)
+            {
+                AddItemsToPlaylist(playlist);
+                return Task.FromResult(true);
+            }
+            if (command.PlayCommand == PlayCommand.PlayNext)
+            {
+                AddItemsToPlaylist(playlist);
+                return Task.FromResult(true);
+            }
+
+            _logger.Debug("{0} - Playing {1} items", _session.DeviceName, playlist.Count);
+            return PlayItems(playlist);
+        }
+
+        public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
+        {
+            switch (command.Command)
+            {
+                case PlaystateCommand.Stop:
+                    Playlist.Clear();
+                    return _device.SetStop();
+
+                case PlaystateCommand.Pause:
+                    return _device.SetPause();
+
+                case PlaystateCommand.Unpause:
+                    return _device.SetPlay();
+
+                case PlaystateCommand.Seek:
+                    var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
+                    if (playlistItem != null && playlistItem.Transcode && playlistItem.IsVideo && _currentItem != null)
+                    {
+                        var newItem = CreatePlaylistItem(_currentItem, command.SeekPositionTicks ?? 0, GetServerAddress());
+                        playlistItem.StartPositionTicks = newItem.StartPositionTicks;
+                        playlistItem.StreamUrl = newItem.StreamUrl;
+                        playlistItem.Didl = newItem.Didl;
+                        return _device.SetAvTransport(playlistItem.StreamUrl, playlistItem.DlnaHeaders, playlistItem.Didl);
+
+                    }
+                    return _device.Seek(TimeSpan.FromTicks(command.SeekPositionTicks ?? 0));
+
+
+                case PlaystateCommand.NextTrack:
+                    _currentItem = null;
+                    return SetNext();
+
+                case PlaystateCommand.PreviousTrack:
+                    _currentItem = null;
+                    return SetPrevious();
+            }
+
+            return Task.FromResult(true);
+        }
+
+        public Task SendSystemCommand(SystemCommand command, CancellationToken cancellationToken)
+        {
+            switch (command)
+            {
+                case SystemCommand.VolumeDown:
+                    return _device.VolumeDown();
+                case SystemCommand.VolumeUp:
+                    return _device.VolumeUp();
+                case SystemCommand.Mute:
+                    return _device.VolumeDown(true);
+                case SystemCommand.Unmute:
+                    return _device.VolumeUp(true);
+                case SystemCommand.ToggleMute:
+                    return _device.ToggleMute();
+                default:
+                    return Task.FromResult(true);
+            }
+        }
+
+        public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken)
+        {
+            return Task.FromResult(true);
+        }
+
+        public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
+        {
+            return Task.FromResult(true);
+        }
+
+        public Task SendServerRestartNotification(CancellationToken cancellationToken)
+        {
+            return Task.FromResult(true);
+        }
+
+        public Task SendServerShutdownNotification(CancellationToken cancellationToken)
+        {
+            return Task.FromResult(true);
+        }
+
+        public Task SendBrowseCommand(BrowseRequest command, CancellationToken cancellationToken)
+        {
+            return Task.FromResult(true);
+        }
+
+        public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
+        {
+            return Task.FromResult(true);
+        }
+
+        public Task SendMessageCommand(MessageCommand command, CancellationToken cancellationToken)
+        {
+            return Task.FromResult(true);
+        }
+
+        #endregion
+
+        #region Playlist
+
+        private List<PlaylistItem> _playlist = new List<PlaylistItem>();
+
+        private List<PlaylistItem> Playlist
+        {
+            get
+            {
+                return _playlist;
+            }
+            set
+            {
+                _playlist = value;
+            }
+        }
+
+        private void AddItemFromId(Guid id, List<BaseItem> list)
+        {
+            var item = _libraryManager.GetItemById(id);
+            if (item.IsFolder)
+            {
+                foreach (var childId in _itemRepository.GetChildren(item.Id))
+                {
+                    AddItemFromId(childId, list);
+                }
+            }
+            else
+            {
+                if (item.MediaType == MediaType.Audio || item.MediaType == MediaType.Video)
+                {
+                    list.Add(item);
+                }
+            }
+        }
+
+        private string GetServerAddress()
+        {
+            return string.Format("{0}://{1}:{2}/mediabrowser",
+
+                "http",
+                _networkManager.GetLocalIpAddresses().FirstOrDefault() ?? "localhost",
+                "8096"
+                );
+        }
+
+        private PlaylistItem CreatePlaylistItem(BaseItem item, long startPostionTicks, string serverAddress)
+        {
+            var streams = _itemRepository.GetMediaStreams(new MediaStreamQuery { ItemId = item.Id }).ToList();
+
+            var playlistItem = PlaylistItem.GetBasicConfig(item, _transcodeSettings);
+            playlistItem.StartPositionTicks = startPostionTicks;
+
+            if (playlistItem.IsAudio)
+                playlistItem.StreamUrl = StreamHelper.GetAudioUrl(playlistItem, serverAddress);
+            else
+            {
+                playlistItem.StreamUrl = StreamHelper.GetVideoUrl(_device.Properties, playlistItem, streams, serverAddress);
+            }
+
+            var didl = DidlBuilder.Build(item, _session.UserId.ToString(), serverAddress, playlistItem.StreamUrl, streams);
+            playlistItem.Didl = didl;
+
+            var header = StreamHelper.GetDlnaHeaders(playlistItem);
+            playlistItem.DlnaHeaders = header;
+            return playlistItem;
+        }
+
+        /// <summary>
+        /// Plays the items.
+        /// </summary>
+        /// <param name="items">The items.</param>
+        /// <returns></returns>
+        private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items)
+        {
+            Playlist.Clear();
+            Playlist.AddRange(items);
+            await SetNext();
+            return true;
+        }
+
+        /// <summary>
+        /// Adds the items to playlist.
+        /// </summary>
+        /// <param name="items">The items.</param>
+        private void AddItemsToPlaylist(IEnumerable<PlaylistItem> items)
+        {
+            Playlist.AddRange(items);
+        }
+
+        private async Task<bool> SetNext()
+        {
+            if (!Playlist.Any() || Playlist.All(i => i.PlayState != 0))
+            {
+                return true;
+            }
+            var currentitem = Playlist.FirstOrDefault(i => i.PlayState == 1);
+
+            if (currentitem != null)
+            {
+                currentitem.PlayState = 2;
+            }
+
+            var nextTrack = Playlist.FirstOrDefault(i => i.PlayState == 0);
+            if (nextTrack == null)
+            {
+                await _device.SetStop();
+                return true;
+            }
+            nextTrack.PlayState = 1;
+            await _device.SetAvTransport(nextTrack.StreamUrl, nextTrack.DlnaHeaders, nextTrack.Didl);
+            if (nextTrack.StartPositionTicks > 0 && !nextTrack.Transcode)
+                await _device.Seek(TimeSpan.FromTicks(nextTrack.StartPositionTicks));
+            return true;
+        }
+
+        public Task<bool> SetPrevious()
+        {
+            if (!Playlist.Any() || Playlist.All(i => i.PlayState != 2))
+                return Task.FromResult(false);
+
+            var currentitem = Playlist.FirstOrDefault(i => i.PlayState == 1);
+
+            var prevTrack = Playlist.LastOrDefault(i => i.PlayState == 2);
+
+            if (currentitem != null)
+            {
+                currentitem.PlayState = 0;
+            }
+
+            if (prevTrack == null)
+                return Task.FromResult(false);
+
+            prevTrack.PlayState = 1;
+            return _device.SetAvTransport(prevTrack.StreamUrl, prevTrack.DlnaHeaders, prevTrack.Didl);
+        }
+
+        #endregion
+
+        private bool _disposed;
+
+        public void Dispose()
+        {
+            if (!_disposed)
+            {
+                _updateTimer.Stop();
+                _disposed = true;
+                _device.Dispose();
+                _logger.Log(LogSeverity.Debug, "PlayTo - Controller disposed");
+            }
+        }
+    }
+}

+ 31 - 0
MediaBrowser.Dlna/PlayTo/DlnaControllerFactory.cs

@@ -0,0 +1,31 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Logging;
+
+namespace MediaBrowser.Dlna.PlayTo
+{
+    public class PlayToControllerFactory : ISessionControllerFactory
+    {
+        private readonly ISessionManager _sessionManager;
+        private readonly IItemRepository _itemRepository;
+        private readonly ILibraryManager _libraryManager;
+        private readonly ILogger _logger;
+        private readonly INetworkManager _networkManager;
+
+        public PlayToControllerFactory(ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogManager logManager, INetworkManager networkManager)
+        {
+            _itemRepository = itemRepository;
+            _sessionManager = sessionManager;
+            _libraryManager = libraryManager;
+            _networkManager = networkManager;
+            _logger = logManager.GetLogger("PlayTo");
+        }
+
+        public ISessionController GetSessionController(SessionInfo session)
+        {
+            return null;
+        }
+    }
+}

+ 271 - 0
MediaBrowser.Dlna/PlayTo/PlayToManager.cs

@@ -0,0 +1,271 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Dlna.PlayTo.Configuration;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Collections.Concurrent;
+using System.Linq;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Dlna.PlayTo
+{
+    class PlayToManager : IDisposable
+    {
+        private bool _disposed = false;
+        private readonly ILogger _logger;
+        private readonly ISessionManager _sessionManager;
+        private readonly IHttpClient _httpClient;
+        private User _defualtUser;
+        private readonly CancellationTokenSource _tokenSource;
+        private ConcurrentDictionary<string, DateTime> _locations;
+
+        private readonly IItemRepository _itemRepository;
+        private readonly ILibraryManager _libraryManager;
+        private readonly INetworkManager _networkManager;
+        
+        public PlayToManager(ILogger logger, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, INetworkManager networkManager)
+        {
+            _locations = new ConcurrentDictionary<string, DateTime>();
+            _tokenSource = new CancellationTokenSource();
+
+            _logger = logger;
+            _sessionManager = sessionManager;
+            _httpClient = httpClient;
+            _itemRepository = itemRepository;
+            _libraryManager = libraryManager;
+            _networkManager = networkManager;
+        }
+
+        public async void Start(User defaultUser)
+        {
+            _defualtUser = defaultUser;
+            _logger.Log(LogSeverity.Info, "PlayTo-Manager starting");
+
+            _locations = new ConcurrentDictionary<string, DateTime>();
+
+            foreach (var network in NetworkInterface.GetAllNetworkInterfaces())
+            {
+                _logger.Debug("Found interface: {0}. Type: {1}. Status: {2}", network.Name, network.NetworkInterfaceType, network.OperationalStatus);
+
+                if (!network.SupportsMulticast || OperationalStatus.Up != network.OperationalStatus || !network.GetIPProperties().MulticastAddresses.Any())
+                    continue;
+
+                var ipV4 = network.GetIPProperties().GetIPv4Properties();
+                if (null == ipV4)
+                    continue;
+
+                IPAddress localIp = null;
+
+                foreach (UnicastIPAddressInformation ipInfo in network.GetIPProperties().UnicastAddresses)
+                {
+                    if (ipInfo.Address.AddressFamily == AddressFamily.InterNetwork)
+                    {
+                        localIp = ipInfo.Address;
+                        break;
+                    }
+                }
+
+                if (localIp == null)
+                {
+                    continue;
+                }
+
+                try
+                {
+                    CreateListener(localIp);
+                }
+                catch (Exception e)
+                {
+                    _logger.ErrorException("Failed to Initilize Socket", e);
+                }
+
+                await Task.Delay(100).ConfigureAwait(false);
+            }
+        }
+
+        public void Stop()
+        {
+        }
+
+        /// <summary>
+        /// Creates a socket for the interface and listends for data.
+        /// </summary>
+        /// <param name="localIp">The local ip.</param>
+        private void CreateListener(IPAddress localIp)
+        {
+            Task.Factory.StartNew(async (o) =>
+            {
+                try
+                {
+                    var socket = GetMulticastSocket();
+
+                    socket.Bind(new IPEndPoint(localIp, 0));
+
+                    _logger.Info("Creating SSDP listener");
+
+                    var receiveBuffer = new byte[64000];
+
+                    CreateNotifier(socket);
+
+                    while (!_tokenSource.IsCancellationRequested)
+                    {
+                        var receivedBytes = await socket.ReceiveAsync(receiveBuffer, 0, 64000);
+
+                        if (receivedBytes > 0)
+                        {
+                            var rawData = Encoding.UTF8.GetString(receiveBuffer, 0, receivedBytes);
+                            var uri = SsdpHelper.ParseSsdpResponse(rawData);
+
+                            TryCreateController(uri);
+                        }
+                    }
+
+                    _logger.Info("SSDP listener - Task completed");
+                }
+                catch (OperationCanceledException c)
+                {
+                }
+                catch (Exception e)
+                {
+                    _logger.ErrorException("Error in listener", e);
+                }
+
+            }, _tokenSource.Token, TaskCreationOptions.LongRunning);
+        }
+
+        private void TryCreateController(Uri uri)
+        {
+            Task.Run(async () =>
+            {
+                try
+                {
+                    await CreateController(uri).ConfigureAwait(false);
+                }
+                catch (OperationCanceledException c)
+                {
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error creating play to controller", ex);
+                }
+            });
+        }
+
+        private void CreateNotifier(Socket socket)
+        {
+            Task.Factory.StartNew(async (o) =>
+            {
+                try
+                {
+                    var request = SsdpHelper.CreateRendererSSDP(3);
+
+                    while (true)
+                    {
+                        socket.SendTo(request, new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900));
+
+                        await Task.Delay(10000).ConfigureAwait(false);
+                    }
+                }
+                catch (OperationCanceledException c)
+                {
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error in notifier", ex);
+                }
+
+            }, _tokenSource.Token, TaskCreationOptions.LongRunning);
+
+        }
+
+        /// <summary>
+        /// Gets a socket configured for SDDP multicasting.
+        /// </summary>
+        /// <returns></returns>
+        private Socket GetMulticastSocket()
+        {
+            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+            socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250")));
+            //socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 3);
+            return socket;
+        }
+
+        /// <summary>
+        /// Creates a new DlnaSessionController.
+        /// and logs the session in SessionManager
+        /// </summary>
+        /// <param name="uri">The URI.</param>
+        /// <returns></returns>
+        private async Task CreateController(Uri uri)
+        {
+            if (!IsUriValid(uri))
+                return;
+
+            var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _logger).ConfigureAwait(false);
+
+            if (device != null && device.RendererCommands != null && !_sessionManager.Sessions.Any(s => string.Equals(s.DeviceId, device.Properties.UUID) && s.IsActive))
+            {
+                var transcodeProfiles = TranscodeSettings.GetProfileSettings(device.Properties);
+
+                var sessionInfo = await _sessionManager.LogSessionActivity(device.Properties.ClientType, device.Properties.Name, device.Properties.UUID, device.Properties.DisplayName, uri.OriginalString, _defualtUser)
+                    .ConfigureAwait(false);
+
+                var controller = sessionInfo.SessionController as PlayToController;
+
+                if (controller == null)
+                {
+                    sessionInfo.SessionController = controller = new PlayToController(sessionInfo, _sessionManager, _itemRepository, _libraryManager, _logger, _networkManager);
+                }
+
+                controller.Init(device, transcodeProfiles);
+                
+                _logger.Info("DLNA Session created for {0} - {1}", device.Properties.Name, device.Properties.ModelName);
+            }
+        }
+
+        /// <summary>
+        /// Determines if the Uri is valid for further inspection or not.
+        /// (the limit for reinspection is 5 minutes)
+        /// </summary>
+        /// <param name="uri">The URI.</param>
+        /// <returns>Returns <b>True</b> if the Uri is valid for further inspection</returns>
+        private bool IsUriValid(Uri uri)
+        {
+            if (uri == null)
+                return false;
+
+            if (!_locations.ContainsKey(uri.OriginalString))
+            {
+                _locations.AddOrUpdate(uri.OriginalString, DateTime.UtcNow, (key, existingVal) => existingVal);
+
+                return true;
+            }
+
+            var time = _locations[uri.OriginalString];
+
+            if ((DateTime.UtcNow - time).TotalMinutes <= 5)
+            {
+                return false;
+            }
+            return _locations.TryUpdate(uri.OriginalString, DateTime.UtcNow, time);
+        }
+
+        public void Dispose()
+        {
+            if (!_disposed)
+            {
+                _disposed = true;
+                _tokenSource.Cancel();
+            }
+        }
+    }
+}

+ 69 - 0
MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs

@@ -0,0 +1,69 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Plugins;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Logging;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Dlna.PlayTo
+{
+    public class PlayToServerEntryPoint : IServerEntryPoint
+    {
+        const string DefaultUser = "Play To";
+
+        private bool _disposed;
+        
+        private readonly IUserManager _userManager;        
+        private readonly PlayToManager _manager;
+
+        public PlayToServerEntryPoint(ILogManager logManager, ISessionManager sessionManager, IUserManager userManager, IHttpClient httpClient, INetworkManager networkManager, IItemRepository itemRepository, ILibraryManager libraryManager)
+        {
+            _userManager = userManager;
+
+            _manager = new PlayToManager(logManager.GetLogger("PlayTo"), sessionManager, httpClient, itemRepository, libraryManager, networkManager);
+        }
+
+        /// <summary>
+        /// Creates the defaultuser if needed.
+        /// </summary>
+        private async Task<User> CreateUserIfNeeded()
+        {
+            var user = _userManager.Users.FirstOrDefault(u => u.Name == DefaultUser);
+
+            if (user == null)
+            {
+                user = await _userManager.CreateUser(DefaultUser);
+
+                user.Configuration.IsHidden = true;
+                user.Configuration.IsAdministrator = false;
+                user.SaveConfiguration();
+            }
+
+            return user;
+        }
+
+        public async void Run()
+        {
+            //var defaultUser = await CreateUserIfNeeded().ConfigureAwait(false);
+
+            //_manager.Start(defaultUser);            
+        }
+     
+        #region Dispose
+
+        public void Dispose()
+        {
+            if (!_disposed)
+            {
+                _disposed = true;
+                _manager.Stop();
+                _manager.Dispose();
+            }
+        }
+
+        #endregion
+    }
+}

+ 72 - 69
MediaBrowser.Dlna/PlayTo/PlaylistItem.cs

@@ -1,4 +1,7 @@
-
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Dlna.PlayTo.Configuration;
+using System;
+
 namespace MediaBrowser.Dlna.PlayTo
 {
     public class PlaylistItem
@@ -23,73 +26,73 @@ namespace MediaBrowser.Dlna.PlayTo
 
         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;
-        //}
+        public static PlaylistItem GetBasicConfig(BaseItem item, TranscodeSettings[] profileTranscodings)
+        {
+
+            var playlistItem = new PlaylistItem();
+            playlistItem.ItemId = item.Id.ToString();
+
+            if (string.Equals(item.MediaType, 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;
+        }
     }
 }

+ 188 - 0
MediaBrowser.Dlna/PlayTo/StreamHelper.cs

@@ -0,0 +1,188 @@
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+
+namespace MediaBrowser.Dlna.PlayTo
+{
+    class StreamHelper
+    {
+        /// <summary>
+        /// Gets the dlna headers.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns></returns>
+        internal static string GetDlnaHeaders(PlaylistItem item)
+        {
+            var orgOp = item.Transcode ? ";DLNA.ORG_OP=00" : ";DLNA.ORG_OP=01";
+
+            var orgCi = item.Transcode ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
+
+            const string dlnaflags = ";DLNA.ORG_FLAGS=01500000000000000000000000000000";
+
+            var contentFeatures = string.Empty;
+
+            if (string.Equals(item.FileFormat, "mp3", StringComparison.OrdinalIgnoreCase))
+            {
+                contentFeatures = "DLNA.ORG_PN=MP3";
+            }
+            else if (string.Equals(item.FileFormat, "wma", StringComparison.OrdinalIgnoreCase))
+            {
+                contentFeatures = "DLNA.ORG_PN=WMABASE";
+            }
+            else if (string.Equals(item.FileFormat, "avi", StringComparison.OrdinalIgnoreCase))
+            {
+                contentFeatures = "DLNA.ORG_PN=AVI";
+            }
+            else if (string.Equals(item.FileFormat, "mkv", StringComparison.OrdinalIgnoreCase))
+            {
+                contentFeatures = "DLNA.ORG_PN=MATROSKA";
+            }
+            else if (string.Equals(item.FileFormat, "mp4", StringComparison.OrdinalIgnoreCase))
+            {
+                contentFeatures = "DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC";
+            }
+            else if (string.Equals(item.FileFormat, "mpeg", StringComparison.OrdinalIgnoreCase))
+            {
+                contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL";
+            }
+            else if (string.Equals(item.FileFormat, "ts", StringComparison.OrdinalIgnoreCase))
+            {
+                contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL";
+            }
+            else if (item.IsVideo)
+            {
+                //Default to AVI for video
+                contentFeatures = "DLNA.ORG_PN=AVI";
+            }
+            else
+            {
+                //Default to MP3 for audio
+                contentFeatures = "DLNA.ORG_PN=MP3";
+            }
+
+            return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
+        }
+
+        #region Audio
+
+        /// <summary>
+        /// Gets the audio URL.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="serverAddress">The server address.</param>
+        /// <returns>System.String.</returns>
+        internal static string GetAudioUrl(PlaylistItem item, string serverAddress)
+        {
+            if (!item.Transcode)
+                return string.Format("{0}/audio/{1}/stream.{2}?Static=True", serverAddress, item.ItemId, item.FileFormat);
+
+            return string.Format("{0}/audio/{1}/stream.mp3?AudioCodec=Mp3", serverAddress, item.ItemId);
+        }
+
+        #endregion
+
+        #region Video
+
+        /// <summary>
+        /// Gets the video URL.
+        /// </summary>
+        /// <param name="deviceProperties">The device properties.</param>
+        /// <param name="item">The item.</param>
+        /// <param name="streams">The streams.</param>
+        /// <param name="serverAddress">The server address.</param>
+        /// <returns>The url to send to the device</returns>
+        internal static string GetVideoUrl(DeviceProperties deviceProperties, PlaylistItem item, List<MediaStream> streams, string serverAddress)
+        {
+            if (!item.Transcode)
+                return string.Format("{0}/Videos/{1}/stream.{2}?Static=True", serverAddress, item.ItemId, item.FileFormat);
+
+            var videostream = streams.Where(m => m.Type == MediaStreamType.Video).OrderBy(m => m.IsDefault).FirstOrDefault();
+            var audiostream = streams.Where(m => m.Type == MediaStreamType.Audio).OrderBy(m => m.IsDefault).FirstOrDefault();
+
+            var videoCodec = GetVideoCodec(videostream);
+            var audioCodec = GetAudioCodec(audiostream);
+            int? videoBitrate = null;
+            int? audioBitrate = null;
+            int? audioChannels = null;
+
+            if (videoCodec != VideoCodecs.Copy)
+                videoBitrate = 2000000;
+
+            if (audioCodec != AudioCodecs.Copy)
+            {
+                audioBitrate = 128000;
+                audioChannels = 2;
+            }
+
+            string dlnaCommand = BuildDlnaUrl(deviceProperties.UUID, videoCodec, audioCodec, null, null, videoBitrate, audioChannels, audioBitrate, item.StartPositionTicks, "baseline", "3");
+            return string.Format("{0}/Videos/{1}/stream.{2}?{3}", serverAddress, item.ItemId, item.FileFormat, dlnaCommand);
+        }
+
+        /// <summary>
+        /// Gets the video codec.
+        /// </summary>
+        /// <param name="videoStream">The video stream.</param>
+        /// <returns></returns>
+        private static VideoCodecs GetVideoCodec(MediaStream videoStream)
+        {
+            switch (videoStream.Codec.ToLower())
+            {
+                case "h264":
+                case "mpeg4":
+                    return VideoCodecs.Copy;
+
+            }
+            return VideoCodecs.H264;
+        }
+
+        /// <summary>
+        /// Gets the audio codec.
+        /// </summary>
+        /// <param name="audioStream">The audio stream.</param>
+        /// <returns></returns>
+        private static AudioCodecs GetAudioCodec(MediaStream audioStream)
+        {
+            if (audioStream != null)
+            {
+                switch (audioStream.Codec.ToLower())
+                {
+                    case "aac":
+                    case "mp3":
+                    case "wma":
+                        return AudioCodecs.Copy;
+
+                }
+            }
+            return AudioCodecs.Aac;
+        }
+
+        /// <summary>
+        /// Builds the dlna URL.
+        /// </summary>
+        private static string BuildDlnaUrl(string deviceID, VideoCodecs? videoCodec, AudioCodecs? audioCodec, int? subtitleIndex, int? audiostreamIndex, int? videoBitrate, int? audiochannels, int? audioBitrate, long? startPositionTicks, string profile, string videoLevel)
+        {
+            var usCulture = new CultureInfo("en-US");
+
+            var dlnaparam = string.Format("Params={0};", deviceID);
+
+            dlnaparam += videoCodec.HasValue ? videoCodec.Value + ";" : ";";
+            dlnaparam += audioCodec.HasValue ? audioCodec.Value + ";" : ";";
+            dlnaparam += audiostreamIndex.HasValue ? audiostreamIndex.Value.ToString(usCulture) + ";" : ";";
+            dlnaparam += subtitleIndex.HasValue ? subtitleIndex.Value.ToString(usCulture) + ";" : ";";
+            dlnaparam += videoBitrate.HasValue ? videoBitrate.Value.ToString(usCulture) + ";" : ";";
+            dlnaparam += audioBitrate.HasValue ? audioBitrate.Value.ToString(usCulture) + ";" : ";";
+            dlnaparam += audiochannels.HasValue ? audiochannels.Value.ToString(usCulture) + ";" : ";";
+            dlnaparam += startPositionTicks.HasValue ? startPositionTicks.Value.ToString(usCulture) + ";" : ";";
+            dlnaparam += profile + ";";
+            dlnaparam += videoLevel + ";";
+
+            return dlnaparam;
+        }
+
+        #endregion
+
+    }
+}

+ 2 - 2
MediaBrowser.Dlna/PlayTo/TransportCommands.cs

@@ -6,7 +6,7 @@ namespace MediaBrowser.Dlna.PlayTo
 {
     public class TransportCommands
     {
-        List<StateVariable> _stateVariables = new List<StateVariable>();
+        private List<StateVariable> _stateVariables = new List<StateVariable>();
         public List<StateVariable> StateVariables
         {
             get
@@ -19,7 +19,7 @@ namespace MediaBrowser.Dlna.PlayTo
             }
         }
 
-        List<ServiceAction> _serviceActions = new List<ServiceAction>();
+        private List<ServiceAction> _serviceActions = new List<ServiceAction>();
         public List<ServiceAction> ServiceActions
         {
             get

+ 4 - 0
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -27,6 +27,7 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Resolvers;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Dlna.PlayTo;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.System;
@@ -744,6 +745,9 @@ namespace MediaBrowser.ServerApplication
             // Server implementations
             list.Add(typeof(ServerApplicationPaths).Assembly);
 
+            // Dlna implementations
+            list.Add(typeof(PlayToServerEntryPoint).Assembly);
+            
             list.AddRange(Assemblies.GetAssembliesWithParts());
 
             // Include composable parts in the running assembly