Просмотр исходного кода

Merge pull request #7325 from eyezak/issue/6450

Cody Robibero 3 лет назад
Родитель
Сommit
e6df698df1
51 измененных файлов с 5822 добавлено и 629 удалено
  1. 1 9
      Jellyfin.Api/Controllers/MediaInfoController.cs
  2. 3 2
      Jellyfin.Api/Controllers/UniversalAudioController.cs
  3. 48 135
      Jellyfin.Api/Helpers/MediaInfoHelper.cs
  4. 1 1
      Jellyfin.Api/Helpers/TranscodingJobHelper.cs
  5. 5 11
      MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
  6. 9 10
      MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
  7. 2 0
      MediaBrowser.Model/Dlna/AudioOptions.cs
  8. 484 424
      MediaBrowser.Model/Dlna/StreamBuilder.cs
  9. 2 3
      MediaBrowser.Model/Dlna/StreamInfo.cs
  10. 2 0
      MediaBrowser.Model/Dlna/VideoOptions.cs
  11. 2 2
      MediaBrowser.Model/Dto/MediaSourceInfo.cs
  12. 1 0
      MediaBrowser.Model/Properties/AssemblyInfo.cs
  13. 36 24
      MediaBrowser.Model/Session/TranscodeReason.cs
  14. 1 8
      MediaBrowser.Model/Session/TranscodingInfo.cs
  15. 36 0
      src/Jellyfin.Extensions/Json/Converters/JsonFlagEnumConverter.cs
  16. 24 0
      src/Jellyfin.Extensions/Json/Converters/JsonFlagEnumConverterFactory.cs
  17. 1 0
      src/Jellyfin.Extensions/Json/JsonDefaults.cs
  18. 6 0
      tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
  19. 466 0
      tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs
  20. 332 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-AndroidPixel.json
  21. 430 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json
  22. 448 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Chrome.json
  23. 90 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-DirectMedia.json
  24. 441 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Firefox.json
  25. 137 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json
  26. 137 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-LowBandwidth.json
  27. 9 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Null.json
  28. 211 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-RokuSSPlus.json
  29. 211 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json
  30. 357 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-SafariNext.json
  31. 139 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-TranscodeMedia.json
  32. 189 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Yatse.json
  33. 189 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Yatse2.json
  34. 73 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-av1-aac-srt-2600k.json
  35. 72 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-av1-vorbis-srt-2600k.json
  36. 73 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-aac-srt-2600k.json
  37. 72 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-ac3-srt-2600k.json
  38. 73 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-srt-2600k.json
  39. 72 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-vtt-2600k.json
  40. 72 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-srt-2600k.json
  41. 72 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-vtt-2600k.json
  42. 87 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aac-srt-2600k.json
  43. 87 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacDef-srt-2600k.json
  44. 89 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacExt-srt-2600k.json
  45. 71 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-srt-2600k.json
  46. 75 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-aac-srt-15200k.json
  47. 89 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aac-srt-15200k.json
  48. 91 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aacExt-srt-15200k.json
  49. 74 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-srt-15200k.json
  50. 102 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-raw.json
  51. 28 0
      tests/Jellyfin.Extensions.Tests/Json/Converters/JsonFlagEnumTests.cs

+ 1 - 9
Jellyfin.Api/Controllers/MediaInfoController.cs

@@ -126,7 +126,7 @@ namespace Jellyfin.Api.Controllers
             var authInfo = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
 
             var profile = playbackInfoDto?.DeviceProfile;
-            _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", profile);
+            _logger.LogDebug("GetPostedPlaybackInfo profile: {@Profile}", profile);
 
             if (profile == null)
             {
@@ -225,14 +225,6 @@ namespace Jellyfin.Api.Controllers
                 }
             }
 
-            if (info.MediaSources != null)
-            {
-                foreach (var mediaSource in info.MediaSources)
-                {
-                    _mediaInfoHelper.NormalizeMediaSourceContainer(mediaSource, profile!, DlnaProfileType.Video);
-                }
-            }
-
             return info;
         }
 

+ 3 - 2
Jellyfin.Api/Controllers/UniversalAudioController.cs

@@ -16,6 +16,7 @@ using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Session;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
@@ -223,7 +224,7 @@ namespace Jellyfin.Api.Controllers
                     DeInterlace = false,
                     RequireNonAnamorphic = false,
                     EnableMpegtsM2TsMode = false,
-                    TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(',', mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
+                    TranscodeReasons = mediaSource.TranscodeReasons == 0 ? null : mediaSource.TranscodeReasons.ToString(),
                     Context = EncodingContext.Static,
                     StreamOptions = new Dictionary<string, string>(),
                     EnableAdaptiveBitrateStreaming = true
@@ -254,7 +255,7 @@ namespace Jellyfin.Api.Controllers
                 CopyTimestamps = true,
                 StartTimeTicks = startTimeTicks,
                 SubtitleMethod = SubtitleDeliveryMethod.Embed,
-                TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(',', mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
+                TranscodeReasons = mediaSource.TranscodeReasons == 0 ? null : mediaSource.TranscodeReasons.ToString(),
                 Context = EncodingContext.Static
             };
 

+ 48 - 135
Jellyfin.Api/Helpers/MediaInfoHelper.cs

@@ -191,7 +191,9 @@ namespace Jellyfin.Api.Helpers
                 DeviceId = auth.DeviceId,
                 ItemId = item.Id,
                 Profile = profile,
-                MaxAudioChannels = maxAudioChannels
+                MaxAudioChannels = maxAudioChannels,
+                AllowAudioStreamCopy = allowAudioStreamCopy,
+                AllowVideoStreamCopy = allowVideoStreamCopy
             };
 
             if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase))
@@ -208,7 +210,7 @@ namespace Jellyfin.Api.Helpers
                 mediaSource.SupportsDirectPlay = false;
             }
 
-            if (!enableDirectStream)
+            if (!enableDirectStream || !allowVideoStreamCopy)
             {
                 mediaSource.SupportsDirectStream = false;
             }
@@ -235,168 +237,79 @@ namespace Jellyfin.Api.Helpers
                     user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding));
             }
 
-            // Beginning of Playback Determination: Attempt DirectPlay first
-            if (mediaSource.SupportsDirectPlay)
-            {
-                if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
-                {
-                    mediaSource.SupportsDirectPlay = false;
-                }
-                else
-                {
-                    var supportsDirectStream = mediaSource.SupportsDirectStream;
-
-                    // Dummy this up to fool StreamBuilder
-                    mediaSource.SupportsDirectStream = true;
-                    options.MaxBitrate = maxBitrate;
+            options.MaxBitrate = GetMaxBitrate(maxBitrate, user, ipAddress);
 
-                    if (item is Audio)
-                    {
-                        if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
-                        {
-                            options.ForceDirectPlay = true;
-                        }
-                    }
-                    else if (item is Video)
-                    {
-                        if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
-                            && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
-                            && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
-                        {
-                            options.ForceDirectPlay = true;
-                        }
-                    }
+            if (!options.ForceDirectStream)
+            {
+                // direct-stream http streaming is currently broken
+                options.EnableDirectStream = false;
+            }
 
-                    // The MediaSource supports direct stream, now test to see if the client supports it
-                    var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
-                        ? streamBuilder.BuildAudioItem(options)
-                        : streamBuilder.BuildVideoItem(options);
+            // Beginning of Playback Determination
+            var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
+                ? streamBuilder.BuildAudioItem(options)
+                : streamBuilder.BuildVideoItem(options);
 
-                    if (streamInfo == null || !streamInfo.IsDirectStream)
-                    {
-                        mediaSource.SupportsDirectPlay = false;
-                    }
+            if (streamInfo != null)
+            {
+                streamInfo.PlaySessionId = playSessionId;
+                streamInfo.StartPositionTicks = startTimeTicks;
 
-                    // Set this back to what it was
-                    mediaSource.SupportsDirectStream = supportsDirectStream;
+                mediaSource.SupportsDirectPlay = streamInfo.PlayMethod == PlayMethod.DirectPlay;
+                // Players do not handle this being set according to PlayMethod
+                mediaSource.SupportsDirectStream = options.EnableDirectStream ? streamInfo.PlayMethod == PlayMethod.DirectPlay || streamInfo.PlayMethod == PlayMethod.DirectStream : streamInfo.PlayMethod == PlayMethod.DirectPlay;
+                mediaSource.SupportsTranscoding = streamInfo.PlayMethod == PlayMethod.DirectStream || mediaSource.TranscodingContainer != null;
 
-                    if (streamInfo != null)
+                if (item is Audio)
+                {
+                    if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
                     {
-                        SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
-                        mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex;
+                        mediaSource.SupportsTranscoding = false;
                     }
                 }
-            }
-
-            if (mediaSource.SupportsDirectStream)
-            {
-                if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
+                else if (item is Video)
                 {
-                    mediaSource.SupportsDirectStream = false;
-                }
-                else
-                {
-                    options.MaxBitrate = GetMaxBitrate(maxBitrate, user, ipAddress);
-
-                    if (item is Audio)
+                    if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
+                        && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
+                        && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
                     {
-                        if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
-                        {
-                            options.ForceDirectStream = true;
-                        }
-                    }
-                    else if (item is Video)
-                    {
-                        if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
-                            && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
-                            && user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
-                        {
-                            options.ForceDirectStream = true;
-                        }
-                    }
-
-                    // The MediaSource supports direct stream, now test to see if the client supports it
-                    var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
-                        ? streamBuilder.BuildAudioItem(options)
-                        : streamBuilder.BuildVideoItem(options);
-
-                    if (streamInfo == null || !streamInfo.IsDirectStream)
-                    {
-                        mediaSource.SupportsDirectStream = false;
-                    }
-
-                    if (streamInfo != null)
-                    {
-                        SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
-                        mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex;
+                        mediaSource.SupportsTranscoding = false;
                     }
                 }
-            }
-
-            if (mediaSource.SupportsTranscoding)
-            {
-                options.MaxBitrate = GetMaxBitrate(maxBitrate, user, ipAddress);
-
-                // The MediaSource supports direct stream, now test to see if the client supports it
-                var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
-                    ? streamBuilder.BuildAudioItem(options)
-                    : streamBuilder.BuildVideoItem(options);
 
                 if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
                 {
-                    if (streamInfo != null)
-                    {
-                        streamInfo.PlaySessionId = playSessionId;
-                        streamInfo.StartPositionTicks = startTimeTicks;
-                        mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
-                        mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
-                        mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
-                        mediaSource.TranscodingContainer = streamInfo.Container;
-                        mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
-
-                        // Do this after the above so that StartPositionTicks is set
-                        SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
-                        mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex;
-                    }
+                    mediaSource.SupportsDirectPlay = false;
+                    mediaSource.SupportsDirectStream = false;
+
+                    mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
+                    mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
+                    mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
+                    mediaSource.TranscodingContainer = streamInfo.Container;
+                    mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
                 }
                 else
                 {
-                    if (streamInfo != null)
+                    if (mediaSource.SupportsTranscoding || mediaSource.SupportsDirectStream)
                     {
-                        streamInfo.PlaySessionId = playSessionId;
+                        streamInfo.PlayMethod = PlayMethod.Transcode;
+                        mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
 
-                        if (streamInfo.PlayMethod == PlayMethod.Transcode)
+                        if (!allowVideoStreamCopy)
                         {
-                            streamInfo.StartPositionTicks = startTimeTicks;
-                            mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
-
-                            if (!allowVideoStreamCopy)
-                            {
-                                mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
-                            }
-
-                            if (!allowAudioStreamCopy)
-                            {
-                                mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
-                            }
-
-                            mediaSource.TranscodingContainer = streamInfo.Container;
-                            mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
+                            mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
                         }
 
                         if (!allowAudioStreamCopy)
                         {
                             mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
                         }
-
-                        mediaSource.TranscodingContainer = streamInfo.Container;
-                        mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
-
-                        // Do this after the above so that StartPositionTicks is set
-                        SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
-                        mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex;
                     }
                 }
+
+                // Do this after the above so that StartPositionTicks is set
+                SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+                mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex;
             }
 
             foreach (var attachment in mediaSource.MediaAttachments)

+ 1 - 1
Jellyfin.Api/Helpers/TranscodingJobHelper.cs

@@ -479,7 +479,7 @@ namespace Jellyfin.Api.Helpers
                     IsAudioDirect = EncodingHelper.IsCopyCodec(state.OutputAudioCodec),
                     IsVideoDirect = EncodingHelper.IsCopyCodec(state.OutputVideoCodec),
                     HardwareAccelerationType = hardwareAccelerationType,
-                    TranscodeReasons = state.TranscodeReasons
+                    TranscodeReason = state.TranscodeReason
                 });
             }
         }

+ 5 - 11
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

@@ -1799,7 +1799,7 @@ namespace MediaBrowser.Controller.MediaEncoding
                 return false;
             }
 
-            return request.EnableAutoStreamCopy;
+            return true;
         }
 
         public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, IEnumerable<string> supportedAudioCodecs)
@@ -1856,17 +1856,11 @@ namespace MediaBrowser.Controller.MediaEncoding
             }
 
             // Video bitrate must fall within requested value
-            if (request.AudioBitRate.HasValue)
+            if (request.AudioBitRate.HasValue
+                && audioStream.BitDepth.HasValue
+                && audioStream.BitRate.Value > request.AudioBitRate.Value)
             {
-                if (!audioStream.BitRate.HasValue || audioStream.BitRate.Value <= 0)
-                {
-                    return false;
-                }
-
-                if (audioStream.BitRate.Value > request.AudioBitRate.Value)
-                {
-                    return false;
-                }
+                return false;
             }
 
             return request.EnableAutoStreamCopy;

+ 9 - 10
MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs

@@ -6,6 +6,7 @@ using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
+using System.Text.Json.Serialization;
 using Jellyfin.Data.Entities;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Drawing;
@@ -23,7 +24,7 @@ namespace MediaBrowser.Controller.MediaEncoding
         public int? OutputAudioBitrate;
         public int? OutputAudioChannels;
 
-        private TranscodeReason[] _transcodeReasons = null;
+        private TranscodeReason? _transcodeReasons = null;
 
         public EncodingJobInfo(TranscodingJobType jobType)
         {
@@ -34,25 +35,23 @@ namespace MediaBrowser.Controller.MediaEncoding
             SupportedSubtitleCodecs = Array.Empty<string>();
         }
 
-        public TranscodeReason[] TranscodeReasons
+        public TranscodeReason TranscodeReason
         {
             get
             {
-                if (_transcodeReasons == null)
+                if (!_transcodeReasons.HasValue)
                 {
                     if (BaseRequest.TranscodeReasons == null)
                     {
-                        return Array.Empty<TranscodeReason>();
+                        _transcodeReasons = 0;
+                        return 0;
                     }
 
-                    _transcodeReasons = BaseRequest.TranscodeReasons
-                        .Split(',')
-                        .Where(i => !string.IsNullOrEmpty(i))
-                        .Select(v => (TranscodeReason)Enum.Parse(typeof(TranscodeReason), v, true))
-                        .ToArray();
+                    _ = Enum.TryParse<TranscodeReason>(BaseRequest.TranscodeReasons, out var reason);
+                    _transcodeReasons = reason;
                 }
 
-                return _transcodeReasons;
+                return _transcodeReasons.Value;
             }
         }
 

+ 2 - 0
MediaBrowser.Model/Dlna/AudioOptions.cs

@@ -27,6 +27,8 @@ namespace MediaBrowser.Model.Dlna
 
         public bool ForceDirectStream { get; set; }
 
+        public bool AllowAudioStreamCopy { get; set; }
+
         public Guid ItemId { get; set; }
 
         public MediaSourceInfo[] MediaSources { get; set; }

Разница между файлами не показана из-за своего большого размера
+ 484 - 424
MediaBrowser.Model/Dlna/StreamBuilder.cs


