Commit Graph
2 Commits
Author SHA1 Message Date
Divarion_D 070d89b3c4 feat(streaming): P2/A2 — tee non-proxy live ffmpeg into the xc_fanout daemon
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).
2026-08-16 18:16:45 +03:00
Divarion_D 6d3c84a473 feat(streaming): P2/S3 — proxy-mode live TS via xc_fanout, flag-gated
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).
2026-08-16 15:54:40 +03:00