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

Backport pull request #15833 from jellyfin/release-10.11.z

Fix missing H.264 and AV1 SDR fallbacks in HLS playlist

Original-merge: 01b20d3b75e00909e7a531a625eda5ecb1a76f42

Merged-by: Bond-009 <bond.009@outlook.com>

Backported-by: Bond_009 <bond.009@outlook.com>
nyanmisaka 20 часов назад
Родитель
Сommit
7f0e71578d
1 измененных файлов с 63 добавлено и 49 удалено
  1. 63 49
      Jellyfin.Api/Helpers/DynamicHlsHelper.cs

+ 63 - 49
Jellyfin.Api/Helpers/DynamicHlsHelper.cs

@@ -154,7 +154,7 @@ public class DynamicHlsHelper
         // from universal audio service, need to override the AudioCodec when the actual request differs from original query
         if (!string.Equals(state.OutputAudioCodec, _httpContextAccessor.HttpContext.Request.Query["AudioCodec"].ToString(), StringComparison.OrdinalIgnoreCase))
         {
-            var newQuery = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(_httpContextAccessor.HttpContext.Request.QueryString.ToString());
+            var newQuery = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(queryString);
             newQuery["AudioCodec"] = state.OutputAudioCodec;
             queryString = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(string.Empty, newQuery);
         }
@@ -173,10 +173,21 @@ public class DynamicHlsHelper
             queryString += "&TranscodeReasons=" + state.Request.TranscodeReasons;
         }
 
-        // Main stream
-        var playlistUrl = isLiveStream ? "live.m3u8" : "main.m3u8";
+        // Video rotation metadata is only supported in fMP4 remuxing
+        if (state.VideoStream is not null
+            && state.VideoRequest is not null
+            && (state.VideoStream?.Rotation ?? 0) != 0
+            && EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
+            && !string.IsNullOrWhiteSpace(state.Request.SegmentContainer)
+            && !string.Equals(state.Request.SegmentContainer, "mp4", StringComparison.OrdinalIgnoreCase))
+        {
+            queryString += "&AllowVideoStreamCopy=false";
+        }
 
-        playlistUrl += queryString;
+        // Main stream
+        var baseUrl = isLiveStream ? "live.m3u8" : "main.m3u8";
+        var playlistUrl = baseUrl + queryString;
+        var playlistQuery = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(queryString);
 
         var subtitleStreams = state.MediaSource
             .MediaStreams
@@ -198,37 +209,36 @@ public class DynamicHlsHelper
             AddSubtitles(state, subtitleStreams, builder, _httpContextAccessor.HttpContext.User);
         }
 
