Browse Source

add basic dlna server browsing

Luke Pulverenti 11 years ago
parent
commit
7f320ce063
38 changed files with 1367 additions and 859 deletions
  1. 12 3
      MediaBrowser.Api/Dlna/DlnaServerService.cs
  2. 10 40
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  3. 4 0
      MediaBrowser.Controller/Dlna/ControlRequest.cs
  4. 4 0
      MediaBrowser.Controller/Entities/Audio/Audio.cs
  5. 4 0
      MediaBrowser.Controller/Entities/Video.cs
  6. 16 2
      MediaBrowser.Dlna/DlnaManager.cs
  7. 0 1
      MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
  8. 1 0
      MediaBrowser.Dlna/PlayTo/DidlBuilder.cs
  9. 64 40
      MediaBrowser.Dlna/PlayTo/DlnaController.cs
  10. 5 3
      MediaBrowser.Dlna/PlayTo/PlayToManager.cs
  11. 16 2
      MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs
  12. 1 34
      MediaBrowser.Dlna/PlayTo/PlaylistItem.cs
  13. 11 428
      MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs
  14. 0 69
      MediaBrowser.Dlna/PlayTo/StreamHelper.cs
  15. 528 208
      MediaBrowser.Dlna/Server/ControlHandler.cs
  16. 1 1
      MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs
  17. 11 1
      MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs
  18. 1 1
      MediaBrowser.Dlna/Server/SsdpHandler.cs
  19. 1 1
      MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
  20. 1 1
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  21. 9 0
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  22. 9 0
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  23. 62 0
      MediaBrowser.Model/Dlna/DlnaMaps.cs
  24. 122 0
      MediaBrowser.Model/Dlna/MediaFormatProfile.cs
  25. 342 0
      MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
  26. 6 2
      MediaBrowser.Model/Dlna/StreamBuilder.cs
  27. 4 0
      MediaBrowser.Model/Dlna/StreamInfo.cs
  28. 9 0
      MediaBrowser.Model/Dto/MediaVersionInfo.cs
  29. 12 0
      MediaBrowser.Model/Entities/BaseItemInfo.cs
  30. 3 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  31. 13 0
      MediaBrowser.Model/Session/PlaybackReports.cs
  32. 6 0
      MediaBrowser.Model/Session/SessionInfoDto.cs
  33. 20 2
      MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
  34. 24 0
      MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
  35. 21 9
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  36. 0 9
      MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
  37. 13 1
      MediaBrowser.Server.Implementations/Session/SessionManager.cs
  38. 1 1
      MediaBrowser.ServerApplication/ApplicationHost.cs

+ 12 - 3
MediaBrowser.Api/Dlna/DlnaServerService.cs

@@ -1,5 +1,7 @@
-using MediaBrowser.Controller.Dlna;
+using System;
+using MediaBrowser.Controller.Dlna;
 using ServiceStack;
+using ServiceStack.Text.Controller;
 using ServiceStack.Web;
 using System.Collections.Generic;
 using System.IO;
@@ -21,9 +23,12 @@ namespace MediaBrowser.Api.Dlna
     {
     }
 
-    [Route("/Dlna/control", "POST", Summary = "Processes a control request")]
+    [Route("/Dlna/{UuId}/control", "POST", Summary = "Processes a control request")]
     public class ProcessControlRequest : IRequiresRequestStream
     {
+        [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string UuId { get; set; }
+        
         public Stream RequestStream { get; set; }
     }
 
@@ -66,12 +71,16 @@ namespace MediaBrowser.Api.Dlna
 
         private async Task<ControlResponse> PostAsync(ProcessControlRequest request)
         {
+            var pathInfo = PathInfo.Parse(Request.PathInfo);
+            var id = pathInfo.GetArgumentValue<string>(1);
+            
             using (var reader = new StreamReader(request.RequestStream))
             {
                 return _dlnaManager.ProcessControlRequest(new ControlRequest
                 {
                     Headers = GetRequestHeaders(),
-                    InputXml = await reader.ReadToEndAsync().ConfigureAwait(false)
+                    InputXml = await reader.ReadToEndAsync().ConfigureAwait(false),
+                    TargetServerUuId = id
                 });
             }
         }

+ 10 - 40
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -300,7 +300,7 @@ namespace MediaBrowser.Api.Playback
                 case EncodingQuality.HighSpeed:
                     return 2;
                 case EncodingQuality.HighQuality:
-                    return isWebm ? Math.Max((int)((Environment.ProcessorCount -1) / 2) , 2) : 0;
+                    return 2;
                 case EncodingQuality.MaxQuality:
                     return isWebm ? Math.Max(Environment.ProcessorCount - 1, 2) : 0;
                 default:
@@ -373,7 +373,6 @@ namespace MediaBrowser.Api.Playback
                         break;
                     case EncodingQuality.MaxQuality:
                         crf = "4";
-                        //profilescore aready set to 0
                         break;
                     default:
                         throw new ArgumentException("Unrecognized quality setting");
@@ -381,7 +380,9 @@ namespace MediaBrowser.Api.Playback
 
                 if (isVc1)
                 {
-                    profileScore = 1;
+                    profileScore ++;
+                    // Max of 2
+                    profileScore = Math.Min(profileScore, 2);
                 }
 
                 // http://www.webmproject.org/docs/encoder-parameters/
@@ -1713,33 +1714,19 @@ namespace MediaBrowser.Api.Playback
             var extension = GetOutputFileExtension(state);
 
             // first bit means Time based seek supported, second byte range seek supported (not sure about the order now), so 01 = only byte seek, 10 = time based, 11 = both, 00 = none
-            var orgOp = ";DLNA.ORG_OP=";
-
-            if (state.RunTimeTicks.HasValue)
-            {
-                // Time-based seeking currently only possible when transcoding
-                orgOp += isStaticallyStreamed ? "0" : "1";
+            var orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetOrgOpValue(state.RunTimeTicks.HasValue, isStaticallyStreamed, state.TranscodeSeekInfo);
 
-                // Byte-based seeking only possible when not transcoding
-                orgOp += isStaticallyStreamed || state.TranscodeSeekInfo == TranscodeSeekInfo.Bytes ? "1" : "0";
-
-                if (!isStaticallyStreamed)
-                {
-                    AddTimeSeekResponseHeaders(state, responseHeaders);
-                }
-            }
-            else
+            if (state.RunTimeTicks.HasValue && !isStaticallyStreamed)
             {
-                // No seeking is available if we don't know the content runtime
-                orgOp += "00";
+                AddTimeSeekResponseHeaders(state, responseHeaders);
             }
 
             // 0 = native, 1 = transcoded
             var orgCi = isStaticallyStreamed ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
 
-            var flagValue = DlnaFlags.DLNA_ORG_FLAG_STREAMING_TRANSFER_MODE |
-                            DlnaFlags.DLNA_ORG_FLAG_BACKGROUND_TRANSFERT_MODE |
-                            DlnaFlags.DLNA_ORG_FLAG_DLNA_V15;
+            var flagValue = DlnaFlags.StreamingTransferMode |
+                            DlnaFlags.BackgroundTransferMode |
+                            DlnaFlags.DlnaV15;
 
             if (isStaticallyStreamed)
             {
@@ -1801,23 +1788,6 @@ namespace MediaBrowser.Api.Playback
             }
         }
 
