From b2c58338f21535f93f9d9f939615274472ce664c Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Wed, 9 Mar 2022 21:28:58 +0800 Subject: [PATCH] Fix the fMP4 HLS audio sync issue on Safari --- .../Controllers/DynamicHlsController.cs | 15 ++++++----- .../MediaEncoding/EncodingHelper.cs | 27 +++++++++++-------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index af69166300..f8e8d975cc 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1599,7 +1599,6 @@ namespace Jellyfin.Api.Controllers state.BaseRequest.BreakOnNonKeyFrames = false; } - var inputModifier = _encodingHelper.GetInputModifier(state, _encodingOptions); var mapArgs = state.IsOutputVideo ? _encodingHelper.GetMapArgs(state) : string.Empty; var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)); @@ -1608,12 +1607,15 @@ namespace Jellyfin.Api.Controllers var outputExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer); var outputTsArg = outputPrefix + "%d" + outputExtension; - var segmentFormat = outputExtension.TrimStart('.'); - if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase)) + var segmentFormat = string.Empty; + var segmentContainer = outputExtension.TrimStart('.'); + var inputModifier = _encodingHelper.GetInputModifier(state, _encodingOptions, segmentContainer); + + if (string.Equals(segmentContainer, "ts", StringComparison.OrdinalIgnoreCase)) { segmentFormat = "mpegts"; } - else if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase)) { var outputFmp4HeaderArg = OperatingSystem.IsWindows() switch { @@ -1627,7 +1629,8 @@ namespace Jellyfin.Api.Controllers } else { - _logger.LogError("Invalid HLS segment container: {SegmentFormat}", segmentFormat); + _logger.LogError("Invalid HLS segment container: {SegmentContainer}, default to mpegts", segmentContainer); + segmentFormat = "mpegts"; } var maxMuxingQueueSize = _encodingOptions.MaxMuxingQueueSize > 128 @@ -1647,7 +1650,7 @@ namespace Jellyfin.Api.Controllers CultureInfo.InvariantCulture, "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -hls_segment_type {8} -start_number {9}{10} -hls_segment_filename \"{12}\" -hls_playlist_type {11} -hls_list_size 0 -y \"{13}\"", inputModifier, - _encodingHelper.GetInputArgument(state, _encodingOptions), + _encodingHelper.GetInputArgument(state, _encodingOptions, segmentContainer), threads, mapArgs, GetVideoArguments(state, startNumber, isEventPlaylist), diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index f4e0ca24af..2e7349f002 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -842,8 +842,9 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// Encoding state. /// Encoding options. + /// Segment Container. /// Input arguments. - public string GetInputArgument(EncodingJobInfo state, EncodingOptions options) + public string GetInputArgument(EncodingJobInfo state, EncodingOptions options, string segmentContainer) { var arg = new StringBuilder(); var inputVidHwaccelArgs = GetInputVideoHwaccelArgs(state, options); @@ -880,7 +881,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Also seek the external subtitles stream. - var seekSubParam = GetFastSeekCommandLineParameter(state, options); + var seekSubParam = GetFastSeekCommandLineParameter(state, options, segmentContainer); if (!string.IsNullOrEmpty(seekSubParam)) { arg.Append(' ').Append(seekSubParam); @@ -897,7 +898,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream != null && state.AudioStream.IsExternal) { // Also seek the external audio stream. - var seekAudioParam = GetFastSeekCommandLineParameter(state, options); + var seekAudioParam = GetFastSeekCommandLineParameter(state, options, segmentContainer); if (!string.IsNullOrEmpty(seekAudioParam)) { arg.Append(' ').Append(seekAudioParam); @@ -2167,9 +2168,10 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// The state. /// The options. + /// Segment Container. /// System.String. /// The fast seek command line parameter. - public string GetFastSeekCommandLineParameter(EncodingJobInfo state, EncodingOptions options) + public string GetFastSeekCommandLineParameter(EncodingJobInfo state, EncodingOptions options, string segmentContainer) { var time = state.BaseRequest.StartTimeTicks ?? 0; var seekParam = string.Empty; @@ -2181,11 +2183,14 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.IsVideoRequest) { var outputVideoCodec = GetVideoEncoder(state, options); + var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.'); // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking + // Disable -noaccurate_seek on mpegts container due to the timestamps issue on some clients, + // but it's still required for fMP4 container otherwise the audio can't be synced to the video. if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase) + && !string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase) && state.TranscodingType != TranscodingJobType.Progressive - && state.TranscodingType != TranscodingJobType.Hls && !state.EnableBreakOnNonKeyFrames(outputVideoCodec) && (state.BaseRequest.StartTimeTicks ?? 0) > 0) { @@ -4836,7 +4841,7 @@ namespace MediaBrowser.Controller.MediaEncoding } } - public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions) + public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer) { var inputModifier = string.Empty; var probeSizeArgument = string.Empty; @@ -4872,7 +4877,7 @@ namespace MediaBrowser.Controller.MediaEncoding inputModifier = inputModifier.Trim(); - inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions); + inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions, segmentContainer); inputModifier = inputModifier.Trim(); if (state.InputProtocol == MediaProtocol.Rtsp) @@ -5179,13 +5184,13 @@ namespace MediaBrowser.Controller.MediaEncoding var threads = GetNumberOfThreads(state, encodingOptions, videoCodec); - var inputModifier = GetInputModifier(state, encodingOptions); + var inputModifier = GetInputModifier(state, encodingOptions, null); return string.Format( CultureInfo.InvariantCulture, "{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"", inputModifier, - GetInputArgument(state, encodingOptions), + GetInputArgument(state, encodingOptions, null), keyFrame, GetMapArgs(state), GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultPreset), @@ -5367,13 +5372,13 @@ namespace MediaBrowser.Controller.MediaEncoding var threads = GetNumberOfThreads(state, encodingOptions, null); - var inputModifier = GetInputModifier(state, encodingOptions); + var inputModifier = GetInputModifier(state, encodingOptions, null); return string.Format( CultureInfo.InvariantCulture, "{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"", inputModifier, - GetInputArgument(state, encodingOptions), + GetInputArgument(state, encodingOptions, null), threads, " -vn", string.Join(' ', audioTranscodeParams),