mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-10-03 20:02:29 +02:00
feat(streams): run live streams under the xc_fanout monitor, copy-only ones on its native remuxer
With fanout_supervise on, StreamProcess::startMonitor() no longer spawns a PHP watchdog. It builds the stream's commands and hands them to the daemon's supervisor (PUT /monitor/<id>), which starts, watches and restarts them: failover, priority backup, forced source, stalled output, audio loss, frame-rate drop and scheduled restart. PHP still builds every command and makes every database write. A copy-only live stream's command is the daemon's native remuxer, `xc_fanout remux`, composed by the new buildNativeLive() exactly as buildLive() composes an ffmpeg line. It reads the source natively and writes the same on-disk HLS and daemon feed as the ffmpeg -f tee output, with no ffmpeg process. fanout_source_backend decides: auto = remuxer with the ffmpeg command as fallback_cmd (taken when the remuxer exits 3, "cannot serve this source": fMP4 or encrypted HLS, rtmp, no keyframes), native = remuxer only, ffmpeg = ffmpeg only. Eligibility is explicit (isNativeEligible / isNativeSource): no transcode, custom ffmpeg, custom map, RTMP output, external push, timestamp repair, read-native or forced input codec; http(s)/udp/rtp sources only. The rest of the panel learns who owns the producer: - superviseStream() asks the daemon first and touches nothing unless it is accepting; without a restart it adopts a running encoder, so cron:streams moves PHP-monitored streams over with no blip. A producer the daemon cannot adopt (PHP LLOD, PHP loopback) is replaced, never left beside the new one. The row is marked watched before the hand-over, so the reconcile cannot release a stream mid-start. - reconcileSupervised() copies the daemon's state (status, pid, source, codecs, resolution, measured bitrate) into streams_servers: every cron:streams pass and every 5 s from the signals daemon. Supervised streams whose row is gone or stopped are released. - stopStream() and the on-demand reaper release before killing anything; killing the producer first is what the supervisor restarts. - isWatched() replaces bare isMonitorAlive() checks in live.php, admin/live.php, rtmp.php and cron:streams: a supervised stream's monitor_pid is the daemon's. MonitorCommand stands down for supervised streams; startMonitor() releases one before falling back to PHP, so turning supervision off does bring streams back on their next restart. - force_stream goes through the daemon for a supervised stream (the .force file is only read by the PHP monitor). - ProcessManager::isStreamRunning() recognises the remuxer, so the archive, thumbnail and delay workers follow it like ffmpeg. - A supervised loopback child tees into the daemon (the supervisor judges a stream by the bytes it receives); legacy loopback is unchanged. Delay streams, created channels and yt-dlp platform sources stay on the PHP monitor. A daemon without /monitors/state (older than this) is never handed a stream, so the panel is safe against an un-upgraded node. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012QL93N6dzGkmKoQgA4oh16
This commit is contained in:
@@ -120,6 +120,30 @@ final class StreamProcessBuildLiveTest extends TestCase {
|
||||
$this->assertStringContainsString('echo $! > ' . STREAMS_PATH . '42_.pid', $out);
|
||||
}
|
||||
|
||||
// ── supervised (launched by the fanout daemon) ─────────────
|
||||
|
||||
/** The daemon is the parent: the shell's redirect/background/pid tail must go. */
|
||||
public function testSupervisedOmitsTheLaunchTail(): void {
|
||||
$out = $this->build(['supervised' => true]);
|
||||
$this->assertStringNotContainsString('>/dev/null', $out);
|
||||
$this->assertStringNotContainsString('echo $!', $out);
|
||||
$this->assertStringNotContainsString(' & ', $out);
|
||||
$this->assertStringContainsString(STREAMS_PATH . '42_.m3u8', $out, 'still names its playlist (adopt_match)');
|
||||
}
|
||||
|
||||
/** A supervised loopback feeds the daemon, which confirms and judges it by those bytes. */
|
||||
public function testSupervisedLoopbackTeesIntoTheDaemon(): void {
|
||||
$out = $this->build(['loopback' => true, 'ingestSock' => '/run/ingest/42.sock', 'supervised' => true]);
|
||||
$this->assertStringContainsString('-f tee', $out);
|
||||
$this->assertStringContainsString('unix:/run/ingest/42.sock', $out);
|
||||
$this->assertStringContainsString('-map 0 -copy_unknown', $out);
|
||||
}
|
||||
|
||||
/** The legacy path registers no ingest for loopback and must stay on-disk only. */
|
||||
public function testLegacyLoopbackStaysOnDiskOnly(): void {
|
||||
$this->assertStringNotContainsString('-f tee', $this->build(['loopback' => true]));
|
||||
}
|
||||
|
||||
// ── custom_ffmpeg branch ───────────────────────────────────
|
||||
|
||||
public function testCustomFfmpegBypassesTemplate(): void {
|
||||
|
||||
Reference in New Issue
Block a user