-        [Flags]
-        private enum DlnaFlags
-        {
-            DLNA_ORG_FLAG_SENDER_PACED = (1 << 31),
-            DLNA_ORG_FLAG_TIME_BASED_SEEK = (1 << 30),
-            DLNA_ORG_FLAG_BYTE_BASED_SEEK = (1 << 29),
-            DLNA_ORG_FLAG_PLAY_CONTAINER = (1 << 28),
-            DLNA_ORG_FLAG_S0_INCREASE = (1 << 27),
-            DLNA_ORG_FLAG_SN_INCREASE = (1 << 26),
-            DLNA_ORG_FLAG_RTSP_PAUSE = (1 << 25),
-            DLNA_ORG_FLAG_STREAMING_TRANSFER_MODE = (1 << 24),
-            DLNA_ORG_FLAG_INTERACTIVE_TRANSFERT_MODE = (1 << 23),
-            DLNA_ORG_FLAG_BACKGROUND_TRANSFERT_MODE = (1 << 22),
-            DLNA_ORG_FLAG_CONNECTION_STALL = (1 << 21),
-            DLNA_ORG_FLAG_DLNA_V15 = (1 << 20),
-        };
-
         private void AddTimeSeekResponseHeaders(StreamState state, IDictionary<string, string> responseHeaders)
         {
             var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(UsCulture);

+ 4 - 0
MediaBrowser.Controller/Dlna/ControlRequest.cs

@@ -8,6 +8,8 @@ namespace MediaBrowser.Controller.Dlna
 
         public string InputXml { get; set; }
 
+        public string TargetServerUuId { get; set; }
+
         public ControlRequest()
         {
             Headers = new Dictionary<string, string>();
@@ -20,6 +22,8 @@ namespace MediaBrowser.Controller.Dlna
 
         public string Xml { get; set; }
 
+        public bool IsSuccessful { get; set; }
+
         public ControlResponse()
         {
             Headers = new Dictionary<string, string>();

+ 4 - 0
MediaBrowser.Controller/Entities/Audio/Audio.cs

@@ -12,6 +12,10 @@ namespace MediaBrowser.Controller.Entities.Audio
     /// </summary>
     public class Audio : BaseItem, IHasMediaStreams, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<SongInfo>
     {
+        public string FormatName { get; set; }
+        public long? Size { get; set; }
+        public string Container { get; set; }
+
         public Audio()
         {
             Artists = new List<string>();

+ 4 - 0
MediaBrowser.Controller/Entities/Video.cs

@@ -26,6 +26,10 @@ namespace MediaBrowser.Controller.Entities
         public List<Guid> AdditionalPartIds { get; set; }
         public List<Guid> LocalAlternateVersionIds { get; set; }
 
+        public string FormatName { get; set; }
+        public long? Size { get; set; }
+        public string Container { get; set; }
+
         public Video()
         {
             PlayableStreamFileNames = new List<string>();

+ 16 - 2
MediaBrowser.Dlna/DlnaManager.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Dlna.Profiles;
 using MediaBrowser.Dlna.Server;
@@ -27,8 +28,11 @@ namespace MediaBrowser.Dlna
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IUserManager _userManager;
         private readonly ILibraryManager _libraryManager;
+        private readonly IDtoService _dtoService;
+        private readonly IImageProcessor _imageProcessor;
+        private readonly IUserDataManager _userDataManager;
 
-        public DlnaManager(IXmlSerializer xmlSerializer, IFileSystem fileSystem, IApplicationPaths appPaths, ILogger logger, IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager)
+        public DlnaManager(IXmlSerializer xmlSerializer, IFileSystem fileSystem, IApplicationPaths appPaths, ILogger logger, IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager)
         {
             _xmlSerializer = xmlSerializer;
             _fileSystem = fileSystem;
@@ -37,6 +41,9 @@ namespace MediaBrowser.Dlna
             _jsonSerializer = jsonSerializer;
             _userManager = userManager;
             _libraryManager = libraryManager;
+            _dtoService = dtoService;
+            _imageProcessor = imageProcessor;
+            _userDataManager = userDataManager;
 
             //DumpProfiles();
         }
@@ -502,7 +509,14 @@ namespace MediaBrowser.Dlna
 
         public ControlResponse ProcessControlRequest(ControlRequest request)
         {
-            return new ControlHandler(_logger, _userManager, _libraryManager)
+            var profile = GetProfile(request.Headers)
+                          ?? GetDefaultProfile();
+
+            var device = DlnaServerEntryPoint.Instance.GetServerUpnpDevice(request.TargetServerUuId);
+
+            var serverAddress = device.Descriptor.ToString().Substring(0, device.Descriptor.ToString().IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
+
+            return new ControlHandler(_logger, _userManager, _libraryManager, profile, serverAddress, _dtoService, _imageProcessor, _userDataManager)
                 .ProcessControlRequest(request);
         }
 

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

@@ -81,7 +81,6 @@
     <Compile Include="Ssdp\SsdpHelper.cs" />
     <Compile Include="PlayTo\SsdpHttpClient.cs" />
     <Compile Include="Common\StateVariable.cs" />
-    <Compile Include="PlayTo\StreamHelper.cs" />
     <Compile Include="PlayTo\TransportCommands.cs" />
     <Compile Include="PlayTo\TransportStateEventArgs.cs" />
     <Compile Include="PlayTo\uBaseObject.cs" />

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

@@ -39,6 +39,7 @@ namespace MediaBrowser.Dlna.PlayTo
         /// <param name="serverAddress">The server address.</param>
         /// <param name="streamUrl">The stream URL.</param>
         /// <param name="streams">The streams.</param>
+        /// <param name="includeImageRes">if set to <c>true</c> [include image resource].</param>
         /// <returns>System.String.</returns>
         public static string Build(BaseItem dto, string userId, string serverAddress, string streamUrl, IEnumerable<MediaStream> streams, bool includeImageRes)
         {

+ 64 - 40
MediaBrowser.Dlna/PlayTo/DlnaController.cs

@@ -1,12 +1,14 @@
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Session;
@@ -32,6 +34,7 @@ namespace MediaBrowser.Dlna.PlayTo
         private readonly IDlnaManager _dlnaManager;
         private readonly IUserManager _userManager;
         private readonly IServerApplicationHost _appHost;
+        private readonly IDtoService _dtoService;
         private bool _playbackStarted;
 
         private const int UpdateTimerIntervalMs = 1000;
@@ -52,7 +55,7 @@ namespace MediaBrowser.Dlna.PlayTo
             }
         }
 
-        public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager, IUserManager userManager, IServerApplicationHost appHost)
+        public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager, IUserManager userManager, IServerApplicationHost appHost, IDtoService dtoService)
         {
             _session = session;
             _itemRepository = itemRepository;
@@ -62,6 +65,7 @@ namespace MediaBrowser.Dlna.PlayTo
             _dlnaManager = dlnaManager;
             _userManager = userManager;
             _appHost = appHost;
+            _dtoService = dtoService;
             _logger = logger;
         }
 
@@ -172,20 +176,23 @@ namespace MediaBrowser.Dlna.PlayTo
 
             if (playlistItem != null)
             {
+                var streamInfo = playlistItem.StreamInfo;
+
                 if (!_playbackStarted)
                 {
                     await _sessionManager.OnPlaybackStart(new PlaybackStartInfo
                     {
                         ItemId = _currentItem.Id.ToString("N"),
                         SessionId = _session.Id,
-                        CanSeek = true,
+                        CanSeek = streamInfo.RunTimeTicks.HasValue,
                         QueueableMediaTypes = new List<string> { _currentItem.MediaType },
-                        MediaSourceId = playlistItem.MediaSourceId,
-                        AudioStreamIndex = playlistItem.AudioStreamIndex,
-                        SubtitleStreamIndex = playlistItem.SubtitleStreamIndex,
+                        MediaSourceId = streamInfo.MediaSourceId,
+                        AudioStreamIndex = streamInfo.AudioStreamIndex,
+                        SubtitleStreamIndex = streamInfo.SubtitleStreamIndex,
                         IsMuted = _device.IsMuted,
                         IsPaused = _device.IsPaused,
-                        VolumeLevel = _device.Volume
+                        VolumeLevel = _device.Volume,
+                        PlayMethod = streamInfo.IsDirectStream ? PlayMethod.DirectStream : PlayMethod.Transcode
 
                     }).ConfigureAwait(false);
 
@@ -196,9 +203,9 @@ namespace MediaBrowser.Dlna.PlayTo
                 {
                     var ticks = _device.Position.Ticks;
 
-                    if (playlistItem.Transcode)
+                    if (!streamInfo.IsDirectStream)
                     {
-                        ticks += playlistItem.StartPositionTicks;
+                        ticks += streamInfo.StartPositionTicks;
                     }
 
                     await _sessionManager.OnPlaybackProgress(new PlaybackProgressInfo
@@ -208,11 +215,12 @@ namespace MediaBrowser.Dlna.PlayTo
                         PositionTicks = ticks,
                         IsMuted = _device.IsMuted,
                         IsPaused = _device.IsPaused,
-                        MediaSourceId = playlistItem.MediaSourceId,
-                        AudioStreamIndex = playlistItem.AudioStreamIndex,
-                        SubtitleStreamIndex = playlistItem.SubtitleStreamIndex,
+                        MediaSourceId = streamInfo.MediaSourceId,
+                        AudioStreamIndex = streamInfo.AudioStreamIndex,
+                        SubtitleStreamIndex = streamInfo.SubtitleStreamIndex,
                         VolumeLevel = _device.Volume,
-                        CanSeek = true
+                        CanSeek = streamInfo.RunTimeTicks.HasValue,
+                        PlayMethod = streamInfo.IsDirectStream ? PlayMethod.DirectStream : PlayMethod.Transcode
 
                     }).ConfigureAwait(false);
                 }
@@ -411,46 +419,43 @@ namespace MediaBrowser.Dlna.PlayTo
 
         private PlaylistItem CreatePlaylistItem(BaseItem item, long startPostionTicks, string serverAddress)
         {
-            var streams = _itemRepository.GetMediaStreams(new MediaStreamQuery
-            {
-                ItemId = item.Id
-
-            }).ToList();
-
             var deviceInfo = _device.Properties;
 
             var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ??
                 _dlnaManager.GetDefaultProfile();
 
-            var playlistItem = GetPlaylistItem(item, streams, profile);
-            playlistItem.StartPositionTicks = startPostionTicks;
-            playlistItem.DeviceProfileId = profile.Id;
+            var mediaSources = item is Audio || item is Video
+                ? _dtoService.GetMediaSources(item)
+                : new List<MediaSourceInfo>();
 
-            if (playlistItem.MediaType == DlnaProfileType.Audio)
-            {
-                playlistItem.StreamUrl = StreamHelper.GetAudioUrl(deviceInfo, playlistItem, streams, serverAddress);
-            }
-            else
-            {
-                playlistItem.StreamUrl = StreamHelper.GetVideoUrl(_device.Properties, playlistItem, streams, serverAddress);
-            }
+            var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId);
+            playlistItem.StreamInfo.StartPositionTicks = startPostionTicks;
+
+            playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(serverAddress);
 
-            playlistItem.Didl = DidlBuilder.Build(item, _session.UserId.ToString(), serverAddress, playlistItem.StreamUrl, streams, profile.EnableAlbumArtInDidl);
+            var mediaStreams = mediaSources
+                .Where(i => string.Equals(i.Id, playlistItem.StreamInfo.MediaSourceId))
+                .SelectMany(i => i.MediaStreams)
+                .ToList();
+
+            playlistItem.Didl = DidlBuilder.Build(item, _session.UserId.ToString(), serverAddress, playlistItem.StreamUrl, mediaStreams, profile.EnableAlbumArtInDidl);
 
             return playlistItem;
         }
 
         private string GetDlnaHeaders(PlaylistItem item)
         {
-            var orgOp = item.Transcode ? ";DLNA.ORG_OP=00" : ";DLNA.ORG_OP=01";
+            var streamInfo = item.StreamInfo;
+
+            var orgOp = !streamInfo.IsDirectStream ? ";DLNA.ORG_OP=00" : ";DLNA.ORG_OP=01";
 
-            var orgCi = item.Transcode ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
+            var orgCi = !streamInfo.IsDirectStream ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
 
             const string dlnaflags = ";DLNA.ORG_FLAGS=01500000000000000000000000000000";
 
             string contentFeatures;
 
-            var container = item.Container.TrimStart('.');
+            var container = streamInfo.Container.TrimStart('.');
 
             if (string.Equals(container, "mp3", StringComparison.OrdinalIgnoreCase))
             {
@@ -488,7 +493,7 @@ namespace MediaBrowser.Dlna.PlayTo
             {
                 contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL";
             }
-            else if (item.MediaType == DlnaProfileType.Video)
+            else if (streamInfo.MediaType == DlnaProfileType.Video)
             {
                 // Default to AVI for video
                 contentFeatures = "DLNA.ORG_PN=AVI";
@@ -502,20 +507,38 @@ namespace MediaBrowser.Dlna.PlayTo
             return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
         }
 
-        private PlaylistItem GetPlaylistItem(BaseItem item, List<MediaStream> mediaStreams, DeviceProfile profile)
+        private PlaylistItem GetPlaylistItem(BaseItem item, List<MediaSourceInfo> mediaSources, DeviceProfile profile, string deviceId)
         {
             var video = item as Video;
 
             if (video != null)
             {
-                return new PlaylistItemFactory().Create(video, mediaStreams, profile);
+                return new PlaylistItem
+                {
+                    StreamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
+                    {
+                        ItemId = item.Id.ToString("N"),
+                        MediaSources = mediaSources,
+                        Profile = profile,
+                        DeviceId = deviceId
+                    })
+                };
             }
 
             var audio = item as Audio;
 
             if (audio != null)
             {
-                return new PlaylistItemFactory().Create(audio, mediaStreams, profile);
+                return new PlaylistItem
+                {
+                    StreamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions
+                    {
+                        ItemId = item.Id.ToString("N"),
+                        MediaSources = mediaSources,
+                        Profile = profile,
+                        DeviceId = deviceId
+                    })
+                };
             }
 
             var photo = item as Photo;
@@ -578,8 +601,9 @@ namespace MediaBrowser.Dlna.PlayTo
 
             await _device.SetAvTransport(nextTrack.StreamUrl, dlnaheaders, nextTrack.Didl);
 
-            if (nextTrack.StartPositionTicks > 0 && !nextTrack.Transcode)
-                await _device.Seek(TimeSpan.FromTicks(nextTrack.StartPositionTicks));
+            var streamInfo = nextTrack.StreamInfo;
+            if (streamInfo.StartPositionTicks > 0 && streamInfo.IsDirectStream)
+                await _device.Seek(TimeSpan.FromTicks(streamInfo.StartPositionTicks));
 
             return true;
         }
@@ -602,7 +626,7 @@ namespace MediaBrowser.Dlna.PlayTo
                 return Task.FromResult(false);
 
             prevTrack.PlayState = 1;
-            return _device.SetAvTransport(prevTrack.StreamUrl, GetDlnaHeaders(prevTrack), prevTrack.Didl);
+            return _device.SetAvTransport(prevTrack.StreamInfo.ToDlnaUrl(GetServerAddress()), GetDlnaHeaders(prevTrack), prevTrack.Didl);
         }
 
         #endregion

+ 5 - 3
MediaBrowser.Dlna/PlayTo/PlayToManager.cs

@@ -2,11 +2,11 @@
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Dlna.Ssdp;
-using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Session;
 using System;
@@ -37,8 +37,9 @@ namespace MediaBrowser.Dlna.PlayTo
         private readonly IDlnaManager _dlnaManager;
         private readonly IServerConfigurationManager _config;
         private readonly IServerApplicationHost _appHost;
+        private readonly IDtoService _dtoService;
 
-        public PlayToManager(ILogger logger, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost)
+        public PlayToManager(ILogger logger, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IDtoService dtoService)
         {
             _locations = new ConcurrentDictionary<string, DateTime>();
             _tokenSource = new CancellationTokenSource();
@@ -52,6 +53,7 @@ namespace MediaBrowser.Dlna.PlayTo
             _userManager = userManager;
             _dlnaManager = dlnaManager;
             _appHost = appHost;
+            _dtoService = dtoService;
             _config = config;
         }
 
@@ -248,7 +250,7 @@ namespace MediaBrowser.Dlna.PlayTo
 
                 if (controller == null)
                 {
-                    sessionInfo.SessionController = controller = new PlayToController(sessionInfo, _sessionManager, _itemRepository, _libraryManager, _logger, _networkManager, _dlnaManager, _userManager, _appHost);
+                    sessionInfo.SessionController = controller = new PlayToController(sessionInfo, _sessionManager, _itemRepository, _libraryManager, _logger, _networkManager, _dlnaManager, _userManager, _appHost, _dtoService);
 
                     controller.Init(device);
 

+ 16 - 2
MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Plugins;
@@ -24,8 +25,9 @@ namespace MediaBrowser.Dlna.PlayTo
         private readonly IUserManager _userManager;
         private readonly IDlnaManager _dlnaManager;
         private readonly IServerApplicationHost _appHost;
+        private readonly IDtoService _dtoService;
 
-        public PlayToServerEntryPoint(ILogManager logManager, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost)
+        public PlayToServerEntryPoint(ILogManager logManager, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IDtoService dtoService)
         {
             _config = config;
             _sessionManager = sessionManager;
@@ -36,6 +38,7 @@ namespace MediaBrowser.Dlna.PlayTo
             _userManager = userManager;
             _dlnaManager = dlnaManager;
             _appHost = appHost;
+            _dtoService = dtoService;
             _logger = logManager.GetLogger("PlayTo");
         }
 
@@ -72,7 +75,18 @@ namespace MediaBrowser.Dlna.PlayTo
             {
                 try
                 {
-                    _manager = new PlayToManager(_logger, _config, _sessionManager, _httpClient, _itemRepo, _libraryManager, _networkManager, _userManager, _dlnaManager, _appHost);
+                    _manager = new PlayToManager(_logger, 
+                        _config, 
+                        _sessionManager, 
+                        _httpClient, 
+                        _itemRepo, 
+                        _libraryManager, 
+                        _networkManager, 
+                        _userManager, 
+                        _dlnaManager, 
+                        _appHost, 
+                        _dtoService);
+
                     _manager.Start();
                 }
                 catch (Exception ex)

+ 1 - 34
MediaBrowser.Dlna/PlayTo/PlaylistItem.cs

@@ -4,45 +4,12 @@ namespace MediaBrowser.Dlna.PlayTo
 {
     public class PlaylistItem
     {
-        public string ItemId { get; set; }
-
-        public string MediaSourceId { get; set; }
-        
-        public bool Transcode { get; set; }
-
-        public DlnaProfileType MediaType { get; set; }
-
-        public string Container { get; set; }
-
         public int PlayState { get; set; }
 
         public string StreamUrl { get; set; }
 
         public string Didl { get; set; }
 
-        public long StartPositionTicks { get; set; }
-
-        public string VideoCodec { get; set; }
-
-        public string AudioCodec { get; set; }
-
-        public int? AudioStreamIndex { get; set; }
-
-        public int? SubtitleStreamIndex { get; set; }
-
-        public int? MaxAudioChannels { get; set; }
-
-        public int? AudioBitrate { get; set; }
-
-        public int? VideoBitrate { get; set; }
-
-        public int? VideoLevel { get; set; }
-
-        public int? MaxWidth { get; set; }
-        public int? MaxHeight { get; set; }
-
-        public int? MaxFramerate { get; set; }
-
-        public string DeviceProfileId { get; set; }
+        public StreamInfo StreamInfo { get; set; }
     }
 }

+ 11 - 428
MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs

@@ -1,10 +1,6 @@
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Entities;
 using System;
-using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
 using System.Linq;
@@ -15,235 +11,40 @@ namespace MediaBrowser.Dlna.PlayTo
     {
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
 
-        public PlaylistItem Create(Audio item, List<MediaStream> mediaStreams, DeviceProfile profile)
+        public PlaylistItem Create(Photo item, DeviceProfile profile)
         {
             var playlistItem = new PlaylistItem
             {
-                ItemId = item.Id.ToString("N"),
-                MediaType = DlnaProfileType.Audio
-            };
-
-            var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
-
-            var directPlay = profile.DirectPlayProfiles
-                .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(i, item, audioStream));
-
-            if (directPlay != null)
-            {
-                var audioCodec = audioStream == null ? null : audioStream.Codec;
-
-                // Make sure audio codec profiles are satisfied
-                if (!string.IsNullOrEmpty(audioCodec) && profile.CodecProfiles.Where(i => i.Type == CodecType.Audio && i.ContainsCodec(audioCodec))
-                    .All(i => AreConditionsSatisfied(i.Conditions, item.Path, null, audioStream)))
+                StreamInfo = new StreamInfo
                 {
-                    playlistItem.Transcode = false;
-                    playlistItem.Container = Path.GetExtension(item.Path);
-
-                    return playlistItem;
+                    ItemId = item.Id.ToString("N"),
+                    MediaType = DlnaProfileType.Photo,
                 }
-            }
-
-            var transcodingProfile = profile.TranscodingProfiles
-                .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(profile, i, item));
-
-            if (transcodingProfile != null)
-            {
-                playlistItem.Transcode = true;
-                playlistItem.Container = "." + transcodingProfile.Container.TrimStart('.');
-                playlistItem.AudioCodec = transcodingProfile.AudioCodec;
-
-                var audioTranscodingConditions = profile.CodecProfiles
-                    .Where(i => i.Type == CodecType.Audio && i.ContainsCodec(transcodingProfile.AudioCodec))
-                    .Take(1)
-                    .SelectMany(i => i.Conditions);
-
-                ApplyTranscodingConditions(playlistItem, audioTranscodingConditions);
-            }
-
-            return playlistItem;
-        }
-
-        public PlaylistItem Create(Photo item, DeviceProfile profile)
-        {
-            var playlistItem = new PlaylistItem
-            {
-                ItemId = item.Id.ToString("N"),
-                MediaType = DlnaProfileType.Photo
             };
 
             var directPlay = profile.DirectPlayProfiles
-                .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(i, item));
+                .FirstOrDefault(i => i.Type == DlnaProfileType.Photo && IsSupported(i, item));
 
             if (directPlay != null)
             {
-                playlistItem.Transcode = false;
-                playlistItem.Container = Path.GetExtension(item.Path);
+                playlistItem.StreamInfo.IsDirectStream = true;
+                playlistItem.StreamInfo.Container = Path.GetExtension(item.Path);
 
                 return playlistItem;
             }
 
             var transcodingProfile = profile.TranscodingProfiles
-                .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(profile, i, item));
+                .FirstOrDefault(i => i.Type == DlnaProfileType.Photo);
 
             if (transcodingProfile != null)
             {
-                playlistItem.Transcode = true;
-                playlistItem.Container = "." + transcodingProfile.Container.TrimStart('.');
+                playlistItem.StreamInfo.IsDirectStream = true;
+                playlistItem.StreamInfo.Container = "." + transcodingProfile.Container.TrimStart('.');
             }
 
             return playlistItem;
         }
 
-        public PlaylistItem Create(Video item, List<MediaStream> mediaStreams, DeviceProfile profile)
-        {
-            var playlistItem = new PlaylistItem
-            {
-                ItemId = item.Id.ToString("N"),
-                MediaType = DlnaProfileType.Video
-            };
-
-            var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
-            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
-
-            var directPlay = profile.DirectPlayProfiles
-                .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(i, item, videoStream, audioStream));
-
-            if (directPlay != null)
-            {
-                var videoCodec = videoStream == null ? null : videoStream.Codec;
-
-                // Make sure video codec profiles are satisfied
-                if (!string.IsNullOrEmpty(videoCodec) && profile.CodecProfiles.Where(i => i.Type == CodecType.Video && i.ContainsCodec(videoCodec))
-                    .All(i => AreConditionsSatisfied(i.Conditions, item.Path, videoStream, audioStream)))
-                {
-                    var audioCodec = audioStream == null ? null : audioStream.Codec;
-
-                    // Make sure audio codec profiles are satisfied
-                    if (string.IsNullOrEmpty(audioCodec) || profile.CodecProfiles.Where(i => i.Type == CodecType.VideoAudio && i.ContainsCodec(audioCodec))
-                        .All(i => AreConditionsSatisfied(i.Conditions, item.Path, videoStream, audioStream)))
-                    {
-                        playlistItem.Transcode = false;
-                        playlistItem.Container = Path.GetExtension(item.Path);
-
-                        return playlistItem;
-                    }
-                }
-            }
-
-            var transcodingProfile = profile.TranscodingProfiles
-                .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(profile, i, item));
-
-            if (transcodingProfile != null)
-            {
-                playlistItem.Transcode = true;
-                playlistItem.Container = "." + transcodingProfile.Container.TrimStart('.');
-                playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',').FirstOrDefault();
-                playlistItem.VideoCodec = transcodingProfile.VideoCodec;
-
-                var videoTranscodingConditions = profile.CodecProfiles
-                    .Where(i => i.Type == CodecType.Video && i.ContainsCodec(transcodingProfile.VideoCodec))
-                    .Take(1)
-                    .SelectMany(i => i.Conditions);
-
-                ApplyTranscodingConditions(playlistItem, videoTranscodingConditions);
-
-                var audioTranscodingConditions = profile.CodecProfiles
-                    .Where(i => i.Type == CodecType.VideoAudio && i.ContainsCodec(transcodingProfile.AudioCodec))
-                    .Take(1)
-                    .SelectMany(i => i.Conditions);
-
-                ApplyTranscodingConditions(playlistItem, audioTranscodingConditions);
-            }
-
-            return playlistItem;
-        }
-
-        private void ApplyTranscodingConditions(PlaylistItem item, IEnumerable<ProfileCondition> conditions)
-        {
-            foreach (var condition in conditions
-                .Where(i => !string.IsNullOrEmpty(i.Value)))
-            {
-                var value = condition.Value;
-
-                switch (condition.Property)
-                {
-                    case ProfileConditionValue.AudioBitrate:
-                    {
-                        int num;
-                        if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
-                        {
-                            item.AudioBitrate = num;
-                        }
-                        break;
-                    }
-                    case ProfileConditionValue.AudioChannels:
-                    {
-                        int num;
-                        if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
-                        {
-                            item.MaxAudioChannels = num;
-                        }
-                        break;
-                    }
-                    case ProfileConditionValue.AudioProfile:
-                    case ProfileConditionValue.Has64BitOffsets:
-                    case ProfileConditionValue.VideoBitDepth:
-                    case ProfileConditionValue.VideoProfile:
-                    {
-                        // Not supported yet
-                        break;
-                    }
-                    case ProfileConditionValue.Height:
-                    {
-                        int num;
-                        if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
-                        {
-                            item.MaxHeight = num;
-                        }
-                        break;
-                    }
-                    case ProfileConditionValue.VideoBitrate:
-                    {
-                        int num;
-                        if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
-                        {
-                            item.VideoBitrate = num;
-                        }
-                        break;
-                    }
-                    case ProfileConditionValue.VideoFramerate:
-                    {
-                        int num;
-                        if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
-                        {
-                            item.MaxFramerate = num;
-                        }
-                        break;
-                    }
-                    case ProfileConditionValue.VideoLevel:
-                    {
-                        int num;
-                        if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
-                        {
-                            item.VideoLevel = num;
-                        }
-                        break;
-                    }
-                    case ProfileConditionValue.Width:
-                    {
-                        int num;
-                        if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
-                        {
-                            item.MaxWidth = num;
-                        }
-                        break;
-                    }
-                    default:
-                        throw new ArgumentException("Unrecognized ProfileConditionValue");
-                }
-            }
-        }
-
         private bool IsSupported(DirectPlayProfile profile, Photo item)
         {
             var mediaPath = item.Path;
@@ -260,223 +61,5 @@ namespace MediaBrowser.Dlna.PlayTo
 
             return true;
         }
