Bring non-proxy live streams into the daemon (ADR 0003, Phase A). Before
launching a standard live stream's ffmpeg, StreamProcess registers a daemon
ingest (FanoutClient::registerIngest → PUT /ingest/<id>, returns the socket)
and, when the daemon accepted it, builds the HLS output through the `tee`
muxer so one ffmpeg feeds BOTH the existing on-disk HLS and the daemon:
-map … -f tee "[f=hls:…]<id>_.m3u8|[f=mpegts:onfail=ignore]unix:<ingest.sock>"
- onfail=ignore keeps the on-disk HLS alive if the daemon output drops
(verified: ffmpeg "continuing with 1/2 slaves"), so a daemon crash never
takes the stream down. A plain second output would abort ffmpeg entirely.
- Daemon unreachable ⇒ registerIngest returns null ⇒ the original on-disk-only
HLS output runs, unchanged (the reachability rollback). Loopback/delay
keep the legacy output.
- Two tee-muxer gotchas handled: tee needs an EXPLICIT -map (plain outputs use
automatic selection; tee errors "does not contain any stream") — reuse the
stream's map or -map 0 -copy_unknown; and `individual_header_trailer` is a
segment-muxer option the hls muxer ignores in flag form but rejects fatally
inside a tee slave, so it is omitted.
- stopStream unregisters the daemon ingest; FanoutClient::call() now checks the
socket exists so unregister is a no-op when the daemon isn't deployed.
Box-validated (real panel + ffmpeg, stream 569): one ffmpeg feeds on-disk HLS
(5 segs, m3u8) AND the daemon (/live TS + /hls in-RAM), no errors; panel
playback via the legacy path unaffected (1.69 MB valid TS). PHPStan clean,
441 unit tests pass.
Refs ADR 0003 (Phase A).
Route proxy-mode live TS through the xc_fanout daemon behind the
`live_fanout` settings flag, taking PHP out of the byte path. When the
flag is on, live.php runs its existing auth unchanged, then instead of the
socket_read→echo relay it registers the source with the daemon over the
control unix socket and hands the viewer to nginx via
`X-Accel-Redirect: /xc_fanout/live/<id>` — the FPM worker is freed
immediately, same pattern as P1 HLS delivery. Flag off (the default) keeps
the legacy relay; a failed registration falls through to it in-request, so
a down daemon degrades gracefully.
- FanoutClient: control-socket client (PUT/DELETE /streams/<id> over
FANOUT_CTL_SOCK via cURL unix socket) + buildSource(), a pure mirror of
ProxyCommand's stream_source/user_agent/proxy/cookie extraction whose
JSON matches the daemon's streamConfig.
- Paths: FANOUT_RUN_PATH / FANOUT_CTL_SOCK / FANOUT_HTTP_SOCK under /run.
- nginx: internal `location ^~ /xc_fanout/live/` → proxy_pass unix daemon
client socket (buffering off, long read timeout).
- FanoutClientTest: 8 cases covering source-config parity and the
empty-urls short-circuit.
Refs ADR 0002. Daemon lives in XC_VM_Binaries (not this repo).