Client delivery is daemon-only since ADR 0003 Phase E, but several
producers never fed the daemon, or fed it in a way no player could use:
- Delayed channels (delay_minutes > 0) were unwatchable: the encoder's tee
is skipped for them (its output is the undelayed stream), the supervisor
refuses them, and DelayCommand never registered an ingest, so every
viewer got not-on-air. DelayCommand now pushes each delayed segment into
the daemon as it publishes it (IngestFeeder), paced over the segment's
duration, with a two-segment burst at start. Its loop polls every 50 ms
instead of hashing the playlist every 1 ms.
- An ffmpeg loopback (php_loopback off, or no supervision) registered no
ingest at all; startStream now tees loopback streams too.
- Encrypted HLS was undecodable for loopback and llod=2: the playlist
declares AES-128 whenever encrypt_hls is on, but those producers
registered without the key, so the daemon served plain segments. Every
producer now passes the key; startLLOD/startLoopback write it before
spawning the child that registers.
- LlodCommand/LoopbackCommand feed through IngestFeeder (no torn packets,
reconnect after a daemon restart). LLOD also honours the stream's
headers/cookie/proxy/default user agent and request_prebuffer, accepts
TS sources by content when the Content-Type is not video/mp2t, kills a
stale segmenter by process title (it read the MONITOR's pid), and writes
its playlist atomically (the loopback relay too).
- LLOD ffmpeg: +nobuffer moves to the input where it has an effect;
-tune zerolatency only for x264/x265 (NVENC rejects it and failed the
start; it gets -zerolatency 1); an LLOD start uses the first source
instead of falling through to the last.
- Remote VOD subtitles URL-encoded the shell-quoted path.
- cron:streams: the restreamer "attached" subqueries lacked GROUP BY, so
on-demand parents could be stopped under a child LB; a stopped on-demand
stream went on to spawn thumbnail/archive workers; the daemon re-feed
restart now skips self-feeding producers (PHP relays, delay) and its
throttle stamp moved out of STREAMS_PATH, where the restart's
`rm -f <id>_*` deleted it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
buildSubtitleImport nested the metadata loop inside the import loop and reused
the outer counter $i. The inner loop advanced $i to count(files), so the outer
import loop exited after its first iteration: only the first subtitle was
imported (-sub_charenc -i), while -map/-metadata was still emitted for every
file — mapping ffmpeg inputs that were never added. Multi-subtitle movies got a
single imported track plus dangling -map references (broken output).
Split the nested loop into two siblings — import every file, then map every
file — and cache the count. All configured subtitles are now imported and
mapped to the correct input index.
Add tests/Unit/StreamProcessSubtitleTest.php covering empty input, a single
local subtitle, remote-server fetch, and the multi-subtitle case that
reproduces the defect (import count was 1, must equal N). Verified red→green
against the pre-fix code.
Verified: php -l clean, make gates green, phpunit 336/336 green.