-
-        private bool IsSupported(DirectPlayProfile profile, Audio item, MediaStream audioStream)
-        {
-            var mediaPath = item.Path;
-
-            if (profile.Container.Length > 0)
-            {
-                // Check container type
-                var mediaContainer = Path.GetExtension(mediaPath);
-                if (!profile.GetContainers().Any(i => string.Equals("." + i.TrimStart('.'), mediaContainer, StringComparison.OrdinalIgnoreCase)))
-                {
-                    return false;
-                }
-            }
-
-            return true;
-        }
-
-        private bool IsSupported(DirectPlayProfile profile, Video item, MediaStream videoStream, MediaStream audioStream)
-        {
-            if (item.VideoType != VideoType.VideoFile)
-            {
-                return false;
-            }
-
-            var mediaPath = item.Path;
-
-            if (profile.Container.Length > 0)
-            {
-                // Check container type
-                var mediaContainer = Path.GetExtension(mediaPath);
-                if (!profile.GetContainers().Any(i => string.Equals("." + i.TrimStart('.'), mediaContainer, StringComparison.OrdinalIgnoreCase)))
-                {
-                    return false;
-                }
-            }
-
-            // Check video codec
-            var videoCodecs = profile.GetVideoCodecs();
-            if (videoCodecs.Count > 0)
-            {
-                var videoCodec = videoStream == null ? null : videoStream.Codec;
-                if (string.IsNullOrWhiteSpace(videoCodec) || !videoCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase))
-                {
-                    return false;
-                }
-            }
-
-            var audioCodecs = profile.GetAudioCodecs();
-            if (audioCodecs.Count > 0)
-            {
-                // Check audio codecs
-                var audioCodec = audioStream == null ? null : audioStream.Codec;
-                if (string.IsNullOrWhiteSpace(audioCodec) || !audioCodecs.Contains(audioCodec, StringComparer.OrdinalIgnoreCase))
-                {
-                    return false;
-                }
-            }
-
-            return true;
-        }
-
-        private bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, Audio item)
-        {
-            // Placeholder for future conditions
-            return true;
-        }
-
-        private bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, Photo item)
-        {
-            // Placeholder for future conditions
-            return true;
-        }
-
-        private bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, Video item)
-        {
-            // Placeholder for future conditions
-            return true;
-        }
-
-        private bool AreConditionsSatisfied(IEnumerable<ProfileCondition> conditions, string mediaPath, MediaStream videoStream, MediaStream audioStream)
-        {
-            return conditions.All(i => IsConditionSatisfied(i, mediaPath, videoStream, audioStream));
-        }
-
-        /// <summary>
-        /// Determines whether [is condition satisfied] [the specified condition].
-        /// </summary>
-        /// <param name="condition">The condition.</param>
-        /// <param name="mediaPath">The media path.</param>
-        /// <param name="videoStream">The video stream.</param>
-        /// <param name="audioStream">The audio stream.</param>
-        /// <returns><c>true</c> if [is condition satisfied] [the specified condition]; otherwise, <c>false</c>.</returns>
-        /// <exception cref="System.InvalidOperationException">Unexpected ProfileConditionType</exception>
-        private bool IsConditionSatisfied(ProfileCondition condition, string mediaPath, MediaStream videoStream, MediaStream audioStream)
-        {
-            if (condition.Property == ProfileConditionValue.Has64BitOffsets)
-            {
-                // TODO: Determine how to evaluate this
-            }
-
-            if (condition.Property == ProfileConditionValue.VideoProfile)
-            {
-                var profile = videoStream == null ? null : videoStream.Profile;
-
-                if (!string.IsNullOrWhiteSpace(profile))
-                {
-                    switch (condition.Condition)
-                    {
-                        case ProfileConditionType.Equals:
-                            return string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase);
-                        case ProfileConditionType.NotEquals:
-                            return !string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase);
-                        default:
-                            throw new InvalidOperationException("Unexpected ProfileConditionType");
-                    }
-                }
-            }
-
-            else if (condition.Property == ProfileConditionValue.AudioProfile)
-            {
-                var profile = audioStream == null ? null : audioStream.Profile;
-
-                if (!string.IsNullOrWhiteSpace(profile))
-                {
-                    switch (condition.Condition)
-                    {
-                        case ProfileConditionType.Equals:
-                            return string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase);
-                        case ProfileConditionType.NotEquals:
-                            return !string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase);
-                        default:
-                            throw new InvalidOperationException("Unexpected ProfileConditionType");
-                    }
-                }
-            }
-
-            else
-            {
-                var actualValue = GetConditionValue(condition, mediaPath, videoStream, audioStream);
-
-                if (actualValue.HasValue)
-                {
-                    long expected;
-                    if (long.TryParse(condition.Value, NumberStyles.Any, _usCulture, out expected))
-                    {
-                        switch (condition.Condition)
-                        {
-                            case ProfileConditionType.Equals:
-                                return actualValue.Value == expected;
-                            case ProfileConditionType.GreaterThanEqual:
-                                return actualValue.Value >= expected;
-                            case ProfileConditionType.LessThanEqual:
-                                return actualValue.Value <= expected;
-                            case ProfileConditionType.NotEquals:
-                                return actualValue.Value != expected;
-                            default:
-                                throw new InvalidOperationException("Unexpected ProfileConditionType");
-                        }
-                    }
-                }
-            }
-
-            // Value doesn't exist in metadata. Fail it if required.
-            return !condition.IsRequired;
-        }
-
-        /// <summary>
-        /// Gets the condition value.
-        /// </summary>
-        /// <param name="condition">The condition.</param>
-        /// <param name="mediaPath">The media path.</param>
-        /// <param name="videoStream">The video stream.</param>
-        /// <param name="audioStream">The audio stream.</param>
-        /// <returns>System.Nullable{System.Int64}.</returns>
-        /// <exception cref="System.InvalidOperationException">Unexpected Property</exception>
-        private long? GetConditionValue(ProfileCondition condition, string mediaPath, MediaStream videoStream, MediaStream audioStream)
-        {
-            switch (condition.Property)
-            {
-                case ProfileConditionValue.AudioBitrate:
-                    return audioStream == null ? null : audioStream.BitRate;
-                case ProfileConditionValue.AudioChannels:
-                    return audioStream == null ? null : audioStream.Channels;
-                case ProfileConditionValue.VideoBitrate:
-                    return videoStream == null ? null : videoStream.BitRate;
-                case ProfileConditionValue.VideoFramerate:
-                    return videoStream == null ? null : (ConvertToLong(videoStream.AverageFrameRate ?? videoStream.RealFrameRate));
-                case ProfileConditionValue.Height:
-                    return videoStream == null ? null : videoStream.Height;
-                case ProfileConditionValue.Width:
-                    return videoStream == null ? null : videoStream.Width;
-                case ProfileConditionValue.VideoLevel:
-                    return videoStream == null ? null : ConvertToLong(videoStream.Level);
-                default:
-                    throw new InvalidOperationException("Unexpected Property");
-            }
-        }
-
-        /// <summary>
-        /// Converts to long.
-        /// </summary>
-        /// <param name="val">The value.</param>
-        /// <returns>System.Nullable{System.Int64}.</returns>
-        private long? ConvertToLong(float? val)
-        {
-            return val.HasValue ? Convert.ToInt64(val.Value) : (long?)null;
-        }
-
-        /// <summary>
-        /// Converts to long.
-        /// </summary>
-        /// <param name="val">The value.</param>
-        /// <returns>System.Nullable{System.Int64}.</returns>
-        private long? ConvertToLong(double? val)
-        {
-            return val.HasValue ? Convert.ToInt64(val.Value) : (long?)null;
-        }
     }
 }

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

