소스 검색

Series: issue-6450

Issue: https://github.com/jellyfin/jellyfin/issues/6450

Enable DirectPlay responses
Rewrite DirectPlay and DirectStream resolution
Prefer copy transcode video codec options
Enhance condition processor
Support DirectStream and Transcode with parity
Rework audio stream selection and add tests for ExternalAudio
Update MediaInfoHelper to only call StreamBuilder once
Isaac Gordezky 3 년 전
부모
커밋
5e779f20ee

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

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

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

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

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

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

+ 2 - 3
MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs

@@ -35,7 +35,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             SupportedSubtitleCodecs = Array.Empty<string>();
             SupportedSubtitleCodecs = Array.Empty<string>();
         }
         }
 
 
-        public TranscodeReason[] TranscodeReasons { get => TranscodeReason.ToArray(); }
+        public TranscodeReason[] TranscodeReasons => TranscodeReason.ToArray();
 
 
         [JsonIgnore]
         [JsonIgnore]
         public TranscodeReason TranscodeReason
         public TranscodeReason TranscodeReason
@@ -50,8 +50,7 @@ namespace MediaBrowser.Controller.MediaEncoding
                         return TranscodeReason.None;
                         return TranscodeReason.None;
                     }
                     }
 
 
-                    TranscodeReason reason = TranscodeReason.None;
-                    Enum.TryParse<TranscodeReason>(BaseRequest.TranscodeReasons, out reason);
+                    _ = Enum.TryParse<TranscodeReason>(BaseRequest.TranscodeReasons, out var reason);
                     _transcodeReasons = reason;
                     _transcodeReasons = reason;
                 }
                 }
 
 

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

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

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 459 - 382
MediaBrowser.Model/Dlna/StreamBuilder.cs


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

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

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

@@ -161,7 +161,7 @@ namespace MediaBrowser.Model.Dto
 
 
         public MediaStream GetDefaultAudioStream(int? defaultIndex)
         public MediaStream GetDefaultAudioStream(int? defaultIndex)
         {
         {
-            if (defaultIndex.HasValue)
+            if (defaultIndex.HasValue && defaultIndex != -1)
             {
             {
                 var val = defaultIndex.Value;
                 var val = defaultIndex.Value;
 
 

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

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

+ 7 - 12
MediaBrowser.Model/Session/TranscodeReason.cs

@@ -31,21 +31,16 @@ namespace MediaBrowser.Model.Session
         AudioChannelsNotSupported = 1 << 14,
         AudioChannelsNotSupported = 1 << 14,
         AudioProfileNotSupported = 1 << 15,
         AudioProfileNotSupported = 1 << 15,
         AudioSampleRateNotSupported = 1 << 16,
         AudioSampleRateNotSupported = 1 << 16,
-        AudioBitDepthNotSupported = 1 << 20,
+        AudioBitDepthNotSupported = 1 << 17,
 
 
         // Bitrate Constraints
         // Bitrate Constraints
-        ContainerBitrateExceedsLimit = 1 << 17,
-        VideoBitrateNotSupported = 1 << 18,
-        AudioBitrateNotSupported = 1 << 19,
+        ContainerBitrateExceedsLimit = 1 << 18,
+        VideoBitrateNotSupported = 1 << 19,
+        AudioBitrateNotSupported = 1 << 20,
 
 
         // Errors
         // Errors
-        UnknownVideoStreamInfo = 1 << 20,
-        UnknownAudioStreamInfo = 1 << 21,
-        DirectPlayError = 1 << 22,
-
-        // Aliases
-        ContainerReasons = ContainerNotSupported | ContainerBitrateExceedsLimit,
-        AudioReasons = AudioCodecNotSupported | AudioBitrateNotSupported | AudioChannelsNotSupported | AudioProfileNotSupported | AudioSampleRateNotSupported | SecondaryAudioNotSupported | AudioBitDepthNotSupported | AudioIsExternal,
-        VideoReasons = VideoCodecNotSupported | VideoResolutionNotSupported | AnamorphicVideoNotSupported | InterlacedVideoNotSupported | VideoBitDepthNotSupported | VideoBitrateNotSupported | VideoFramerateNotSupported | VideoLevelNotSupported | RefFramesNotSupported,
+        UnknownVideoStreamInfo = 1 << 21,
+        UnknownAudioStreamInfo = 1 << 22,
+        DirectPlayError = 1 << 23,
     }
     }
 }
 }

