From: Jellyfin Packaging Date: Wed, 8 Jan 2025 00:00:00 +0000 Subject: [PATCH] Progressive download improvements for v10.11.5+ This patch includes all progressive download and transcoding improvements: - Use /downloads/ folder for full-file progressive downloads - Fix download transcoding not stopping when client disconnects - Improve MP4 movflags for better seeking compatibility (mpv/iina/infuse) - Fix timestamp seeking for progressive downloads - Use separate transcode paths for HLS vs progressive downloads --- Jellyfin.Api/Controllers/VideosController.cs | 3 ++- Jellyfin.Api/Helpers/StreamingHelpers.cs | 15 +++++++++++++++ .../MediaEncoding/EncodingHelper.cs | 22 ++++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index 97f3239bbc..6f9fbadddb 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -369,7 +369,8 @@ public class VideosController : BaseJellyfinApiController { var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head; // CTS lifecycle is managed internally. - var cancellationTokenSource = new CancellationTokenSource(); + // Link to HttpContext.RequestAborted so transcode stops when client disconnects + var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(HttpContext.RequestAborted); var streamingRequest = new VideoRequestDto { Id = itemId, diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index b3f5b9a801..f366fd7a23 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -378,8 +378,23 @@ public static class StreamingHelpers var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture); var ext = outputFileExtension.ToLowerInvariant(); + + // Use different transcode paths for HLS vs Progressive downloads + // HLS segments: use RAM-based transcode path (fast, auto-cleanup) + // Progressive downloads: use disk-based path (large files OK, slower cleanup) var folder = serverConfigurationManager.GetTranscodePath(); + // Check if this is a full file download (no segments) vs HLS streaming + var streamingRequest = state.BaseRequest as StreamingRequestDto; + + if (streamingRequest?.SegmentContainer is null) + { + // Full file download - use disk-based transcode path + var diskTranscodePath = Path.Combine(folder, "downloads"); + Directory.CreateDirectory(diskTranscodePath); + folder = diskTranscodePath; + } + return Path.Combine(folder, filename + ext); } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index e088cd358d..a31a735bed 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -20,6 +20,7 @@ using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Streaming; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; @@ -7475,8 +7476,20 @@ namespace MediaBrowser.Controller.MediaEncoding if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase) && state.BaseRequest.Context == EncodingContext.Streaming) { - // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js - format = " -f mp4 -movflags frag_keyframe+empty_moov+delay_moov"; + // Use fragmented MP4 for adaptive streaming (HLS/DASH with segments) + // Use faststart for progressive downloads (better seeking and metadata) + var streamingRequest = state.BaseRequest as StreamingRequestDto; + if (streamingRequest?.SegmentContainer is not null) + { + // Fragmented MP4 for HLS/DASH + format = " -f mp4 -movflags frag_keyframe+empty_moov+delay_moov"; + } + else + { + // Progressive download - use faststart for proper seeking and duration + // Use frag_keyframe for better seeking compatibility with mpv + format = " -f mp4 -movflags frag_keyframe+faststart+default_base_moof"; + } } var threads = GetNumberOfThreads(state, encodingOptions, videoCodec); @@ -7582,6 +7595,15 @@ namespace MediaBrowser.Controller.MediaEncoding args += " -start_at_zero"; } } + else if (state.TranscodingType == TranscodingJobType.Progressive && !state.BaseRequest.CopyTimestamps) + { + // For progressive downloads without copyTimestamps, ensure timestamps start at 0 + // This fixes seeking issues in strict players like mpv, iina, and infuse + args += " -avoid_negative_ts make_zero -start_at_zero"; + } var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset); -- 2.43.0