@@ -1,69 +0,0 @@
-using MediaBrowser.Model.Entities;
-using System.Collections.Generic;
-using System.Globalization;
-
-namespace MediaBrowser.Dlna.PlayTo
-{
-    class StreamHelper
-    {
-        /// <summary>
-        /// Gets the audio 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>System.String.</returns>
-        internal static string GetAudioUrl(DeviceInfo deviceProperties, PlaylistItem item, List<MediaStream> streams, string serverAddress)
-        {
-            var dlnaCommand = BuildDlnaUrl(deviceProperties, item);
-
-            return string.Format("{0}/audio/{1}/stream{2}?{3}", serverAddress, item.ItemId, "." + item.Container.TrimStart('.'), dlnaCommand);
-        }
-
-        /// <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(DeviceInfo deviceProperties, PlaylistItem item, List<MediaStream> streams, string serverAddress)
-        {
-            var dlnaCommand = BuildDlnaUrl(deviceProperties, item);
-
-            return string.Format("{0}/Videos/{1}/stream{2}?{3}", serverAddress, item.ItemId, item.Container, dlnaCommand);
-        }
-
-        /// <summary>
-        /// Builds the dlna URL.
-        /// </summary>
-        private static string BuildDlnaUrl(DeviceInfo deviceProperties, PlaylistItem item)
-        {
-            var usCulture = new CultureInfo("en-US");
-            
-            var list = new List<string>
-            {
-                item.DeviceProfileId ?? string.Empty,
-                deviceProperties.UUID ?? string.Empty,
-                item.MediaSourceId ?? string.Empty,
-                (!item.Transcode).ToString().ToLower(),
-                item.VideoCodec ?? string.Empty,
-                item.AudioCodec ?? string.Empty,
-                item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(usCulture) : string.Empty,
-                item.SubtitleStreamIndex.HasValue ? item.SubtitleStreamIndex.Value.ToString(usCulture) : string.Empty,
-                item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(usCulture) : string.Empty,
-                item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(usCulture) : string.Empty,
-                item.MaxAudioChannels.HasValue ? item.MaxAudioChannels.Value.ToString(usCulture) : string.Empty,
-                item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(usCulture) : string.Empty,
-                item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(usCulture) : string.Empty,
-                item.MaxHeight.HasValue ? item.MaxHeight.Value.ToString(usCulture) : string.Empty,
-                item.StartPositionTicks.ToString(usCulture),
-                item.VideoLevel.HasValue ? item.VideoLevel.Value.ToString(usCulture) : string.Empty
-            };
-
-            return string.Format("Params={0}", string.Join(";", list.ToArray()));
-        }
-    }
-}

+ 528 - 208
MediaBrowser.Dlna/Server/ControlHandler.cs

@@ -1,15 +1,23 @@
 using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Querying;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
 using System.Text;
+using System.Threading;
 using System.Xml;
 
 namespace MediaBrowser.Dlna.Server
@@ -19,7 +27,12 @@ namespace MediaBrowser.Dlna.Server
         private readonly ILogger _logger;
         private readonly IUserManager _userManager;
         private readonly ILibraryManager _libraryManager;
-        private DeviceProfile _profile;
+        private readonly DeviceProfile _profile;
+        private readonly IDtoService _dtoService;
+        private readonly IImageProcessor _imageProcessor;
+        private readonly IUserDataManager _userDataManager;
+
+        private readonly string _serverAddress;
 
         private const string NS_DC = "http://purl.org/dc/elements/1.1/";
         private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
@@ -31,11 +44,16 @@ namespace MediaBrowser.Dlna.Server
         private int systemID = 0;
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
 
-        public ControlHandler(ILogger logger, IUserManager userManager, ILibraryManager libraryManager)
+        public ControlHandler(ILogger logger, IUserManager userManager, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager)
         {
             _logger = logger;
             _userManager = userManager;
             _libraryManager = libraryManager;
+            _profile = profile;
+            _serverAddress = serverAddress;
+            _dtoService = dtoService;
+            _imageProcessor = imageProcessor;
+            _userDataManager = userDataManager;
         }
 
         public ControlResponse ProcessControlRequest(ControlRequest request)
@@ -46,6 +64,8 @@ namespace MediaBrowser.Dlna.Server
             }
             catch (Exception ex)
             {
+                _logger.ErrorException("Error processing control request", ex);
+
                 return GetErrorResponse(ex);
             }
         }
@@ -120,7 +140,8 @@ namespace MediaBrowser.Dlna.Server
 
             var controlResponse = new ControlResponse
             {
-                Xml = env.OuterXml
+                Xml = env.OuterXml,
+                IsSuccessful = true
             };
 
             controlResponse.Headers.Add("EXT", string.Empty);
@@ -153,7 +174,8 @@ namespace MediaBrowser.Dlna.Server
 
             return new ControlResponse
             {
-                Xml = env.OuterXml
+                Xml = env.OuterXml,
+                IsSuccessful = false
             };
         }
 
@@ -161,7 +183,16 @@ namespace MediaBrowser.Dlna.Server
         {
             var id = sparams["ObjectID"];
 
-            var newbookmark = long.Parse(sparams["PosSecond"]);
+            var item = _libraryManager.GetItemById(new Guid(id));
+
+            var newbookmark = int.Parse(sparams["PosSecond"], _usCulture);
+
+            var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+
+            userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks;
+
+            _userDataManager.SaveUserData(user.Id, item, userdata, UserDataSaveReason.TogglePlayed,
+                CancellationToken.None);
 
             return new Headers();
         }
@@ -209,13 +240,13 @@ namespace MediaBrowser.Dlna.Server
             var id = sparams["ObjectID"];
             var flag = sparams["BrowseFlag"];
 
-            int requested = 20;
             var provided = 0;
+            int requested = 0;
             int start = 0;
 
             if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out requested) && requested <= 0)
             {
-                requested = 20;
+                requested = 0;
             }
             if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out start) && start <= 0)
             {
@@ -232,11 +263,11 @@ namespace MediaBrowser.Dlna.Server
             didl.SetAttribute("xmlns:sec", NS_SEC);
             result.AppendChild(didl);
 
-            var folder = string.IsNullOrWhiteSpace(id)
+            var folder = string.IsNullOrWhiteSpace(id) || string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
                 ? user.RootFolder
                 : (Folder)_libraryManager.GetItemById(new Guid(id));
 
-            var children = folder.GetChildren(user, true).ToList();
+            var children = GetChildrenSorted(folder, user).ToList();
 
             if (string.Equals(flag, "BrowseMetadata"))
             {
@@ -245,40 +276,29 @@ namespace MediaBrowser.Dlna.Server
             }
             else
             {
-                foreach (var i in children.OfType<Folder>())
+                if (start > 0)
                 {
-                    if (start > 0)
-                    {
-                        start--;
-                        continue;
-                    }
-
-                    var childCount = i.GetChildren(user, true).Count();
-
-                    Browse_AddFolder(result, i, childCount);
-
-                    if (++provided == requested)
-                    {
-                        break;
-                    }
+                    children = children.Skip(start).ToList();
+                }
+                if (requested > 0)
+                {
+                    children = children.Take(requested).ToList();
                 }
 
-                if (provided != requested)
+                provided = children.Count;
+
+                foreach (var i in children)
                 {
-                    foreach (var i in children.Where(i => !i.IsFolder))
+                    if (i.IsFolder)
                     {
-                        if (start > 0)
-                        {
-                            start--;
-                            continue;
-                        }
+                        var f = (Folder)i;
+                        var childCount = GetChildrenSorted(f, user).Count();
 
+                        Browse_AddFolder(result, f, childCount);
+                    }
+                    else
+                    {
                         Browse_AddItem(result, i, user);
-
-                        if (++provided == requested)
-                        {
-                            break;
-                        }
                     }
                 }
             }
@@ -294,10 +314,23 @@ namespace MediaBrowser.Dlna.Server
             };
         }
 
+        private IEnumerable<BaseItem> GetChildrenSorted(Folder folder, User user)
+        {
+            var children = folder.GetChildren(user, true).Where(i => i.LocationType != LocationType.Virtual);
+
+            if (folder is Series || folder is Season || folder is BoxSet)
+            {
+                return children;
+            }
+
+            return _libraryManager.Sort(children, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending);
+        }
+
         private void Browse_AddFolder(XmlDocument result, Folder f, int childCount)
         {
             var container = result.CreateElement(string.Empty, "container", NS_DIDL);
             container.SetAttribute("restricted", "0");
+            container.SetAttribute("searchable", "1");
             container.SetAttribute("childCount", childCount.ToString(_usCulture));
             container.SetAttribute("id", f.Id.ToString("N"));
 
@@ -311,20 +344,28 @@ namespace MediaBrowser.Dlna.Server
                 container.SetAttribute("parentID", parent.Id.ToString("N"));
             }
 
-            var title = result.CreateElement("dc", "title", NS_DC);
-            title.InnerText = f.Name;
-            container.AppendChild(title);
+            AddCommonFields(f, container);
 
-            var date = result.CreateElement("dc", "date", NS_DC);
-            date.InnerText = f.DateModified.ToString("o");
-            container.AppendChild(date);
+            AddCover(f, container);
 
-            var objectClass = result.CreateElement("upnp", "class", NS_UPNP);
-            objectClass.InnerText = "object.container.storageFolder";
-            container.AppendChild(objectClass);
+            container.AppendChild(CreateObjectClass(result, f));
             result.DocumentElement.AppendChild(container);
         }
 
+        private void AddValue(XmlElement elem, string prefix, string name, string value, string namespaceUri)
+        {
+            try
+            {
+                var date = elem.OwnerDocument.CreateElement(prefix, name, namespaceUri);
+                date.InnerText = value;
+                elem.AppendChild(date);
+            }
+            catch (XmlException)
+            {
+                //_logger.Error("Error adding xml value: " + value);
+            }
+        }
+
         private void Browse_AddItem(XmlDocument result, BaseItem item, User user)
         {
             var element = result.CreateElement(string.Empty, "item", NS_DIDL);
@@ -342,52 +383,217 @@ namespace MediaBrowser.Dlna.Server
 
             AddGeneralProperties(item, element);
 
-            AddActors(item, element);
+            // refID?
+            // storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
 
-            var title = result.CreateElement("dc", "title", NS_DC);
-            title.InnerText = item.Name;
-            element.AppendChild(title);
-
-            var res = result.CreateElement(string.Empty, "res", NS_DIDL);
-
-            //res.InnerText = String.Format(
-            //  "http://{0}:{1}{2}file/{3}",
-            //  request.LocalEndPoint.Address,
-            //  request.LocalEndPoint.Port,
-            //  prefix,
-            //  resource.Id
-            //  );
-
-            //if (props.TryGetValue("SizeRaw", out prop))
-            //{
-            //    res.SetAttribute("size", prop);
-            //}
-            //if (props.TryGetValue("Resolution", out prop))
-            //{
-            //    res.SetAttribute("resolution", prop);
-            //}
-            //if (props.TryGetValue("Duration", out prop))
-            //{
-            //    res.SetAttribute("duration", prop);
-            //}
-
-            //res.SetAttribute("protocolInfo", String.Format(
-            //    "http-get:*:{1}:{0};DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={2}",
-            //    resource.PN, DlnaMaps.Mime[resource.Type], DlnaMaps.DefaultStreaming
-            //    ));
+            var audio = item as Audio;
+            if (audio != null)
+            {
+                AddAudioResource(element, audio);
+            }
 
-            element.AppendChild(res);
+            var video = item as Video;
+            if (video != null)
+            {
+                AddVideoResource(element, video);
+            }
 
             AddCover(item, element);
 
             result.DocumentElement.AppendChild(element);
         }
 