+ 18 - 6
MediaBrowser.Model/Session/TranscodeReasonExtensions.cs

@@ -1,22 +1,34 @@
-#pragma warning disable CS1591
-
 using System;
 using System;
 using System.Linq;
 using System.Linq;
 
 
 namespace MediaBrowser.Model.Session
 namespace MediaBrowser.Model.Session
 {
 {
+    /// <summary>
+    /// Extension methods for serializing TranscodeReason.
+    /// </summary>
     public static class TranscodeReasonExtensions
     public static class TranscodeReasonExtensions
     {
     {
-        private static TranscodeReason[] values = Enum.GetValues<TranscodeReason>();
+        private static readonly TranscodeReason[] _values = Enum.GetValues<TranscodeReason>();
 
 
-        public static string Serialize(this MediaBrowser.Model.Session.TranscodeReason reasons, string sep = ",")
+        /// <summary>
+        /// Serializes a TranscodeReason into a delimiter-separated string.
+        /// </summary>
+        /// <param name="reasons">The <see cref="TranscodeReason"/> enumeration.</param>
+        /// <param name="sep">The string separator to use. defualt <c>,</c>.</param>
+        /// <returns>string of transcode reasons delimited.</returns>
+        public static string Serialize(this TranscodeReason reasons, string sep = ",")
         {
         {
             return string.Join(sep, reasons.ToArray());
             return string.Join(sep, reasons.ToArray());
         }
         }
 
 
-        public static TranscodeReason[] ToArray(this MediaBrowser.Model.Session.TranscodeReason reasons)
+        /// <summary>
+        /// Serializes a TranscodeReason into an array of individual TranscodeReason bits.
+        /// </summary>
+        /// <param name="reasons">The <see cref="TranscodeReason"/> enumeration.</param>
+        /// <returns>Array of <c>TranscodeReason</c>.</returns>
+        public static TranscodeReason[] ToArray(this TranscodeReason reasons)
         {
         {
-            return values.Where(r => r != 0 && reasons.HasFlag(r)).ToArray();
+            return _values.Where(r => r != 0 && reasons.HasFlag(r)).ToArray();
         }
         }
     }
     }
 }
 }

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

@@ -31,7 +31,7 @@ namespace MediaBrowser.Model.Session
 
 
         public HardwareEncodingType? HardwareAccelerationType { get; set; }
         public HardwareEncodingType? HardwareAccelerationType { get; set; }
 
 
-        public TranscodeReason[] TranscodeReasons { get => TranscodeReason.ToArray(); }
+        public TranscodeReason[] TranscodeReasons => TranscodeReason.ToArray();
 
 
         [JsonIgnore]
         [JsonIgnore]
         public TranscodeReason TranscodeReason { get; set; }
         public TranscodeReason TranscodeReason { get; set; }

+ 197 - 156
tests/Jellyfin.Dlna.Tests/StreamBuilderTests.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Specialized;
 using System.Collections.Specialized;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
+using System.Runtime.Serialization;
 using System.Text.Json;
 using System.Text.Json;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Jellyfin.Extensions.Json;
 using Jellyfin.Extensions.Json;
