FanoutConfig::desired maps a new fanout_debug setting to the daemon's debug_cats
config key: "" (off, the default), "all", or a comma-separated category list
kept to the daemon's known categories (unknown names dropped, so a typo turns
debug off rather than writing a file the daemon second-guesses). The daemon
applies it on its next config poll, so an operator can turn debug on for a
misbehaving node and off again without a restart that would drop every viewer.
The PHPUnit suite could not be run here — no PHP on this box has the
dom/mbstring/xml/xmlwriter extensions phpunit.phar needs — so the three tests
added to FanoutConfigTest are unrun; the debugCats() mapping was instead
verified directly by reflection (off/all/list/unknown all map as asserted).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The panel wrote prebuffer_max_sec = max(40, client_prebuffer,
restreamer_prebuffer, hls_window x seg_time). The ring is the daemon's
memory, and the flat 40 kept every watched channel at 40 s however far the
operator lowered Client Prebuffer or the HLS window: no panel setting could
shrink it, and a hand edit of config.json is overwritten by the next sync.
The ring now covers the HLS window and every prebuffer a viewer can ask for
(client, restreamer, the daemon default), each with one segment of headroom
on top — a join asking for as much as the ring holds starts in the block the
next keyframe prunes, and a viewer slower than one GOP there is dropped. It is
never under two segments, and still capped at the daemon's 120. At the
defaults (30 s prebuffer, 6 x 6 s HLS) that is 36 s instead of 40; with a
10 s prebuffer and a 3-segment window it is 18 s.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbYsGKhirq9eRK8e6wsCHR
Whether live streams on each server are handed to the xc_fanout daemon's
encoder supervisor instead of getting a per-stream PHP watchdog
(console.php monitor). On by default: the supervisor does everything the
PHP monitor did, from one process per node instead of one per channel,
and the PHP monitor stays the fallback for a daemon that cannot be
reached.
Touches the places every fanout_* setting lives: migration 018 for
upgrades, database.sql for fresh installs (277/277 columns and values
still aligned), the FanoutConfig mapping that writes `supervise` into the
daemon's config.json (applied live by the daemon, no restart), the
settings UI, the checkbox whitelist in SettingsService, all seven language
files, and the unit test. The source-backend tooltip now says what the
backend means for the panel's own streams as well.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012QL93N6dzGkmKoQgA4oh16
Reverts the five commits PR #1 brought in and the two test follow-ups made
after it merged:
a1d454fe Enhance assertions in FanoutConfigTest
19607a91 Update StreamProcessBuildLiveTest.php
b3714269 fix(fanout): stop, force and rogue-kill must account for the daemon
cea545fe feat(fanout): reconcile supervised streams back into the panel
8e24a242 feat(fanout): make encoder supervision a real, settable option
8244ece1 feat(fanout): health policy, source forcing and supervision reconcile
56c0619e feat(fanout): hand live encoders to the daemon to supervise
Only those. The upstream commits that reached main through the same merge
(the GeoIP refactor, release 2.5.0, the update guard, the mass-page fix)
stay: reverting the merge commit itself would have taken them out too.
The panel side of supervision is re-done in the commits that follow,
together with the xc_fanout native remuxer (xc_fanout 0.13.0): every live
stream handed to the daemon's monitor, copy-only streams run on
`xc_fanout remux` instead of ffmpeg, the ffmpeg command as an explicit
fallback.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012QL93N6dzGkmKoQgA4oh16
Production-readiness pass on the panel side of daemon encoder supervision.
Until now the feature was gated on a setting that did not exist as a
column, so it could only be enabled by hand-editing the database and the
daemon never learned about it at all.
* Migration 018 adds `fanout_supervise`, off for every existing install.
* Admin -> Settings gains the toggle, alongside the other fanout tuning.
* SettingsService saves it (it is a checkbox, so it has to be in the
boolean list or it can never be turned back off).
* FanoutConfig writes it into the daemon's config file as `supervise`,
which is how the node learns it may supervise at all. Both halves must
be on for anything to change, and either one off is a full rollback.
* StreamProcess reads `fanout_supervise` rather than the placeholder
name, matching the panel's `fanout_*` convention.
Also: the recorded command file now says WHO ran the stream. `_.fanout`
when the daemon owns the process (the bare command it was handed) and
`_.ffmpeg` when this node ran it itself (with the redirect-and-background
tail). Two names rather than one because the first question in any
incident is which path the stream took, and a single filename cannot
answer it; the stale one is removed so a stream that switched paths does
not leave a lie behind.
FanoutConfigTest covers the new key both ways, including that a settings
array predating it reads as off -- that absence is the upgrade path for
every existing install.
Verified: php -l clean on every changed file.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
xc_fanout 0.12.0 can convert a non-mp2t source to MPEG-TS in-process instead of
spawning an ffmpeg child per stream. For the common IPTV case — HLS whose
segments are already MPEG-TS — that child was doing little more than
concatenating bytes the daemon can concatenate itself, at ~27 MB RSS apiece,
plus a process spawn and a probe window on every on-demand join and reconnect.
The daemon reads which path to take from `source_backend` in its config.json;
this adds the panel setting that writes it.
auto (default) — convert natively where the daemon's reader can, ffmpeg for
everything it declines
ffmpeg — always spawn ffmpeg; the pre-0.12 behaviour and the
kill-switch if a native pull ever misbehaves
native — native only, no fallback. Diagnostic: it answers "what is
actually eligible on this node", and a declined source there
is a dead channel rather than a slightly more expensive one.
auto is the default because the fallback makes it strictly safer than
ffmpeg-always: anything the native reader will not take (fMP4/CMAF HLS, RTMP,
SRT, RTSP, AES-128 encrypted sources, anything it cannot positively identify)
runs exactly the pipeline it ran before.
A `<select>`, not a numeric input, so it is deliberately kept out of the
numeric-coercion list in settings.php that would otherwise mangle the string.
An unrecognised value maps to auto rather than being written through — the
daemon clamps unknown values itself, but a config file we write should not carry
a backend that does not exist, and a typo must never take channels off air.
Touches the six places every fanout_* setting lives: migration 017 for upgrades,
database.sql for fresh installs (column, INSERT list and VALUES — 276/276 still
aligned), the FanoutConfig mapping, the settings UI, all seven language files,
and the unit test. SettingsService needs no change: its whitelist is only for
checkboxes, and verifyPostTable already maps a posted field to its column.
Verified against the real daemon end to end: the panel writing "ffmpeg" boots
xc_fanout with backend=ffmpeg, and changing it to "auto" is picked up live on
the next config poll. Read-modify-write still preserves daemon keys the panel
does not manage.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGCQcWGMg1Wn8RdCxYoioN
The panel only owned two derived fanout config keys (hls_target_sec ←
seg_time, prebuffer_max_sec derived); the daemon's other tuning knobs lived
only in its config.json and could not be set from the panel. Promote them to
real settings so admins control the whole config — the unconditional
fanout_sync (previous commit) then keeps config.json in step.
Adds 9 settings columns (fanout_hls_window, fanout_grace_sec,
fanout_write_timeout_sec, fanout_chunk_bytes, fanout_max_gop_bytes,
fanout_source_insecure, fanout_default_prebuffer_sec,
fanout_idle_buffer_grace_sec, fanout_idle_buffer_ratio) with defaults and
ranges mirroring the daemon schema (XC_VM_Fanout internal/config/config.go):
migration 014 + install SQL, inputs on the admin Settings page (numeric plus
one switchery checkbox), and the source_insecure toggle in the settings
boolean-normalization list.
FanoutConfig::desired() now writes all 11 keys, clamped to the daemon's own
ranges so a bad panel value can't push it into a pathological state;
hls_window comes from the setting (not the daemon snapshot) and feeds the
prebuffer_max_sec derivation. prebuffer_max_sec / hls_target_sec stay derived.
Adds FanoutConfigTest (mapping, clamps, derived ring, idempotency,
unknown-key preservation). Full suite green.