+        private string GetDeviceId()
+        {
+            return "erer";
+        }
+
+        private void AddVideoResource(XmlElement container, Video video)
+        {
+            var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
+
+            var sources = _dtoService.GetMediaSources(video);
+
+            int? maxBitrateSetting = null;
+
+            var streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
+            {
+                ItemId = video.Id.ToString("N"),
+                MediaSources = sources,
+                Profile = _profile,
+                DeviceId = GetDeviceId(),
+                MaxBitrate = maxBitrateSetting
+            });
+
+            var url = streamInfo.ToDlnaUrl(_serverAddress);
+            res.InnerText = url;
+
+            var mediaSource = sources.First(i => string.Equals(i.Id, streamInfo.MediaSourceId));
+
+            if (mediaSource.RunTimeTicks.HasValue)
+            {
+                res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
+            }
+
+            if (streamInfo.IsDirectStream && mediaSource.Size.HasValue)
+            {
+                res.SetAttribute("size", mediaSource.Size.Value.ToString(_usCulture));
+            }
+
+            var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video && !string.Equals(i.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase));
+            var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
+
+            var targetAudioBitrate = streamInfo.AudioBitrate ?? (audioStream == null ? null : audioStream.BitRate);
+            var targetSampleRate = audioStream == null ? null : audioStream.SampleRate;
+            var targetChannels = streamInfo.MaxAudioChannels ?? (audioStream == null ? null : audioStream.Channels);
+
+            var targetWidth = streamInfo.MaxWidth ?? (videoStream == null ? null : videoStream.Width);
+            var targetHeight = streamInfo.MaxHeight ?? (videoStream == null ? null : videoStream.Height);
+
+            var targetVideoCodec = streamInfo.IsDirectStream
+                ? (videoStream == null ? null : videoStream.Codec)
+                : streamInfo.VideoCodec;
+
+            var targetAudioCodec = streamInfo.IsDirectStream
+             ? (audioStream == null ? null : audioStream.Codec)
+             : streamInfo.AudioCodec;
+
+            var targetBitrate = maxBitrateSetting ?? mediaSource.Bitrate;
+
+            if (targetChannels.HasValue)
+            {
+                res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
+            }
+
+            if (targetSampleRate.HasValue)
+            {
+                res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
+            }
+
+            if (targetAudioBitrate.HasValue)
+            {
+                res.SetAttribute("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
+            }
+
+            var formatProfile = new MediaFormatProfileResolver().ResolveVideoFormat(streamInfo.Container,
+                targetVideoCodec,
+                targetAudioCodec,
+                targetWidth,
+                targetHeight,
+                targetBitrate,
+                TransportStreamTimestamp.NONE);
+
+            var filename = url.Substring(0, url.IndexOf('?'));
+
+            var orgOpValue = DlnaMaps.GetOrgOpValue(mediaSource.RunTimeTicks.HasValue, streamInfo.IsDirectStream, streamInfo.TranscodeSeekInfo);
+
+            var orgCi = streamInfo.IsDirectStream ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
+
+            res.SetAttribute("protocolInfo", String.Format(
+                "http-get:*:{0}:DLNA.ORG_PN={1};DLNA.ORG_OP={2};DLNA.ORG_CI={3};DLNA.ORG_FLAGS={4}",
+                MimeTypes.GetMimeType(filename),
+                formatProfile,
+                orgOpValue,
+                orgCi,
+                DlnaMaps.DefaultStreaming
+                ));
+
+            container.AppendChild(res);
+        }
+
+        private void AddAudioResource(XmlElement container, Audio audio)
+        {
+            var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
+
+            var sources = _dtoService.GetMediaSources(audio);
+
+            var streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions
+            {
+                ItemId = audio.Id.ToString("N"),
+                MediaSources = sources,
+                Profile = _profile,
+                DeviceId = GetDeviceId()
+            });
+
+            var url = streamInfo.ToDlnaUrl(_serverAddress);
+            res.InnerText = url;
+
+            var mediaSource = sources.First(i => string.Equals(i.Id, streamInfo.MediaSourceId));
+
+            if (mediaSource.RunTimeTicks.HasValue)
+            {
+                res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
+            }
+
+            if (streamInfo.IsDirectStream && mediaSource.Size.HasValue)
+            {
+                res.SetAttribute("size", mediaSource.Size.Value.ToString(_usCulture));
+            }
+
+            var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
+
+            var targetAudioBitrate = streamInfo.AudioBitrate ?? (audioStream == null ? null : audioStream.BitRate);
+            var targetSampleRate = audioStream == null ? null : audioStream.SampleRate;
+            var targetChannels = streamInfo.MaxAudioChannels ?? (audioStream == null ? null : audioStream.Channels);
+
+            if (targetChannels.HasValue)
+            {
+                res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
+            }
+
+            if (targetSampleRate.HasValue)
+            {
+                res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
+            }
+
+            if (targetAudioBitrate.HasValue)
+            {
+                res.SetAttribute("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
+            }
+
+            var formatProfile = new MediaFormatProfileResolver().ResolveAudioFormat(streamInfo.Container, targetAudioBitrate, targetSampleRate, targetChannels);
+
+            var filename = url.Substring(0, url.IndexOf('?'));
+
+            var orgOpValue = DlnaMaps.GetOrgOpValue(mediaSource.RunTimeTicks.HasValue, streamInfo.IsDirectStream, streamInfo.TranscodeSeekInfo);
+
+            var orgCi = streamInfo.IsDirectStream ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
+
+            res.SetAttribute("protocolInfo", String.Format(
+                "http-get:*:{0}:DLNA.ORG_PN={1};DLNA.ORG_OP={2};DLNA.ORG_CI={3};DLNA.ORG_FLAGS={4}",
+                MimeTypes.GetMimeType(filename),
+                formatProfile,
+                orgOpValue,
+                orgCi,
+                DlnaMaps.DefaultStreaming
+                ));
+
+            container.AppendChild(res);
+        }
+
         private XmlElement CreateObjectClass(XmlDocument result, BaseItem item)
         {
             var objectClass = result.CreateElement("upnp", "class", NS_UPNP);
 
-            if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
+            if (item.IsFolder)
+            {
+                string classType = null;
+
+                if (!_profile.RequiresPlainFolders)
+                {
+                    if (item is MusicAlbum)
+                    {
+                        classType = "object.container.musicAlbum";
+                    }
+                    if (item is MusicArtist)
+                    {
+                        classType = "object.container.musicArtist";
+                    }
+                }
+
+                objectClass.InnerText = classType ?? "object.container.storageFolder";
+            }
+            else if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
             {
                 objectClass.InnerText = "object.item.audioItem.musicTrack";
             }
@@ -397,7 +603,14 @@ namespace MediaBrowser.Dlna.Server
             }
             else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
             {
-                objectClass.InnerText = "object.item.videoItem.movie";
+                if (!_profile.RequiresPlainVideoItems && item is Movie)
+                {
+                    objectClass.InnerText = "object.item.videoItem.movie";
+                }
+                else
+                {
+                    objectClass.InnerText = "object.item.videoItem";
+                }
             }
             else
             {
@@ -407,152 +620,259 @@ namespace MediaBrowser.Dlna.Server
             return objectClass;
         }
 
-        private void AddActors(BaseItem item, XmlElement element)
+        private void AddPeople(BaseItem item, XmlElement element)
         {
             foreach (var actor in item.People)
             {
-                var e = element.OwnerDocument.CreateElement("upnp", "actor", NS_UPNP);
-                e.InnerText = actor.Name;
-                element.AppendChild(e);
+                AddValue(element, "upnp", (actor.Type ?? PersonType.Actor).ToLower(), actor.Name, NS_UPNP);
             }
         }
 
         private void AddBookmarkInfo(BaseItem item, User user, XmlElement element)
         {
-            //var bookmark = bookmarkable.Bookmark;
-            //if (bookmark.HasValue)
-            //{
-            //    var dcmInfo = item.OwnerDocument.CreateElement("sec", "dcmInfo", NS_SEC);
-            //    dcmInfo.InnerText = string.Format("BM={0}", bookmark.Value);
-            //    item.AppendChild(dcmInfo);
-            //}
+            var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+
+            if (userdata.PlaybackPositionTicks > 0)
+            {
+                var dcmInfo = element.OwnerDocument.CreateElement("sec", "dcmInfo", NS_SEC);
+                dcmInfo.InnerText = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(userdata.PlaybackPositionTicks).TotalSeconds).ToString(_usCulture));
+                element.AppendChild(dcmInfo);
+            }
         }
 
-        private  void AddGeneralProperties(BaseItem item, XmlElement element)
+        /// <summary>
+        /// Adds fields used by both items and folders
+        /// </summary>
+        /// <param name="item"></param>
+        /// <param name="element"></param>
+        private void AddCommonFields(BaseItem item, XmlElement element)
         {
-            //var prop = string.Empty;
-            //if (props.TryGetValue("DateO", out prop))
-            //{
-            //    var e = item.OwnerDocument.CreateElement("dc", "date", NS_DC);
-            //    e.InnerText = prop;
-            //    item.AppendChild(e);
-            //}
-            //if (props.TryGetValue("Genre", out prop))
-            //{
-            //    var e = item.OwnerDocument.CreateElement("upnp", "genre", NS_UPNP);
-            //    e.InnerText = prop;
-            //    item.AppendChild(e);
-            //}
+            if (item.PremiereDate.HasValue)
+            {
+                AddValue(element, "dc", "date", item.PremiereDate.Value.ToString("o"), NS_DC);
+            }
+
+            if (item.Genres.Count > 0)
+            {
+                AddValue(element, "upnp", "genre", item.Genres[0], NS_UPNP);
+            }
+
+            if (item.Studios.Count > 0)
+            {
+                AddValue(element, "upnp", "publisher", item.Studios[0], NS_UPNP);
+            }
+
+            AddValue(element, "dc", "title", item.Name, NS_DC);
 
             if (!string.IsNullOrWhiteSpace(item.Overview))
             {
-                var e = element.OwnerDocument.CreateElement("dc", "description", NS_DC);
-                e.InnerText = item.Overview;
-                element.AppendChild(e);
-            }
-
-            //if (props.TryGetValue("Artist", out prop))
-            //{
-            //    var e = item.OwnerDocument.CreateElement("upnp", "artist", NS_UPNP);
-            //    e.SetAttribute("role", "AlbumArtist");
-            //    e.InnerText = prop;
-            //    item.AppendChild(e);
-            //}
-            //if (props.TryGetValue("Performer", out prop))
-            //{
-            //    var e = item.OwnerDocument.CreateElement("upnp", "artist", NS_UPNP);
-            //    e.SetAttribute("role", "Performer");
-            //    e.InnerText = prop;
-            //    item.AppendChild(e);
-            //    e = item.OwnerDocument.CreateElement("dc", "creator", NS_DC);
-            //    e.InnerText = prop;
-            //    item.AppendChild(e);
-            //}
-            //if (props.TryGetValue("Album", out prop))
-            //{
-            //    var e = item.OwnerDocument.CreateElement("upnp", "album", NS_UPNP);
-            //    e.InnerText = prop;
-            //    item.AppendChild(e);
-            //}
-            //if (props.TryGetValue("Track", out prop))
-            //{
-            //    var e = item.OwnerDocument.CreateElement("upnp", "originalTrackNumber", NS_UPNP);
-            //    e.InnerText = prop;
-            //    item.AppendChild(e);
-            //}
-            //if (props.TryGetValue("Creator", out prop))
-            //{
-            //    var e = item.OwnerDocument.CreateElement("dc", "creator", NS_DC);
-            //    e.InnerText = prop;
-            //    item.AppendChild(e);
-            //}
-
-            //if (props.TryGetValue("Director", out prop))
-            //{
-            //    var e = item.OwnerDocument.CreateElement("upnp", "director", NS_UPNP);
-            //    e.InnerText = prop;
-            //    item.AppendChild(e);
-            //}
+                AddValue(element, "dc", "description", item.Overview, NS_DC);
+            }
+
+            if (!string.IsNullOrEmpty(item.OfficialRating))
+            {
+                AddValue(element, "dc", "rating", item.OfficialRating, NS_DC);
+            }
+
+            AddPeople(item, element);
+        }
+
+        private void AddGeneralProperties(BaseItem item, XmlElement element)
+        {
+            AddCommonFields(item, element);
+
+            var audio = item as Audio;
+
+            if (audio != null)
+            {
+                if (audio.Artists.Count > 0)
+                {
+                    AddValue(element, "upnp", "artist", audio.Artists[0], NS_UPNP);
+                }
+
+                if (!string.IsNullOrEmpty(audio.Album))
+                {
+                    AddValue(element, "upnp", "album", audio.Album, NS_UPNP);
+                }
+
+                if (!string.IsNullOrEmpty(audio.AlbumArtist))
+                {
+                    AddValue(element, "upnp", "albumArtist", audio.AlbumArtist, NS_UPNP);
+                }
+            }
+
+            var album = item as MusicAlbum;
+
+            if (album != null)
+            {
+                if (!string.IsNullOrEmpty(album.AlbumArtist))
+                {
+                    AddValue(element, "upnp", "artist", album.AlbumArtist, NS_UPNP);
+                    AddValue(element, "upnp", "albumArtist", album.AlbumArtist, NS_UPNP);
+                }
+            }
+
+            var musicVideo = item as MusicVideo;
+
+            if (musicVideo != null)
+            {
+                if (!string.IsNullOrEmpty(musicVideo.Artist))
+                {
+                    AddValue(element, "upnp", "artist", musicVideo.Artist, NS_UPNP);
+                }
+
+                if (!string.IsNullOrEmpty(musicVideo.Album))
+                {
+                    AddValue(element, "upnp", "album", musicVideo.Album, NS_UPNP);
+                }
+            }
+
+            if (item.IndexNumber.HasValue)
+            {
+                AddValue(element, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
+            }
         }
 
         private void AddCover(BaseItem item, XmlElement element)
         {
-            //var result = item.OwnerDocument;
-            //var cover = resource as IMediaCover;
-            //if (cover == null)
-            //{
-            //    return;
-            //}
-            //try
-            //{
-            //    var c = cover.Cover;
-            //    var curl = String.Format(
-            //      "http://{0}:{1}{2}cover/{3}",
-            //      request.LocalEndPoint.Address,
-            //      request.LocalEndPoint.Port,
-            //      prefix,
-            //      resource.Id
-            //      );
-            //    var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP);
-            //    var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
-            //    profile.InnerText = "JPEG_TN";
-            //    icon.SetAttributeNode(profile);
-            //    icon.InnerText = curl;
-            //    item.AppendChild(icon);
-            //    icon = result.CreateElement("upnp", "icon", NS_UPNP);
-            //    profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
-            //    profile.InnerText = "JPEG_TN";
-            //    icon.SetAttributeNode(profile);
-            //    icon.InnerText = curl;
-            //    item.AppendChild(icon);
-
-            //    var res = result.CreateElement(string.Empty, "res", NS_DIDL);
-            //    res.InnerText = curl;
-
-            //    res.SetAttribute("protocolInfo", string.Format(
-            //        "http-get:*:{1}:{0};DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={2}",
-            //        c.PN, DlnaMaps.Mime[c.Type], DlnaMaps.DefaultStreaming
-            //        ));
-            //    var width = c.MetaWidth;
-            //    var height = c.MetaHeight;
-            //    if (width.HasValue && height.HasValue)
-            //    {
-            //        res.SetAttribute("resolution", string.Format("{0}x{1}", width.Value, height.Value));
-            //    }
-            //    else
-            //    {
-            //        res.SetAttribute("resolution", "200x200");
-            //    }
-            //    res.SetAttribute("protocolInfo", string.Format(
-            //      "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=01;DLNA.ORG_CI=1;DLNA.ORG_FLAGS={0}",
-            //      DlnaMaps.DefaultInteractive
-            //      ));
-            //    item.AppendChild(res);
-            //}
-            //catch (Exception)
-            //{
-            //    return;
-            //}
+            var imageInfo = GetImageInfo(item);
+
+            if (imageInfo == null)
+            {
+                return;
+            }
+
+            var result = element.OwnerDocument;
+
+            var curl = GetImageUrl(imageInfo);
+
+            var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP);
+            var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
+            profile.InnerText = "JPEG_TN";
+            icon.SetAttributeNode(profile);
+            icon.InnerText = curl;
+            element.AppendChild(icon);
+
+            icon = result.CreateElement("upnp", "icon", NS_UPNP);
+            profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
+            profile.InnerText = "JPEG_TN";
+            icon.SetAttributeNode(profile);
+            icon.InnerText = curl;
+            element.AppendChild(icon);
+
+            if (!_profile.EnableAlbumArtInDidl)
+            {
+                return;
+            }
+
+            var res = result.CreateElement(string.Empty, "res", NS_DIDL);
+            res.InnerText = curl;
+
+            int? width = imageInfo.Width;
+            int? height = imageInfo.Height;
+
+            var mediaProfile = new MediaFormatProfileResolver().ResolveImageFormat("jpg", width, height);
+
+            res.SetAttribute("protocolInfo", string.Format(
+                "http-get:*:{1}DLNA.ORG_PN=:{0};DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={2}",
+                mediaProfile, "image/jpeg", DlnaMaps.DefaultStreaming
+                ));
+
+            if (width.HasValue && height.HasValue)
+            {
+                res.SetAttribute("resolution", string.Format("{0}x{1}", width.Value, height.Value));
+            }
+            else
+            {
+                // TODO: Devices need to see something here?
+                res.SetAttribute("resolution", "200x200");
+            }
+
+            element.AppendChild(res);
+        }
+
+        private ImageDownloadInfo GetImageInfo(BaseItem item)
+        {
+            if (item.HasImage(ImageType.Primary))
+            {
+                return GetImageInfo(item, ImageType.Primary);
+            }
+            if (item.HasImage(ImageType.Thumb))
+            {
+                return GetImageInfo(item, ImageType.Thumb);
+            }
+
+            if (item is Audio || item is Episode)
+            {
+                item = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Primary));
+
+                if (item != null)
+                {
+                    return GetImageInfo(item, ImageType.Primary);
+                }
+            }
+
+            return null;
+        }
+
+        private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
+        {
+            var imageInfo = item.GetImageInfo(type, 0);
+            string tag = null;
+
+            try
+            {
+                var guid = _imageProcessor.GetImageCacheTag(item, ImageType.Primary);
+
+                tag = guid.HasValue ? guid.Value.ToString("N") : null;
+            }
+            catch
+            {
+
+            }
+
+            int? width = null;
+            int? height = null;
+
+            try
+            {
+                var size = _imageProcessor.GetImageSize(imageInfo.Path, imageInfo.DateModified);
+
+                width = Convert.ToInt32(size.Width);
+                height = Convert.ToInt32(size.Height);
+            }
+            catch
+            {
+
+            }
+            
+            return new ImageDownloadInfo
+            {
+                ItemId = item.Id.ToString("N"),
+                Type = ImageType.Primary,
+                ImageTag = tag,
+                Width = width,
+                Height = height
+            };
+        }
+
+        class ImageDownloadInfo
+        {
+            internal string ItemId;
+            internal string ImageTag;
+            internal ImageType Type;
+
+            internal int? Width;
+            internal int? Height;
+        }
+
+        private string GetImageUrl(ImageDownloadInfo info)
+        {
+            return string.Format("{0}/Items/{1}/Images/{2}?tag={3}&format=jpg",
+                _serverAddress,
+                info.ItemId,
+                info.Type,
+                info.ImageTag);
         }
     }
 }

