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>
54 lines
1.8 KiB
Desktop File
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
|