mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-01-15 08:23:28 -03:00
Merge pull request #15833 from nyanmisaka/fix-h264-av1-sdr-hls-fallback
Fix missing H.264 and AV1 SDR fallbacks in HLS playlist
This commit is contained in:
@@ -154,7 +154,7 @@ public class DynamicHlsHelper
|
|||||||
// from universal audio service, need to override the AudioCodec when the actual request differs from original query
|
// 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))
|
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;
|
newQuery["AudioCodec"] = state.OutputAudioCodec;
|
||||||
queryString = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(string.Empty, newQuery);
|
queryString = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(string.Empty, newQuery);
|
||||||
}
|
}
|
||||||
@@ -173,10 +173,21 @@ public class DynamicHlsHelper
|
|||||||
queryString += "&TranscodeReasons=" + state.Request.TranscodeReasons;
|
queryString += "&TranscodeReasons=" + state.Request.TranscodeReasons;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main stream
|
// Video rotation metadata is only supported in fMP4 remuxing
|
||||||
var playlistUrl = isLiveStream ? "live.m3u8" : "main.m3u8";
|
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
|
var subtitleStreams = state.MediaSource
|
||||||
.MediaStreams
|
.MediaStreams
|
||||||
@@ -198,37 +209,36 @@ public class DynamicHlsHelper
|
|||||||
AddSubtitles(state, subtitleStreams, builder, _httpContextAccessor.HttpContext.User);
|
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);
|
var basicPlaylist = AppendPlaylist(builder, state, playlistUrl, totalBitrate, subtitleGroup);
|
||||||
|
|
||||||
if (state.VideoStream is not null && state.VideoRequest is not null)
|
if (state.VideoStream is not null && state.VideoRequest is not null)
|
||||||
{
|
{
|
||||||
var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
|
var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
|
||||||
|
|
||||||
// Provide SDR HEVC entrance for backward compatibility.
|
// Provide AV1 and HEVC SDR entrances for backward compatibility.
|
||||||
if (encodingOptions.AllowHevcEncoding
|
foreach (var sdrVideoCodec in new[] { "av1", "hevc" })
|
||||||
&& !encodingOptions.AllowAv1Encoding
|
|
||||||
&& EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
|
|
||||||
&& state.VideoStream.VideoRange == VideoRange.HDR
|
|
||||||
&& string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
var requestedVideoProfiles = state.GetRequestedProfiles("hevc");
|
var isAv1EncodingAllowed = encodingOptions.AllowAv1Encoding
|
||||||
if (requestedVideoProfiles is not null && requestedVideoProfiles.Length > 0)
|
&& 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.
|
// Force AV1 and HEVC Main Profile and disable video stream copy.
|
||||||
state.OutputVideoCodec = "hevc";
|
state.OutputVideoCodec = sdrVideoCodec;
|
||||||
var sdrVideoUrl = ReplaceProfile(playlistUrl, "hevc", string.Join(',', requestedVideoProfiles), "main");
|
|
||||||
sdrVideoUrl += "&AllowVideoStreamCopy=false";
|
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.
|
// HACK: Use the same bitrate so that the client can choose by other attributes, such as color range.
|
||||||
AppendPlaylist(builder, state, sdrVideoUrl, totalBitrate, subtitleGroup);
|
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.
|
// 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,
|
// 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.
|
// but in fact it is capable of playing videos up to Level 6.1.
|
||||||
if (encodingOptions.AllowHevcEncoding
|
if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
|
||||||
&& !encodingOptions.AllowAv1Encoding
|
|
||||||
&& EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
|
|
||||||
&& state.VideoStream.Level.HasValue
|
&& state.VideoStream.Level.HasValue
|
||||||
&& state.VideoStream.Level > 150
|
&& state.VideoStream.Level > 150
|
||||||
&& state.VideoStream.VideoRange == VideoRange.SDR
|
&& state.VideoStream.VideoRange == VideoRange.SDR
|
||||||
@@ -273,12 +301,15 @@ public class DynamicHlsHelper
|
|||||||
var variation = GetBitrateVariation(totalBitrate);
|
var variation = GetBitrateVariation(totalBitrate);
|
||||||
|
|
||||||
var newBitrate = totalBitrate - variation;
|
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);
|
AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup);
|
||||||
|
|
||||||
variation *= 2;
|
variation *= 2;
|
||||||
newBitrate = totalBitrate - variation;
|
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);
|
AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -863,23 +894,6 @@ public class DynamicHlsHelper
|
|||||||
return variation;
|
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)
|
private string ReplacePlaylistCodecsField(StringBuilder playlist, StringBuilder oldValue, StringBuilder newValue)
|
||||||
{
|
{
|
||||||
var oldPlaylist = playlist.ToString();
|
var oldPlaylist = playlist.ToString();
|
||||||
|
|||||||
Reference in New Issue
Block a user