+ 1 - 1
MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs

@@ -176,7 +176,7 @@ namespace MediaBrowser.Dlna.Server
                 ServiceType = "urn:schemas-upnp-org:service:ContentDirectory:1",
                 ServiceId = "urn:upnp-org:serviceId:ContentDirectory",
                 ScpdUrl = "/mediabrowser/dlna/contentdirectory.xml",
-                ControlUrl = "/mediabrowser/dlna/control"
+                ControlUrl = "/mediabrowser/dlna/" + _serverUdn + "/control"
             });
 
             return list;

+ 11 - 1
MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common;
+using System.Linq;
+using MediaBrowser.Common;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
@@ -18,8 +19,12 @@ namespace MediaBrowser.Dlna.Server
         private readonly IApplicationHost _appHost;
         private readonly INetworkManager _network;
 
+        public static DlnaServerEntryPoint Instance;
+
         public DlnaServerEntryPoint(IServerConfigurationManager config, ILogManager logManager, IApplicationHost appHost, INetworkManager network)
         {
+            Instance = this;
+
             _config = config;
             _appHost = appHost;
             _network = network;
@@ -86,6 +91,11 @@ namespace MediaBrowser.Dlna.Server
             }
         }
 
+        public UpnpDevice GetServerUpnpDevice(string uuid)
+        {
+            return _ssdpHandler.Devices.FirstOrDefault(i => string.Equals(uuid, i.Uuid.ToString("N"), StringComparison.OrdinalIgnoreCase));
+        }
+
         private void DisposeServer()
         {
             lock (_syncLock)

+ 1 - 1
MediaBrowser.Dlna/Server/SsdpHandler.cs

@@ -44,7 +44,7 @@ namespace MediaBrowser.Dlna.Server
             Start();
         }
 
-        private IEnumerable<UpnpDevice> Devices
+        public IEnumerable<UpnpDevice> Devices
         {
             get
             {

+ 1 - 1
MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs

@@ -224,7 +224,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 case EncodingQuality.HighSpeed:
                     return 2;
                 case EncodingQuality.HighQuality:
-                    return isWebm ? Math.Max((int)((Environment.ProcessorCount -1) / 2) , 2) : 0;
+                    return 2;
                 case EncodingQuality.MaxQuality:
                     return isWebm ? Math.Max(Environment.ProcessorCount - 1, 2) : 0;
                 default:

+ 1 - 1
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -768,7 +768,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             }
 
             // Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
-            var args = useIFrame ? string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2},thumbnail=40\" -f image2 \"{1}\"", inputPath, "-", vf) :
+            var args = useIFrame ? string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2},thumbnail=30\" -f image2 \"{1}\"", inputPath, "-", vf) :
                 string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, "-", vf);
 
             var probeSize = GetProbeSizeArgument(type);

+ 9 - 0
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -119,6 +119,15 @@
     <Compile Include="..\MediaBrowser.Model\Dlna\DirectPlayProfile.cs">
       <Link>Dlna\DirectPlayProfile.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Dlna\DlnaMaps.cs">
+      <Link>Dlna\DlnaMaps.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfile.cs">
+      <Link>Dlna\MediaFormatProfile.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfileResolver.cs">
+      <Link>Dlna\MediaFormatProfileResolver.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Dlna\ResponseProfile.cs">
       <Link>Dlna\ResponseProfile.cs</Link>
     </Compile>

+ 9 - 0
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -106,6 +106,15 @@
     <Compile Include="..\MediaBrowser.Model\Dlna\DirectPlayProfile.cs">
       <Link>Dlna\DirectPlayProfile.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Dlna\DlnaMaps.cs">
+      <Link>Dlna\DlnaMaps.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfile.cs">
+      <Link>Dlna\MediaFormatProfile.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfileResolver.cs">
+      <Link>Dlna\MediaFormatProfileResolver.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Dlna\ResponseProfile.cs">
       <Link>Dlna\ResponseProfile.cs</Link>
     </Compile>

+ 62 - 0
MediaBrowser.Model/Dlna/DlnaMaps.cs

@@ -0,0 +1,62 @@
+using System;
+
+namespace MediaBrowser.Model.Dlna
+{
+    public class DlnaMaps
+    {
+        public static readonly string DefaultStreaming =
+             FlagsToString(DlnaFlags.StreamingTransferMode |
+                           DlnaFlags.BackgroundTransferMode |
+                           DlnaFlags.ConnectionStall |
+                           DlnaFlags.ByteBasedSeek |
+                           DlnaFlags.DlnaV15);
+
+        public static readonly string DefaultInteractive =
+          FlagsToString(DlnaFlags.InteractiveTransferMode |
+                        DlnaFlags.BackgroundTransferMode |
+                        DlnaFlags.ConnectionStall |
+                        DlnaFlags.ByteBasedSeek |
+                        DlnaFlags.DlnaV15);
+
+        public static string FlagsToString(DlnaFlags flags)
+        {
+            return string.Format("{0:X8}{1:D24}", (ulong)flags, 0);
+        }
+
+        public static string GetOrgOpValue(bool hasKnownRuntime, bool isDirectStream, TranscodeSeekInfo profileTranscodeSeekInfo)
+        {
+            if (hasKnownRuntime)
+            {
+                var orgOp = string.Empty;
+
+                // Time-based seeking currently only possible when transcoding
+                orgOp += isDirectStream ? "0" : "1";
+
+                // Byte-based seeking only possible when not transcoding
+                orgOp += isDirectStream || profileTranscodeSeekInfo == TranscodeSeekInfo.Bytes ? "1" : "0";
+
+                return orgOp;
+            }
+
+            // No seeking is available if we don't know the content runtime
+            return "00";
+        }
+    }
+
+    [Flags]
+    public enum DlnaFlags : ulong
+    {
+        BackgroundTransferMode = (1 << 22),
+        ByteBasedSeek = (1 << 29),
+        ConnectionStall = (1 << 21),
+        DlnaV15 = (1 << 20),
+        InteractiveTransferMode = (1 << 23),
+        PlayContainer = (1 << 28),
+        RtspPause = (1 << 25),
+        S0Increase = (1 << 27),
+        SenderPaced = (1L << 31),
+        SnIncrease = (1 << 26),
+        StreamingTransferMode = (1 << 24),
+        TimeBasedSeek = (1 << 30)
+    }
+}

+ 122 - 0
MediaBrowser.Model/Dlna/MediaFormatProfile.cs

@@ -0,0 +1,122 @@
+
+using System;
+
+namespace MediaBrowser.Model.Dlna
+{
+    public enum MediaFormatProfile
+    {
+        MP3,
+        WMA_BASE,
+        WMA_FULL,
+        LPCM16_44_MONO,
+        LPCM16_44_STEREO,
+        LPCM16_48_MONO,
+        LPCM16_48_STEREO,
+        AAC_ISO,
+        AAC_ISO_320,
+        AAC_ADTS,
+        AAC_ADTS_320,
+        FLAC,
+        OGG,
+
+        JPEG_SM,
+        JPEG_MED,
+        JPEG_LRG,
+        JPEG_TN,
+        PNG_LRG,
+        PNG_TN,
+        GIF_LRG,
+        RAW,
+
+        MPEG1,
+        MPEG_PS_PAL,
+        MPEG_PS_NTSC,
+        MPEG_TS_SD_EU,
+        MPEG_TS_SD_EU_ISO,
+        MPEG_TS_SD_EU_T,
+        MPEG_TS_SD_NA,
+        MPEG_TS_SD_NA_ISO,
+        MPEG_TS_SD_NA_T,
+        MPEG_TS_SD_KO,
+        MPEG_TS_SD_KO_ISO,
+        MPEG_TS_SD_KO_T,
+        MPEG_TS_JP_T,
+        AVI,
+        MATROSKA,
+        FLV,
+        DVR_MS,
+        WTV,
+        OGV,
+        AVC_MP4_MP_SD_AAC_MULT5,
+        AVC_MP4_MP_SD_MPEG1_L3,
+        AVC_MP4_MP_SD_AC3,
+        AVC_MP4_MP_HD_720p_AAC,
+        AVC_MP4_MP_HD_1080i_AAC,
+        AVC_MP4_HP_HD_AAC,
+        AVC_TS_MP_HD_AAC_MULT5,
+        AVC_TS_MP_HD_AAC_MULT5_T,
+        AVC_TS_MP_HD_AAC_MULT5_ISO,
+        AVC_TS_MP_HD_MPEG1_L3,
+        AVC_TS_MP_HD_MPEG1_L3_T,
+        AVC_TS_MP_HD_MPEG1_L3_ISO,
+        AVC_TS_MP_HD_AC3,
+        AVC_TS_MP_HD_AC3_T,
+        AVC_TS_MP_HD_AC3_ISO,
+        AVC_TS_HP_HD_MPEG1_L2_T,
+        AVC_TS_HP_HD_MPEG1_L2_ISO,
+        AVC_TS_MP_SD_AAC_MULT5,
+        AVC_TS_MP_SD_AAC_MULT5_T,
+        AVC_TS_MP_SD_AAC_MULT5_ISO,
+        AVC_TS_MP_SD_MPEG1_L3,
+        AVC_TS_MP_SD_MPEG1_L3_T,
+        AVC_TS_MP_SD_MPEG1_L3_ISO,
+        AVC_TS_HP_SD_MPEG1_L2_T,
+        AVC_TS_HP_SD_MPEG1_L2_ISO,
+        AVC_TS_MP_SD_AC3,
+        AVC_TS_MP_SD_AC3_T,
+        AVC_TS_MP_SD_AC3_ISO,
+        AVC_TS_HD_DTS_T,
+        AVC_TS_HD_DTS_ISO,
+        WMVMED_BASE,
+        WMVMED_FULL,
+        WMVMED_PRO,
+        WMVHIGH_FULL,
+        WMVHIGH_PRO,
+        VC1_ASF_AP_L1_WMA,
+        VC1_ASF_AP_L2_WMA,
+        VC1_ASF_AP_L3_WMA,
+        VC1_TS_AP_L1_AC3_ISO,
+        VC1_TS_AP_L2_AC3_ISO,
+        VC1_TS_HD_DTS_ISO,
+        VC1_TS_HD_DTS_T,
+        MPEG4_P2_MP4_ASP_AAC,
+        MPEG4_P2_MP4_SP_L6_AAC,
+        MPEG4_P2_MP4_NDSD,
+        MPEG4_P2_TS_ASP_AAC,
+        MPEG4_P2_TS_ASP_AAC_T,
+        MPEG4_P2_TS_ASP_AAC_ISO,
+        MPEG4_P2_TS_ASP_MPEG1_L3,
+        MPEG4_P2_TS_ASP_MPEG1_L3_T,
+        MPEG4_P2_TS_ASP_MPEG1_L3_ISO,
+        MPEG4_P2_TS_ASP_MPEG2_L2,
+        MPEG4_P2_TS_ASP_MPEG2_L2_T,
+        MPEG4_P2_TS_ASP_MPEG2_L2_ISO,
+        MPEG4_P2_TS_ASP_AC3,
+        MPEG4_P2_TS_ASP_AC3_T,
+        MPEG4_P2_TS_ASP_AC3_ISO,
+        AVC_TS_HD_50_LPCM_T,
+        AVC_MP4_LPCM,
+        MPEG4_P2_3GPP_SP_L0B_AAC,
+        MPEG4_P2_3GPP_SP_L0B_AMR,
+        AVC_3GPP_BL_QCIF15_AAC,
+        MPEG4_H263_3GPP_P0_L10_AMR,
+        MPEG4_H263_MP4_P0_L10_AAC
+    }
+
+    public enum TransportStreamTimestamp
+    {
+        NONE, 
+        ZERO, 
+        VALID
+    }
+}