@@ -19,97 +20,117 @@ namespace Jellyfin.MediaBrowser.Model.Tests
     {
     {
         [Theory]
         [Theory]
         // Chrome
         // Chrome
-        [InlineData("Chrome", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
-        [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Chrome", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream
+        [InlineData("Chrome", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Chrome", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Chrome", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450
+        [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
         [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
         [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
-        [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")]
-        [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false'
-        [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false'
-        [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
+        [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
         // Firefox
         // Firefox
-        [InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
-        [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Firefox", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream
+        [InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Firefox", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Firefox", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450
+        [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
         [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
         [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
-        [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")]
-        [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false'
-        [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false'
-        [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
+        [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
         // Safari
         // Safari
-        [InlineData("SafariNext", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
-        [InlineData("SafariNext", "mp4-h264-ac3-aacDef-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
+        [InlineData("SafariNext", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-hevc-ac3-aacExt-srt-15200k", PlayMethod.DirectPlay)] // #6450
         // AndroidPixel
         // AndroidPixel
-        [InlineData("AndroidPixel", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
-        [InlineData("AndroidPixel", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
-        [InlineData("AndroidPixel", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("AndroidPixel", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("AndroidPixel", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("AndroidPixel", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("AndroidPixel", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450
         [InlineData("AndroidPixel", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
         [InlineData("AndroidPixel", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
         [InlineData("AndroidPixel", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
         [InlineData("AndroidPixel", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
         // Yatse
         // Yatse
-        [InlineData("Yatse", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
-        [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Yatse", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")]
-        [InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
-        [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay
+        [InlineData("Yatse", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("Yatse", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
+        [InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
         // RokuSSPlus
         // RokuSSPlus
-        [InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
-        [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
-        [InlineData("RokuSSPlus", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
-        [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #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, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay
-        [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectStream
+        [InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450 should be DirectPlay
+        [InlineData("RokuSSPlus", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("RokuSSPlus", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
         // JellyfinMediaPlayer
         // 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, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay
-        [InlineData("JellyfinMediaPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #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
+        [InlineData("JellyfinMediaPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("JellyfinMediaPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("JellyfinMediaPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+        // Chrome-NoHLS
+        [InlineData("Chrome-NoHLS", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Chrome-NoHLS", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Chrome-NoHLS", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Chrome-NoHLS", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450
+        [InlineData("Chrome-NoHLS", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome-NoHLS", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")]
+        [InlineData("Chrome-NoHLS", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")]
+        [InlineData("Chrome-NoHLS", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome-NoHLS", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome-NoHLS", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
         // TranscodeMedia
         // TranscodeMedia
-        [InlineData("TranscodeMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
-        [InlineData("TranscodeMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
-        [InlineData("TranscodeMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
-        [InlineData("TranscodeMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay
-        [InlineData("TranscodeMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay
-        [InlineData("TranscodeMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay
-        [InlineData("TranscodeMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay
-        [InlineData("TranscodeMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerNotSupported | TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay
+        [InlineData("TranscodeMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")]
+        [InlineData("TranscodeMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")]
+        [InlineData("TranscodeMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")]
+        [InlineData("TranscodeMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")]
+        [InlineData("TranscodeMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")]
+        [InlineData("TranscodeMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")]
+        [InlineData("TranscodeMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")]
+        [InlineData("TranscodeMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Transcode")]
         // DirectMedia
         // 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
+        [InlineData("DirectMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")]
+        [InlineData("DirectMedia", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")]
+        [InlineData("DirectMedia", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")]
+        [InlineData("DirectMedia", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")]
+        [InlineData("DirectMedia", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")]
+        [InlineData("DirectMedia", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")]
+        [InlineData("DirectMedia", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
+        [InlineData("DirectMedia", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
+        [InlineData("DirectMedia", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)]
         // LowBandwidth
         // LowBandwidth
-        [InlineData("LowBandwidth", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay
-        [InlineData("LowBandwidth", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay
-        [InlineData("LowBandwidth", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay
-        [InlineData("LowBandwidth", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay
-        [InlineData("LowBandwidth", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay
-        [InlineData("LowBandwidth", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay
-        [InlineData("LowBandwidth", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay
-        [InlineData("LowBandwidth", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450 should be DirectPlay
+        [InlineData("LowBandwidth", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("LowBandwidth", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("LowBandwidth", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("LowBandwidth", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("LowBandwidth", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("LowBandwidth", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("LowBandwidth", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
+        [InlineData("LowBandwidth", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
         // Null
         // Null
-        [InlineData("Null", "mp4-h264-aac-vtt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Null", "mp4-h264-ac3-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Null", "mp4-h264-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Null", "mp4-hevc-aac-srt-15200k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Null", "mp4-hevc-ac3-aac-srt-15200k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Null", "mkv-vp9-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Null", "mkv-vp9-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Null", "mkv-vp9-vorbis-vtt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit | TranscodeReason.SubtitleCodecNotSupported)] // #6450 should be DirectPlay
+        [InlineData("Null", "mp4-h264-aac-vtt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
+        [InlineData("Null", "mp4-h264-ac3-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
+        [InlineData("Null", "mp4-h264-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
+        [InlineData("Null", "mp4-hevc-aac-srt-15200k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
+        [InlineData("Null", "mp4-hevc-ac3-aac-srt-15200k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
+        [InlineData("Null", "mkv-vp9-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
+        [InlineData("Null", "mkv-vp9-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
+        [InlineData("Null", "mkv-vp9-vorbis-vtt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
+
+        // [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")]
         public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = TranscodeReason.None, string transcodeMode = "DirectStream", string transcodeProtocol = "")
         public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = TranscodeReason.None, string transcodeMode = "DirectStream", string transcodeProtocol = "")
         {
         {
             var options = await GetVideoOptions(deviceName, mediaSource);
             var options = await GetVideoOptions(deviceName, mediaSource);
@@ -118,88 +139,103 @@ namespace Jellyfin.MediaBrowser.Model.Tests
 
 
         [Theory]
         [Theory]
         // Chrome
         // Chrome
-        [InlineData("Chrome", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
-        [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream
+        [InlineData("Chrome", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 <BUG: this is direct played>
+        [InlineData("Chrome", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
         [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
         [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
-        [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")]
-        [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false'
-        [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false'
-        [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
+        [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
         // Firefox
         // Firefox
-        [InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
-        [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectStream
+        [InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Firefox", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
         [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
         [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
-        [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")]
-        [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false'
-        [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be 'false'
-        [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
+        [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
+        [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
         // Safari
         // 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
+        [InlineData("SafariNext", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("SafariNext", "mp4-hevc-ac3-aacExt-srt-15200k", PlayMethod.DirectPlay)] // #6450
         // AndroidPixel
         // 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-h264-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("AndroidPixel", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("AndroidPixel", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450
         [InlineData("AndroidPixel", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
         [InlineData("AndroidPixel", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
         [InlineData("AndroidPixel", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
         [InlineData("AndroidPixel", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
         // Yatse
         // Yatse
-        [InlineData("Yatse", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
-        [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")]
-        [InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
-        [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay
+        [InlineData("Yatse", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Yatse", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
+        [InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
         // RokuSSPlus
         // RokuSSPlus
-        [InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
-        [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
-        [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported)] // #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, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectPlay
-        [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.Transcode, TranscodeReason.AudioCodecNotSupported, "Transcode")] // #6450 should be DirectStream
+        [InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 should be DirectPlay
+        [InlineData("RokuSSPlus", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("RokuSSPlus", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("RokuSSPlus", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+        [InlineData("RokuSSPlus", "mp4-hevc-ac3-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
         // JellyfinMediaPlayer
         // 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, TranscodeReason.ContainerBitrateExceedsLimit)] // #6450 should be DirectPlay
-        [InlineData("JellyfinMediaPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit)] // #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
-        public async Task BuildVideoItemWithFirstExplicitStream(string deviceName, string mediaSource, PlayMethod?playMethod, TranscodeReason why = TranscodeReason.None, string transcodeMode = "DirectStream", string transcodeProtocol = "")
+        [InlineData("JellyfinMediaPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("JellyfinMediaPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("JellyfinMediaPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450
+        [InlineData("JellyfinMediaPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] // #6450
+        [InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] // #6450
+        public async Task BuildVideoItemWithFirstExplicitStream(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = TranscodeReason.None, string transcodeMode = "DirectStream", string transcodeProtocol = "")
         {
         {
             var options = await GetVideoOptions(deviceName, mediaSource);
             var options = await GetVideoOptions(deviceName, mediaSource);
             options.AudioStreamIndex = 1;
             options.AudioStreamIndex = 1;
-            options.SubtitleStreamIndex = options.MediaSources[0].MediaStreams.Count() - 1;
-            BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol);
+            options.SubtitleStreamIndex = options.MediaSources[0].MediaStreams.Count - 1;
+
+            var streamInfo = BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol);
+            Assert.Equal(streamInfo?.AudioStreamIndex, options.AudioStreamIndex);
+            Assert.Equal(streamInfo?.SubtitleStreamIndex, options.SubtitleStreamIndex);
         }
         }
 
 
         [Theory]
         [Theory]
         // Chrome
         // Chrome
-        [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] // #6450 should have container & profile video reasons?
+        [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Chrome", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450
+        [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
         // Firefox
         // Firefox
-        [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] // #6450 should have container & profile video reasons?
+        [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+        [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
         // Yatse
         // Yatse
-        [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported)] // #6450 should be DirectPlay
-        [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.SecondaryAudioNotSupported, "Transcode")] // #6450 should be DirectPlay
+        [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
         // RokuSSPlus
         // RokuSSPlus
-        [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
-        [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream)] // #6450 should be DirectPlay
-        public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod playMethod, TranscodeReason why = TranscodeReason.None, string transcodeMode = "DirectStream", string transcodeProtocol = "")
+        [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, TranscodeReason.None, "Remux")] // #6450
+        public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = TranscodeReason.None, string transcodeMode = "DirectStream", string transcodeProtocol = "")
         {
         {
             var options = await GetVideoOptions(deviceName, mediaSource);
             var options = await GetVideoOptions(deviceName, mediaSource);
-            var streamCount = options.MediaSources[0].MediaStreams.Count();
+            var streamCount = options.MediaSources[0].MediaStreams.Count;
             options.AudioStreamIndex = streamCount - 2;
             options.AudioStreamIndex = streamCount - 2;
             options.SubtitleStreamIndex = streamCount - 1;
             options.SubtitleStreamIndex = streamCount - 1;
-            BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol);
+
+            var streamInfo = BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol);
+            Assert.Equal(streamInfo?.AudioStreamIndex, options.AudioStreamIndex);
+            Assert.Equal(streamInfo?.SubtitleStreamIndex, options.SubtitleStreamIndex);
         }
         }
 
 
-        private void BuildVideoItemSimpleTest(VideoOptions options, PlayMethod? playMethod, TranscodeReason why, string transcodeMode, string transcodeProtocol)
+        private StreamInfo? BuildVideoItemSimpleTest(VideoOptions options, PlayMethod? playMethod, TranscodeReason why, string transcodeMode, string transcodeProtocol)
         {
         {
             if (string.IsNullOrEmpty(transcodeProtocol))
             if (string.IsNullOrEmpty(transcodeProtocol))
             {
             {
@@ -235,7 +271,8 @@ namespace Jellyfin.MediaBrowser.Model.Tests
             {
             {
                 // check expected container
                 // check expected container
                 var containers = ContainerProfile.SplitValue(mediaSource.Container);
                 var containers = ContainerProfile.SplitValue(mediaSource.Container);
-                Assert.Contains(uri.Extension, containers);
+                // TODO: test transcode too
+                // Assert.Contains(uri.Extension, containers);
 
 
                 // check expected video codec (1)
                 // check expected video codec (1)
                 Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
                 Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
@@ -243,15 +280,19 @@ namespace Jellyfin.MediaBrowser.Model.Tests
 
 
                 // check expected audio codecs (1)
                 // check expected audio codecs (1)
                 Assert.Contains(targetAudioStream.Codec, val.TargetAudioCodec);
                 Assert.Contains(targetAudioStream.Codec, val.TargetAudioCodec);
-                Assert.Single(val.AudioCodecs);
+                Assert.Single(val.TargetAudioCodec);
+                // Assert.Single(val.AudioCodecs);
 
 
-                // TODO: validate transcoding options as well
+                if (transcodeMode == "DirectStream")
+                {
+                    Assert.Equal(val.Container, uri.Extension);
+                }
             }
             }
             else if (playMethod == PlayMethod.DirectStream || playMethod == PlayMethod.Transcode)
             else if (playMethod == PlayMethod.DirectStream || playMethod == PlayMethod.Transcode)
             {
             {
                 Assert.NotNull(val.Container);
                 Assert.NotNull(val.Container);
-                // Assert.NotEmpty(val.VideoCodecs);
-                // Assert.NotEmpty(val.AudioCodecs);
+                Assert.NotEmpty(val.VideoCodecs);
+                Assert.NotEmpty(val.AudioCodecs);
 
 
                 // check expected container (todo: this could be a test param)
                 // check expected container (todo: this could be a test param)
                 if (transcodeProtocol == "http")
                 if (transcodeProtocol == "http")
@@ -259,7 +300,7 @@ namespace Jellyfin.MediaBrowser.Model.Tests
                     // Assert.Equal("webm", val.Container);
                     // Assert.Equal("webm", val.Container);
                     Assert.Equal(val.Container, uri.Extension);
                     Assert.Equal(val.Container, uri.Extension);
                     Assert.Equal("stream", uri.Filename);
                     Assert.Equal("stream", uri.Filename);
-                    // Assert.Equal("http", val.SubProtocol);
+                    Assert.Equal("http", val.SubProtocol);
                 }
                 }
                 else
                 else
                 {
                 {
@@ -272,12 +313,11 @@ namespace Jellyfin.MediaBrowser.Model.Tests
                 // Full transcode
                 // Full transcode
                 if (transcodeMode == "Transcode")
                 if (transcodeMode == "Transcode")
                 {
                 {
-                    // TODO: what else to validate here
-                    if ((val.TranscodeReasons & TranscodeReason.ContainerReasons) == TranscodeReason.None)
+                    if ((val.TranscodeReasons & (StreamBuilder.ContainerReasons | TranscodeReason.DirectPlayError)) == TranscodeReason.None)
                     {
                     {
-                        // Assert.All(
-                        //     videoStreams,
-                        //     stream => Assert.DoesNotContain(stream.Codec, val.VideoCodecs));
+                        Assert.All(
+                            videoStreams,
+                            stream => Assert.DoesNotContain(stream.Codec, val.VideoCodecs));
                     }
                     }
 
 
                     // todo: fill out tests here
                     // todo: fill out tests here
@@ -295,7 +335,7 @@ namespace Jellyfin.MediaBrowser.Model.Tests
                         if (!targetAudioStream.IsExternal)
                         if (!targetAudioStream.IsExternal)
                         {
                         {
                             // check expected audio codecs (1)
                             // check expected audio codecs (1)
-                            // Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs);
+                            Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs);
                         }
                         }
                     }
                     }
                     else if (transcodeMode == "Remux")
                     else if (transcodeMode == "Remux")
@@ -309,10 +349,10 @@ namespace Jellyfin.MediaBrowser.Model.Tests
                     var videoStream = targetVideoStream;
                     var videoStream = targetVideoStream;
                     Assert.False(val.EstimateContentLength);
                     Assert.False(val.EstimateContentLength);
                     Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo);
                     Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo);
-                    // Assert.Contains(videoStream.Profile?.ToLowerInvariant() ?? string.Empty, val.TargetVideoProfile?.Split(",").Select(s => s.ToLowerInvariant()) ?? new string[0]);
-                    // Assert.Equal(videoStream.Level, val.TargetVideoLevel);
-                    // Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth);
-                    // Assert.InRange(val.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue);
+                    Assert.Contains(videoStream.Profile?.ToLowerInvariant() ?? string.Empty, val.TargetVideoProfile?.Split(",").Select(s => s.ToLowerInvariant()) ?? Array.Empty<string>());
+                    Assert.Equal(videoStream.Level, val.TargetVideoLevel);
+                    Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth);
+                    Assert.InRange(val.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue);
 
 
                     // audio codec not supported
                     // audio codec not supported
                     if ((why & TranscodeReason.AudioCodecNotSupported) != TranscodeReason.None)
                     if ((why & TranscodeReason.AudioCodecNotSupported) != TranscodeReason.None)
@@ -335,24 +375,23 @@ namespace Jellyfin.MediaBrowser.Model.Tests
                             {
                             {
                                 if (!stream.IsExternal)
                                 if (!stream.IsExternal)
                                 {
                                 {
-                                    // Assert.DoesNotContain(stream.Codec, val.AudioCodecs);
+                                    Assert.DoesNotContain(stream.Codec, val.AudioCodecs);
                                 }
                                 }
                             });
                             });
                         }
                         }
                     }
                     }
                 }
                 }
             }
             }
-
-            if (playMethod == null)
+            else if (playMethod == null)
             {
             {
-                // what should the actual result be here?
                 Assert.Null(val.SubProtocol);
                 Assert.Null(val.SubProtocol);
-                Assert.EndsWith("/stream", uri.Path, StringComparison.InvariantCulture);
+                Assert.Equal("stream", uri.Filename);
 
 
                 Assert.False(val.EstimateContentLength);
                 Assert.False(val.EstimateContentLength);
                 Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo);
                 Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo);
-                // Assert.True(val.CopyTimestamps);
             }
             }
+
+            return val;
         }
         }
 
 
         private static async ValueTask<T> TestData<T>(string name)
         private static async ValueTask<T> TestData<T>(string name)
@@ -366,7 +405,7 @@ namespace Jellyfin.MediaBrowser.Model.Tests
                     return value;
                     return value;
                 }
                 }
 
 
-                throw new Exception("Invalid test data: " + name);
+                throw new SerializationException("Invalid test data: " + name);
             }
             }
         }
         }
 
 
@@ -394,6 +433,8 @@ namespace Jellyfin.MediaBrowser.Model.Tests
                 MediaSources = mediaSources,
                 MediaSources = mediaSources,
                 DeviceId = "test-deviceId",
                 DeviceId = "test-deviceId",
                 Profile = dp,
                 Profile = dp,
+                AllowAudioStreamCopy = true,
+                AllowVideoStreamCopy = true,
             };
             };
         }
         }
 
 

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.