Browse Source

add basic dlna server browsing

Luke Pulverenti 11 năm trước cách đây
mục cha
commit
7f320ce063
38 tập tin đã thay đổi với 1367 bổ sung859 xóa
  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);