+ 342 - 0
MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs

@@ -0,0 +1,342 @@
+using System;
+
+namespace MediaBrowser.Model.Dlna
+{
+    public class MediaFormatProfileResolver
+    {
+        public MediaFormatProfile ResolveVideoFormat(string container, string videoCodec, string audioCodec, int? width, int? height, int? bitrate, TransportStreamTimestamp timestampType)
+        {
+            if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase))
+                return ResolveVideoASFFormat(videoCodec, audioCodec, width, height, bitrate);
+            if (string.Equals(container, "mp4", StringComparison.OrdinalIgnoreCase))
+                return ResolveVideoMP4Format(videoCodec, audioCodec, width, height, bitrate);
+            if (string.Equals(container, "avi", StringComparison.OrdinalIgnoreCase))
+                return MediaFormatProfile.AVI;
+            if (string.Equals(container, "mkv", StringComparison.OrdinalIgnoreCase))
+                return MediaFormatProfile.MATROSKA;
+            if (string.Equals(container, "mpeg2ps", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
+                // MediaFormatProfile.MPEG_PS_PAL, MediaFormatProfile.MPEG_PS_NTSC
+                return MediaFormatProfile.MPEG_PS_NTSC;
+            if (string.Equals(container, "mpeg1video", StringComparison.OrdinalIgnoreCase))
+                return MediaFormatProfile.MPEG1;
+            if (string.Equals(container, "mpeg2ts", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "mpegts", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase))
+                return ResolveVideoMPEG2TSFormat(videoCodec, audioCodec, width, height, bitrate, timestampType);
+            if (string.Equals(container, "flv", StringComparison.OrdinalIgnoreCase))
+                return MediaFormatProfile.FLV;
+            if (string.Equals(container, "wtv", StringComparison.OrdinalIgnoreCase))
+                return MediaFormatProfile.WTV;
+            if (string.Equals(container, "3gp", StringComparison.OrdinalIgnoreCase))
+                return ResolveVideo3GPFormat(videoCodec, audioCodec, width, height, bitrate);
+            if (string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase))
+                return MediaFormatProfile.OGV;
+
+            throw new ArgumentException("Unsupported container: " + container);
+        }
+
+        private MediaFormatProfile ResolveVideoMPEG2TSFormat(string videoCodec, string audioCodec, int? width, int? height, int? bitrate, TransportStreamTimestamp timestampType)
+        {
+            //  String suffix = "";
+            //  if (isNoTimestamp(timestampType))
+            //    suffix = "_ISO";
+            //  else if (timestampType == TransportStreamTimestamp.VALID) {
+            //    suffix = "_T";
+            //  }
+
+            //  String resolution = "S";
+            //  if ((width.intValue() > 720) || (height.intValue() > 576)) {
+            //    resolution = "H";
+            //  }
+
+            //  if (videoCodec == VideoCodec.MPEG2)
+            //  {
+            //    List!(MediaFormatProfile) profiles = Arrays.asList(cast(MediaFormatProfile[])[ MediaFormatProfile.valueOf("MPEG_TS_SD_EU" + suffix), MediaFormatProfile.valueOf("MPEG_TS_SD_NA" + suffix), MediaFormatProfile.valueOf("MPEG_TS_SD_KO" + suffix) ]);
+
+            //    if ((timestampType == TransportStreamTimestamp.VALID) && (audioCodec == AudioCodec.AAC)) {
+            //      profiles.add(MediaFormatProfile.MPEG_TS_JP_T);
+            //    }
+            //    return profiles;
+            //  }if (videoCodec == VideoCodec.H264)
+            //  {
+            //    if (audioCodec == AudioCodec.LPCM)
+            //      return Collections.singletonList(MediaFormatProfile.AVC_TS_HD_50_LPCM_T);
+            //    if (audioCodec == AudioCodec.DTS) {
+            //      if (isNoTimestamp(timestampType)) {
+            //        return Collections.singletonList(MediaFormatProfile.AVC_TS_HD_DTS_ISO);
+            //      }
+            //      return Collections.singletonList(MediaFormatProfile.AVC_TS_HD_DTS_T);
+            //    }
+            //    if (audioCodec == AudioCodec.MP2) {
+            //      if (isNoTimestamp(timestampType)) {
+            //        return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_HP_%sD_MPEG1_L2_ISO", cast(Object[])[ resolution ])));
+            //      }
+            //      return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_HP_%sD_MPEG1_L2_T", cast(Object[])[ resolution ])));
+            //    }
+
+            //    if (audioCodec == AudioCodec.AAC)
+            //      return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_MP_%sD_AAC_MULT5%s", cast(Object[])[ resolution, suffix ])));
+            //    if (audioCodec == AudioCodec.MP3)
+            //      return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_MP_%sD_MPEG1_L3%s", cast(Object[])[ resolution, suffix ])));
+            //    if ((audioCodec is null) || (audioCodec == AudioCodec.AC3)) {
+            //      return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_MP_%sD_AC3%s", cast(Object[])[ resolution, suffix ])));
+            //    }
+            //  }
+            //  else if (videoCodec == VideoCodec.VC1) {
+            //    if ((audioCodec is null) || (audioCodec == AudioCodec.AC3))
+            //    {
+            //      if ((width.intValue() > 720) || (height.intValue() > 576)) {
+            //        return Collections.singletonList(MediaFormatProfile.VC1_TS_AP_L2_AC3_ISO);
+            //      }
+            //      return Collections.singletonList(MediaFormatProfile.VC1_TS_AP_L1_AC3_ISO);
+            //    }
+            //    if (audioCodec == AudioCodec.DTS) {
+            //      suffix = suffix.equals("_ISO") ? suffix : "_T";
+            //      return Collections.singletonList(MediaFormatProfile.valueOf(String.format("VC1_TS_HD_DTS%s", cast(Object[])[ suffix ])));
+            //    }
+            //  } else if ((videoCodec == VideoCodec.MPEG4) || (videoCodec == VideoCodec.MSMPEG4)) {
+            //    if (audioCodec == AudioCodec.AAC)
+            //      return Collections.singletonList(MediaFormatProfile.valueOf(String.format("MPEG4_P2_TS_ASP_AAC%s", cast(Object[])[ suffix ])));
+            //    if (audioCodec == AudioCodec.MP3)
+            //      return Collections.singletonList(MediaFormatProfile.valueOf(String.format("MPEG4_P2_TS_ASP_MPEG1_L3%s", cast(Object[])[ suffix ])));
+            //    if (audioCodec == AudioCodec.MP2)
+            //      return Collections.singletonList(MediaFormatProfile.valueOf(String.format("MPEG4_P2_TS_ASP_MPEG2_L2%s", cast(Object[])[ suffix ])));
+            //    if ((audioCodec is null) || (audioCodec == AudioCodec.AC3)) {
+            //      return Collections.singletonList(MediaFormatProfile.valueOf(String.format("MPEG4_P2_TS_ASP_AC3%s", cast(Object[])[ suffix ])));
+            //    }
+            //  }
+
+            throw new ArgumentException("Mpeg video file does not match any supported DLNA profile");
+        }
+
+        private MediaFormatProfile ResolveVideoMP4Format(string videoCodec, string audioCodec, int? width, int? height, int? bitrate)
+        {
+            if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
+            {
+                if (string.Equals(audioCodec, "lpcm", StringComparison.OrdinalIgnoreCase))
+                    return MediaFormatProfile.AVC_MP4_LPCM;
+                if (string.IsNullOrEmpty(audioCodec) ||
+                    string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
+                {
+                    return MediaFormatProfile.AVC_MP4_MP_SD_AC3;
+                }
+                if (string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
+                {
+                    return MediaFormatProfile.AVC_MP4_MP_SD_MPEG1_L3;
+                }
+                if (width.HasValue && height.HasValue)
+                {
+                    if ((width.Value <= 720) && (height.Value <= 576))
+                    {
+                        if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
+                            return MediaFormatProfile.AVC_MP4_MP_SD_AAC_MULT5;
+                    }
+                    else if ((width.Value <= 1280) && (height.Value <= 720))
+                    {
+                        if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
+                            return MediaFormatProfile.AVC_MP4_MP_HD_720p_AAC;
+                    }
+                    else if ((width.Value <= 1920) && (height.Value <= 1080))
+                    {
+                        if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
+                        {
+                            return MediaFormatProfile.AVC_MP4_MP_HD_1080i_AAC;
+                        }
+                    }
+                }
+            }
+            else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase) ||
+                string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
+            {
+                if (width.HasValue && height.HasValue && width.Value <= 720 && height.Value <= 576)
+                {
+                    if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
+                        return MediaFormatProfile.MPEG4_P2_MP4_ASP_AAC;
+                    if (string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase) || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
+                    {
+                        return MediaFormatProfile.MPEG4_P2_MP4_NDSD;
+                    }
+                }
+                else if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
+                {
+                    return MediaFormatProfile.MPEG4_P2_MP4_SP_L6_AAC;
+                }
+            }
+            else if (string.Equals(videoCodec, "h263", StringComparison.OrdinalIgnoreCase) && string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
+            {
+                return MediaFormatProfile.MPEG4_H263_MP4_P0_L10_AAC;
+            }
+
+            throw new ArgumentException("MP4 video file does not match any supported DLNA profile");
+        }
+
+        private MediaFormatProfile ResolveVideo3GPFormat(string videoCodec, string audioCodec, int? width, int? height, int? bitrate)
+        {
+            if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
+            {
+                if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
+                    return MediaFormatProfile.AVC_3GPP_BL_QCIF15_AAC;
+            }
+            else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase) ||
+                string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
+            {
+                if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "wma", StringComparison.OrdinalIgnoreCase))
+                    return MediaFormatProfile.MPEG4_P2_3GPP_SP_L0B_AAC;
+                if (string.Equals(audioCodec, "amrnb", StringComparison.OrdinalIgnoreCase))
+                    return MediaFormatProfile.MPEG4_P2_3GPP_SP_L0B_AMR;
+            }
+            else if (string.Equals(videoCodec, "h263", StringComparison.OrdinalIgnoreCase) && string.Equals(audioCodec, "amrnb", StringComparison.OrdinalIgnoreCase))
+            {
+                return MediaFormatProfile.MPEG4_H263_3GPP_P0_L10_AMR;
+            }
+
+            throw new ArgumentException("3GP video file does not match any supported DLNA profile");
+        }
+        private MediaFormatProfile ResolveVideoASFFormat(string videoCodec, string audioCodec, int? width, int? height, int? bitrate)
+        {
+            if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase) &&
+                (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "wma", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "wmapro", StringComparison.OrdinalIgnoreCase)))
+            {
+
+                if (width.HasValue && height.HasValue)
+                {
+                    if ((width.Value <= 720) && (height.Value <= 576))
+                    {
+                        if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "wma", StringComparison.OrdinalIgnoreCase))
+                        {
+                            return MediaFormatProfile.WMVMED_FULL;
+                        }
+                        return MediaFormatProfile.WMVMED_PRO;
+                    }
+                }
+
+                if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "wma", StringComparison.OrdinalIgnoreCase))
+                {
+                    return MediaFormatProfile.WMVHIGH_FULL;
+                }
+                return MediaFormatProfile.WMVHIGH_PRO;
+            }
+
+            if (string.Equals(videoCodec, "vc1", StringComparison.OrdinalIgnoreCase))
+            {
+                if (width.HasValue && height.HasValue)
+                {
+                    if ((width.Value <= 720) && (height.Value <= 576))
+                        return MediaFormatProfile.VC1_ASF_AP_L1_WMA;
+                    if ((width.Value <= 1280) && (height.Value <= 720))
+                        return MediaFormatProfile.VC1_ASF_AP_L2_WMA;
+                    if ((width.Value <= 1920) && (height.Value <= 1080))
+                        return MediaFormatProfile.VC1_ASF_AP_L3_WMA;
+                }
+            }
+            else if (string.Equals(videoCodec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
+            {
+                return MediaFormatProfile.DVR_MS;
+            }
+
+            throw new ArgumentException("ASF video file does not match any supported DLNA profile");
+        }
+
+        public MediaFormatProfile ResolveAudioFormat(string container, int? bitrate, int? frequency, int? channels)
+        {
+            if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase))
+                return ResolveAudioASFFormat(bitrate, frequency, channels);
+            if (string.Equals(container, "mp3", StringComparison.OrdinalIgnoreCase))
+                return MediaFormatProfile.MP3;
+            if (string.Equals(container, "lpcm", StringComparison.OrdinalIgnoreCase))
+                return ResolveAudioLPCMFormat(bitrate, frequency, channels);
+            if (string.Equals(container, "mp4", StringComparison.OrdinalIgnoreCase))
+                return ResolveAudioMP4Format(bitrate, frequency, channels);
+            if (string.Equals(container, "adts", StringComparison.OrdinalIgnoreCase))
+                return ResolveAudioADTSFormat(bitrate, frequency, channels);
+            if (string.Equals(container, "flac", StringComparison.OrdinalIgnoreCase))
+                return MediaFormatProfile.FLAC;
+            if (string.Equals(container, "oga", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase))
+                return MediaFormatProfile.OGG;
+            throw new ArgumentException("Unsupported container: " + container);
+        }
+
+        private MediaFormatProfile ResolveAudioASFFormat(int? bitrate, int? frequency, int? channels)
+        {
+            if (bitrate.HasValue && bitrate.Value <= 193)
+            {
+                return MediaFormatProfile.WMA_BASE;
+            }
+            return MediaFormatProfile.WMA_FULL;
+        }
+
+        private MediaFormatProfile ResolveAudioLPCMFormat(int? bitrate, int? frequency, int? channels)
+        {
+            if (frequency.HasValue && channels.HasValue)
+            {
+                if (frequency.Value == 44100 && channels.Value == 1)
+                {
+                    return MediaFormatProfile.LPCM16_44_MONO;
+                }
+                if (frequency.Value == 44100 && channels.Value == 2)
+                {
+                    return MediaFormatProfile.LPCM16_44_STEREO;
+                }
+                if (frequency.Value == 48000 && channels.Value == 1)
+                {
+                    return MediaFormatProfile.LPCM16_48_MONO;
+                }
+                if (frequency.Value == 48000 && channels.Value == 1)
+                {
+                    return MediaFormatProfile.LPCM16_48_STEREO;
+                }
+
+                throw new ArgumentException("Unsupported LPCM format of file %s. Only 44100 / 48000 Hz and Mono / Stereo files are allowed.");
+            }
+
+            return MediaFormatProfile.LPCM16_48_STEREO;
+        }
+
+        private MediaFormatProfile ResolveAudioMP4Format(int? bitrate, int? frequency, int? channels)
+        {
+            if (bitrate.HasValue && bitrate.Value <= 320)
+            {
+                return MediaFormatProfile.AAC_ISO_320;
+            }
+            return MediaFormatProfile.AAC_ISO;
+        }
+
+        private MediaFormatProfile ResolveAudioADTSFormat(int? bitrate, int? frequency, int? channels)
+        {
+            if (bitrate.HasValue && bitrate.Value <= 320)
+            {
+                return MediaFormatProfile.AAC_ADTS_320;
+            }
+            return MediaFormatProfile.AAC_ADTS;
+        }
+
+        public MediaFormatProfile ResolveImageFormat(string container, int? width, int? height)
+        {
+            if (string.Equals(container, "jpeg", StringComparison.OrdinalIgnoreCase) ||
+                string.Equals(container, "jpg", StringComparison.OrdinalIgnoreCase))
+                return ResolveImageJPGFormat(width, height);
+            if (string.Equals(container, "png", StringComparison.OrdinalIgnoreCase))
+                return MediaFormatProfile.PNG_LRG;
+            if (string.Equals(container, "gif", StringComparison.OrdinalIgnoreCase))
+                return MediaFormatProfile.GIF_LRG;
+            if (string.Equals(container, "raw", StringComparison.OrdinalIgnoreCase))
+                return MediaFormatProfile.RAW;
+
+            throw new ArgumentException("Unsupported container: " + container);
+        }
+
+        private MediaFormatProfile ResolveImageJPGFormat(int? width, int? height)
+        {
+            if (width.HasValue && height.HasValue)
+            {
+                if ((width.Value <= 640) && (height.Value <= 480))
+                    return MediaFormatProfile.JPEG_SM;
+
+                if ((width.Value <= 1024) && (height.Value <= 768))
+                {
+                    return MediaFormatProfile.JPEG_MED;
+                }
+            }
+
+            return MediaFormatProfile.JPEG_LRG;
+        }
+    }
+}

+ 6 - 2
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -81,7 +81,8 @@ namespace MediaBrowser.Model.Dlna
             {
                 ItemId = options.ItemId,
                 MediaType = DlnaProfileType.Audio,
-                MediaSourceId = item.Id
+                MediaSourceId = item.Id,
+                RunTimeTicks = item.RunTimeTicks
             };
 
             var audioStream = item.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
@@ -114,6 +115,7 @@ namespace MediaBrowser.Model.Dlna
             if (transcodingProfile != null)
             {
                 playlistItem.IsDirectStream = false;
+                playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
                 playlistItem.Container = transcodingProfile.Container;
                 playlistItem.AudioCodec = transcodingProfile.AudioCodec;
 
@@ -150,7 +152,8 @@ namespace MediaBrowser.Model.Dlna
             {
                 ItemId = options.ItemId,
                 MediaType = DlnaProfileType.Video,
-                MediaSourceId = item.Id
+                MediaSourceId = item.Id,
+                RunTimeTicks = item.RunTimeTicks
             };
 
             var audioStream = item.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
@@ -193,6 +196,7 @@ namespace MediaBrowser.Model.Dlna
             {
                 playlistItem.IsDirectStream = false;
                 playlistItem.Container = transcodingProfile.Container;
+                playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
                 playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',').FirstOrDefault();
                 playlistItem.VideoCodec = transcodingProfile.VideoCodec;
 

+ 4 - 0
MediaBrowser.Model/Dlna/StreamInfo.cs

@@ -46,6 +46,10 @@ namespace MediaBrowser.Model.Dlna
         public string DeviceProfileId { get; set; }
         public string DeviceId { get; set; }
 
+        public long? RunTimeTicks { get; set; }
+
+        public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
+
         public string ToUrl(string baseUrl)
         {
             return ToDlnaUrl(baseUrl);

+ 9 - 0
MediaBrowser.Model/Dto/MediaVersionInfo.cs

@@ -10,6 +10,7 @@ namespace MediaBrowser.Model.Dto
         public string Path { get; set; }
 
         public string Container { get; set; }
+        public long? Size { get; set; }
 
         public LocationType LocationType { get; set; }
         
@@ -25,6 +26,14 @@ namespace MediaBrowser.Model.Dto
         
         public List<MediaStream> MediaStreams { get; set; }
 
+        public List<string> Formats { get; set; }
+        
         public int? Bitrate { get; set; }
+
+        public MediaSourceInfo()
+        {
+            Formats = new List<string>();
+            MediaStreams = new List<MediaStream>();
+        }
     }
 }

+ 12 - 0
MediaBrowser.Model/Entities/BaseItemInfo.cs

@@ -52,6 +52,18 @@ namespace MediaBrowser.Model.Entities
         /// </summary>
         /// <value>The primary image item identifier.</value>
         public string PrimaryImageItemId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the logo image tag.
+        /// </summary>
+        /// <value>The logo image tag.</value>
+        public Guid? LogoImageTag { get; set; }
+
+        /// <summary>
+        /// Gets or sets the logo item identifier.
+        /// </summary>
+        /// <value>The logo item identifier.</value>
+        public string LogoItemId { get; set; }
         
         /// <summary>
         /// Gets or sets the thumb image tag.

+ 3 - 0
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -72,6 +72,9 @@
     <Compile Include="Dlna\DeviceProfile.cs" />
     <Compile Include="Dlna\DeviceProfileInfo.cs" />
     <Compile Include="Dlna\DirectPlayProfile.cs" />
+    <Compile Include="Dlna\DlnaMaps.cs" />
+    <Compile Include="Dlna\MediaFormatProfile.cs" />
+    <Compile Include="Dlna\MediaFormatProfileResolver.cs" />
     <Compile Include="Dlna\ResponseProfile.cs" />
     <Compile Include="Dlna\StreamBuilder.cs" />
     <Compile Include="Dlna\StreamInfo.cs" />

+ 13 - 0
MediaBrowser.Model/Session/PlaybackReports.cs

@@ -90,6 +90,19 @@ namespace MediaBrowser.Model.Session
         /// </summary>
         /// <value>The volume level.</value>
         public int? VolumeLevel { get; set; }
+
+        /// <summary>
+        /// Gets or sets the play method.
+        /// </summary>
+        /// <value>The play method.</value>
+        public PlayMethod PlayMethod { get; set; }
+    }
+
+    public enum PlayMethod
+    {
+        Transcode = 0,
+        DirectStream = 1,
+        DirectPlay = 2
     }
 
     /// <summary>

+ 6 - 0
MediaBrowser.Model/Session/SessionInfoDto.cs

@@ -233,5 +233,11 @@ namespace MediaBrowser.Model.Session
         /// </summary>
         /// <value>The now playing media version identifier.</value>
         public string MediaSourceId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the play method.
+        /// </summary>
+        /// <value>The play method.</value>
+        public PlayMethod? PlayMethod { get; set; }
     }
 }

+ 20 - 2
MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs

@@ -121,9 +121,27 @@ namespace MediaBrowser.Providers.MediaInfo
                 }
             }
 
-            if (data.format.tags != null)
+            if (data.format != null)
             {
-                FetchDataFromTags(audio, data.format.tags);
+                audio.FormatName = data.format.format_name;
+
+                var extension = (Path.GetExtension(audio.Path) ?? string.Empty).TrimStart('.');
+
+                audio.Container = extension;
+
+                if (!string.IsNullOrEmpty(data.format.size))
+                {
+                    audio.Size = long.Parse(data.format.size , _usCulture);
+                }
+                else
+                {
+                    audio.Size = null;
+                }
+
+                if (data.format.tags != null)
+                {
+                    FetchDataFromTags(audio, data.format.tags);
+                }
             }
 
             return _itemRepo.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken);

+ 24 - 0
MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs

@@ -18,6 +18,7 @@ using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
 
 namespace MediaBrowser.Providers.MediaInfo
 {
@@ -159,6 +160,29 @@ namespace MediaBrowser.Providers.MediaInfo
                 {
                     video.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks;
                 }
+
+                video.FormatName = (data.format.format_name ?? string.Empty)
+                    .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);
+
+                if (video.VideoType == VideoType.VideoFile)
+                {
+                    var extension = (Path.GetExtension(video.Path) ?? string.Empty).TrimStart('.');
+
+                    video.Container = extension;
+                }
+                else
+                {
+                    video.Container = null;
+                }
+
+                if (!string.IsNullOrEmpty(data.format.size))
+                {
+                    video.Size = long.Parse(data.format.size, _usCulture);
+                }
+                else
+                {
+                    video.Size = null;
+                }
             }
 
             var mediaStreams = MediaEncoderHelpers.GetMediaInfo(data).MediaStreams;

+ 21 - 9
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -1234,15 +1234,21 @@ namespace MediaBrowser.Server.Implementations.Dto
                 Path = GetMappedPath(i),
                 RunTimeTicks = i.RunTimeTicks,
                 Video3DFormat = i.Video3DFormat,
-                VideoType = i.VideoType
+                VideoType = i.VideoType,
+                Container = i.Container,
+                Size = i.Size,
+                Formats = (i.FormatName ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList()
             };
 
-            if (i.VideoType == VideoType.VideoFile || i.VideoType == VideoType.Iso)
+            if (string.IsNullOrEmpty(info.Container))
             {
-                var locationType = i.LocationType;
-                if (!string.IsNullOrWhiteSpace(i.Path) && locationType != LocationType.Remote && locationType != LocationType.Virtual)
+                if (i.VideoType == VideoType.VideoFile || i.VideoType == VideoType.Iso)
                 {
-                    info.Container = Path.GetExtension(i.Path).TrimStart('.');
+                    var locationType = i.LocationType;
+                    if (!string.IsNullOrWhiteSpace(i.Path) && locationType != LocationType.Remote && locationType != LocationType.Virtual)
+                    {
+                        info.Container = Path.GetExtension(i.Path).TrimStart('.');
+                    }
                 }
             }
 
@@ -1265,13 +1271,19 @@ namespace MediaBrowser.Server.Implementations.Dto
                 MediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery { ItemId = i.Id }).ToList(),
                 Name = i.Name,
                 Path = GetMappedPath(i),
-                RunTimeTicks = i.RunTimeTicks
+                RunTimeTicks = i.RunTimeTicks,
+                Container = i.Container,
+                Size = i.Size,
+                Formats = (i.FormatName ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList()
             };
 
-            var locationType = i.LocationType;
-            if (!string.IsNullOrWhiteSpace(i.Path) && locationType != LocationType.Remote && locationType != LocationType.Virtual)
+            if (string.IsNullOrEmpty(info.Container))
             {
-                info.Container = Path.GetExtension(i.Path).TrimStart('.');
+                var locationType = i.LocationType;
+                if (!string.IsNullOrWhiteSpace(i.Path) && locationType != LocationType.Remote && locationType != LocationType.Virtual)
+                {
+                    info.Container = Path.GetExtension(i.Path).TrimStart('.');
+                }
             }
 
             var bitrate = info.MediaStreams.Where(m => m.Type == MediaStreamType.Audio).Select(m => m.BitRate ?? 0).Sum();

+ 0 - 9
MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs

@@ -158,15 +158,6 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
                             continue;
                         }
 
-                        if (video.VideoType == VideoType.BluRay)
-                        {
-                            // Can only extract reliably on single file blurays
-                            if (video.PlayableStreamFileNames == null || video.PlayableStreamFileNames.Count != 1)
-                            {
-                                continue;
-                            }
-                        }
-
                         // Add some time for the first chapter to make sure we don't end up with a black image
                         var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(FirstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks);
 

+ 13 - 1
MediaBrowser.Server.Implementations/Session/SessionManager.cs

@@ -294,6 +294,7 @@ namespace MediaBrowser.Server.Implementations.Session
             session.PlayState.VolumeLevel = info.VolumeLevel;
             session.PlayState.AudioStreamIndex = info.AudioStreamIndex;
             session.PlayState.SubtitleStreamIndex = info.SubtitleStreamIndex;
+            session.PlayState.PlayMethod = info.PlayMethod;
         }
 
         /// <summary>
@@ -1253,8 +1254,8 @@ namespace MediaBrowser.Server.Implementations.Session
             }
 
             var backropItem = item.HasImage(ImageType.Backdrop) ? item : null;
-
             var thumbItem = item.HasImage(ImageType.Thumb) ? item : null;
+            var logoItem = item.HasImage(ImageType.Logo) ? item : null;
 
             if (thumbItem == null)
             {
@@ -1292,6 +1293,11 @@ namespace MediaBrowser.Server.Implementations.Session
                 thumbItem = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Thumb));
             }
 
+            if (logoItem == null)
+            {
+                logoItem = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Logo));
+            }
+
             if (thumbItem != null)
             {
                 info.ThumbImageTag = GetImageCacheTag(thumbItem, ImageType.Thumb);
@@ -1304,6 +1310,12 @@ namespace MediaBrowser.Server.Implementations.Session
                 info.BackdropItemId = GetDtoId(backropItem);
             }
 
+            if (logoItem != null)
+            {
+                info.LogoImageTag = GetImageCacheTag(logoItem, ImageType.Logo);
+                info.LogoItemId = GetDtoId(logoItem);
+            }
+
             return info;
         }
 

+ 1 - 1
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -506,7 +506,7 @@ namespace MediaBrowser.ServerApplication
             var appThemeManager = new AppThemeManager(ApplicationPaths, FileSystemManager, JsonSerializer, Logger);
             RegisterSingleInstance<IAppThemeManager>(appThemeManager);
 
-            var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("DLNA"), JsonSerializer, UserManager, LibraryManager);
+            var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("DLNA"), JsonSerializer, UserManager, LibraryManager, DtoService, ImageProcessor, UserDataManager);
             RegisterSingleInstance<IDlnaManager>(dlnaManager);
 
             var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor);