Przeglądaj źródła

StreamBuilder basic tests

Isaac Gordezky 3 lat temu
rodzic
commit
a3057afde8
31 zmienionych plików z 4758 dodań i 0 usunięć
  1. 6 0
      tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
  2. 235 0
      tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs
  3. 332 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-AndroidPixel.json
  4. 430 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json
  5. 448 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Chrome.json
  6. 90 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-DirectMedia.json
  7. 441 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Firefox.json
  8. 137 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json
  9. 137 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-LowBandwidth.json
  10. 9 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Null.json
  11. 211 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-RokuSSPlus.json
  12. 211 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json
  13. 371 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-SafariNext.json
  14. 123 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-TranscodeMedia.json
  15. 189 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Yatse.json
  16. 189 0
      tests/Jellyfin.Dlna.Tests/Test Data/DeviceProfile-Yatse2.json
  17. 73 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-aac-srt-2600k.json
  18. 72 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-ac3-srt-2600k.json
  19. 73 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-srt-2600k.json
  20. 72 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mkv-vp9-vorbis-vtt-2600k.json
  21. 72 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-srt-2600k.json
  22. 72 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-aac-vtt-2600k.json
  23. 87 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aac-srt-2600k.json
  24. 87 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacDef-srt-2600k.json
  25. 89 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aacExt-srt-2600k.json
  26. 71 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-srt-2600k.json
  27. 75 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-aac-srt-15200k.json
  28. 89 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aac-srt-15200k.json
  29. 91 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-aacExt-srt-15200k.json
  30. 74 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-mp4-hevc-ac3-srt-15200k.json
  31. 102 0
      tests/Jellyfin.Dlna.Tests/Test Data/MediaSourceInfo-raw.json

+ 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">

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

