Config Page

This commit is contained in:
chk
2026-06-07 10:03:34 +02:00
parent f205418640
commit faccbf55ce
16 changed files with 5375 additions and 69 deletions

View File

@@ -77,13 +77,14 @@ class MpjpegParser {
//
// Events: 'frame' (Buffer) je ein Live-JPEG
class CameraSwitch extends EventEmitter {
constructor({ id, device, liveSize = '640x480', liveFps = 30, hiresSize = '1280x960', hiresFps = 15, encode = 'copybsf', hiresEncode, onDemand = true, idleGraceMs = 15000 }) {
constructor({ id, device, liveSize = '640x480', liveFps = 30, hiresSize = '1280x960', hiresFps = 15, encode = 'copybsf', hiresEncode, onDemand = true, idleGraceMs = 15000, stream = true }) {
super();
this.setMaxListeners(0); // beliebig viele Stream-Clients
this.id = id;
this.device = device;
this.liveSize = liveSize;
this.liveFps = liveFps;
this.streamEnabled = stream; // UI "Aus": Kamera darf NICHT live gehen (gated _spawnLive)
this.hiresSize = hiresSize;
this.hiresFps = hiresFps;
this.encode = encode; // für Live: 'copybsf' (Default) | 'mjpeg' (Re-Encode)
@@ -104,14 +105,14 @@ class CameraSwitch extends EventEmitter {
start() {
// On-Demand: lazy Live startet erst beim ersten Verbraucher (acquire()).
if (this.onDemand) return;
if (this.state === 'stopped' && !this.proc) this._spawnLive();
if (this.streamEnabled && this.state === 'stopped' && !this.proc) this._spawnLive();
}
// ── Verbraucher-Zählung / On-Demand ────────────────────────────────────────
acquire() {
this.subscribers++;
if (this.idleTimer) { clearTimeout(this.idleTimer); this.idleTimer = null; }
if (this.onDemand && this.state === 'stopped' && !this.lock && !this.proc) this._spawnLive();
if (this.onDemand && this.streamEnabled && this.state === 'stopped' && !this.lock && !this.proc) this._spawnLive();
}
release() {
@@ -149,6 +150,7 @@ class CameraSwitch extends EventEmitter {
// ── Live-Producer (Dauerbetrieb, Auto-Restart bei Crash) ───────────────────
_spawnLive() {
if (!this.streamEnabled) return; // UI "Aus" → Kamera bleibt dunkel
this.stopping = false;
const args = [
'-hide_banner', '-loglevel', 'warning',
@@ -193,14 +195,49 @@ class CameraSwitch extends EventEmitter {
}
_scheduleRestart() {
if (!this.streamEnabled) return; // UI "Aus" → kein Auto-Restart
if (this.onDemand && this.subscribers === 0) return; // niemand schaut → nicht neu starten
if (this.restartTimer) return;
this.restartTimer = setTimeout(() => {
this.restartTimer = null;
if (this.state === 'stopped' && !this.lock && (!this.onDemand || this.subscribers > 0)) this._spawnLive();
if (this.streamEnabled && this.state === 'stopped' && !this.lock && (!this.onDemand || this.subscribers > 0)) this._spawnLive();
}, 1500);
}
// ── Hot-Reload (config.html / POST /api/config) ────────────────────────────
// Wendet eine neue Live-Auflösung bzw. Stream-An/Aus zur Laufzeit an, OHNE
// Container-Restart. Nutzt die vorhandenen Bausteine (_killCurrentAndWait /
// _spawnLive) und respektiert Lock (HD-Grab) sowie On-Demand.
async reconfigure({ liveSize, stream } = {}) {
if (typeof stream === 'boolean') this.streamEnabled = stream;
// Während eines HD-Grabs nicht eingreifen die neue liveSize gilt nach dem
// Grab (grabHires startet Live über _spawnLive neu, das this.liveSize liest).
if (this.lock) { if (liveSize) this.liveSize = liveSize; return; }
const sizeChanged = !!liveSize && liveSize !== this.liveSize;
if (liveSize) this.liveSize = liveSize;
// Stream deaktiviert → laufenden Live-Prozess stoppen, NICHT neu starten.
if (!this.streamEnabled) {
if (this.proc && this.state === 'live') await this._killCurrentAndWait();
this.state = 'stopped';
return;
}
// Auflösung geändert → laufenden Prozess beenden (FD frei via close-Event).
if (sizeChanged && this.proc && this.state === 'live') {
await this._killCurrentAndWait();
this.state = 'stopped';
}
// (Neu) starten, wenn nichts läuft und Verbraucher da sind (On-Demand) bzw.
// Dauerbetrieb. _spawnLive ist zusätzlich durch streamEnabled gegated.
if (this.state === 'stopped' && !this.proc && (!this.onDemand || this.subscribers > 0)) {
this._spawnLive();
}
}
// ── HD-Grab ────────────────────────────────────────────────────────────────
// Wenn liveSize == hiresSize: kein Format-Wechsel nötig. Live-Frame direkt
// zurückgeben (on-demand startet den Stream bei Bedarf). Schnell, kein Gerät-