+ 2 - 3
MediaBrowser.Model/Dlna/StreamInfo.cs

@@ -23,7 +23,6 @@ namespace MediaBrowser.Model.Dlna
             AudioCodecs = Array.Empty<string>();
             VideoCodecs = Array.Empty<string>();
             SubtitleCodecs = Array.Empty<string>();
-            TranscodeReasons = Array.Empty<TranscodeReason>();
             StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
         }
 
@@ -103,7 +102,7 @@ namespace MediaBrowser.Model.Dlna
 
         public string PlaySessionId { get; set; }
 
-        public TranscodeReason[] TranscodeReasons { get; set; }
+        public TranscodeReason TranscodeReasons { get; set; }
 
         public Dictionary<string, string> StreamOptions { get; private set; }
 
@@ -799,7 +798,7 @@ namespace MediaBrowser.Model.Dlna
 
             if (!item.IsDirectStream)
             {
-                list.Add(new NameValuePair("TranscodeReasons", string.Join(',', item.TranscodeReasons.Distinct())));
+                list.Add(new NameValuePair("TranscodeReasons", item.TranscodeReasons.ToString()));
             }
 
             return list;

+ 2 - 0
MediaBrowser.Model/Dlna/VideoOptions.cs

@@ -10,5 +10,7 @@ namespace MediaBrowser.Model.Dlna
         public int? AudioStreamIndex { get; set; }
 
         public int? SubtitleStreamIndex { get; set; }
+
+        public bool AllowVideoStreamCopy { get; set; }
     }
 }

+ 2 - 2
MediaBrowser.Model/Dto/MediaSourceInfo.cs

@@ -109,7 +109,7 @@ namespace MediaBrowser.Model.Dto
         public int? AnalyzeDurationMs { get; set; }
 
         [JsonIgnore]
-        public TranscodeReason[] TranscodeReasons { get; set; }
+        public TranscodeReason TranscodeReasons { get; set; }
 
         public int? DefaultAudioStreamIndex { get; set; }
 