@@ -0,0 +1,235 @@
+using System;
+using System.IO;
+using System.Linq;
+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.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
+        [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectStream
+        [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)]
+        [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)]
+        [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be 'false'
+        [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be 'false'
+        [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        // Firefox
+        [InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
+        [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectStream
+        [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)]
+        [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)]
+        [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be 'false'
+        [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be 'false'
+        [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        // Safari
+        [InlineData("SafariNext", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("SafariNext", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("SafariNext", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("SafariNext", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should probably be DirectPlay
+        [InlineData("SafariNext", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should probably be DirectPlay
+        // AndroidPixel
+        [InlineData("AndroidPixel", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("AndroidPixel", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("AndroidPixel", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("AndroidPixel", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)]
+        [InlineData("AndroidPixel", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)]
+        // Yatse
+        [InlineData("Yatse", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
+        [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, true)]
+        [InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
+        // RokuSSPlus
+        [InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
+        [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectStream
+        [InlineData("RokuSSPlus", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
+        [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectStream
+        // JellyfinMediaPlayer
+        [InlineData("JellyfinMediaPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("JellyfinMediaPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode)] // #6450 should be DirectPlay
+        [InlineData("JellyfinMediaPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode)] // #6450 should be DirectPlay
+        [InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        // TranscodeMedia
+        [InlineData("TranscodeMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
+        [InlineData("TranscodeMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
+        [InlineData("TranscodeMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
+        [InlineData("TranscodeMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
+        [InlineData("TranscodeMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
+        [InlineData("TranscodeMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
+        [InlineData("TranscodeMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
+        [InlineData("TranscodeMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
+        // DirectMedia
+        [InlineData("DirectMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("DirectMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("DirectMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("DirectMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("DirectMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("DirectMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("DirectMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("DirectMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        // LowBandwidth
+        [InlineData("LowBandwidth", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
+        [InlineData("LowBandwidth", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
+        [InlineData("LowBandwidth", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode)] // #6450 should be DirectPlay
+        [InlineData("LowBandwidth", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
+        [InlineData("LowBandwidth", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
+        [InlineData("LowBandwidth", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
+        [InlineData("LowBandwidth", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
+        [InlineData("LowBandwidth", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, true)] // #6450 should be DirectPlay
+        // Null
+        [InlineData("Null", "mp4-h264-aac-vtt-2600k", null)] // #6450 should be DirectPlay
+        [InlineData("Null", "mp4-h264-ac3-aac-srt-2600k", null)] // #6450 should be DirectPlay
+        [InlineData("Null", "mp4-h264-ac3-srt-2600k", null)] // #6450 should be DirectPlay
+        [InlineData("Null", "mp4-hevc-aac-srt-15200k", null)] // #6450 should be DirectPlay
+        [InlineData("Null", "mp4-hevc-ac3-aac-srt-15200k", null)] // #6450 should be DirectPlay
+        [InlineData("Null", "mkv-vp9-aac-srt-2600k", null)] // #6450 should be DirectPlay
+        [InlineData("Null", "mkv-vp9-ac3-srt-2600k", null)] // #6450 should be DirectPlay
+        [InlineData("Null", "mkv-vp9-vorbis-vtt-2600k", null)] // #6450 should be DirectPlay
+        public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, bool fullTranscode = false)
+        {
+            var builder = GetStreamBuilder();
+            var options = await GetVideoOptions(deviceName, mediaSource);
+
+            var val = builder.BuildVideoItem(options);
+            Assert.NotNull(val);
+
+            if (playMethod != null)
+            {
+                Assert.Equal(playMethod, val.PlayMethod);
+            }
+
+            var videoStreams = options.MediaSources.SelectMany(source => source.MediaStreams).Where(stream => stream.Type == MediaStreamType.Video);
+            var audioStreams = options.MediaSources.SelectMany(source => source.MediaStreams).Where(stream => stream.Type == MediaStreamType.Audio);
+
+            var url = new UriBuilder(val.ToUrl("https://server/", "ACCESSTOKEN"));
+            var query = System.Web.HttpUtility.ParseQueryString(url.Query);
+
+            if (playMethod == PlayMethod.DirectPlay)
+            {
+                // Assert.Contains(query.Get("VidoeCodec"), videoStreams.Select(stream => stream.Codec));
+                // Assert.Contains(query.Get("AudioCodec"), audioStreams.Select(stream => stream.Codec));
+                Assert.Contains(
+                    videoStreams,
+                    stream => val.TargetVideoCodec.Contains(stream.Codec));
+                Assert.Contains(
+                    audioStreams,
+                    stream => val.TargetAudioCodec.Contains(stream.Codec));
+            }
+
+            if (playMethod == PlayMethod.DirectStream)
+            {
+                Assert.Matches("stream[.][^.]+$", url.Path);
+            }
+
+            if (playMethod == PlayMethod.Transcode)
+            {
+                if (fullTranscode)
+                {
+                    Assert.Equal("hls", val.SubProtocol);
+                    Assert.EndsWith("master.m3u8", url.Path, StringComparison.InvariantCulture);
+
+                    // Assert.All(
+                    //     videoStreams,
+                    //     stream => Assert.DoesNotContain(stream.Codec, val.TargetVideoCodec));
+                }
+                else
+                {
+                    Assert.Equal("hls", val.SubProtocol);
+                    Assert.EndsWith("master.m3u8", url.Path, StringComparison.InvariantCulture);
+
+                    Assert.Contains(
+                        videoStreams,
+                        stream => val.TargetVideoCodec.Contains(stream.Codec));
+                    // Assert.All(
+                    //     audioStreams,
+                    //     stream => Assert.DoesNotContain(stream.Codec, val.TargetAudioCodec));
+
+                    Assert.False(val.EstimateContentLength);
+                    Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo);
+                    // Assert.True(val.CopyTimestamps);
+
+                    var videoStream = videoStreams.First(stream => val.TargetVideoCodec.Contains(stream.Codec));
+
+                    Assert.Contains(videoStream.Codec, val.TargetVideoCodec);
+                    // Assert.Contains(videoStream.Profile.ToLowerInvariant(), val.TargetVideoProfile.Split(","));
+                    // Assert.Equal(videoStream.Level, val.TargetVideoLevel);
+                    // Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth);
+                    // Assert.Equal(videoStream.BitRate, val.VideoBitrate);
+                }
+            }
+
+            if (playMethod == null)
+            {
+                // what should the actual result be here?
+                Assert.Null(val.SubProtocol);
+                Assert.EndsWith("/stream", url.Path, StringComparison.InvariantCulture);
+
+                Assert.False(val.EstimateContentLength);
+                Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo);
+                // Assert.True(val.CopyTimestamps);
+            }
+        }
+
+        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 Exception("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,
+            };
+        }
+    }
+}

+ 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"
+}

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

@@ -0,0 +1,371 @@
+{
+    "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": "hevc,h264,vp8,vp9",
+            "Type": "Video",
+            "$type": "DirectPlayProfile"
+        },
+        {
+            "Container": "mov",
+            "AudioCodec": "aac,mp3,ac3,eac3,flac,alac,vorbis",
+            "VideoCodec": "hevc,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": "ts",
+            "Type": "Video",
+            "VideoCodec": "hevc,h264",
+            "AudioCodec": "aac,mp3,ac3,eac3",
+            "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,vpx",
+            "AudioCodec": "vorbis",
+            "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": "hevc,h264",
+            "AudioCodec": "aac,mp3,ac3,eac3,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": "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"
+}

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

@@ -0,0 +1,123 @@
+{
+    "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": "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"
+}

+ 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-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
+}