Files
ytstream/deploy/ytstream-proxy.service
T
Claude d3616e7532 Bound time-to-first-byte so a first play actually plays
Playback failed in Jellyfin the day after deployment, for uncached videos
only. The handler blocked on the full download-and-mux before sending
anything at all -- not even response headers -- so a 46-minute upload sat
silent for 79 seconds and the client gave up. The proxy counted it a
success, which is why /healthz and doctor both looked fine.

Yesterday's "DirectPlay verified" only ever ran against videos already
pulled during testing, so the first-play path was never exercised.

The output is already a fragmented MP4, so it is readable while being
written; a finished file only buys a correct duration and working seeks.
Cap the wait at --first-byte-grace (12s, explicit in the unit) and stream
whatever has not muxed by then. Measured: 156s -> 12.0s TTFB on a
66-minute upload, with ranges on a complete file unchanged.

The cost is a first play with no seek bar when the grace is missed. Every
later play of that video is perfect.

Also fixes a hang found while testing this: the streaming loop waited on
"finished and good" rather than "finished", so a producer that died after
writing some bytes held the connection for the full 45s stall timeout and
then dropped it.

The stale yt-dlp warning pointed at youtube-automate's venv, the tree we
are decommissioning; it now names ytstream's. /healthz reports the serving
mode and grace. Eight new proxy tests, 353 passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 10:31:47 +01:00

54 lines
1.8 KiB
Desktop File

[Unit]
Description=ytstream just-in-time YouTube streaming proxy
Documentation=file:///opt/ytstream/plan.md
After=network-online.target docker.service
Wants=network-online.target
# The bgutil POT provider runs in Docker on 127.0.0.1:4416 and yt-dlp cannot
# fetch anything without it.
Requires=docker.service
[Service]
Type=simple
# Group=mediaserver and UMask=0002 are load-bearing: Jellyfin reaches the media
# tree only through the mediaserver group. See plan.md §9.
User=susan
Group=mediaserver
UMask=0002
# LOAD-BEARING. The only yt-dlp with the bgutil POT plugin is the one in this
# venv. /usr/local/bin/yt-dlp is a 2023.11.16 binary that cannot talk to YouTube
# at all, and if it wins the PATH race every playback fails with no clear reason.
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.
WorkingDirectory=/opt/ytstream
ExecStart=/var/lib/ytstream/venv/bin/python3 /opt/ytstream/proxy/ytstream_proxy.py \
--host 127.0.0.1 --port 8099 \
--work /dev/shm/ytstream \
--cache-gb 8 \
--max-pipelines 2 \
--max-retries 2 \
--max-starts 20 --starts-window 3600 \
--first-byte-grace 12
Restart=always
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectControlGroups=true
ProtectKernelTunables=true
RestrictSUIDSGID=true
# /dev/shm is the cache; nothing else needs to be writable.
ReadWritePaths=/dev/shm
[Install]
WantedBy=multi-user.target