From 8d052a6cb17421e6c7774bafc6957029f3c9d4ae Mon Sep 17 00:00:00 2001
From: Abitofevrything <54505189+abitofevrything@users.noreply.github.com>
Date: Sun, 18 Jan 2026 12:17:06 +0100
Subject: [PATCH] Merge pull request #15926 from
abitofevrything/feat/accurate_hls_seeking
Refactor HLS transcode seeking
---
Jellyfin.Api/Controllers/AudioController.cs | 6 ----
.../Controllers/DynamicHlsController.cs | 36 -------------------
.../Controllers/UniversalAudioController.cs | 8 +----
Jellyfin.Api/Controllers/VideosController.cs | 6 ----
Jellyfin.Api/Helpers/StreamingHelpers.cs | 2 +-
.../MediaEncoding/BaseEncodingJobOptions.cs | 2 --
.../MediaEncoding/EncodingHelper.cs | 35 ++++++++++--------
.../MediaEncoding/EncodingJobInfo.cs | 15 --------
.../Transcoding/TranscodeManager.cs | 2 +-
.../Configuration/EncodingOptions.cs | 8 +++++
.../Configuration/HlsAudioSeekStrategy.cs | 23 ++++++++++++
MediaBrowser.Model/Dlna/StreamBuilder.cs | 1 -
MediaBrowser.Model/Dlna/StreamInfo.cs | 8 -----
MediaBrowser.Model/Dlna/TranscodingProfile.cs | 4 +--
.../Dlna/LegacyStreamInfo.cs | 2 --
.../Test Data/DeviceProfile-AndroidPixel.json | 3 --
.../DeviceProfile-AndroidTVExoPlayer.json | 2 --
.../Test Data/DeviceProfile-Chrome-NoHLS.json | 11 ------
.../Test Data/DeviceProfile-Chrome.json | 7 ++--
.../Test Data/DeviceProfile-Firefox.json | 3 --
.../DeviceProfile-JellyfinMediaPlayer.json | 3 --
.../Test Data/DeviceProfile-LowBandwidth.json | 3 --
.../Test Data/DeviceProfile-RokuSSPlus.json | 6 ----
.../DeviceProfile-RokuSSPlusNext.json | 6 ----
.../Test Data/DeviceProfile-SafariNext.json | 7 ++--
.../DeviceProfile-Tizen3-stereo.json | 13 -------
.../DeviceProfile-Tizen4-4K-5.1.json | 13 -------
.../DeviceProfile-TranscodeMedia.json | 4 ---
.../Test Data/DeviceProfile-WebOS-23.json | 4 +--
.../Test Data/DeviceProfile-Yatse.json | 3 --
.../Test Data/DeviceProfile-Yatse2.json | 3 --
31 files changed, 61 insertions(+), 188 deletions(-)
create mode 100644 MediaBrowser.Model/Configuration/HlsAudioSeekStrategy.cs
diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs
index e334e12640..4be79ff5a0 100644
--- a/Jellyfin.Api/Controllers/AudioController.cs
+++ b/Jellyfin.Api/Controllers/AudioController.cs
@@ -50,7 +50,6 @@ public class AudioController : BaseJellyfinApiController
/// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.
/// Whether or not to allow copying of the video stream url.
/// Whether or not to allow copying of the audio stream url.
- /// Optional. Whether to break on non key frames.
/// Optional. Specify a specific audio sample rate, e.g. 44100.
/// Optional. The maximum audio bit depth.
/// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.
@@ -107,7 +106,6 @@ public class AudioController : BaseJellyfinApiController
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
- [FromQuery] bool? breakOnNonKeyFrames,
[FromQuery] int? audioSampleRate,
[FromQuery] int? maxAudioBitDepth,
[FromQuery] int? audioBitRate,
@@ -159,7 +157,6 @@ public class AudioController : BaseJellyfinApiController
EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
- BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
AudioSampleRate = audioSampleRate,
MaxAudioChannels = maxAudioChannels,
AudioBitRate = audioBitRate,
@@ -217,7 +214,6 @@ public class AudioController : BaseJellyfinApiController
/// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.
/// Whether or not to allow copying of the video stream url.
/// Whether or not to allow copying of the audio stream url.
- /// Optional. Whether to break on non key frames.
/// Optional. Specify a specific audio sample rate, e.g. 44100.
/// Optional. The maximum audio bit depth.
/// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.
@@ -274,7 +270,6 @@ public class AudioController : BaseJellyfinApiController
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
- [FromQuery] bool? breakOnNonKeyFrames,
[FromQuery] int? audioSampleRate,
[FromQuery] int? maxAudioBitDepth,
[FromQuery] int? audioBitRate,
@@ -326,7 +321,6 @@ public class AudioController : BaseJellyfinApiController
EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
- BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
AudioSampleRate = audioSampleRate,
MaxAudioChannels = maxAudioChannels,
AudioBitRate = audioBitRate,
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index 1e3e2740f0..15b04051f4 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -122,7 +122,6 @@ public class DynamicHlsController : BaseJellyfinApiController
/// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.
/// Whether or not to allow copying of the video stream url.
/// Whether or not to allow copying of the audio stream url.
- /// Optional. Whether to break on non key frames.
/// Optional. Specify a specific audio sample rate, e.g. 44100.
/// Optional. The maximum audio bit depth.
/// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.
@@ -182,7 +181,6 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
- [FromQuery] bool? breakOnNonKeyFrames,
[FromQuery] int? audioSampleRate,
[FromQuery] int? maxAudioBitDepth,
[FromQuery] int? audioBitRate,
@@ -238,7 +236,6 @@ public class DynamicHlsController : BaseJellyfinApiController
EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
- BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
AudioSampleRate = audioSampleRate,
MaxAudioChannels = maxAudioChannels,
AudioBitRate = audioBitRate,
@@ -364,7 +361,6 @@ public class DynamicHlsController : BaseJellyfinApiController
/// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.
/// Whether or not to allow copying of the video stream url.
/// Whether or not to allow copying of the audio stream url.
- /// Optional. Whether to break on non key frames.
/// Optional. Specify a specific audio sample rate, e.g. 44100.
/// Optional. The maximum audio bit depth.
/// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.
@@ -425,7 +421,6 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
- [FromQuery] bool? breakOnNonKeyFrames,
[FromQuery] int? audioSampleRate,
[FromQuery] int? maxAudioBitDepth,
[FromQuery] int? audioBitRate,
@@ -481,7 +476,6 @@ public class DynamicHlsController : BaseJellyfinApiController
EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
- BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
AudioSampleRate = audioSampleRate,
MaxAudioChannels = maxAudioChannels,
AudioBitRate = audioBitRate,
@@ -543,7 +537,6 @@ public class DynamicHlsController : BaseJellyfinApiController
/// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.
/// Whether or not to allow copying of the video stream url.
/// Whether or not to allow copying of the audio stream url.
- /// Optional. Whether to break on non key frames.
/// Optional. Specify a specific audio sample rate, e.g. 44100.
/// Optional. The maximum audio bit depth.
/// Optional. The maximum streaming bitrate.
@@ -601,7 +594,6 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
- [FromQuery] bool? breakOnNonKeyFrames,
[FromQuery] int? audioSampleRate,
[FromQuery] int? maxAudioBitDepth,
[FromQuery] int? maxStreamingBitrate,
@@ -654,7 +646,6 @@ public class DynamicHlsController : BaseJellyfinApiController
EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
- BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
AudioSampleRate = audioSampleRate,
MaxAudioChannels = maxAudioChannels,
AudioBitRate = audioBitRate ?? maxStreamingBitrate,
@@ -713,7 +704,6 @@ public class DynamicHlsController : BaseJellyfinApiController
/// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.
/// Whether or not to allow copying of the video stream url.
/// Whether or not to allow copying of the audio stream url.
- /// Optional. Whether to break on non key frames.
/// Optional. Specify a specific audio sample rate, e.g. 44100.
/// Optional. The maximum audio bit depth.
/// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.
@@ -771,7 +761,6 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
- [FromQuery] bool? breakOnNonKeyFrames,
[FromQuery] int? audioSampleRate,
[FromQuery] int? maxAudioBitDepth,
[FromQuery] int? audioBitRate,
@@ -826,7 +815,6 @@ public class DynamicHlsController : BaseJellyfinApiController
EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
- BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
AudioSampleRate = audioSampleRate,
MaxAudioChannels = maxAudioChannels,
AudioBitRate = audioBitRate,
@@ -887,7 +875,6 @@ public class DynamicHlsController : BaseJellyfinApiController
/// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.
/// Whether or not to allow copying of the video stream url.
/// Whether or not to allow copying of the audio stream url.
- /// Optional. Whether to break on non key frames.
/// Optional. Specify a specific audio sample rate, e.g. 44100.
/// Optional. The maximum audio bit depth.
/// Optional. The maximum streaming bitrate.
@@ -943,7 +930,6 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
- [FromQuery] bool? breakOnNonKeyFrames,
[FromQuery] int? audioSampleRate,
[FromQuery] int? maxAudioBitDepth,
[FromQuery] int? maxStreamingBitrate,
@@ -996,7 +982,6 @@ public class DynamicHlsController : BaseJellyfinApiController
EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
- BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
AudioSampleRate = audioSampleRate,
MaxAudioChannels = maxAudioChannels,
AudioBitRate = audioBitRate ?? maxStreamingBitrate,
@@ -1060,7 +1045,6 @@ public class DynamicHlsController : BaseJellyfinApiController
/// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.
/// Whether or not to allow copying of the video stream url.
/// Whether or not to allow copying of the audio stream url.
- /// Optional. Whether to break on non key frames.
/// Optional. Specify a specific audio sample rate, e.g. 44100.
/// Optional. The maximum audio bit depth.
/// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.
@@ -1124,7 +1108,6 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
- [FromQuery] bool? breakOnNonKeyFrames,
[FromQuery] int? audioSampleRate,
[FromQuery] int? maxAudioBitDepth,
[FromQuery] int? audioBitRate,
@@ -1181,7 +1164,6 @@ public class DynamicHlsController : BaseJellyfinApiController
EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
- BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
AudioSampleRate = audioSampleRate,
MaxAudioChannels = maxAudioChannels,
AudioBitRate = audioBitRate,
@@ -1247,7 +1229,6 @@ public class DynamicHlsController : BaseJellyfinApiController
/// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.
/// Whether or not to allow copying of the video stream url.
/// Whether or not to allow copying of the audio stream url.
- /// Optional. Whether to break on non key frames.
/// Optional. Specify a specific audio sample rate, e.g. 44100.
/// Optional. The maximum audio bit depth.
/// Optional. The maximum streaming bitrate.
@@ -1309,7 +1290,6 @@ public class DynamicHlsController : BaseJellyfinApiController
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
- [FromQuery] bool? breakOnNonKeyFrames,
[FromQuery] int? audioSampleRate,
[FromQuery] int? maxAudioBitDepth,
[FromQuery] int? maxStreamingBitrate,
@@ -1364,7 +1344,6 @@ public class DynamicHlsController : BaseJellyfinApiController
EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
- BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
AudioSampleRate = audioSampleRate,
MaxAudioChannels = maxAudioChannels,
AudioBitRate = audioBitRate ?? maxStreamingBitrate,
@@ -1586,16 +1565,6 @@ public class DynamicHlsController : BaseJellyfinApiController
var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions);
var threads = EncodingHelper.GetNumberOfThreads(state, _encodingOptions, videoCodec);
- if (state.BaseRequest.BreakOnNonKeyFrames)
- {
- // FIXME: this is actually a workaround, as ideally it really should be the client which decides whether non-keyframe
- // breakpoints are supported; but current implementation always uses "ffmpeg input seeking" which is liable
- // to produce a missing part of video stream before first keyframe is encountered, which may lead to
- // awkward cases like a few starting HLS segments having no video whatsoever, which breaks hls.js
- _logger.LogInformation("Current HLS implementation doesn't support non-keyframe breaks but one is requested, ignoring that request");
- state.BaseRequest.BreakOnNonKeyFrames = false;
- }
-
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));
@@ -1746,11 +1715,6 @@ public class DynamicHlsController : BaseJellyfinApiController
var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions);
var copyArgs = "-codec:a:0 copy" + bitStreamArgs + strictArgs;
- if (EncodingHelper.IsCopyCodec(videoCodec) && state.EnableBreakOnNonKeyFrames(videoCodec))
- {
- return copyArgs + " -copypriorss:a:0 0";
- }
-
return copyArgs;
}
diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs
index fd63347030..b1a91ae70f 100644
--- a/Jellyfin.Api/Controllers/UniversalAudioController.cs
+++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs
@@ -83,7 +83,6 @@ public class UniversalAudioController : BaseJellyfinApiController
/// Optional. The maximum audio bit depth.
/// Optional. Whether to enable remote media.
/// Optional. Whether to enable Audio Encoding.
- /// Optional. Whether to break on non key frames.
/// Whether to enable redirection. Defaults to true.
/// Audio stream returned.
/// Redirected to remote audio stream.
@@ -114,7 +113,6 @@ public class UniversalAudioController : BaseJellyfinApiController
[FromQuery] int? maxAudioBitDepth,
[FromQuery] bool? enableRemoteMedia,
[FromQuery] bool enableAudioVbrEncoding = true,
- [FromQuery] bool breakOnNonKeyFrames = false,
[FromQuery] bool enableRedirection = true)
{
userId = RequestHelpers.GetUserId(User, userId);
@@ -127,7 +125,7 @@ public class UniversalAudioController : BaseJellyfinApiController
return NotFound();
}
- var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);
+ var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);
_logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", deviceProfile);
@@ -208,7 +206,6 @@ public class UniversalAudioController : BaseJellyfinApiController
EnableAutoStreamCopy = true,
AllowAudioStreamCopy = true,
AllowVideoStreamCopy = true,
- BreakOnNonKeyFrames = breakOnNonKeyFrames,
AudioSampleRate = maxAudioSampleRate,
MaxAudioChannels = maxAudioChannels,
MaxAudioBitDepth = maxAudioBitDepth,
@@ -242,7 +239,6 @@ public class UniversalAudioController : BaseJellyfinApiController
EnableAutoStreamCopy = true,
AllowAudioStreamCopy = true,
AllowVideoStreamCopy = true,
- BreakOnNonKeyFrames = breakOnNonKeyFrames,
AudioSampleRate = maxAudioSampleRate,
MaxAudioChannels = maxAudioChannels,
AudioBitRate = isStatic ? null : (audioBitRate ?? maxStreamingBitrate),
@@ -263,7 +259,6 @@ public class UniversalAudioController : BaseJellyfinApiController
string? transcodingContainer,
string? audioCodec,
MediaStreamProtocol? transcodingProtocol,
- bool? breakOnNonKeyFrames,
int? transcodingAudioChannels,
int? maxAudioSampleRate,
int? maxAudioBitDepth,
@@ -298,7 +293,6 @@ public class UniversalAudioController : BaseJellyfinApiController
Container = transcodingContainer ?? "mp3",
AudioCodec = audioCodec ?? "mp3",
Protocol = transcodingProtocol ?? MediaStreamProtocol.http,
- BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
MaxAudioChannels = transcodingAudioChannels?.ToString(CultureInfo.InvariantCulture)
}
};
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index e7c6f23ce5..ccf8e90632 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -270,7 +270,6 @@ public class VideosController : BaseJellyfinApiController
/// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.
/// Whether or not to allow copying of the video stream url.
/// Whether or not to allow copying of the audio stream url.
- /// Optional. Whether to break on non key frames.
/// Optional. Specify a specific audio sample rate, e.g. 44100.
/// Optional. The maximum audio bit depth.
/// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.
@@ -329,7 +328,6 @@ public class VideosController : BaseJellyfinApiController
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
- [FromQuery] bool? breakOnNonKeyFrames,
[FromQuery] int? audioSampleRate,
[FromQuery] int? maxAudioBitDepth,
[FromQuery] int? audioBitRate,
@@ -386,7 +384,6 @@ public class VideosController : BaseJellyfinApiController
EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
- BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
AudioSampleRate = audioSampleRate,
MaxAudioChannels = maxAudioChannels,
AudioBitRate = audioBitRate,
@@ -511,7 +508,6 @@ public class VideosController : BaseJellyfinApiController
/// Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.
/// Whether or not to allow copying of the video stream url.
/// Whether or not to allow copying of the audio stream url.
- /// Optional. Whether to break on non key frames.
/// Optional. Specify a specific audio sample rate, e.g. 44100.
/// Optional. The maximum audio bit depth.
/// Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.
@@ -570,7 +566,6 @@ public class VideosController : BaseJellyfinApiController
[FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy,
- [FromQuery] bool? breakOnNonKeyFrames,
[FromQuery] int? audioSampleRate,
[FromQuery] int? maxAudioBitDepth,
[FromQuery] int? audioBitRate,
@@ -624,7 +619,6 @@ public class VideosController : BaseJellyfinApiController
enableAutoStreamCopy,
allowVideoStreamCopy,
allowAudioStreamCopy,
- breakOnNonKeyFrames,
audioSampleRate,
maxAudioBitDepth,
audioBitRate,
diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs
index b3f5b9a801..1e984542ec 100644
--- a/Jellyfin.Api/Helpers/StreamingHelpers.cs
+++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs
@@ -201,7 +201,7 @@ public static class StreamingHelpers
state.OutputVideoCodec = state.Request.VideoCodec;
state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
- encodingHelper.TryStreamCopy(state);
+ encodingHelper.TryStreamCopy(state, encodingOptions);
if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && state.OutputVideoBitrate.HasValue)
{
diff --git a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
index 20f51ddb71..10f2f04af6 100644
--- a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
+++ b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
@@ -43,8 +43,6 @@ namespace MediaBrowser.Controller.MediaEncoding
public bool AllowAudioStreamCopy { get; set; }
- public bool BreakOnNonKeyFrames { get; set; }
-
///
/// Gets or sets the audio sample rate.
///
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 91d88dc08b..11eee1a372 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -2914,8 +2914,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (time > 0)
{
- // For direct streaming/remuxing, we seek at the exact position of the keyframe
- // However, ffmpeg will seek to previous keyframe when the exact time is the input
+ // For direct streaming/remuxing, HLS segments start at keyframes.
+ // However, ffmpeg will seek to previous keyframe when the exact frame time is the input
// Workaround this by adding 0.5s offset to the seeking time to get the exact keyframe on most videos.
// This will help subtitle syncing.
var isHlsRemuxing = state.IsVideoRequest && state.TranscodingType is TranscodingJobType.Hls && IsCopyCodec(state.OutputVideoCodec);
@@ -2932,17 +2932,16 @@ 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.EnableBreakOnNonKeyFrames(outputVideoCodec)
- && (state.BaseRequest.StartTimeTicks ?? 0) > 0)
+ // If we are remuxing, then the copied stream cannot be seeked accurately (it will seek to the nearest
+ // keyframe). If we are using fMP4, then force all other streams to use the same inaccurate seeking to
+ // avoid A/V sync issues which cause playback issues on some devices.
+ // When remuxing video, the segment start times correspond to key frames in the source stream, so this
+ // option shouldn't change the seeked point that much.
+ // Important: make sure not to use it with wtv because it breaks seeking
+ if (state.TranscodingType is TranscodingJobType.Hls
+ && string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase)
+ && (IsCopyCodec(state.OutputVideoCodec) || IsCopyCodec(state.OutputAudioCodec))
+ && !string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase))
{
seekParam += " -noaccurate_seek";
}
@@ -7084,7 +7083,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
#nullable disable
- public void TryStreamCopy(EncodingJobInfo state)
+ public void TryStreamCopy(EncodingJobInfo state, EncodingOptions options)
{
if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream))
{
@@ -7101,8 +7100,14 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ var preventHlsAudioCopy = state.TranscodingType is TranscodingJobType.Hls
+ && state.VideoStream is not null
+ && !IsCopyCodec(state.OutputVideoCodec)
+ && options.HlsAudioSeekStrategy is HlsAudioSeekStrategy.TranscodeAudio;
+
if (state.AudioStream is not null
- && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs))
+ && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs)
+ && !preventHlsAudioCopy)
{
state.OutputAudioCodec = "copy";
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index 43680f5c01..7d0384ef27 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -515,21 +515,6 @@ namespace MediaBrowser.Controller.MediaEncoding
public int HlsListSize => 0;
- public bool EnableBreakOnNonKeyFrames(string videoCodec)
- {
- if (TranscodingType != TranscodingJobType.Progressive)
- {
- if (IsSegmentedLiveStream)
- {
- return false;
- }
-
- return BaseRequest.BreakOnNonKeyFrames && EncodingHelper.IsCopyCodec(videoCodec);
- }
-
- return false;
- }
-
private int? GetMediaStreamCount(MediaStreamType type, int limit)
{
var count = MediaSource.GetStreamCount(type);
diff --git a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs
index 2fd054f110..defd855ec0 100644
--- a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs
+++ b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs
@@ -673,7 +673,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
if (state.VideoRequest is not null)
{
- _encodingHelper.TryStreamCopy(state);
+ _encodingHelper.TryStreamCopy(state, encodingOptions);
}
}
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
index f7f386d289..98fc2e632f 100644
--- a/MediaBrowser.Model/Configuration/EncodingOptions.cs
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -1,6 +1,7 @@
#pragma warning disable CA1819 // XML serialization handles collections improperly, so we need to use arrays
#nullable disable
+using System.ComponentModel;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Configuration;
@@ -60,6 +61,7 @@ public class EncodingOptions
SubtitleExtractionTimeoutMinutes = 30;
AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = ["mkv"];
HardwareDecodingCodecs = ["h264", "vc1"];
+ HlsAudioSeekStrategy = HlsAudioSeekStrategy.DisableAccurateSeek;
}
///
@@ -301,4 +303,10 @@ public class EncodingOptions
/// Gets or sets the file extensions on-demand metadata based keyframe extraction is enabled for.
///
public string[] AllowOnDemandMetadataBasedKeyframeExtractionForExtensions { get; set; }
+
+ ///
+ /// Gets or sets the method used for audio seeking in HLS.
+ ///
+ [DefaultValue(HlsAudioSeekStrategy.DisableAccurateSeek)]
+ public HlsAudioSeekStrategy HlsAudioSeekStrategy { get; set; }
}
diff --git a/MediaBrowser.Model/Configuration/HlsAudioSeekStrategy.cs b/MediaBrowser.Model/Configuration/HlsAudioSeekStrategy.cs
new file mode 100644
index 0000000000..49feeb435f
--- /dev/null
+++ b/MediaBrowser.Model/Configuration/HlsAudioSeekStrategy.cs
@@ -0,0 +1,23 @@
+namespace MediaBrowser.Model.Configuration
+{
+ ///
+ /// An enum representing the options to seek the input audio stream when
+ /// transcoding HLS segments.
+ ///
+ public enum HlsAudioSeekStrategy
+ {
+ ///
+ /// If the video stream is transcoded and the audio stream is copied,
+ /// seek the video stream to the same keyframe as the audio stream. The
+ /// resulting timestamps in the output streams may be inaccurate.
+ ///
+ DisableAccurateSeek = 0,
+
+ ///
+ /// Prevent audio streams from being copied if the video stream is transcoded.
+ /// The resulting timestamps will be accurate, but additional audio transcoding
+ /// overhead will be incurred.
+ ///
+ TranscodeAudio = 1,
+ }
+}
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 61e04a8134..42cb208d08 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -610,7 +610,6 @@ namespace MediaBrowser.Model.Dlna
playlistItem.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
playlistItem.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
- playlistItem.BreakOnNonKeyFrames = transcodingProfile.BreakOnNonKeyFrames;
playlistItem.EnableAudioVbrEncoding = transcodingProfile.EnableAudioVbrEncoding;
if (transcodingProfile.MinSegments > 0)
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index 92404de508..9edb4115cc 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -86,11 +86,6 @@ public class StreamInfo
/// The minimum segments count.
public int? MinSegments { get; set; }
- ///
- /// Gets or sets a value indicating whether the stream can be broken on non-keyframes.
- ///
- public bool BreakOnNonKeyFrames { get; set; }
-
///
/// Gets or sets a value indicating whether the stream requires AVC.
///
@@ -1018,9 +1013,6 @@ public class StreamInfo
sb.Append("&MinSegments=");
sb.Append(MinSegments.Value.ToString(CultureInfo.InvariantCulture));
}
-
- sb.Append("&BreakOnNonKeyFrames=");
- sb.Append(BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture));
}
else
{
diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs
index 5797d42506..f49b24976a 100644
--- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs
+++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs
@@ -41,7 +41,6 @@ public class TranscodingProfile
MaxAudioChannels = other.MaxAudioChannels;
MinSegments = other.MinSegments;
SegmentLength = other.SegmentLength;
- BreakOnNonKeyFrames = other.BreakOnNonKeyFrames;
Conditions = other.Conditions;
EnableAudioVbrEncoding = other.EnableAudioVbrEncoding;
}
@@ -143,7 +142,8 @@ public class TranscodingProfile
///
[DefaultValue(false)]
[XmlAttribute("breakOnNonKeyFrames")]
- public bool BreakOnNonKeyFrames { get; set; }
+ [Obsolete("This is always false")]
+ public bool? BreakOnNonKeyFrames { get; set; }
///
/// Gets or sets the profile conditions.
diff --git a/tests/Jellyfin.Model.Tests/Dlna/LegacyStreamInfo.cs b/tests/Jellyfin.Model.Tests/Dlna/LegacyStreamInfo.cs
index e32baef55d..6436d7d0e4 100644
--- a/tests/Jellyfin.Model.Tests/Dlna/LegacyStreamInfo.cs
+++ b/tests/Jellyfin.Model.Tests/Dlna/LegacyStreamInfo.cs
@@ -134,8 +134,6 @@ public class LegacyStreamInfo : StreamInfo
{
list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture)));
}
-
- list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture)));
}
else
{
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidPixel.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidPixel.json
index 68ce3ea4ab..643ff2638c 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidPixel.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidPixel.json
@@ -152,7 +152,6 @@
"EnableSubtitlesInManifest": false,
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -169,7 +168,6 @@
"EnableSubtitlesInManifest": false,
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -185,7 +183,6 @@
"EnableSubtitlesInManifest": false,
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
}
],
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidTVExoPlayer.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidTVExoPlayer.json
index 3d3968268f..44f63f384f 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidTVExoPlayer.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidTVExoPlayer.json
@@ -130,7 +130,6 @@
"EnableSubtitlesInManifest": false,
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -146,7 +145,6 @@
"EnableSubtitlesInManifest": false,
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
}
],
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json
index 5d1f5f1620..f1fc9e0dbe 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json
@@ -127,7 +127,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -144,7 +143,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -161,7 +159,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -178,7 +175,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -195,7 +191,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -212,7 +207,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -229,7 +223,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -246,7 +239,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -263,7 +255,6 @@
"EnableSubtitlesInManifest": false,
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -281,7 +272,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -298,7 +288,6 @@
"EnableSubtitlesInManifest": false,
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
}
],
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome.json
index e2f75b569b..7e37a6236e 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome.json
@@ -107,7 +107,6 @@
"Protocol": "hls",
"MaxAudioChannels": "2",
"MinSegments": "2",
- "BreakOnNonKeyFrames": true,
"EnableAudioVbrEncoding": true
},
{
@@ -182,8 +181,7 @@
"Context": "Streaming",
"Protocol": "hls",
"MaxAudioChannels": "2",
- "MinSegments": "2",
- "BreakOnNonKeyFrames": true
+ "MinSegments": "2"
},
{
"Container": "ts",
@@ -193,8 +191,7 @@
"Context": "Streaming",
"Protocol": "hls",
"MaxAudioChannels": "2",
- "MinSegments": "2",
- "BreakOnNonKeyFrames": true
+ "MinSegments": "2"
}
],
"ContainerProfiles": [],
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Firefox.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Firefox.json
index 21ae7e5cb3..4380d80efa 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Firefox.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Firefox.json
@@ -95,7 +95,6 @@
"TranscodingProfiles": [
{
"AudioCodec": "aac",
- "BreakOnNonKeyFrames": true,
"Container": "mp4",
"Context": "Streaming",
"EnableAudioVbrEncoding": true,
@@ -170,7 +169,6 @@
},
{
"AudioCodec": "aac,mp2,opus,flac",
- "BreakOnNonKeyFrames": true,
"Container": "mp4",
"Context": "Streaming",
"MaxAudioChannels": "2",
@@ -181,7 +179,6 @@
},
{
"AudioCodec": "aac,mp3,mp2",
- "BreakOnNonKeyFrames": true,
"Container": "ts",
"Context": "Streaming",
"MaxAudioChannels": "2",
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json
index da9a1a4ada..cca1c16ee7 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json
@@ -30,7 +30,6 @@
"EnableSubtitlesInManifest": false,
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -48,7 +47,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -62,7 +60,6 @@
"EnableSubtitlesInManifest": false,
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
}
],
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-LowBandwidth.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-LowBandwidth.json
index 82b73fb0f8..b7cd170b9f 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-LowBandwidth.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-LowBandwidth.json
@@ -30,7 +30,6 @@
"EnableSubtitlesInManifest": false,
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -48,7 +47,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -62,7 +60,6 @@
"EnableSubtitlesInManifest": false,
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
}
],
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlus.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlus.json
index 37b923558b..b823ac4b85 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlus.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlus.json
@@ -49,7 +49,6 @@
"MaxAudioChannels": " 2",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -66,7 +65,6 @@
"MaxAudioChannels": "2",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -83,7 +81,6 @@
"MaxAudioChannels": "2",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -100,7 +97,6 @@
"MaxAudioChannels": " 2",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -118,7 +114,6 @@
"MaxAudioChannels": " 2",
"MinSegments": 1,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -135,7 +130,6 @@
"EnableSubtitlesInManifest": false,
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
}
],
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json
index 542bf6370a..708ff73c4d 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json
@@ -49,7 +49,6 @@
"MaxAudioChannels": " 2",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -66,7 +65,6 @@
"MaxAudioChannels": "2",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -83,7 +81,6 @@
"MaxAudioChannels": "2",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -100,7 +97,6 @@
"MaxAudioChannels": " 2",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -118,7 +114,6 @@
"MaxAudioChannels": " 2",
"MinSegments": 1,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -135,7 +130,6 @@
"EnableSubtitlesInManifest": false,
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
}
],
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-SafariNext.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-SafariNext.json
index f61d0e36bd..10382fa82e 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-SafariNext.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-SafariNext.json
@@ -114,7 +114,6 @@
"Protocol": "hls",
"MaxAudioChannels": "6",
"MinSegments": "2",
- "BreakOnNonKeyFrames": true,
"EnableAudioVbrEncoding": true
},
{
@@ -173,8 +172,7 @@
"Context": "Streaming",
"Protocol": "hls",
"MaxAudioChannels": "2",
- "MinSegments": "2",
- "BreakOnNonKeyFrames": true
+ "MinSegments": "2"
},
{
"Container": "ts",
@@ -184,8 +182,7 @@
"Context": "Streaming",
"Protocol": "hls",
"MaxAudioChannels": "2",
- "MinSegments": "2",
- "BreakOnNonKeyFrames": true
+ "MinSegments": "2"
}
],
"ContainerProfiles": [],
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json
index 9d43d2166d..3625b099c1 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json
@@ -165,7 +165,6 @@
"MaxAudioChannels": "2",
"MinSegments": 1,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": true,
"$type": "TranscodingProfile"
},
{
@@ -182,7 +181,6 @@
"MaxAudioChannels": "2",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -199,7 +197,6 @@
"MaxAudioChannels": "2",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -216,7 +213,6 @@
"MaxAudioChannels": "2",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -233,7 +229,6 @@
"MaxAudioChannels": "2",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -250,7 +245,6 @@
"MaxAudioChannels": "2",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -267,7 +261,6 @@
"MaxAudioChannels": "2",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -284,7 +277,6 @@
"MaxAudioChannels": "2",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -301,7 +293,6 @@
"MaxAudioChannels": "2",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -319,7 +310,6 @@
"MaxAudioChannels": "2",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"Conditions": [
{
"Condition": "LessThanEqual",
@@ -346,7 +336,6 @@
"MaxAudioChannels": "2",
"MinSegments": 1,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"Conditions": [
{
"Condition": "LessThanEqual",
@@ -373,7 +362,6 @@
"MaxAudioChannels": "2",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"Conditions": [
{
"Condition": "LessThanEqual",
@@ -399,7 +387,6 @@
"EnableSubtitlesInManifest": false,
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"Conditions": [
{
"Condition": "LessThanEqual",
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json
index 3859ef9941..deee650b29 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json
@@ -165,7 +165,6 @@
"MaxAudioChannels": "6",
"MinSegments": 1,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": true,
"$type": "TranscodingProfile"
},
{
@@ -182,7 +181,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -199,7 +197,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -216,7 +213,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -233,7 +229,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -250,7 +245,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -267,7 +261,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -284,7 +277,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -301,7 +293,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -319,7 +310,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"Conditions": [
{
"Condition": "LessThanEqual",
@@ -346,7 +336,6 @@
"MaxAudioChannels": "6",
"MinSegments": 1,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"Conditions": [
{
"Condition": "LessThanEqual",
@@ -373,7 +362,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"Conditions": [
{
"Condition": "LessThanEqual",
@@ -399,7 +387,6 @@
"EnableSubtitlesInManifest": false,
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"Conditions": [
{
"Condition": "LessThanEqual",
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-TranscodeMedia.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-TranscodeMedia.json
index 9fc1ae6bb2..38de51b045 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-TranscodeMedia.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-TranscodeMedia.json
@@ -16,7 +16,6 @@
"EnableSubtitlesInManifest": false,
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -28,7 +27,6 @@
"Protocol": "hls",
"MaxAudioChannels": "2",
"MinSegments": "2",
- "BreakOnNonKeyFrames": true,
"$type": "TranscodingProfile"
},
{
@@ -40,7 +38,6 @@
"Protocol": "hls",
"MaxAudioChannels": "2",
"MinSegments": "2",
- "BreakOnNonKeyFrames": true,
"$type": "TranscodingProfile"
},
{
@@ -64,7 +61,6 @@
"EnableSubtitlesInManifest": false,
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
}
],
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-WebOS-23.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-WebOS-23.json
index 094b0723b1..3ff11a684f 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-WebOS-23.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-WebOS-23.json
@@ -135,7 +135,6 @@
"Protocol": "hls",
"MaxAudioChannels": "6",
"MinSegments": "1",
- "BreakOnNonKeyFrames": false,
"EnableAudioVbrEncoding": true
},
{
@@ -210,8 +209,7 @@
"Context": "Streaming",
"Protocol": "hls",
"MaxAudioChannels": "6",
- "MinSegments": "1",
- "BreakOnNonKeyFrames": false
+ "MinSegments": "1"
}
],
"ContainerProfiles": [],
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse.json
index 256c8dc2f0..838a1f920c 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse.json
@@ -52,7 +52,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -70,7 +69,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -88,7 +86,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
}
],
diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse2.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse2.json
index 256c8dc2f0..838a1f920c 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse2.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse2.json
@@ -52,7 +52,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -70,7 +69,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
},
{
@@ -88,7 +86,6 @@
"MaxAudioChannels": "6",
"MinSegments": 0,
"SegmentLength": 0,
- "BreakOnNonKeyFrames": false,
"$type": "TranscodingProfile"
}
],