-        // Video rotation metadata is only supported in fMP4 remuxing
-        if (state.VideoStream is not null
-            && state.VideoRequest is not null
-            && (state.VideoStream?.Rotation ?? 0) != 0
-            && EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
-            && !string.IsNullOrWhiteSpace(state.Request.SegmentContainer)
-            && !string.Equals(state.Request.SegmentContainer, "mp4", StringComparison.OrdinalIgnoreCase))
-        {
-            playlistUrl += "&AllowVideoStreamCopy=false";
-        }
-
         var basicPlaylist = AppendPlaylist(builder, state, playlistUrl, totalBitrate, subtitleGroup);
 
         if (state.VideoStream is not null && state.VideoRequest is not null)
         {
             var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
 
-            // Provide SDR HEVC entrance for backward compatibility.
-            if (encodingOptions.AllowHevcEncoding
-                && !encodingOptions.AllowAv1Encoding
-                && EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
-                && state.VideoStream.VideoRange == VideoRange.HDR
-                && string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
+            // Provide AV1 and HEVC SDR entrances for backward compatibility.
+            foreach (var sdrVideoCodec in new[] { "av1", "hevc" })
             {
-                var requestedVideoProfiles = state.GetRequestedProfiles("hevc");
-                if (requestedVideoProfiles is not null && requestedVideoProfiles.Length > 0)
+                var isAv1EncodingAllowed = encodingOptions.AllowAv1Encoding
+                    && string.Equals(sdrVideoCodec, "av1", StringComparison.OrdinalIgnoreCase)
+                    && string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase);
+                var isHevcEncodingAllowed = encodingOptions.AllowHevcEncoding
+                    && string.Equals(sdrVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
+                    && string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase);
+                var isEncodingAllowed = isAv1EncodingAllowed || isHevcEncodingAllowed;
+
+                if (isEncodingAllowed
+                    && EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
+                    && state.VideoStream.VideoRange == VideoRange.HDR)
                 {
-                    // Force HEVC Main Profile and disable video stream copy.
-                    state.OutputVideoCodec = "hevc";
-                    var sdrVideoUrl = ReplaceProfile(playlistUrl, "hevc", string.Join(',', requestedVideoProfiles), "main");
-                    sdrVideoUrl += "&AllowVideoStreamCopy=false";
+                    // Force AV1 and HEVC Main Profile and disable video stream copy.
+                    state.OutputVideoCodec = sdrVideoCodec;
+
+                    var sdrPlaylistQuery = playlistQuery;
+                    sdrPlaylistQuery["VideoCodec"] = sdrVideoCodec;
+                    sdrPlaylistQuery[sdrVideoCodec + "-profile"] = "main";
+                    sdrPlaylistQuery["AllowVideoStreamCopy"] = "false";
+
+                    var sdrVideoUrl = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(baseUrl, sdrPlaylistQuery);
 
                     // HACK: Use the same bitrate so that the client can choose by other attributes, such as color range.
                     AppendPlaylist(builder, state, sdrVideoUrl, totalBitrate, subtitleGroup);
@@ -238,12 +248,30 @@ public class DynamicHlsHelper
                 }
             }
 
+            // Provide H.264 SDR entrance for backward compatibility.
+            if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
+                && state.VideoStream.VideoRange == VideoRange.HDR)
+            {
+                // Force H.264 and disable video stream copy.
+                state.OutputVideoCodec = "h264";
+
+                var sdrPlaylistQuery = playlistQuery;
+                sdrPlaylistQuery["VideoCodec"] = "h264";
+                sdrPlaylistQuery["AllowVideoStreamCopy"] = "false";
+
+                var sdrVideoUrl = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(baseUrl, sdrPlaylistQuery);
+
+                // HACK: Use the same bitrate so that the client can choose by other attributes, such as color range.
+                AppendPlaylist(builder, state, sdrVideoUrl, totalBitrate, subtitleGroup);
+
+                // Restore the video codec
+                state.OutputVideoCodec = "copy";
+            }
+
             // Provide Level 5.0 entrance for backward compatibility.
             // e.g. Apple A10 chips refuse the master playlist containing SDR HEVC Main Level 5.1 video,
             // but in fact it is capable of playing videos up to Level 6.1.
-            if (encodingOptions.AllowHevcEncoding
-                && !encodingOptions.AllowAv1Encoding
-                && EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
+            if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
                 && state.VideoStream.Level.HasValue
                 && state.VideoStream.Level > 150
                 && state.VideoStream.VideoRange == VideoRange.SDR
@@ -273,12 +301,15 @@ public class DynamicHlsHelper
             var variation = GetBitrateVariation(totalBitrate);
 
             var newBitrate = totalBitrate - variation;
-            var variantUrl = ReplaceVideoBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation);
+            var variantQuery = playlistQuery;
+            variantQuery["VideoBitrate"] = (requestedVideoBitrate - variation).ToString(CultureInfo.InvariantCulture);
+            var variantUrl = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(baseUrl, variantQuery);
             AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup);
 
             variation *= 2;
             newBitrate = totalBitrate - variation;
-            variantUrl = ReplaceVideoBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation);
+            variantQuery["VideoBitrate"] = (requestedVideoBitrate - variation).ToString(CultureInfo.InvariantCulture);
+            variantUrl = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(baseUrl, variantQuery);
             AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup);
         }
 
@@ -863,23 +894,6 @@ public class DynamicHlsHelper
         return variation;
     }
 
-    private string ReplaceVideoBitrate(string url, int oldValue, int newValue)
-    {
-        return url.Replace(
-            "videobitrate=" + oldValue.ToString(CultureInfo.InvariantCulture),
-            "videobitrate=" + newValue.ToString(CultureInfo.InvariantCulture),
-            StringComparison.OrdinalIgnoreCase);
-    }
-
-    private string ReplaceProfile(string url, string codec, string oldValue, string newValue)
-    {
-        string profileStr = codec + "-profile=";
-        return url.Replace(
-            profileStr + oldValue,
-            profileStr + newValue,
-            StringComparison.OrdinalIgnoreCase);
-    }
-
     private string ReplacePlaylistCodecsField(StringBuilder playlist, StringBuilder oldValue, StringBuilder newValue)
     {
         var oldPlaylist = playlist.ToString();