@@ -161,7 +161,7 @@ namespace MediaBrowser.Model.Dto
 
         public MediaStream GetDefaultAudioStream(int? defaultIndex)
         {
-            if (defaultIndex.HasValue)
+            if (defaultIndex.HasValue && defaultIndex != -1)
             {
                 var val = defaultIndex.Value;
 

+ 1 - 0
MediaBrowser.Model/Properties/AssemblyInfo.cs

@@ -16,6 +16,7 @@ using System.Runtime.InteropServices;
 [assembly: AssemblyCulture("")]
 [assembly: NeutralResourcesLanguage("en")]
 [assembly: InternalsVisibleTo("Jellyfin.Model.Tests")]
+[assembly: InternalsVisibleTo("Jellyfin.Dlna.Tests")]
 
 // Setting ComVisible to false makes the types in this assembly not visible
 // to COM components.  If you need to access a type in this assembly from

+ 36 - 24
MediaBrowser.Model/Session/TranscodeReason.cs

@@ -1,32 +1,44 @@
 #pragma warning disable CS1591
 
+using System;
+
 namespace MediaBrowser.Model.Session
 {
+    [Flags]
     public enum TranscodeReason
     {
-        ContainerNotSupported = 0,
-        VideoCodecNotSupported = 1,
-        AudioCodecNotSupported = 2,
-        ContainerBitrateExceedsLimit = 3,
-        AudioBitrateNotSupported = 4,
-        AudioChannelsNotSupported = 5,
-        VideoResolutionNotSupported = 6,
-        UnknownVideoStreamInfo = 7,
-        UnknownAudioStreamInfo = 8,
-        AudioProfileNotSupported = 9,
-        AudioSampleRateNotSupported = 10,
-        AnamorphicVideoNotSupported = 11,
-        InterlacedVideoNotSupported = 12,
-        SecondaryAudioNotSupported = 13,
-        RefFramesNotSupported = 14,
-        VideoBitDepthNotSupported = 15,
-        VideoBitrateNotSupported = 16,
-        VideoFramerateNotSupported = 17,
-        VideoLevelNotSupported = 18,
-        VideoProfileNotSupported = 19,
-        AudioBitDepthNotSupported = 20,
-        SubtitleCodecNotSupported = 21,
-        DirectPlayError = 22,
-        AudioIsExternal = 23
+        // Primary
+        ContainerNotSupported = 1 << 0,
+        VideoCodecNotSupported = 1 << 1,
+        AudioCodecNotSupported = 1 << 2,
+        SubtitleCodecNotSupported = 1 << 3,
+        AudioIsExternal = 1 << 4,
+        SecondaryAudioNotSupported = 1 << 5,
+
+        // Video Constraints
+        VideoProfileNotSupported = 1 << 6,
+        VideoLevelNotSupported = 1 << 7,
+        VideoResolutionNotSupported = 1 << 8,
+        VideoBitDepthNotSupported = 1 << 9,
+        VideoFramerateNotSupported = 1 << 10,
+        RefFramesNotSupported = 1 << 11,
+        AnamorphicVideoNotSupported = 1 << 12,
+        InterlacedVideoNotSupported = 1 << 13,
+
+        // Audio Constraints
+        AudioChannelsNotSupported = 1 << 14,
+        AudioProfileNotSupported = 1 << 15,
+        AudioSampleRateNotSupported = 1 << 16,
+        AudioBitDepthNotSupported = 1 << 17,
+
+        // Bitrate Constraints
+        ContainerBitrateExceedsLimit = 1 << 18,
+        VideoBitrateNotSupported = 1 << 19,
+        AudioBitrateNotSupported = 1 << 20,
+
+        // Errors
+        UnknownVideoStreamInfo = 1 << 21,
+        UnknownAudioStreamInfo = 1 << 22,
+        DirectPlayError = 1 << 23,
     }
 }

+ 1 - 8
MediaBrowser.Model/Session/TranscodingInfo.cs

@@ -1,17 +1,10 @@
 #nullable disable
 #pragma warning disable CS1591
 
-using System;
-
 namespace MediaBrowser.Model.Session
 {
     public class TranscodingInfo
     {
-        public TranscodingInfo()
-        {
-            TranscodeReasons = Array.Empty<TranscodeReason>();
-        }
-
         public string AudioCodec { get; set; }
 
         public string VideoCodec { get; set; }
@@ -36,6 +29,6 @@ namespace MediaBrowser.Model.Session
 
         public HardwareEncodingType? HardwareAccelerationType { get; set; }
 
-        public TranscodeReason[] TranscodeReasons { get; set; }
+        public TranscodeReason TranscodeReason { get; set; }
     }
 }

+ 36 - 0
src/Jellyfin.Extensions/Json/Converters/JsonFlagEnumConverter.cs

@@ -0,0 +1,36 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Jellyfin.Extensions.Json.Converters;
+
+/// <summary>
+/// Enum flag to json array converter.
+/// </summary>
+/// <typeparam name="T">The type of enum.</typeparam>
+public class JsonFlagEnumConverter<T> : JsonConverter<T>
+    where T : struct, Enum
+{
+    private static readonly T[] _enumValues = Enum.GetValues<T>();
+
+    /// <inheritdoc />
+    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+    {
+        throw new NotImplementedException();
+    }
+
+    /// <inheritdoc />
+    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
+    {
+        writer.WriteStartArray();
+        foreach (var enumValue in _enumValues)
+        {
+            if (value.HasFlag(enumValue))
+            {
+                writer.WriteStringValue(enumValue.ToString());
+            }
+        }
+
+        writer.WriteEndArray();
+    }
+}

+ 24 - 0
src/Jellyfin.Extensions/Json/Converters/JsonFlagEnumConverterFactory.cs

@@ -0,0 +1,24 @@
+using System;
+using System.Reflection;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Jellyfin.Extensions.Json.Converters;
+
+/// <summary>
+/// Json flag enum converter factory.
+/// </summary>
+public class JsonFlagEnumConverterFactory : JsonConverterFactory
+{
+    /// <inheritdoc />
+    public override bool CanConvert(Type typeToConvert)
+    {
+        return typeToConvert.IsEnum && typeToConvert.IsDefined(typeof(FlagsAttribute));
+    }
+
+    /// <inheritdoc />
+    public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
+    {
+        return (JsonConverter?)Activator.CreateInstance(typeof(JsonFlagEnumConverter<>).MakeGenericType(typeToConvert));
+    }
+}

+ 1 - 0
src/Jellyfin.Extensions/Json/JsonDefaults.cs

@@ -36,6 +36,7 @@ namespace Jellyfin.Extensions.Json
                 new JsonGuidConverter(),
                 new JsonNullableGuidConverter(),
                 new JsonVersionConverter(),
+                new JsonFlagEnumConverterFactory(),
                 new JsonStringEnumConverter(),
                 new JsonNullableStructConverterFactory(),
                 new JsonBoolNumberConverter(),

+ 6 - 0
tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj

@@ -14,6 +14,12 @@
     <PackageReference Include="coverlet.collector" Version="3.1.2" />
   </ItemGroup>
 
+  <ItemGroup>
+    <None Include="Test Data\**\*.*">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+  </ItemGroup>
+
   <!-- Code Analyzers -->
   <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
     <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3">

+ 466 - 0
tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs

@@ -0,0 +1,466 @@
+using System;
+using System.Collections.Specialized;
+using System.IO;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Jellyfin.Extensions.Json;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Session;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.MediaBrowser.Model.Tests
+{
+    public class StreamBuilderTests
+    {
+        [Theory]
+        // Chrome
+        [InlineData("Chrome", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Chrome", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Chrome", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450
+        [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
+        [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
+        [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        // Firefox
+        [InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Firefox", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Firefox", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450
+        [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
+        [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
+        [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        // Safari
+        [InlineData("SafariNext", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Remux", "HLS.mp4")] // #6450
+        [InlineData("SafariNext", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Remux", "HLS.mp4")] // #6450
+        [InlineData("SafariNext", "mp4-hevc-ac3-aacExt-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Remux", "HLS.mp4")] // #6450
+        // AndroidPixel
+        [InlineData("AndroidPixel", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("AndroidPixel", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("AndroidPixel", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("AndroidPixel", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("AndroidPixel", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("AndroidPixel", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        // Yatse
+        [InlineData("Yatse", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("Yatse", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
+        [InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        // RokuSSPlus
+        [InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 should be DirectPlay
+        [InlineData("RokuSSPlus", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("RokuSSPlus", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        // JellyfinMediaPlayer
+        [InlineData("JellyfinMediaPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("JellyfinMediaPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("JellyfinMediaPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+        // Chrome-NoHLS
+        [InlineData("Chrome-NoHLS", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Chrome-NoHLS", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Chrome-NoHLS", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Chrome-NoHLS", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450
+        [InlineData("Chrome-NoHLS", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome-NoHLS", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")]
+        [InlineData("Chrome-NoHLS", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")]
+        [InlineData("Chrome-NoHLS", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome-NoHLS", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome-NoHLS", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        // TranscodeMedia
+        [InlineData("TranscodeMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Remux", "HLS.mp4")]
+        [InlineData("TranscodeMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Remux", "HLS.mp4")]
+        [InlineData("TranscodeMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "DirectStream", "HLS.mp4")]
+        [InlineData("TranscodeMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Remux", "HLS.mp4")]
+        [InlineData("TranscodeMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Remux", "HLS.mp4")]
+        [InlineData("TranscodeMedia", "mkv-av1-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "DirectStream", "http")]
+        [InlineData("TranscodeMedia", "mkv-av1-vorbis-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Remux", "http")]
+        [InlineData("TranscodeMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "DirectStream", "http")]
+        [InlineData("TranscodeMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "DirectStream", "http")]
+        [InlineData("TranscodeMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Remux", "http")]
+        // DirectMedia
+        [InlineData("DirectMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
+        [InlineData("DirectMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
+        [InlineData("DirectMedia", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
+        [InlineData("DirectMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
+        [InlineData("DirectMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
+        [InlineData("DirectMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
+        [InlineData("DirectMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
+        [InlineData("DirectMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
+        [InlineData("DirectMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)]
+        // LowBandwidth
+        [InlineData("LowBandwidth", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("LowBandwidth", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("LowBandwidth", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("LowBandwidth", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("LowBandwidth", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("LowBandwidth", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("LowBandwidth", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("LowBandwidth", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        // Null
+        [InlineData("Null", "mp4-h264-aac-vtt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
+        [InlineData("Null", "mp4-h264-ac3-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
+        [InlineData("Null", "mp4-h264-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
+        [InlineData("Null", "mp4-hevc-aac-srt-15200k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
+        [InlineData("Null", "mp4-hevc-ac3-aac-srt-15200k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
+        [InlineData("Null", "mkv-vp9-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
+        [InlineData("Null", "mkv-vp9-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
+        [InlineData("Null", "mkv-vp9-vorbis-vtt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
+        public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
+        {
+            var options = await GetVideoOptions(deviceName, mediaSource);
+            BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol);
+        }
+
+        [Theory]
+        // Chrome
+        [InlineData("Chrome", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 <BUG: this is direct played>
+        [InlineData("Chrome", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
+        [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
+        [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        // Firefox
+        [InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Firefox", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
+        [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
+        [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        // Safari
+        [InlineData("SafariNext", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Remux", "HLS.mp4")] // #6450
+        [InlineData("SafariNext", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Remux", "HLS.mp4")] // #6450
+        [InlineData("SafariNext", "mp4-hevc-ac3-aacExt-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Remux", "HLS.mp4")] // #6450
+        // AndroidPixel
+        [InlineData("AndroidPixel", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("AndroidPixel", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("AndroidPixel", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("AndroidPixel", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("AndroidPixel", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        // Yatse
+        [InlineData("Yatse", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Yatse", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
+        [InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        // RokuSSPlus
+        [InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
+        [InlineData("RokuSSPlus", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("RokuSSPlus", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        // JellyfinMediaPlayer
+        [InlineData("JellyfinMediaPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("JellyfinMediaPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450
+        [InlineData("JellyfinMediaPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450
+        [InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+        public async Task BuildVideoItemWithFirstExplicitStream(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
+        {
+            var options = await GetVideoOptions(deviceName, mediaSource);
+            options.AudioStreamIndex = 1;
+            options.SubtitleStreamIndex = options.MediaSources[0].MediaStreams.Count - 1;
+
+            var streamInfo = BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol);
+            Assert.Equal(streamInfo?.AudioStreamIndex, options.AudioStreamIndex);
+            Assert.Equal(streamInfo?.SubtitleStreamIndex, options.SubtitleStreamIndex);
+        }
+
+        [Theory]
+        // Chrome
+        [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Chrome", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450
+        [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
+        // Firefox
+        [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
+        // Yatse
+        [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        // RokuSSPlus
+        [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+        public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
+        {
+            var options = await GetVideoOptions(deviceName, mediaSource);
+            var streamCount = options.MediaSources[0].MediaStreams.Count;
+            options.AudioStreamIndex = streamCount - 2;
+            options.SubtitleStreamIndex = streamCount - 1;
+
+            var streamInfo = BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol);
+            Assert.Equal(streamInfo?.AudioStreamIndex, options.AudioStreamIndex);
+            Assert.Equal(streamInfo?.SubtitleStreamIndex, options.SubtitleStreamIndex);
+        }
+
+        private StreamInfo? BuildVideoItemSimpleTest(VideoOptions options, PlayMethod? playMethod, TranscodeReason why, string transcodeMode, string transcodeProtocol)
+        {
+            if (string.IsNullOrEmpty(transcodeProtocol))
+            {
+                transcodeProtocol = playMethod == PlayMethod.DirectStream ? "http" : "HLS.ts";
+            }
+
+            var builder = GetStreamBuilder();
+
+            var val = builder.BuildVideoItem(options);
+            Assert.NotNull(val);
+
+            if (playMethod != null)
+            {
+                Assert.Equal(playMethod, val.PlayMethod);
+            }
+
+            Assert.Equal(why, val.TranscodeReasons);
+
+            var audioStreamIndexInput = options.AudioStreamIndex;
+            var targetVideoStream = val.TargetVideoStream;
+            var targetAudioStream = val.TargetAudioStream;
+
+            var mediaSource = options.MediaSources.First(source => source.Id == val.MediaSourceId);
+            Assert.NotNull(mediaSource);
+            var videoStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Video);
+            var audioStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio);
+            // TODO: check AudioStreamIndex vs options.AudioStreamIndex
+            var inputAudioStream = mediaSource.GetDefaultAudioStream(audioStreamIndexInput ?? mediaSource.DefaultAudioStreamIndex);
+
+            var uri = ParseUri(val);
+
+            if (playMethod == PlayMethod.DirectPlay)
+            {
+                // check expected container
+                var containers = ContainerProfile.SplitValue(mediaSource.Container);
+                // TODO: test transcode too
+                // Assert.Contains(uri.Extension, containers);
+
+                // check expected video codec (1)
+                Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
+                Assert.Single(val.TargetVideoCodec);
+
+                // check expected audio codecs (1)
+                Assert.Contains(targetAudioStream.Codec, val.TargetAudioCodec);
+                Assert.Single(val.TargetAudioCodec);
+                // Assert.Single(val.AudioCodecs);
+
+                if (transcodeMode == "DirectStream")
+                {
+                    Assert.Equal(val.Container, uri.Extension);
+                }
+            }
+            else if (playMethod == PlayMethod.DirectStream || playMethod == PlayMethod.Transcode)
+            {
+                Assert.NotNull(val.Container);
+                Assert.NotEmpty(val.VideoCodecs);
+                Assert.NotEmpty(val.AudioCodecs);
+
+                // check expected container (todo: this could be a test param)
+                if (transcodeProtocol == "http")
+                {
+                    // Assert.Equal("webm", val.Container);
+                    Assert.Equal(val.Container, uri.Extension);
+                    Assert.Equal("stream", uri.Filename);
+                    Assert.Equal("http", val.SubProtocol);
+                }
+                else if (transcodeProtocol == "HLS.mp4")
+                {
+                    Assert.Equal("mp4", val.Container);
+                    Assert.Equal("m3u8", uri.Extension);
+                    Assert.Equal("master", uri.Filename);
+                    Assert.Equal("hls", val.SubProtocol);
+                }
+                else
+                {
+                    Assert.Equal("ts", val.Container);
+                    Assert.Equal("m3u8", uri.Extension);
+                    Assert.Equal("master", uri.Filename);
+                    Assert.Equal("hls", val.SubProtocol);
+                }
+
+                // Full transcode
+                if (transcodeMode == "Transcode")
+                {
+                    if ((val.TranscodeReasons & (StreamBuilder.ContainerReasons | TranscodeReason.DirectPlayError)) == 0)
+                    {
+                        Assert.All(
+                            videoStreams,
+                            stream => Assert.DoesNotContain(stream.Codec, val.VideoCodecs));
+                    }
+
+                    // todo: fill out tests here
+                }
+
+                // DirectStream and Remux
+                else
+                {
+                    // check expected video codec (1)
+                    Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
+                    Assert.Single(val.TargetVideoCodec);
+
+                    if (transcodeMode == "DirectStream")
+                    {
+                        if (!targetAudioStream.IsExternal)
+                        {
+                            // check expected audio codecs (1)
+                            Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs);
+                        }
+                    }
+                    else if (transcodeMode == "Remux")
+                    {
+                        // check expected audio codecs (1)
+                        Assert.Contains(targetAudioStream.Codec, val.AudioCodecs);
+                        Assert.Single(val.AudioCodecs);
+                    }
+
+                    // video details
+                    var videoStream = targetVideoStream;
+                    Assert.False(val.EstimateContentLength);
+                    Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo);
+                    Assert.Contains(videoStream.Profile?.ToLowerInvariant() ?? string.Empty, val.TargetVideoProfile?.Split(",").Select(s => s.ToLowerInvariant()) ?? Array.Empty<string>());
+                    Assert.Equal(videoStream.Level, val.TargetVideoLevel);
+                    Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth);
+                    Assert.InRange(val.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue);
+
+                    // audio codec not supported
+                    if ((why & TranscodeReason.AudioCodecNotSupported) != 0)
+                    {
+                        // audio stream specified
+                        if (options.AudioStreamIndex >= 0)
+                        {
+                            // TODO:fixme
+                            if (!targetAudioStream.IsExternal)
+                            {
+                                Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs);
+                            }
+                        }
+
+                        // audio stream not specified
+                        else
+                        {
+                            // TODO:fixme
+                            Assert.All(audioStreams, stream =>
+                            {
+                                if (!stream.IsExternal)
+                                {
+                                    Assert.DoesNotContain(stream.Codec, val.AudioCodecs);
+                                }
+                            });
+                        }
+                    }
+                }
+            }
+            else if (playMethod == null)
+            {
+                Assert.Null(val.SubProtocol);
+                Assert.Equal("stream", uri.Filename);
+
+                Assert.False(val.EstimateContentLength);
+                Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo);
+            }
+
+            return val;
+        }
+
+        private static async ValueTask<T> TestData<T>(string name)
+        {
+            var path = Path.Join("Test Data", typeof(T).Name + "-" + name + ".json");
+            using (var stream = File.OpenRead(path))
+            {
+                var value = await JsonSerializer.DeserializeAsync<T>(stream, JsonDefaults.Options);
+                if (value != null)
+                {
+                    return value;
+                }
+
+                throw new SerializationException("Invalid test data: " + name);
+            }
+        }
+
+        private StreamBuilder GetStreamBuilder()
+        {
+            var transcodeSupport = new Mock<ITranscoderSupport>();
+            var logger = new NullLogger<StreamBuilderTests>();
+
+            return new StreamBuilder(transcodeSupport.Object, logger);
+        }
+
+        private static async ValueTask<VideoOptions> GetVideoOptions(string deviceProfile, params string[] sources)
+        {
+            var mediaSources = sources.Select(src => TestData<MediaSourceInfo>(src))
+                .Select(val => val.Result)
+                .ToArray();
+            var mediaSourceId = mediaSources[0]?.Id;
+
+            var dp = await TestData<DeviceProfile>(deviceProfile);
+
+            return new VideoOptions()
+            {
+                ItemId = new Guid("11D229B7-2D48-4B95-9F9B-49F6AB75E613"),
+                MediaSourceId = mediaSourceId,
+                MediaSources = mediaSources,
+                DeviceId = "test-deviceId",
+                Profile = dp,
+                AllowAudioStreamCopy = true,
+                AllowVideoStreamCopy = true,
+            };
+        }
+
+        private static (string Path, NameValueCollection Query, string Filename, string Extension) ParseUri(StreamInfo val)
+        {
+            var href = val.ToUrl("media:", "ACCESSTOKEN").Split("?", 2);
+            var path = href[0];
+
+            var queryString = href.ElementAtOrDefault(1);
+            var query = string.IsNullOrEmpty(queryString) ? System.Web.HttpUtility.ParseQueryString(queryString ?? string.Empty) : new NameValueCollection();
+
+            var filename = System.IO.Path.GetFileNameWithoutExtension(path);
+            var extension = System.IO.Path.GetExtension(path);
+            if (extension.Length > 0)
+            {
+                extension = extension.Substring(1);
+            }
+
+            return (path, query, filename, extension);
+        }
+    }
+}

+ 332 - 0
tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-AndroidPixel.json

@@ -0,0 +1,332 @@
+{
+    "Name": "Jellyfin Android",
+    "EnableAlbumArtInDidl": false,
+    "EnableSingleAlbumArtLimit": false,
+    "EnableSingleSubtitleLimit": false,
+    "SupportedMediaTypes": "Audio,Photo,Video",
+    "MaxAlbumArtWidth": 2147483647,
+    "MaxAlbumArtHeight": 2147483647,
+    "MaxStreamingBitrate": 8000000,
+    "MaxStaticBitrate": 8000000,
+    "MusicStreamingTranscodingBitrate": 128000,
+    "TimelineOffsetSeconds": 0,
+    "RequiresPlainVideoItems": false,
+    "RequiresPlainFolders": false,
+    "EnableMSMediaReceiverRegistrar": false,
+    "IgnoreTranscodeByteRangeRequests": false,
+    "DirectPlayProfiles": [
+        {
+            "Container": "mp4",
+            "AudioCodec": "mp3,aac,alac,ac3",
+            "VideoCodec": "h263,mpeg4,h264,hevc,av1",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mp4",
+            "AudioCodec": "mp3,aac,alac,ac3",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "fmp4",
+            "AudioCodec": "mp3,aac,ac3,eac3",
+            "VideoCodec": "h263,mpeg4,h264,hevc,av1",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "fmp4",
+            "AudioCodec": "mp3,aac,ac3,eac3",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "webm",
+            "AudioCodec": "vorbis,opus",
+            "VideoCodec": "vp8,vp9,av1",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "webm",
+            "AudioCodec": "vorbis,opus",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mkv",
+            "AudioCodec": "pcm_s8,pcm_s16be,pcm_s16le,pcm_s24le,pcm_s32le,pcm_f32le,pcm_alaw,pcm_mulaw,mp3,aac,vorbis,opus,flac,alac,ac3,eac3,dts,mlp,truehd",
+            "VideoCodec": "h263,mpeg4,h264,hevc,av1,vp8,vp9,av1",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mkv",
+            "AudioCodec": "pcm_s8,pcm_s16be,pcm_s16le,pcm_s24le,pcm_s32le,pcm_f32le,pcm_alaw,pcm_mulaw,mp3,aac,vorbis,opus,flac,alac,ac3,eac3,dts,mlp,truehd",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mp3",
+            "AudioCodec": "mp3",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "ogg",
+            "AudioCodec": "vorbis,opus,flac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "wav",
+            "AudioCodec": "pcm_s8,pcm_s16be,pcm_s16le,pcm_s24le,pcm_s32le,pcm_f32le,pcm_alaw,pcm_mulaw",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mpegts",
+            "AudioCodec": "pcm_s8,pcm_s16be,pcm_s16le,pcm_s24le,pcm_s32le,pcm_f32le,pcm_alaw,pcm_mulaw,mp3,aac,ac3,eac3,dts,mlp,truehd",
+            "VideoCodec": "mpeg4,h264,hevc",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mpegts",
+            "AudioCodec": "pcm_s8,pcm_s16be,pcm_s16le,pcm_s24le,pcm_s32le,pcm_f32le,pcm_alaw,pcm_mulaw,mp3,aac,ac3,eac3,dts,mlp,truehd",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "flv",
+            "AudioCodec": "mp3,aac",
+            "VideoCodec": "mpeg4,h264",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "flv",
+            "AudioCodec": "mp3,aac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "aac",
+            "AudioCodec": "aac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "flac",
+            "AudioCodec": "flac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "3gp",
+            "AudioCodec": "3gpp,aac,flac",
+            "VideoCodec": "h263,mpeg4,h264,hevc",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "3gp",
+            "AudioCodec": "3gpp,aac,flac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        }
+    ],
+    "TranscodingProfiles": [
+        {
+            "Container": "ts",
+            "Type": "Video",
+            "VideoCodec": "h264",
+            "AudioCodec": "mp1,mp2,mp3,aac,ac3,eac3,dts,mlp,truehd",
+            "Protocol": "hls",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mkv",
+            "Type": "Video",
+            "VideoCodec": "h264",
+            "AudioCodec": "pcm_s8,pcm_s16be,pcm_s16le,pcm_s24le,pcm_s32le,pcm_f32le,pcm_alaw,pcm_mulaw,mp1,mp2,mp3,aac,vorbis,opus,flac,alac,ac3,eac3,dts,mlp,truehd",
+            "Protocol": "hls",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "AudioCodec": "mp3",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        }
+    ],
+    "ContainerProfiles": [
+        {
+            "Type": "Video",
+            "Container": "mp4",
+            "$type": "ContainerProfile"
+        },
+        {
+            "Type": "Audio",
+            "Container": "mp4",
+            "$type": "ContainerProfile"
+        },
+        {
+            "Type": "Video",
+            "Container": "fmp4",
+            "$type": "ContainerProfile"
+        },
+        {
+            "Type": "Audio",
+            "Container": "fmp4",
+            "$type": "ContainerProfile"
+        },
+        {
+            "Type": "Video",
+            "Container": "webm",
+            "$type": "ContainerProfile"
+        },
+        {
+            "Type": "Audio",
+            "Container": "webm",
+            "$type": "ContainerProfile"
+        },
+        {
+            "Type": "Video",
+            "Container": "mkv",
+            "$type": "ContainerProfile"
+        },
+        {
+            "Type": "Audio",
+            "Container": "mkv",
+            "$type": "ContainerProfile"
+        },
+        {
+            "Type": "Audio",
+            "Container": "mp3",
+            "$type": "ContainerProfile"
+        },
+        {
+            "Type": "Audio",
+            "Container": "ogg",
+            "$type": "ContainerProfile"
+        },
+        {
+            "Type": "Audio",
+            "Container": "wav",
+            "$type": "ContainerProfile"
+        },
+        {
+            "Type": "Video",
+            "Container": "mpegts",
+            "$type": "ContainerProfile"
+        },
+        {
+            "Type": "Audio",
+            "Container": "mpegts",
+            "$type": "ContainerProfile"
+        },
+        {
+            "Type": "Video",
+            "Container": "flv",
+            "$type": "ContainerProfile"
+        },
+        {
+            "Type": "Audio",
+            "Container": "flv",
+            "$type": "ContainerProfile"
+        },
+        {
+            "Type": "Audio",
+            "Container": "aac",
+            "$type": "ContainerProfile"
+        },
+        {
+            "Type": "Audio",
+            "Container": "flac",
+            "$type": "ContainerProfile"
+        },
+        {
+            "Type": "Video",
+            "Container": "3gp",
+            "$type": "ContainerProfile"
+        },
+        {
+            "Type": "Audio",
+            "Container": "3gp",
+            "$type": "ContainerProfile"
+        }
+    ],
+    "SubtitleProfiles": [
+        {
+            "Format": "srt",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "subrip",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ttml",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "srt",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "subrip",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ttml",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "vtt",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "webvtt",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        }
+    ],
+    "$type": "DeviceProfile"
+}

+ 430 - 0
tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json

@@ -0,0 +1,430 @@
+{
+    "EnableAlbumArtInDidl": false,
+    "EnableSingleAlbumArtLimit": false,
+    "EnableSingleSubtitleLimit": false,
+    "SupportedMediaTypes": "Audio,Photo,Video",
+    "MaxAlbumArtWidth": 0,
+    "MaxAlbumArtHeight": 0,
+    "MaxStreamingBitrate": 120000000,
+    "MaxStaticBitrate": 100000000,
+    "MusicStreamingTranscodingBitrate": 384000,
+    "TimelineOffsetSeconds": 0,
+    "RequiresPlainVideoItems": false,
+    "RequiresPlainFolders": false,
+    "EnableMSMediaReceiverRegistrar": false,
+    "IgnoreTranscodeByteRangeRequests": false,
+    "DirectPlayProfiles": [
+        {
+            "Container": "webm",
+            "AudioCodec": "vorbis,opus",
+            "VideoCodec": "vp8,vp9,av1",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mp4,m4v",
+            "AudioCodec": "aac,mp3,opus,flac,alac,vorbis",
+            "VideoCodec": "h264,vp8,vp9,av1",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mov",
+            "AudioCodec": "aac,mp3,opus,flac,alac,vorbis",
+            "VideoCodec": "h264",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "opus",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "webm",
+            "AudioCodec": "opus",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "aac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "m4a",
+            "AudioCodec": "aac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "m4b",
+            "AudioCodec": "aac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "flac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "alac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "m4a",
+            "AudioCodec": "alac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "m4b",
+            "AudioCodec": "alac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "webma",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "webm",
+            "AudioCodec": "webma",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "wav",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "ogg",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        }
+    ],
+    "TranscodingProfiles": [
+        {
+            "Container": "aac",
+            "Type": "Audio",
+            "AudioCodec": "aac",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "AudioCodec": "mp3",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "opus",
+            "Type": "Audio",
+            "AudioCodec": "opus",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "wav",
+            "Type": "Audio",
+            "AudioCodec": "wav",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "opus",
+            "Type": "Audio",
+            "AudioCodec": "opus",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "AudioCodec": "mp3",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "aac",
+            "Type": "Audio",
+            "AudioCodec": "aac",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "wav",
+            "Type": "Audio",
+            "AudioCodec": "wav",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp4",
+            "Type": "Video",
+            "VideoCodec": "h264",
+            "AudioCodec": "aac,mp3,opus,flac,alac,vorbis",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "webm",
+            "Type": "Video",
+            "VideoCodec": "vp8,vp9,av1,vpx",
+            "AudioCodec": "vorbis,opus",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp4",
+            "Type": "Video",
+            "VideoCodec": "h264",
+            "AudioCodec": "aac,mp3,opus,flac,alac,vorbis",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        }
+    ],
+    "CodecProfiles": [
+        {
+            "Type": "VideoAudio",
+            "Conditions": [
+                {
+                    "Condition": "Equals",
+                    "Property": "IsSecondaryAudio",
+                    "Value": "false",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "Codec": "aac",
+            "$type": "CodecProfile"
+        },
+        {
+            "Type": "VideoAudio",
+            "Conditions": [
+                {
+                    "Condition": "Equals",
+                    "Property": "IsSecondaryAudio",
+                    "Value": "false",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "$type": "CodecProfile"
+        },
+        {
+            "Type": "Video",
+            "Conditions": [
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsAnamorphic",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "EqualsAny",
+                    "Property": "VideoProfile",
+                    "Value": "high|main|baseline|constrained baseline|high 10",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "LessThanEqual",
+                    "Property": "VideoLevel",
+                    "Value": "52",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsInterlaced",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "Codec": "h264",
+            "$type": "CodecProfile"
+        },
+        {
+            "Type": "Video",
+            "Conditions": [
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsAnamorphic",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "EqualsAny",
+                    "Property": "VideoProfile",
+                    "Value": "main",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "LessThanEqual",
+                    "Property": "VideoLevel",
+                    "Value": "120",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsInterlaced",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "Codec": "hevc",
+            "$type": "CodecProfile"
+        }
+    ],
+    "ResponseProfiles": [
+        {
+            "Container": "m4v",
+            "Type": "Video",
+            "MimeType": "video/mp4",
+            "$type": "ResponseProfile"
+        }
+    ],
+    "SubtitleProfiles": [
+        {
+            "Format": "vtt",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ass",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ssa",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        }
+    ],
+    "$type": "DeviceProfile"
+}

+ 448 - 0
tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Chrome.json

@@ -0,0 +1,448 @@
+{
+    "EnableAlbumArtInDidl": false,
+    "EnableSingleAlbumArtLimit": false,
+    "EnableSingleSubtitleLimit": false,
+    "SupportedMediaTypes": "Audio,Photo,Video",
+    "MaxAlbumArtWidth": 0,
+    "MaxAlbumArtHeight": 0,
+    "MaxStreamingBitrate": 120000000,
+    "MaxStaticBitrate": 100000000,
+    "MusicStreamingTranscodingBitrate": 384000,
+    "TimelineOffsetSeconds": 0,
+    "RequiresPlainVideoItems": false,
+    "RequiresPlainFolders": false,
+    "EnableMSMediaReceiverRegistrar": false,
+    "IgnoreTranscodeByteRangeRequests": false,
+    "DirectPlayProfiles": [
+        {
+            "Container": "webm",
+            "AudioCodec": "vorbis,opus",
+            "VideoCodec": "vp8,vp9,av1",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mp4,m4v",
+            "AudioCodec": "aac,mp3,opus,flac,alac,vorbis",
+            "VideoCodec": "h264,vp8,vp9,av1",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mov",
+            "AudioCodec": "aac,mp3,opus,flac,alac,vorbis",
+            "VideoCodec": "h264",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "opus",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "webm",
+            "AudioCodec": "opus",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "aac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "m4a",
+            "AudioCodec": "aac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "m4b",
+            "AudioCodec": "aac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "flac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "alac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "m4a",
+            "AudioCodec": "alac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "m4b",
+            "AudioCodec": "alac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "webma",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "webm",
+            "AudioCodec": "webma",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "wav",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "ogg",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        }
+    ],
+    "TranscodingProfiles": [
+        {
+            "Container": "ts",
+            "Type": "Audio",
+            "AudioCodec": "aac",
+            "Protocol": "hls",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 2,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": true,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "aac",
+            "Type": "Audio",
+            "AudioCodec": "aac",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "AudioCodec": "mp3",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "opus",
+            "Type": "Audio",
+            "AudioCodec": "opus",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "wav",
+            "Type": "Audio",
+            "AudioCodec": "wav",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "opus",
+            "Type": "Audio",
+            "AudioCodec": "opus",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "AudioCodec": "mp3",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "aac",
+            "Type": "Audio",
+            "AudioCodec": "aac",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "wav",
+            "Type": "Audio",
+            "AudioCodec": "wav",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "ts",
+            "Type": "Video",
+            "VideoCodec": "h264",
+            "AudioCodec": "aac,mp3",
+            "Protocol": "hls",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 2,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": true,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "webm",
+            "Type": "Video",
+            "VideoCodec": "vp8,vp9,av1,vpx",
+            "AudioCodec": "vorbis,opus",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp4",
+            "Type": "Video",
+            "VideoCodec": "h264",
+            "AudioCodec": "aac,mp3,opus,flac,alac,vorbis",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        }
+    ],
+    "CodecProfiles": [
+        {
+            "Type": "VideoAudio",
+            "Conditions": [
+                {
+                    "Condition": "Equals",
+                    "Property": "IsSecondaryAudio",
+                    "Value": "false",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "Codec": "aac",
+            "$type": "CodecProfile"
+        },
+        {
+            "Type": "VideoAudio",
+            "Conditions": [
+                {
+                    "Condition": "Equals",
+                    "Property": "IsSecondaryAudio",
+                    "Value": "false",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "$type": "CodecProfile"
+        },
+        {
+            "Type": "Video",
+            "Conditions": [
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsAnamorphic",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "EqualsAny",
+                    "Property": "VideoProfile",
+                    "Value": "high|main|baseline|constrained baseline|high 10",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "LessThanEqual",
+                    "Property": "VideoLevel",
+                    "Value": "52",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsInterlaced",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "Codec": "h264",
+            "$type": "CodecProfile"
+        },
+        {
+            "Type": "Video",
+            "Conditions": [
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsAnamorphic",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "EqualsAny",
+                    "Property": "VideoProfile",
+                    "Value": "main",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "LessThanEqual",
+                    "Property": "VideoLevel",
+                    "Value": "120",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsInterlaced",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "Codec": "hevc",
+            "$type": "CodecProfile"
+        }
+    ],
+    "ResponseProfiles": [
+        {
+            "Container": "m4v",
+            "Type": "Video",
+            "MimeType": "video/mp4",
+            "$type": "ResponseProfile"
+        }
+    ],
+    "SubtitleProfiles": [
+        {
+            "Format": "vtt",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ass",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ssa",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        }
+    ],
+    "$type": "DeviceProfile"
+}

+ 90 - 0
tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-DirectMedia.json

@@ -0,0 +1,90 @@
+{
+    "Name": "Jellyfin Media Player",
+    "SupportedMediaTypes": "Audio,Photo,Video",
+    "MaxStreamingBitrate": 20000000,
+    "MaxStaticBitrate": 20000000,
+    "MusicStreamingTranscodingBitrate": 1280000,
+    "TimelineOffsetSeconds": 5,
+    "DirectPlayProfiles": [
+        {
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Type": "Photo",
+            "$type": "DirectPlayProfile"
+        }
+    ],
+    "SubtitleProfiles": [
+        {
+            "Format": "srt",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "srt",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ass",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ass",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "sub",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "sub",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ssa",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ssa",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "smi",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "smi",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "pgssub",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "dvdsub",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "pgs",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        }
+    ],
+    "$type": "DeviceProfile"
+}

+ 441 - 0
tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Firefox.json

@@ -0,0 +1,441 @@
+{
+    "EnableAlbumArtInDidl": false,
+    "EnableSingleAlbumArtLimit": false,
+    "EnableSingleSubtitleLimit": false,
+    "SupportedMediaTypes": "Audio,Photo,Video",
+    "MaxAlbumArtWidth": 0,
+    "MaxAlbumArtHeight": 0,
+    "MaxStreamingBitrate": 120000000,
+    "MaxStaticBitrate": 100000000,
+    "MusicStreamingTranscodingBitrate": 384000,
+    "TimelineOffsetSeconds": 0,
+    "RequiresPlainVideoItems": false,
+    "RequiresPlainFolders": false,
+    "EnableMSMediaReceiverRegistrar": false,
+    "IgnoreTranscodeByteRangeRequests": false,
+    "DirectPlayProfiles": [
+        {
+            "Container": "webm",
+            "AudioCodec": "vorbis,opus",
+            "VideoCodec": "vp8,vp9,av1",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mp4,m4v",
+            "AudioCodec": "aac,mp3,opus,flac,alac,vorbis",
+            "VideoCodec": "h264,vp8,vp9,av1",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "opus",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "webm",
+            "AudioCodec": "opus",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "aac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "m4a",
+            "AudioCodec": "aac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "m4b",
+            "AudioCodec": "aac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "flac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "alac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "m4a",
+            "AudioCodec": "alac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "m4b",
+            "AudioCodec": "alac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "webma",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "webm",
+            "AudioCodec": "webma",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "wav",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "ogg",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        }
+    ],
+    "TranscodingProfiles": [
+        {
+            "Container": "ts",
+            "Type": "Audio",
+            "AudioCodec": "aac",
+            "Protocol": "hls",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 2,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": true,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "aac",
+            "Type": "Audio",
+            "AudioCodec": "aac",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "AudioCodec": "mp3",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "opus",
+            "Type": "Audio",
+            "AudioCodec": "opus",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "wav",
+            "Type": "Audio",
+            "AudioCodec": "wav",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "opus",
+            "Type": "Audio",
+            "AudioCodec": "opus",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "AudioCodec": "mp3",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "aac",
+            "Type": "Audio",
+            "AudioCodec": "aac",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "wav",
+            "Type": "Audio",
+            "AudioCodec": "wav",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "ts",
+            "Type": "Video",
+            "VideoCodec": "h264",
+            "AudioCodec": "aac,mp3",
+            "Protocol": "hls",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 2,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": true,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "webm",
+            "Type": "Video",
+            "VideoCodec": "vp8,vp9,av1,vpx",
+            "AudioCodec": "vorbis,opus",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp4",
+            "Type": "Video",
+            "VideoCodec": "h264",
+            "AudioCodec": "aac,mp3,opus,flac,alac,vorbis",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        }
+    ],
+    "CodecProfiles": [
+        {
+            "Type": "VideoAudio",
+            "Conditions": [
+                {
+                    "Condition": "Equals",
+                    "Property": "IsSecondaryAudio",
+                    "Value": "false",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "Codec": "aac",
+            "$type": "CodecProfile"
+        },
+        {
+            "Type": "VideoAudio",
+            "Conditions": [
+                {
+                    "Condition": "Equals",
+                    "Property": "IsSecondaryAudio",
+                    "Value": "false",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "$type": "CodecProfile"
+        },
+        {
+            "Type": "Video",
+            "Conditions": [
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsAnamorphic",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "EqualsAny",
+                    "Property": "VideoProfile",
+                    "Value": "high|main|baseline|constrained baseline",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "LessThanEqual",
+                    "Property": "VideoLevel",
+                    "Value": "52",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsInterlaced",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "Codec": "h264",
+            "$type": "CodecProfile"
+        },
+        {
+            "Type": "Video",
+            "Conditions": [
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsAnamorphic",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "EqualsAny",
+                    "Property": "VideoProfile",
+                    "Value": "main",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "LessThanEqual",
+                    "Property": "VideoLevel",
+                    "Value": "120",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsInterlaced",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "Codec": "hevc",
+            "$type": "CodecProfile"
+        }
+    ],
+    "ResponseProfiles": [
+        {
+            "Container": "m4v",
+            "Type": "Video",
+            "MimeType": "video/mp4",
+            "$type": "ResponseProfile"
+        }
+    ],
+    "SubtitleProfiles": [
+        {
+            "Format": "vtt",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ass",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ssa",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        }
+    ],
+    "$type": "DeviceProfile"
+}

+ 137 - 0
tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json

@@ -0,0 +1,137 @@
+{
+    "Name": "Jellyfin Media Player",
+    "SupportedMediaTypes": "Audio,Photo,Video",
+    "MaxStreamingBitrate": 8000000,
+    "MaxStaticBitrate": 8000000,
+    "MusicStreamingTranscodingBitrate": 1280000,
+    "TimelineOffsetSeconds": 5,
+    "DirectPlayProfiles": [
+        {
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Type": "Photo",
+            "$type": "DirectPlayProfile"
+        }
+    ],
+    "TranscodingProfiles": [
+        {
+            "Type": "Audio",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "ts",
+            "Type": "Video",
+            "VideoCodec": "h264,h265,hevc,mpeg4,mpeg2video",
+            "AudioCodec": "aac,mp3,ac3,opus,flac,vorbis",
+            "Protocol": "hls",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "jpeg",
+            "Type": "Photo",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        }
+    ],
+    "SubtitleProfiles": [
+        {
+            "Format": "srt",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "srt",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ass",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ass",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "sub",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "sub",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ssa",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ssa",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "smi",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "smi",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "pgssub",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "dvdsub",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "pgs",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        }
+    ],
+    "$type": "DeviceProfile"
+}

+ 137 - 0
tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-LowBandwidth.json

@@ -0,0 +1,137 @@
+{
+    "Name": "Jellyfin Media Player",
+    "SupportedMediaTypes": "Audio,Photo,Video",
+    "MaxStreamingBitrate": 120000,
+    "MaxStaticBitrate": 100000,
+    "MusicStreamingTranscodingBitrate": 3840,
+    "TimelineOffsetSeconds": 5,
+    "DirectPlayProfiles": [
+        {
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Type": "Photo",
+            "$type": "DirectPlayProfile"
+        }
+    ],
+    "TranscodingProfiles": [
+        {
+            "Type": "Audio",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "ts",
+            "Type": "Video",
+            "VideoCodec": "h264,h265,hevc,mpeg4,mpeg2video",
+            "AudioCodec": "aac,mp3,ac3,opus,flac,vorbis",
+            "Protocol": "hls",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "jpeg",
+            "Type": "Photo",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        }
+    ],
+    "SubtitleProfiles": [
+        {
+            "Format": "srt",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "srt",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ass",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ass",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "sub",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "sub",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ssa",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ssa",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "smi",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "smi",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "pgssub",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "dvdsub",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "pgs",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        }
+    ],
+    "$type": "DeviceProfile"
+}

+ 9 - 0
tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Null.json

@@ -0,0 +1,9 @@
+{
+    "Name": "Jellyfin Media Player",
+    "SupportedMediaTypes": "Audio,Photo,Video",
+    "MaxStreamingBitrate": 120000,
+    "MaxStaticBitrate": 100000,
+    "MusicStreamingTranscodingBitrate": 3840,
+    "TimelineOffsetSeconds": 5,
+    "$type": "DeviceProfile"
+}

+ 211 - 0
tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-RokuSSPlus.json

@@ -0,0 +1,211 @@
+{
+    "EnableAlbumArtInDidl": false,
+    "EnableSingleAlbumArtLimit": false,
+    "EnableSingleSubtitleLimit": false,
+    "SupportedMediaTypes": "Audio,Photo,Video",
+    "MaxAlbumArtWidth": 0,
+    "MaxAlbumArtHeight": 0,
+    "MaxStreamingBitrate": 120000000,
+    "MaxStaticBitrate": 100000000,
+    "MusicStreamingTranscodingBitrate": 192000,
+    "TimelineOffsetSeconds": 0,
+    "RequiresPlainVideoItems": false,
+    "RequiresPlainFolders": false,
+    "EnableMSMediaReceiverRegistrar": false,
+    "IgnoreTranscodeByteRangeRequests": false,
+    "DirectPlayProfiles": [
+        {
+            "Container": "mp4,m4v,mov",
+            "AudioCodec": "mp3,pcm,lpcm,wav,alac,aac",
+            "VideoCodec": "h264,h265,hevc,mpeg2video",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mkv,webm",
+            "AudioCodec": "mp3,pcm,lpcm,wav,flac,alac,aac,opus,vorbis",
+            "VideoCodec": "h264,vp8,h265,hevc,vp9,mpeg2video",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mp3,pcm,lpcm,wav,wma,flac,alac,aac,wmapro",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        }
+    ],
+    "TranscodingProfiles": [
+        {
+            "Container": "aac",
+            "Type": "Audio",
+            "AudioCodec": "aac",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": " 2",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "AudioCodec": "mp3",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "2",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "AudioCodec": "mp3",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "2",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "aac",
+            "Type": "Audio",
+            "AudioCodec": "aac",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": " 2",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "ts",
+            "Type": "Video",
+            "VideoCodec": "h264,mpeg2video",
+            "AudioCodec": "aac",
+            "Protocol": "hls",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": " 2",
+            "MinSegments": 1,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp4",
+            "Type": "Video",
+            "VideoCodec": "h264,h265,hevc,mpeg2video",
+            "AudioCodec": "mp3,pcm,lpcm,wav,alac,aac",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        }
+    ],
+    "CodecProfiles": [
+        {
+            "Type": "Video",
+            "Conditions": [
+                {
+                    "Condition": "EqualsAny",
+                    "Property": "VideoProfile",
+                    "Value": "high|main|baseline|constrained baseline",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "LessThanEqual",
+                    "Property": "VideoLevel",
+                    "Value": "51",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "Codec": "h264",
+            "$type": "CodecProfile"
+        },
+        {
+            "Type": "Video",
+            "Conditions": [
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsAnamorphic",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "EqualsAny",
+                    "Property": "VideoProfile",
+                    "Value": "main|main 10",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsInterlaced",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "Codec": "hevc",
+            "$type": "CodecProfile"
+        }
+    ],
+    "SubtitleProfiles": [
+        {
+            "Format": "vtt",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "srt",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ttml",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        }
+    ],
+    "$type": "DeviceProfile"
+}

+ 211 - 0
tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json

@@ -0,0 +1,211 @@
+{
+    "EnableAlbumArtInDidl": false,
+    "EnableSingleAlbumArtLimit": false,
+    "EnableSingleSubtitleLimit": false,
+    "SupportedMediaTypes": "Audio,Photo,Video",
+    "MaxAlbumArtWidth": 0,
+    "MaxAlbumArtHeight": 0,
+    "MaxStreamingBitrate": 120000000,
+    "MaxStaticBitrate": 100000000,
+    "MusicStreamingTranscodingBitrate": 192000,
+    "TimelineOffsetSeconds": 0,
+    "RequiresPlainVideoItems": false,
+    "RequiresPlainFolders": false,
+    "EnableMSMediaReceiverRegistrar": false,
+    "IgnoreTranscodeByteRangeRequests": false,
+    "DirectPlayProfiles": [
+        {
+            "Container": "mp4,m4v,mov",
+            "AudioCodec": "mp3,pcm,lpcm,wav,alac,aac",
+            "VideoCodec": "h264,h265,hevc,mpeg2video",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mkv,webm",
+            "AudioCodec": "mp3,pcm,lpcm,wav,flac,alac,aac,opus,vorbis",
+            "VideoCodec": "h264,vp8,h265,hevc,vp9,mpeg2video",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mp3,pcm,lpcm,wav,wma,flac,alac,aac,wmapro",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        }
+    ],
+    "TranscodingProfiles": [
+        {
+            "Container": "aac",
+            "Type": "Audio",
+            "AudioCodec": "aac",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": " 2",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "AudioCodec": "mp3",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "2",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "AudioCodec": "mp3",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "2",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "aac",
+            "Type": "Audio",
+            "AudioCodec": "aac",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": " 2",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "ts",
+            "Type": "Video",
+            "VideoCodec": "h264,h265,hevc,mpeg2video",
+            "AudioCodec": "aac",
+            "Protocol": "hls",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": " 2",
+            "MinSegments": 1,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp4",
+            "Type": "Video",
+            "VideoCodec": "h264,h265,hevc,mpeg2video",
+            "AudioCodec": "mp3,pcm,lpcm,wav,alac,aac",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        }
+    ],
+    "CodecProfiles": [
+        {
+            "Type": "Video",
+            "Conditions": [
+                {
+                    "Condition": "EqualsAny",
+                    "Property": "VideoProfile",
+                    "Value": "high|main|baseline|constrained baseline",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "LessThanEqual",
+                    "Property": "VideoLevel",
+                    "Value": "51",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "Codec": "h264",
+            "$type": "CodecProfile"
+        },
+        {
+            "Type": "Video",
+            "Conditions": [
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsAnamorphic",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "EqualsAny",
+                    "Property": "VideoProfile",
+                    "Value": "main|main 10",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsInterlaced",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "Codec": "hevc",
+            "$type": "CodecProfile"
+        }
+    ],
+    "SubtitleProfiles": [
+        {
+            "Format": "vtt",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "srt",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ttml",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        }
+    ],
+    "$type": "DeviceProfile"
+}

+ 357 - 0
tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-SafariNext.json

@@ -0,0 +1,357 @@
+{
+    "EnableAlbumArtInDidl": false,
+    "EnableSingleAlbumArtLimit": false,
+    "EnableSingleSubtitleLimit": false,
+    "SupportedMediaTypes": "Audio,Photo,Video",
+    "MaxAlbumArtWidth": 0,
+    "MaxAlbumArtHeight": 0,
+    "MaxStreamingBitrate": 120000000,
+    "MaxStaticBitrate": 100000000,
+    "MusicStreamingTranscodingBitrate": 384000,
+    "TimelineOffsetSeconds": 0,
+    "RequiresPlainVideoItems": false,
+    "RequiresPlainFolders": false,
+    "EnableMSMediaReceiverRegistrar": false,
+    "IgnoreTranscodeByteRangeRequests": false,
+    "DirectPlayProfiles": [
+        {
+            "Container": "webm",
+            "AudioCodec": "vorbis",
+            "VideoCodec": "vp8,vp9",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mp4,m4v",
+            "AudioCodec": "aac,mp3,ac3,eac3,flac,alac,vorbis",
+            "VideoCodec": "h264,vp8,vp9",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mov",
+            "AudioCodec": "aac,mp3,ac3,eac3,flac,alac,vorbis",
+            "VideoCodec": "h264",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "aac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "m4a",
+            "AudioCodec": "aac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "m4b",
+            "AudioCodec": "aac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "flac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "alac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "m4a",
+            "AudioCodec": "alac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "m4b",
+            "AudioCodec": "alac",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "webma",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "webm",
+            "AudioCodec": "webma",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "wav",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        }
+    ],
+    "TranscodingProfiles": [
+        {
+            "Container": "aac",
+            "Type": "Audio",
+            "AudioCodec": "aac",
+            "Protocol": "hls",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 2,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": true,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "aac",
+            "Type": "Audio",
+            "AudioCodec": "aac",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "AudioCodec": "mp3",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "wav",
+            "Type": "Audio",
+            "AudioCodec": "wav",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "AudioCodec": "mp3",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "aac",
+            "Type": "Audio",
+            "AudioCodec": "aac",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "wav",
+            "Type": "Audio",
+            "AudioCodec": "wav",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp4",
+            "Type": "Video",
+            "AudioCodec": "aac,ac3,eac3,flac,alac",
+            "VideoCodec": "hevc,h264",
+            "Context": "Streaming",
+            "Protocol": "hls",
+            "MaxAudioChannels": "2",
+            "MinSegments": "2",
+            "BreakOnNonKeyFrames": true
+        },
+        {
+            "Container": "ts",
+            "Type": "Video",
+            "AudioCodec": "aac,mp3,ac3,eac3",
+            "VideoCodec": "h264",
+            "Context": "Streaming",
+            "Protocol": "hls",
+            "MaxAudioChannels": "2",
+            "MinSegments": "2",
+            "BreakOnNonKeyFrames": true
+        },
+        {
+            "Container": "webm",
+            "Type": "Video",
+            "AudioCodec": "vorbis",
+            "VideoCodec": "vp8,vpx",
+            "Context": "Streaming",
+            "Protocol": "http",
+            "MaxAudioChannels": "2"
+        },
+        {
+            "Container": "mp4",
+            "Type": "Video",
+            "AudioCodec": "aac,mp3,ac3,eac3,flac,alac,vorbis",
+            "VideoCodec": "h264",
+            "Context": "Static",
+            "Protocol": "http"
+        }
+    ],
+    "CodecProfiles": [
+        {
+            "Type": "Video",
+            "Conditions": [
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsAnamorphic",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "EqualsAny",
+                    "Property": "VideoProfile",
+                    "Value": "high|main|baseline|constrained baseline",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "LessThanEqual",
+                    "Property": "VideoLevel",
+                    "Value": "52",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsInterlaced",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "Codec": "h264",
+            "$type": "CodecProfile"
+        },
+        {
+            "Type": "Video",
+            "Conditions": [
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsAnamorphic",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "EqualsAny",
+                    "Property": "VideoProfile",
+                    "Value": "main|main 10",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "LessThanEqual",
+                    "Property": "VideoLevel",
+                    "Value": "183",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                },
+                {
+                    "Condition": "NotEquals",
+                    "Property": "IsInterlaced",
+                    "Value": "true",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "Codec": "hevc",
+            "$type": "CodecProfile"
+        }
+    ],
+    "ResponseProfiles": [
+        {
+            "Container": "m4v",
+            "Type": "Video",
+            "MimeType": "video/mp4",
+            "$type": "ResponseProfile"
+        }
+    ],
+    "SubtitleProfiles": [
+        {
+            "Format": "vtt",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ass",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ssa",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        }
+    ],
+    "$type": "DeviceProfile"
+}

+ 139 - 0
tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-TranscodeMedia.json

@@ -0,0 +1,139 @@
+{
+    "Name": "Jellyfin Media Player",
+    "SupportedMediaTypes": "Audio,Photo,Video",
+    "MaxStreamingBitrate": 20000000,
+    "MaxStaticBitrate": 20000000,
+    "MusicStreamingTranscodingBitrate": 1280000,
+    "TimelineOffsetSeconds": 5,
+    "TranscodingProfiles": [
+        {
+            "Type": "Audio",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp4",
+            "Type": "Video",
+            "AudioCodec": "aac,flac,alac",
+            "VideoCodec": "hevc,h264",
+            "Context": "Streaming",
+            "Protocol": "hls",
+            "MaxAudioChannels": "2",
+            "MinSegments": "2",
+            "BreakOnNonKeyFrames": true,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "ts",
+            "Type": "Video",
+            "AudioCodec": "aac,mp3",
+            "VideoCodec": "h264",
+            "Context": "Streaming",
+            "Protocol": "hls",
+            "MaxAudioChannels": "2",
+            "MinSegments": "2",
+            "BreakOnNonKeyFrames": true,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "webm",
+            "Type": "Video",
+            "AudioCodec": "vorbis",
+            "VideoCodec": "vp9,vp8,vpx,av1",
+            "Context": "Streaming",
+            "Protocol": "http",
+            "MaxAudioChannels": "2",
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "jpeg",
+            "Type": "Photo",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        }
+    ],
+    "SubtitleProfiles": [
+        {
+            "Format": "srt",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "srt",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ass",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ass",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "sub",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "sub",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ssa",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ssa",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "smi",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "smi",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "pgssub",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "dvdsub",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "pgs",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        }
+    ],
+    "$type": "DeviceProfile"
+}

+ 189 - 0
tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Yatse.json

@@ -0,0 +1,189 @@
+{
+    "EnableAlbumArtInDidl": false,
+    "EnableSingleAlbumArtLimit": false,
+    "EnableSingleSubtitleLimit": false,
+    "SupportedMediaTypes": "Audio,Photo,Video",
+    "MaxAlbumArtWidth": 0,
+    "MaxAlbumArtHeight": 0,
+    "MaxStreamingBitrate": 120000000,
+    "MaxStaticBitrate": 100000000,
+    "MusicStreamingTranscodingBitrate": 192000,
+    "TimelineOffsetSeconds": 0,
+    "RequiresPlainVideoItems": false,
+    "RequiresPlainFolders": false,
+    "EnableMSMediaReceiverRegistrar": false,
+    "IgnoreTranscodeByteRangeRequests": false,
+    "DirectPlayProfiles": [
+        {
+            "Container": "",
+            "AudioCodec": "aac",
+            "VideoCodec": "",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "ts,mp4,mka,m4a,mp3,mp2,wav,flac,ogg",
+            "AudioCodec": "",
+            "VideoCodec": "",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "",
+            "AudioCodec": "",
+            "VideoCodec": "",
+            "Type": "Photo",
+            "$type": "DirectPlayProfile"
+        }
+    ],
+    "TranscodingProfiles": [
+        {
+            "Container": "ts",
+            "Type": "Video",
+            "VideoCodec": "h264",
+            "AudioCodec": "aac",
+            "Protocol": "hls",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": true,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "VideoCodec": "",
+            "AudioCodec": "mp3",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "VideoCodec": "",
+            "AudioCodec": "mp3",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        }
+    ],
+    "CodecProfiles": [
+        {
+            "Type": "VideoAudio",
+            "Conditions": [
+                {
+                    "Condition": "Equals",
+                    "Property": "IsSecondaryAudio",
+                    "Value": "false",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "Codec": "",
+            "Container": "",
+            "$type": "CodecProfile"
+        }
+    ],
+    "ResponseProfiles": [
+        {
+            "Container": "m4v",
+            "Type": "Video",
+            "MimeType": "video/mp4",
+            "$type": "ResponseProfile"
+        },
+        {
+            "Container": "mov",
+            "Type": "Video",
+            "MimeType": "video/webm",
+            "$type": "ResponseProfile"
+        }
+    ],
+    "SubtitleProfiles": [
+        {
+            "Format": "vtt",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "srt",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ass",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ssa",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "smi",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "subrip",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "sub",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "dvdsub",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "pgs",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "pgssub",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "srt",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "sub",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        }
+    ],
+    "$type": "DeviceProfile"
+}

+ 189 - 0
tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Yatse2.json

@@ -0,0 +1,189 @@
+{
+    "EnableAlbumArtInDidl": false,
+    "EnableSingleAlbumArtLimit": false,
+    "EnableSingleSubtitleLimit": false,
+    "SupportedMediaTypes": "Audio,Photo,Video",
+    "MaxAlbumArtWidth": 0,
+    "MaxAlbumArtHeight": 0,
+    "MaxStreamingBitrate": 120000000,
+    "MaxStaticBitrate": 100000000,
+    "MusicStreamingTranscodingBitrate": 192000,
+    "TimelineOffsetSeconds": 0,
+    "RequiresPlainVideoItems": false,
+    "RequiresPlainFolders": false,
+    "EnableMSMediaReceiverRegistrar": false,
+    "IgnoreTranscodeByteRangeRequests": false,
+    "DirectPlayProfiles": [
+        {
+            "Container": "",
+            "AudioCodec": "aac",
+            "VideoCodec": "",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "ts,mp4,mka,m4a,mp3,mp2,wav,flac,ogg",
+            "AudioCodec": "",
+            "VideoCodec": "",
+            "Type": "Audio",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "",
+            "AudioCodec": "",
+            "VideoCodec": "",
+            "Type": "Photo",
+            "$type": "DirectPlayProfile"
+        }
+    ],
+    "TranscodingProfiles": [
+        {
+            "Container": "ts",
+            "Type": "Video",
+            "VideoCodec": "h264",
+            "AudioCodec": "aac",
+            "Protocol": "hls",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": true,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "VideoCodec": "",
+            "AudioCodec": "mp3",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Static",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        },
+        {
+            "Container": "mp3",
+            "Type": "Audio",
+            "VideoCodec": "",
+            "AudioCodec": "mp3",
+            "Protocol": "http",
+            "EstimateContentLength": false,
+            "EnableMpegtsM2TsMode": false,
+            "TranscodeSeekInfo": "Auto",
+            "CopyTimestamps": false,
+            "Context": "Streaming",
+            "EnableSubtitlesInManifest": false,
+            "MaxAudioChannels": "6",
+            "MinSegments": 0,
+            "SegmentLength": 0,
+            "BreakOnNonKeyFrames": false,
+            "$type": "TranscodingProfile"
+        }
+    ],
+    "CodecProfiles": [
+        {
+            "Type": "VideoAudio",
+            "Conditions": [
+                {
+                    "Condition": "Equals",
+                    "Property": "IsSecondaryAudio",
+                    "Value": "false",
+                    "IsRequired": false,
+                    "$type": "ProfileCondition"
+                }
+            ],
+            "Codec": "",
+            "Container": "",
+            "$type": "CodecProfile"
+        }
+    ],
+    "ResponseProfiles": [
+        {
+            "Container": "m4v",
+            "Type": "Video",
+            "MimeType": "video/mp4",
+            "$type": "ResponseProfile"
+        },
+        {
+            "Container": "mov",
+            "Type": "Video",
+            "MimeType": "video/webm",
+            "$type": "ResponseProfile"
+        }
+    ],
+    "SubtitleProfiles": [
+        {
+            "Format": "vtt",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "srt",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ass",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "ssa",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "smi",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "subrip",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "sub",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "dvdsub",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "pgs",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "pgssub",
+            "Method": "Embed",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "srt",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        },
+        {
+            "Format": "sub",
+            "Method": "External",
+            "$type": "SubtitleProfile"
+        }
+    ],
+    "$type": "DeviceProfile"
+}

+ 73 - 0
tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-av1-aac-srt-2600k.json

@@ -0,0 +1,73 @@
+{
+    "Id": "a766d122b58e45d9492d17af66748bf5",
+    "Path": "/Media/MyVideo-720p.mkv",
+    "Container": "mkv,webm",
+    "Size": 835317696,
+    "Name": "MyVideo-1080p",
+    "ETag": "579a34c6d5dfb23f61539a51220b6a23",
+    "RunTimeTicks": 25801230336,
+    "SupportsTranscoding": true,
+    "SupportsDirectStream": true,
+    "SupportsDirectPlay": true,
+    "SupportsProbing": true,
+    "MediaStreams": [
+        {
+            "Codec": "av1",
+            "Language": "eng",
+            "ColorTransfer": "bt709",
+            "ColorPrimaries": "bt709",
+            "TimeBase": "1/11988",
+            "VideoRange": "SDR",
+            "DisplayTitle": "1080p AV1 SDR",
+            "NalLengthSize": "0",
+            "BitRate": 2032876,
+            "BitDepth": 8,
+            "RefFrames": 1,
+            "IsDefault": true,
+            "Height": 720,
+            "Width": 1280,
+            "AverageFrameRate": 23.976,
+            "RealFrameRate": 23.976,
+            "Profile": "Main",
+            "Type": 1,
+            "AspectRatio": "16:9",
+            "PixelFormat": "yuv420p",
+            "Level": -99
+        },
+        {
+            "Codec": "aac",
+            "CodecTag": "mp4a",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - AAC - Stereo - Default",
+            "ChannelLayout": "stereo",
+            "BitRate": 164741,
+            "Channels": 2,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Profile": "LC",
+            "Index": 1,
+            "Score": 203
+        },
+        {
+            "Codec": "srt",
+            "Language": "eng",
+            "TimeBase": "1/1000000",
+            "localizedUndefined": "Undefined",
+            "localizedDefault": "Default",
+            "localizedForced": "Forced",
+            "DisplayTitle": "En - Default",
+            "BitRate": 92,
+            "IsDefault": true,
+            "Type": 2,
+            "Index": 2,
+            "Score": 6421,
+            "IsExternal": true,
+            "IsTextSubtitleStream": true,
+            "SupportsExternalStream": true
+        }
+    ],
+    "Bitrate": 2590008,
+    "DefaultAudioStreamIndex": 1,
+    "DefaultSubtitleStreamIndex": 2
+}

+ 72 - 0
tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-av1-vorbis-srt-2600k.json

@@ -0,0 +1,72 @@
+{
+    "Id": "a766d122b58e45d9492d17af66748bf5",
+    "Path": "/Media/MyVideo-720p.mkv",
+    "Container": "mkv,webm",
+    "Size": 835317696,
+    "Name": "MyVideo-1080p",
+    "ETag": "579a34c6d5dfb23f61539a51220b6a23",
+    "RunTimeTicks": 25801230336,
+    "SupportsTranscoding": true,
+    "SupportsDirectStream": true,
+    "SupportsDirectPlay": true,
+    "SupportsProbing": true,
+    "MediaStreams": [
+        {
+            "Codec": "av1",
+            "Language": "eng",
+            "ColorTransfer": "bt709",
+            "ColorPrimaries": "bt709",
+            "TimeBase": "1/11988",
+            "VideoRange": "SDR",
+            "DisplayTitle": "1080p AV1 SDR",
+            "NalLengthSize": "0",
+            "BitRate": 2032876,
+            "BitDepth": 8,
+            "RefFrames": 1,
+            "IsDefault": true,
+            "Height": 720,
+            "Width": 1280,
+            "AverageFrameRate": 23.976,
+            "RealFrameRate": 23.976,
+            "Profile": "Main",
+            "Type": 1,
+            "AspectRatio": "16:9",
+            "PixelFormat": "yuv420p",
+            "Level": -99
+        },
+        {
+            "Codec": "vorbis",
+            "CodecTag": "ogg",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - Vorbis - Stereo - Default",
+            "ChannelLayout": "stereo",
+            "BitRate": 164741,
+            "Channels": 2,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Index": 1,
+            "Score": 203
+        },
+        {
+            "Codec": "srt",
+            "Language": "eng",
+            "TimeBase": "1/1000000",
+            "localizedUndefined": "Undefined",
+            "localizedDefault": "Default",
+            "localizedForced": "Forced",
+            "DisplayTitle": "En - Default",
+            "BitRate": 92,
+            "IsDefault": true,
+            "Type": 2,
+            "Index": 2,
+            "Score": 6421,
+            "IsExternal": true,
+            "IsTextSubtitleStream": true,
+            "SupportsExternalStream": true
+        }
+    ],
+    "Bitrate": 2590008,
+    "DefaultAudioStreamIndex": 1,
+    "DefaultSubtitleStreamIndex": 2
+}

+ 73 - 0
tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-aac-srt-2600k.json

@@ -0,0 +1,73 @@
+{
+    "Id": "a766d122b58e45d9492d17af66748bf5",
+    "Path": "/Media/MyVideo-720p.mkv",
+    "Container": "mkv,webm",
+    "Size": 835317696,
+    "Name": "MyVideo-1080p",
+    "ETag": "579a34c6d5dfb23f61539a51220b6a23",
+    "RunTimeTicks": 25801230336,
+    "SupportsTranscoding": true,
+    "SupportsDirectStream": true,
+    "SupportsDirectPlay": true,
+    "SupportsProbing": true,
+    "MediaStreams": [
+        {
+            "Codec": "vp9",
+            "Language": "eng",
+            "ColorTransfer": "bt709",
+            "ColorPrimaries": "bt709",
+            "TimeBase": "1/11988",
+            "VideoRange": "SDR",
+            "DisplayTitle": "1080p VP9 SDR",
+            "NalLengthSize": "0",
+            "BitRate": 2032876,
+            "BitDepth": 8,
+            "RefFrames": 1,
+            "IsDefault": true,
+            "Height": 720,
+            "Width": 1280,
+            "AverageFrameRate": 23.976,
+            "RealFrameRate": 23.976,
+            "Profile": "Profile 0",
+            "Type": 1,
+            "AspectRatio": "16:9",
+            "PixelFormat": "yuv420p",
+            "Level": -99
+        },
+        {
+            "Codec": "aac",
+            "CodecTag": "mp4a",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - AAC - Stereo - Default",
+            "ChannelLayout": "stereo",
+            "BitRate": 164741,
+            "Channels": 2,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Profile": "LC",
+            "Index": 1,
+            "Score": 203
+        },
+        {
+            "Codec": "srt",
+            "Language": "eng",
+            "TimeBase": "1/1000000",
+            "localizedUndefined": "Undefined",
+            "localizedDefault": "Default",
+            "localizedForced": "Forced",
+            "DisplayTitle": "En - Default",
+            "BitRate": 92,
+            "IsDefault": true,
+            "Type": 2,
+            "Index": 2,
+            "Score": 6421,
+            "IsExternal": true,
+            "IsTextSubtitleStream": true,
+            "SupportsExternalStream": true
+        }
+    ],
+    "Bitrate": 2590008,
+    "DefaultAudioStreamIndex": 1,
+    "DefaultSubtitleStreamIndex": 2
+}

+ 72 - 0
tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-ac3-srt-2600k.json

@@ -0,0 +1,72 @@
+{
+    "Id": "a766d122b58e45d9492d17af66748bf5",
+    "Path": "/Media/MyVideo-720p.mkv",
+    "Container": "mkv,webm",
+    "Size": 835317696,
+    "Name": "MyVideo-1080p",
+    "ETag": "579a34c6d5dfb23f61539a51220b6a23",
+    "RunTimeTicks": 25801230336,
+    "SupportsTranscoding": true,
+    "SupportsDirectStream": true,
+    "SupportsDirectPlay": true,
+    "SupportsProbing": true,
+    "MediaStreams": [
+        {
+            "Codec": "vp9",
+            "Language": "eng",
+            "ColorTransfer": "bt709",
+            "ColorPrimaries": "bt709",
+            "TimeBase": "1/11988",
+            "VideoRange": "SDR",
+            "DisplayTitle": "1080p VP9 SDR",
+            "NalLengthSize": "0",
+            "BitRate": 2032876,
+            "BitDepth": 8,
+            "RefFrames": 1,
+            "IsDefault": true,
+            "Height": 720,
+            "Width": 1280,
+            "AverageFrameRate": 23.976,
+            "RealFrameRate": 23.976,
+            "Profile": "Profile 0",
+            "Type": 1,
+            "AspectRatio": "16:9",
+            "PixelFormat": "yuv420p",
+            "Level": -99
+        },
+        {
+            "Codec": "ac3",
+            "CodecTag": "ac-3",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - Dolby Digital - 5.1 - Default",
+            "ChannelLayout": "5.1",
+            "BitRate": 384000,
+            "Channels": 6,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Index": 1,
+            "Score": 202
+        },
+        {
+            "Codec": "srt",
+            "Language": "eng",
+            "TimeBase": "1/1000000",
+            "localizedUndefined": "Undefined",
+            "localizedDefault": "Default",
+            "localizedForced": "Forced",
+            "DisplayTitle": "En - Default",
+            "BitRate": 92,
+            "IsDefault": true,
+            "Type": 2,
+            "Index": 2,
+            "Score": 6421,
+            "IsExternal": true,
+            "IsTextSubtitleStream": true,
+            "SupportsExternalStream": true
+        }
+    ],
+    "Bitrate": 2590008,
+    "DefaultAudioStreamIndex": 1,
+    "DefaultSubtitleStreamIndex": 2
+}

+ 73 - 0
tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-srt-2600k.json

@@ -0,0 +1,73 @@
+{
+    "Id": "a766d122b58e45d9492d17af66748bf5",
+    "Path": "/Media/MyVideo-720p.mkv",
+    "Container": "mkv,webm",
+    "Size": 835317696,
+    "Name": "MyVideo-1080p",
+    "ETag": "579a34c6d5dfb23f61539a51220b6a23",
+    "RunTimeTicks": 25801230336,
+    "SupportsTranscoding": true,
+    "SupportsDirectStream": true,
+    "SupportsDirectPlay": true,
+    "SupportsProbing": true,
+    "MediaStreams": [
+        {
+            "Codec": "vp9",
+            "Language": "eng",
+            "ColorTransfer": "bt709",
+            "ColorPrimaries": "bt709",
+            "TimeBase": "1/11988",
+            "VideoRange": "SDR",
+            "DisplayTitle": "1080p VP9 SDR",
+            "NalLengthSize": "0",
+            "BitRate": 2032876,
+            "BitDepth": 8,
+            "RefFrames": 1,
+            "IsDefault": true,
+            "Height": 720,
+            "Width": 1280,
+            "AverageFrameRate": 23.976,
+            "RealFrameRate": 23.976,
+            "Profile": "Profile 0",
+            "Type": 1,
+            "AspectRatio": "16:9",
+            "PixelFormat": "yuv420p",
+            "Level": -99
+        },
+        {
+            "Codec": "vorbis",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - Vorbis - Stereo - Default",
+            "ChannelLayout": "stereo",
+            "BitRate": 164741,
+            "Channels": 2,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Profile": "LC",
+            "Index": 1,
+            "Score": 203
+        },
+        {
+            "Codec": "srt",
+            "Language": "eng",
+            "TimeBase": "1/1000000",
+            "localizedUndefined": "Undefined",
+            "localizedDefault": "Default",
+            "localizedForced": "Forced",
+            "DisplayTitle": "En - Default",
+            "BitRate": 92,
+            "IsDefault": true,
+            "Type": 2,
+            "Index": 2,
+            "Score": 6421,
+            "IsExternal": true,
+            "IsTextSubtitleStream": true,
+            "SupportsExternalStream": true
+        }
+    ],
+    "Bitrate": 2590008,
+    "RequiredHttpHeaders": {},
+    "DefaultAudioStreamIndex": 1,
+    "DefaultSubtitleStreamIndex": 2
+}

+ 72 - 0
tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-vtt-2600k.json

@@ -0,0 +1,72 @@
+{
+    "Id": "a766d122b58e45d9492d17af66748bf5",
+    "Path": "/Media/MyVideo-720p.mkv",
+    "Container": "mkv,webm",
+    "Size": 835317696,
+    "Name": "MyVideo-1080p",
+    "ETag": "579a34c6d5dfb23f61539a51220b6a23",
+    "RunTimeTicks": 25801230336,
+    "SupportsTranscoding": true,
+    "SupportsDirectStream": true,
+    "SupportsDirectPlay": true,
+    "SupportsProbing": true,
+    "MediaStreams": [
+        {
+            "Codec": "vp9",
+            "Language": "eng",
+            "ColorTransfer": "bt709",
+            "ColorPrimaries": "bt709",
+            "TimeBase": "1/11988",
+            "VideoRange": "SDR",
+            "DisplayTitle": "1080p VP9 SDR",
+            "NalLengthSize": "0",
+            "BitRate": 2032876,
+            "BitDepth": 8,
+            "RefFrames": 1,
+            "IsDefault": true,
+            "Height": 720,
+            "Width": 1280,
+            "AverageFrameRate": 23.976,
+            "RealFrameRate": 23.976,
+            "Profile": "Profile 0",
+            "Type": 1,
+            "AspectRatio": "16:9",
+            "PixelFormat": "yuv420p",
+            "Level": -99
+        },
+        {
+            "Codec": "vorbis",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - Vorbis - Stereo - Default",
+            "ChannelLayout": "stereo",
+            "BitRate": 164741,
+            "Channels": 2,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Profile": "LC",
+            "Index": 1,
+            "Score": 203
+        },
+        {
+            "Codec": "webvtt",
+            "Language": "eng",
+            "TimeBase": "1/1000000",
+            "localizedUndefined": "Undefined",
+            "localizedDefault": "Default",
+            "localizedForced": "Forced",
+            "DisplayTitle": "En - Default",
+            "BitRate": 92,
+            "IsDefault": true,
+            "Type": 2,
+            "Index": 2,
+            "Score": 6421,
+            "IsExternal": true,
+            "IsTextSubtitleStream": true,
+            "SupportsExternalStream": true
+        }
+    ],
+    "Bitrate": 2590008,
+    "DefaultAudioStreamIndex": 1,
+    "DefaultSubtitleStreamIndex": 2
+}

+ 72 - 0
tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-srt-2600k.json

@@ -0,0 +1,72 @@
+{
+    "Id": "a766d122b58e45d9492d17af77748bf5",
+    "Path": "/Media/MyVideo-720p.mp4",
+    "Container": "mov,mp4,m4a,3gp,3g2,mj2",
+    "Size": 835317696,
+    "Name": "MyVideo-720p",
+    "ETag": "579a34c6d5dfb21d81539a51220b6a23",
+    "RunTimeTicks": 25801230336,
+    "SupportsTranscoding": true,
+    "SupportsDirectStream": true,
+    "SupportsDirectPlay": true,
+    "SupportsProbing": true,
+    "MediaStreams": [
+        {
+            "Codec": "h264",
+            "CodecTag": "avc1",
+            "Language": "eng",
+            "TimeBase": "1/11988",
+            "VideoRange": "SDR",
+            "DisplayTitle": "720p H264 SDR",
+            "NalLengthSize": "0",
+            "BitRate": 2032876,
+            "BitDepth": 8,
+            "RefFrames": 1,
+            "IsDefault": true,
+            "Height": 720,
+            "Width": 1280,
+            "AverageFrameRate": 23.976,
+            "RealFrameRate": 23.976,
+            "Profile": "High",
+            "Type": 1,
+            "AspectRatio": "16:9",
+            "PixelFormat": "yuv420p",
+            "Level": 41
+        },
+        {
+            "Codec": "aac",
+            "CodecTag": "mp4a",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - AAC - Stereo - Default",
+            "ChannelLayout": "stereo",
+            "BitRate": 164741,
+            "Channels": 2,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Profile": "LC",
+            "Index": 1,
+            "Score": 203
+        },
+        {
+            "Codec": "srt",
+            "Language": "eng",
+            "TimeBase": "1/1000000",
+            "localizedUndefined": "Undefined",
+            "localizedDefault": "Default",
+            "localizedForced": "Forced",
+            "DisplayTitle": "En - Default",
+            "BitRate": 92,
+            "IsDefault": true,
+            "Type": 2,
+            "Index": 2,
+            "Score": 6421,
+            "IsExternal": true,
+            "IsTextSubtitleStream": true,
+            "SupportsExternalStream": true
+        }
+    ],
+    "Bitrate": 2590008,
+    "DefaultAudioStreamIndex": 1,
+    "DefaultSubtitleStreamIndex": 2
+}

+ 72 - 0
tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-vtt-2600k.json

@@ -0,0 +1,72 @@
+{
+    "Id": "a766d122b58e45d9492d17af77748bf5",
+    "Path": "/Media/MyVideo-720p.mp4",
+    "Container": "mov,mp4,m4a,3gp,3g2,mj2",
+    "Size": 835317696,
+    "Name": "MyVideo-720p",
+    "ETag": "579a34c6d5dfb21d81539a51220b6a23",
+    "RunTimeTicks": 25801230336,
+    "SupportsTranscoding": true,
+    "SupportsDirectStream": true,
+    "SupportsDirectPlay": true,
+    "SupportsProbing": true,
+    "MediaStreams": [
+        {
+            "Codec": "h264",
+            "CodecTag": "avc1",
+            "Language": "eng",
+            "TimeBase": "1/11988",
+            "VideoRange": "SDR",
+            "DisplayTitle": "720p H264 SDR",
+            "NalLengthSize": "0",
+            "BitRate": 2032876,
+            "BitDepth": 8,
+            "RefFrames": 1,
+            "IsDefault": true,
+            "Height": 720,
+            "Width": 1280,
+            "AverageFrameRate": 23.976,
+            "RealFrameRate": 23.976,
+            "Profile": "High",
+            "Type": 1,
+            "AspectRatio": "16:9",
+            "PixelFormat": "yuv420p",
+            "Level": 41
+        },
+        {
+            "Codec": "aac",
+            "CodecTag": "mp4a",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - AAC - Stereo - Default",
+            "ChannelLayout": "stereo",
+            "BitRate": 164741,
+            "Channels": 2,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Profile": "LC",
+            "Index": 1,
+            "Score": 203
+        },
+        {
+            "Codec": "webvtt",
+            "Language": "eng",
+            "TimeBase": "1/1000000",
+            "localizedUndefined": "Undefined",
+            "localizedDefault": "Default",
+            "localizedForced": "Forced",
+            "DisplayTitle": "En - Default",
+            "BitRate": 92,
+            "IsDefault": true,
+            "Type": 2,
+            "Index": 2,
+            "Score": 6421,
+            "IsExternal": true,
+            "IsTextSubtitleStream": true,
+            "SupportsExternalStream": true
+        }
+    ],
+    "Bitrate": 2590008,
+    "DefaultAudioStreamIndex": 1,
+    "DefaultSubtitleStreamIndex": 2
+}

+ 87 - 0
tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aac-srt-2600k.json

@@ -0,0 +1,87 @@
+{
+    "Id": "a766d122b58e45d9492d17af77748bf5",
+    "Path": "/Media/MyVideo-720p.mp4",
+    "Container": "mov,mp4,m4a,3gp,3g2,mj2",
+    "Size": 835317696,
+    "Name": "MyVideo-720p",
+    "ETag": "579a34c6d5dfb21d81539a51220b6a23",
+    "RunTimeTicks": 25801230336,
+    "SupportsTranscoding": true,
+    "SupportsDirectStream": true,
+    "SupportsDirectPlay": true,
+    "SupportsProbing": true,
+    "MediaStreams": [
+        {
+            "Codec": "h264",
+            "CodecTag": "avc1",
+            "Language": "eng",
+            "TimeBase": "1/11988",
+            "VideoRange": "SDR",
+            "DisplayTitle": "720p H264 SDR",
+            "NalLengthSize": "0",
+            "BitRate": 2032876,
+            "BitDepth": 8,
+            "RefFrames": 1,
+            "IsDefault": true,
+            "Height": 720,
+            "Width": 1280,
+            "AverageFrameRate": 23.976,
+            "RealFrameRate": 23.976,
+            "Profile": "High",
+            "Type": 1,
+            "AspectRatio": "16:9",
+            "PixelFormat": "yuv420p",
+            "Level": 41
+        },
+        {
+            "Codec": "ac3",
+            "CodecTag": "ac-3",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - Dolby Digital - 5.1 - Default",
+            "ChannelLayout": "5.1",
+            "BitRate": 384000,
+            "Channels": 6,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Index": 1,
+            "Score": 202
+        },
+        {
+            "Codec": "aac",
+            "CodecTag": "mp4a",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - AAC - Stereo - Default",
+            "ChannelLayout": "stereo",
+            "BitRate": 164741,
+            "Channels": 2,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Profile": "LC",
+            "Index": 2,
+            "Score": 203
+        },
+        {
+            "Codec": "srt",
+            "Language": "eng",
+            "TimeBase": "1/1000000",
+            "localizedUndefined": "Undefined",
+            "localizedDefault": "Default",
+            "localizedForced": "Forced",
+            "DisplayTitle": "En - Default",
+            "BitRate": 92,
+            "IsDefault": true,
+            "Type": 2,
+            "Index": 3,
+            "Score": 6421,
+            "IsExternal": true,
+            "IsTextSubtitleStream": true,
+            "SupportsExternalStream": true,
+            "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt"
+        }
+    ],
+    "Bitrate": 2590008,
+    "DefaultAudioStreamIndex": 1,
+    "DefaultSubtitleStreamIndex": 3
+}

+ 87 - 0
tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacDef-srt-2600k.json

@@ -0,0 +1,87 @@
+{
+    "Id": "a766d122b58e45d9492d17af77748bf5",
+    "Path": "/Media/MyVideo-720p.mp4",
+    "Container": "mov,mp4,m4a,3gp,3g2,mj2",
+    "Size": 835317696,
+    "Name": "MyVideo-720p",
+    "ETag": "579a34c6d5dfb21d81539a51220b6a23",
+    "RunTimeTicks": 25801230336,
+    "SupportsTranscoding": true,
+    "SupportsDirectStream": true,
+    "SupportsDirectPlay": true,
+    "SupportsProbing": true,
+    "MediaStreams": [
+        {
+            "Codec": "h264",
+            "CodecTag": "avc1",
+            "Language": "eng",
+            "TimeBase": "1/11988",
+            "VideoRange": "SDR",
+            "DisplayTitle": "720p H264 SDR",
+            "NalLengthSize": "0",
+            "BitRate": 2032876,
+            "BitDepth": 8,
+            "RefFrames": 1,
+            "IsDefault": true,
+            "Height": 720,
+            "Width": 1280,
+            "AverageFrameRate": 23.976,
+            "RealFrameRate": 23.976,
+            "Profile": "High",
+            "Type": 1,
+            "AspectRatio": "16:9",
+            "PixelFormat": "yuv420p",
+            "Level": 41
+        },
+        {
+            "Codec": "ac3",
+            "CodecTag": "ac-3",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - Dolby Digital - 5.1 - Default",
+            "ChannelLayout": "5.1",
+            "BitRate": 384000,
+            "Channels": 6,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Index": 1,
+            "Score": 202
+        },
+        {
+            "Codec": "aac",
+            "CodecTag": "mp4a",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - AAC - Stereo - Default",
+            "ChannelLayout": "stereo",
+            "BitRate": 164741,
+            "Channels": 2,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Profile": "LC",
+            "Index": 2,
+            "Score": 203
+        },
+        {
+            "Codec": "srt",
+            "Language": "eng",
+            "TimeBase": "1/1000000",
+            "localizedUndefined": "Undefined",
+            "localizedDefault": "Default",
+            "localizedForced": "Forced",
+            "DisplayTitle": "En - Default",
+            "BitRate": 92,
+            "IsDefault": true,
+            "Type": 2,
+            "Index": 3,
+            "Score": 6421,
+            "IsExternal": true,
+            "IsTextSubtitleStream": true,
+            "SupportsExternalStream": true,
+            "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt"
+        }
+    ],
+    "Bitrate": 2590008,
+    "DefaultAudioStreamIndex": 2,
+    "DefaultSubtitleStreamIndex": 3
+}

+ 89 - 0
tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacExt-srt-2600k.json

@@ -0,0 +1,89 @@
+{
+    "Id": "a766d122b58e45d9492d17af77748bf5",
+    "Path": "/Media/MyVideo-720p.mp4",
+    "Container": "mov,mp4,m4a,3gp,3g2,mj2",
+    "Size": 835317696,
+    "Name": "MyVideo-720p",
+    "ETag": "579a34c6d5dfb21d81539a51220b6a23",
+    "RunTimeTicks": 25801230336,
+    "SupportsTranscoding": true,
+    "SupportsDirectStream": true,
+    "SupportsDirectPlay": true,
+    "SupportsProbing": true,
+    "MediaStreams": [
+        {
+            "Codec": "h264",
+            "CodecTag": "avc1",
+            "Language": "eng",
+            "TimeBase": "1/11988",
+            "VideoRange": "SDR",
+            "DisplayTitle": "720p H264 SDR",
+            "NalLengthSize": "0",
+            "BitRate": 2032876,
+            "BitDepth": 8,
+            "RefFrames": 1,
+            "IsDefault": true,
+            "Height": 720,
+            "Width": 1280,
+            "AverageFrameRate": 23.976,
+            "RealFrameRate": 23.976,
+            "Profile": "High",
+            "Type": 1,
+            "AspectRatio": "16:9",
+            "PixelFormat": "yuv420p",
+            "Level": 41
+        },
+        {
+            "Codec": "ac3",
+            "CodecTag": "ac-3",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - Dolby Digital - 5.1 - Default",
+            "ChannelLayout": "5.1",
+            "BitRate": 384000,
+            "Channels": 6,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Index": 1,
+            "Score": 202
+        },
+        {
+            "Codec": "aac",
+            "CodecTag": "mp4a",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - AAC - Stereo - Default",
+            "ChannelLayout": "stereo",
+            "BitRate": 164741,
+            "Channels": 2,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "IsExternal": true,
+            "Profile": "LC",
+            "Index": 2,
+            "Score": 203,
+            "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt"
+        },
+        {
+            "Codec": "srt",
+            "Language": "eng",
+            "TimeBase": "1/1000000",
+            "localizedUndefined": "Undefined",
+            "localizedDefault": "Default",
+            "localizedForced": "Forced",
+            "DisplayTitle": "En - Default",
+            "BitRate": 92,
+            "IsDefault": true,
+            "Type": 2,
+            "Index": 3,
+            "Score": 6421,
+            "IsExternal": true,
+            "IsTextSubtitleStream": true,
+            "SupportsExternalStream": true,
+            "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt"
+        }
+    ],
+    "Bitrate": 2590008,
+    "DefaultAudioStreamIndex": 1,
+    "DefaultSubtitleStreamIndex": 3
+}

+ 71 - 0
tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-srt-2600k.json

@@ -0,0 +1,71 @@
+{
+    "Id": "a766d122b58e45d9492d17af77748bf5",
+    "Path": "/Media/MyVideo-720p.mp4",
+    "Container": "mov,mp4,m4a,3gp,3g2,mj2",
+    "Size": 835317696,
+    "Name": "MyVideo-720p",
+    "ETag": "579a34c6d5dfb21d81539a51220b6a23",
+    "RunTimeTicks": 25801230336,
+    "SupportsTranscoding": true,
+    "SupportsDirectStream": true,
+    "SupportsDirectPlay": true,
+    "SupportsProbing": true,
+    "MediaStreams": [
+        {
+            "Codec": "h264",
+            "CodecTag": "avc1",
+            "Language": "eng",
+            "TimeBase": "1/11988",
+            "VideoRange": "SDR",
+            "DisplayTitle": "720p H264 SDR",
+            "NalLengthSize": "0",
+            "BitRate": 2032876,
+            "BitDepth": 8,
+            "RefFrames": 1,
+            "IsDefault": true,
+            "Height": 720,
+            "Width": 1280,
+            "AverageFrameRate": 23.976,
+            "RealFrameRate": 23.976,
+            "Profile": "High",
+            "Type": 1,
+            "AspectRatio": "16:9",
+            "PixelFormat": "yuv420p",
+            "Level": 41
+        },
+        {
+            "Codec": "ac3",
+            "CodecTag": "ac-3",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - Dolby Digital - 5.1 - Default",
+            "ChannelLayout": "5.1",
+            "BitRate": 384000,
+            "Channels": 6,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Index": 1,
+            "Score": 202
+        },
+        {
+            "Codec": "srt",
+            "Language": "eng",
+            "TimeBase": "1/1000000",
+            "localizedUndefined": "Undefined",
+            "localizedDefault": "Default",
+            "localizedForced": "Forced",
+            "DisplayTitle": "En - Default",
+            "BitRate": 92,
+            "IsDefault": true,
+            "Type": 2,
+            "Index": 2,
+            "Score": 6421,
+            "IsExternal": true,
+            "IsTextSubtitleStream": true,
+            "SupportsExternalStream": true
+        }
+    ],
+    "Bitrate": 2590008,
+    "DefaultAudioStreamIndex": 1,
+    "DefaultSubtitleStreamIndex": 2
+}

+ 75 - 0
tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-aac-srt-15200k.json

@@ -0,0 +1,75 @@
+{
+    "Id": "f6eab7118618ab26e61e495a1853481a",
+    "Path": "/Media/MyVideo-WEBDL-2160p.mp4",
+    "Container": "mov,mp4,m4a,3gp,3g2,mj2",
+    "Size": 6521110016,
+    "Name": "MyVideo WEBDL-2160p",
+    "ETag": "a2fb84b618ba2467fe377543f879e9bf",
+    "RunTimeTicks": 34318510080,
+    "SupportsTranscoding": true,
+    "SupportsDirectStream": true,
+    "SupportsDirectPlay": true,
+    "SupportsProbing": true,
+    "MediaStreams": [
+        {
+            "Codec": "hevc",
+            "CodecTag": "hev1",
+            "Language": "eng",
+            "ColorSpace": "bt2020nc",
+            "ColorTransfer": "smpte2084",
+            "ColorPrimaries": "bt2020",
+            "TimeBase": "1/16000",
+            "VideoRange": "HDR",
+            "DisplayTitle": "4K HEVC HDR",
+            "BitRate": 14715079,
+            "BitDepth": 8,
+            "RefFrames": 1,
+            "IsDefault": true,
+            "Height": 2160,
+            "Width": 3840,
+            "AverageFrameRate": 23.976,
+            "RealFrameRate": 23.976,
+            "Profile": "Main 10",
+            "Type": 1,
+            "AspectRatio": "16:9",
+            "PixelFormat": "yuv420p10le",
+            "Level": 150
+        },
+        {
+            "Codec": "aac",
+            "CodecTag": "mp4a",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - AAC - Stereo - Default",
+            "ChannelLayout": "stereo",
+            "BitRate": 164741,
+            "Channels": 2,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Profile": "LC",
+            "Index": 1,
+            "Score": 203
+        },
+        {
+            "Codec": "srt",
+            "Language": "eng",
+            "TimeBase": "1/1000000",
+            "localizedUndefined": "Undefined",
+            "localizedDefault": "Default",
+            "localizedForced": "Forced",
+            "DisplayTitle": "En - Default",
+            "BitRate": 92,
+            "IsDefault": true,
+            "Type": 2,
+            "Index": 2,
+            "Score": 6421,
+            "IsExternal": true,
+            "IsTextSubtitleStream": true,
+            "SupportsExternalStream": true,
+            "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt"
+        }
+    ],
+    "Bitrate": 15201382,
+    "DefaultAudioStreamIndex": 1,
+    "DefaultSubtitleStreamIndex": 2
+}

+ 89 - 0
tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aac-srt-15200k.json

@@ -0,0 +1,89 @@
+{
+    "Id": "f6eab7118618ab26e61e495a1853481a",
+    "Path": "/Media/MyVideo-WEBDL-2160p.mp4",
+    "Container": "mov,mp4,m4a,3gp,3g2,mj2",
+    "Size": 6521110016,
+    "Name": "MyVideo WEBDL-2160p",
+    "ETag": "a2fb84b618ba2467fe377543f879e9bf",
+    "RunTimeTicks": 34318510080,
+    "SupportsTranscoding": true,
+    "SupportsDirectStream": true,
+    "SupportsDirectPlay": true,
+    "SupportsProbing": true,
+    "MediaStreams": [
+        {
+            "Codec": "hevc",
+            "CodecTag": "hev1",
+            "Language": "eng",
+            "ColorSpace": "bt2020nc",
+            "ColorTransfer": "smpte2084",
+            "ColorPrimaries": "bt2020",
+            "TimeBase": "1/16000",
+            "VideoRange": "HDR",
+            "DisplayTitle": "4K HEVC HDR",
+            "BitRate": 14715079,
+            "BitDepth": 8,
+            "RefFrames": 1,
+            "IsDefault": true,
+            "Height": 2160,
+            "Width": 3840,
+            "AverageFrameRate": 23.976,
+            "RealFrameRate": 23.976,
+            "Profile": "Main 10",
+            "Type": 1,
+            "AspectRatio": "16:9",
+            "PixelFormat": "yuv420p10le",
+            "Level": 150
+        },
+        {
+            "Codec": "ac3",
+            "CodecTag": "ac-3",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - Dolby Digital - 5.1 - Default",
+            "ChannelLayout": "5.1",
+            "BitRate": 384000,
+            "Channels": 6,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Index": 1,
+            "Score": 202
+        },
+        {
+            "Codec": "aac",
+            "CodecTag": "mp4a",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - AAC - Stereo - Default",
+            "ChannelLayout": "stereo",
+            "BitRate": 164741,
+            "Channels": 2,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Profile": "LC",
+            "Index": 2,
+            "Score": 203
+        },
+        {
+            "Codec": "srt",
+            "Language": "eng",
+            "TimeBase": "1/1000000",
+            "localizedUndefined": "Undefined",
+            "localizedDefault": "Default",
+            "localizedForced": "Forced",
+            "DisplayTitle": "En - Default",
+            "BitRate": 92,
+            "IsDefault": true,
+            "Type": 2,
+            "Index": 3,
+            "Score": 6421,
+            "IsExternal": true,
+            "IsTextSubtitleStream": true,
+            "SupportsExternalStream": true,
+            "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt"
+        }
+    ],
+    "Bitrate": 15201382,
+    "DefaultAudioStreamIndex": 1,
+    "DefaultSubtitleStreamIndex": 3
+}

+ 91 - 0
tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aacExt-srt-15200k.json

@@ -0,0 +1,91 @@
+{
+    "Id": "f6eab7118618ab26e61e495a1853481a",
+    "Path": "/Media/MyVideo-WEBDL-2160p.mp4",
+    "Container": "mov,mp4,m4a,3gp,3g2,mj2",
+    "Size": 6521110016,
+    "Name": "MyVideo WEBDL-2160p",
+    "ETag": "a2fb84b618ba2467fe377543f879e9bf",
+    "RunTimeTicks": 34318510080,
+    "SupportsTranscoding": true,
+    "SupportsDirectStream": true,
+    "SupportsDirectPlay": true,
+    "SupportsProbing": true,
+    "MediaStreams": [
+        {
+            "Codec": "hevc",
+            "CodecTag": "hev1",
+            "Language": "eng",
+            "ColorSpace": "bt2020nc",
+            "ColorTransfer": "smpte2084",
+            "ColorPrimaries": "bt2020",
+            "TimeBase": "1/16000",
+            "VideoRange": "HDR",
+            "DisplayTitle": "4K HEVC HDR",
+            "BitRate": 14715079,
+            "BitDepth": 8,
+            "RefFrames": 1,
+            "IsDefault": true,
+            "Height": 2160,
+            "Width": 3840,
+            "AverageFrameRate": 23.976,
+            "RealFrameRate": 23.976,
+            "Profile": "Main 10",
+            "Type": 1,
+            "AspectRatio": "16:9",
+            "PixelFormat": "yuv420p10le",
+            "Level": 150
+        },
+        {
+            "Codec": "ac3",
+            "CodecTag": "ac-3",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - Dolby Digital - 5.1 - Default",
+            "ChannelLayout": "5.1",
+            "BitRate": 384000,
+            "Channels": 6,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Index": 1,
+            "Score": 202
+        },
+        {
+            "Codec": "aac",
+            "CodecTag": "mp4a",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - AAC - Stereo - Default",
+            "ChannelLayout": "stereo",
+            "BitRate": 164741,
+            "Channels": 2,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "IsExternal": true,
+            "Profile": "LC",
+            "Index": 2,
+            "Score": 203,
+            "Path": "/Media/MyVideo-WEBDL-2160p.eng.m4a"
+        },
+        {
+            "Codec": "srt",
+            "Language": "eng",
+            "TimeBase": "1/1000000",
+            "localizedUndefined": "Undefined",
+            "localizedDefault": "Default",
+            "localizedForced": "Forced",
+            "DisplayTitle": "En - Default",
+            "BitRate": 92,
+            "IsDefault": true,
+            "Type": 2,
+            "Index": 3,
+            "Score": 6421,
+            "IsExternal": true,
+            "IsTextSubtitleStream": true,
+            "SupportsExternalStream": true,
+            "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt"
+        }
+    ],
+    "Bitrate": 15201382,
+    "DefaultAudioStreamIndex": 1,
+    "DefaultSubtitleStreamIndex": 3
+}

+ 74 - 0
tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-srt-15200k.json

@@ -0,0 +1,74 @@
+{
+    "Id": "f6eab7118618ab26e61e495a1853481a",
+    "Path": "/Media/MyVideo-WEBDL-2160p.mp4",
+    "Container": "mov,mp4,m4a,3gp,3g2,mj2",
+    "Size": 6521110016,
+    "Name": "MyVideo WEBDL-2160p",
+    "ETag": "a2fb84b618ba2467fe377543f879e9bf",
+    "RunTimeTicks": 34318510080,
+    "SupportsTranscoding": true,
+    "SupportsDirectStream": true,
+    "SupportsDirectPlay": true,
+    "SupportsProbing": true,
+    "MediaStreams": [
+        {
+            "Codec": "hevc",
+            "CodecTag": "hev1",
+            "Language": "eng",
+            "ColorSpace": "bt2020nc",
+            "ColorTransfer": "smpte2084",
+            "ColorPrimaries": "bt2020",
+            "TimeBase": "1/16000",
+            "VideoRange": "HDR",
+            "DisplayTitle": "4K HEVC HDR",
+            "BitRate": 14715079,
+            "BitDepth": 8,
+            "RefFrames": 1,
+            "IsDefault": true,
+            "Height": 2160,
+            "Width": 3840,
+            "AverageFrameRate": 23.976,
+            "RealFrameRate": 23.976,
+            "Profile": "Main 10",
+            "Type": 1,
+            "AspectRatio": "16:9",
+            "PixelFormat": "yuv420p10le",
+            "Level": 150
+        },
+        {
+            "Codec": "ac3",
+            "CodecTag": "ac-3",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - Dolby Digital - 5.1 - Default",
+            "ChannelLayout": "5.1",
+            "BitRate": 384000,
+            "Channels": 6,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Index": 1,
+            "Score": 202
+        },
+        {
+            "Codec": "srt",
+            "Language": "eng",
+            "TimeBase": "1/1000000",
+            "localizedUndefined": "Undefined",
+            "localizedDefault": "Default",
+            "localizedForced": "Forced",
+            "DisplayTitle": "En - Default",
+            "BitRate": 92,
+            "IsDefault": true,
+            "Type": 2,
+            "Index": 2,
+            "Score": 6421,
+            "IsExternal": true,
+            "IsTextSubtitleStream": true,
+            "SupportsExternalStream": true,
+            "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt"
+        }
+    ],
+    "Bitrate": 15201382,
+    "DefaultAudioStreamIndex": 1,
+    "DefaultSubtitleStreamIndex": 2
+}

+ 102 - 0
tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-raw.json

@@ -0,0 +1,102 @@
+{
+    "Id": "a766d122b58e45d9492d17af77748bf5",
+    "Path": "/Media/MyVideo-720p.mp4",
+    "Container": "mov,mp4,m4a,3gp,3g2,mj2",
+    "Size": 835317696,
+    "Name": "MyVideo-720p",
+    "ETag": "579a34c6d5dfb21d81539a51220b6a23",
+    "RunTimeTicks": 25801230336,
+    "SupportsTranscoding": true,
+    "SupportsDirectStream": true,
+    "SupportsDirectPlay": true,
+    "SupportsProbing": true,
+    "MediaStreams": [
+        {
+            "Codec": "h264",
+            "CodecTag": "avc1",
+            "Language": "eng",
+            "TimeBase": "1/11988",
+            "VideoRange": "SDR",
+            "DisplayTitle": "720p H264 SDR",
+            "NalLengthSize": "0",
+            "BitRate": 2032876,
+            "BitDepth": 8,
+            "RefFrames": 1,
+            "IsDefault": true,
+            "Height": 720,
+            "Width": 1280,
+            "AverageFrameRate": 23.976,
+            "RealFrameRate": 23.976,
+            "Profile": "High",
+            "Type": 1,
+            "AspectRatio": "16:9",
+            "PixelFormat": "yuv420p",
+            "Level": 41
+        },
+        {
+            "Codec": "ac3",
+            "CodecTag": "ac-3",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - Dolby Digital - 5.1 - Default",
+            "ChannelLayout": "5.1",
+            "BitRate": 384000,
+            "Channels": 6,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Index": 1,
+            "Score": 202
+        },
+        {
+            "Codec": "aac",
+            "CodecTag": "mp4a",
+            "Language": "eng",
+            "TimeBase": "1/48000",
+            "DisplayTitle": "En - AAC - Stereo - Default",
+            "ChannelLayout": "stereo",
+            "BitRate": 164741,
+            "Channels": 2,
+            "SampleRate": 48000,
+            "IsDefault": true,
+            "Profile": "LC",
+            "Index": 2,
+            "Score": 203
+        },
+        {
+            "Codec": "mov_text",
+            "CodecTag": "tx3g",
+            "Language": "eng",
+            "TimeBase": "1/1000000",
+            "localizedUndefined": "Undefined",
+            "localizedDefault": "Default",
+            "localizedForced": "Forced",
+            "DisplayTitle": "En - Default",
+            "BitRate": 92,
+            "IsDefault": true,
+            "Type": 2,
+            "Index": 3,
+            "Score": 6421,
+            "IsTextSubtitleStream": true,
+            "SupportsExternalStream": true
+        },
+        {
+            "Codec": "srt",
+            "Language": "eng",
+            "localizedUndefined": "Undefined",
+            "localizedDefault": "Default",
+            "localizedForced": "Forced",
+            "DisplayTitle": "En - Default",
+            "IsDefault": true,
+            "Type": 2,
+            "Index": 4,
+            "Score": 6422,
+            "IsExternal": true,
+            "IsTextSubtitleStream": true,
+            "SupportsExternalStream": true,
+            "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt"
+        }
+    ],
+    "Bitrate": 2590008,
+    "RequiredHttpHeaders": {},
+    "DefaultSubtitleStreamIndex": 1
+}

+ 28 - 0
tests/Jellyfin.Extensions.Tests/Json/Converters/JsonFlagEnumTests.cs

@@ -0,0 +1,28 @@
+using System.Text.Json;
+using Jellyfin.Extensions.Json.Converters;
+using MediaBrowser.Model.Session;
+using Xunit;
+
+namespace Jellyfin.Extensions.Tests.Json.Converters;
+
+public class JsonFlagEnumTests
+{
+    private readonly JsonSerializerOptions _jsonOptions = new()
+    {
+        Converters =
+        {
+            new JsonFlagEnumConverter<TranscodeReason>()
+        }
+    };
+
+    [Theory]
+    [InlineData(TranscodeReason.AudioIsExternal | TranscodeReason.ContainerNotSupported, "[\"ContainerNotSupported\",\"AudioIsExternal\"]")]
+    [InlineData(TranscodeReason.AudioIsExternal | TranscodeReason.ContainerNotSupported | TranscodeReason.VideoBitDepthNotSupported, "[\"ContainerNotSupported\",\"AudioIsExternal\",\"VideoBitDepthNotSupported\"]")]
+    [InlineData((TranscodeReason)0, "[]")]
+    public void Serialize_Transcode_Reason(TranscodeReason transcodeReason, string output)
+    {
+        var result = JsonSerializer.Serialize(transcodeReason, _jsonOptions);
+
+        Assert.Equal(output, result);
+    }
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов