Never serve a partial file: it makes Jellyfin transcode
The 12s first-byte grace was the wrong trade and a real play found it within the hour. A 2-hour upload took 3 minutes to start, played 6 seconds, and stalled. Jellyfin had run ffmpeg with -probesize 1G against the growing stream and then transcoded to HLS with libx264. The cause is the container. A fragmented MP4 with empty_moov has no duration in its header, so the only way to get one is to sum every fragment -- probing a growing file reads all of it. Jellyfin cannot establish duration, codec or bitrate, so it abandons direct play and transcodes a stream it also cannot seek. It was targeting 4.83 Mbps against a source measured at 3.29: re-encoding a stream that already fit, because it could not measure it. The same video once complete reports SupportsDirectPlay with the exact runtime and bitrate. So FIRST_BYTE_GRACE defaults to infinite again, with --wait-timeout raised to 600s for a 2-hour upload. A cold long video is slow to start, which is accepted: the fetch outlives the request so a retry is instant, and a retryable stall beats a transcode that wastes a gigabyte and cannot work. Both failure modes are recorded at the constant in the order measured so the 12s cap is not reintroduced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,11 +21,15 @@ UMask=0002
|
||||
Environment=PATH=/var/lib/ytstream/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
|
||||
# --first-byte-grace is the difference between "plays" and "playback error".
|
||||
# Waiting for a complete mux costs ~3s to extract plus duration/50..110 to pull,
|
||||
# so a 46-minute upload blocked this socket for 79 silent seconds and Jellyfin
|
||||
# gave up long before. Do not raise this to --wait-timeout without re-reading
|
||||
# the module docstring: that restores the behaviour that broke first plays.
|
||||
# No --first-byte-grace: requests wait for the COMPLETE mux, bounded by
|
||||
# --wait-timeout. Do not add a grace here without reading FIRST_BYTE_GRACE in the
|
||||
# proxy. Short version, both measured 2026-08-13: waiting sends nothing for ~80s
|
||||
# on a 46-minute upload and the client gives up, but serving the partial file
|
||||
# instead makes Jellyfin drag a gigabyte through this proxy to probe a fragmented
|
||||
# MP4 that has no duration in its header, and then TRANSCODE a stream it cannot
|
||||
# seek -- 3 minutes to start, 6 seconds of video, then a permanent stall.
|
||||
# A slow cold start is the better failure: the fetch outlives the request, so the
|
||||
# retry is instant. --wait-timeout is 600 to cover a 2-hour upload (2.4 GB).
|
||||
|
||||
WorkingDirectory=/opt/ytstream
|
||||
ExecStart=/var/lib/ytstream/venv/bin/python3 /opt/ytstream/proxy/ytstream_proxy.py \
|
||||
@@ -35,7 +39,7 @@ ExecStart=/var/lib/ytstream/venv/bin/python3 /opt/ytstream/proxy/ytstream_proxy.
|
||||
--max-pipelines 2 \
|
||||
--max-retries 2 \
|
||||
--max-starts 20 --starts-window 3600 \
|
||||
--first-byte-grace 12
|
||||
--wait-timeout 600
|
||||
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
Reference in New Issue
Block a user