Standbild
This commit is contained in:
@@ -250,7 +250,7 @@ class CameraSwitch extends EventEmitter {
|
||||
async grabHires(opts = {}) {
|
||||
// Shortcut: keine Format-Umschaltung wenn Live- und Hires-Auflösung identisch
|
||||
if (this.liveSize === this.hiresSize) {
|
||||
return this.getFrame();
|
||||
return this.grabSnapshot();
|
||||
}
|
||||
|
||||
const hiresW = parseInt(this.hiresSize.split('x')[0], 10);
|
||||
@@ -278,7 +278,7 @@ class CameraSwitch extends EventEmitter {
|
||||
|
||||
// 2. hires-FFmpeg starten, warmlaufen lassen (settleFrames), besten Frame greifen.
|
||||
// minWidth lehnt etwaige Übergangs-Frames in falscher Auflösung ab.
|
||||
const jpeg = await this._captureHires({ minSize, minWidth, settleFrames, maxWaitMs });
|
||||
const jpeg = await this._captureAt(this.hiresSize, this.hiresFps, this.hiresEncode, { minSize, minWidth, settleFrames, maxWaitMs });
|
||||
const gotW = readJpegWidth(jpeg) ?? '?';
|
||||
console.log(`[cam ${this.id}] HD OK – ${jpeg.length} bytes, Breite=${gotW}px (Soll: ${hiresW}px, ${Date.now() - t0}ms)`);
|
||||
return jpeg;
|
||||
@@ -291,6 +291,36 @@ class CameraSwitch extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Einzelbild-Snapshot (für /api/snapshot, inkl. Snapshot-Modus) ──────────
|
||||
// Läuft Live → aktuelles Frame (schnell, kein Geräte-Neustart). Sonst:
|
||||
// • streamEnabled (Live an, aber Frame noch nicht da) → getFrame() (on-demand).
|
||||
// • stream:false (Snapshot-Modus) → one-shot open/grab/close an liveSize. Das
|
||||
// Gerät bleibt dazwischen geschlossen → spart CPU/USB; der Viewer pollt das
|
||||
// alle paar Sekunden. streamEnabled bleibt aus (kein Dauer-Stream).
|
||||
async grabSnapshot() {
|
||||
if (this.latest) return this.latest; // Live läuft → sofort
|
||||
if (this.streamEnabled) return this.getFrame(); // Live an, Frame kommt gleich
|
||||
|
||||
if (this.lock) throw new Error('Gerät belegt (Grab läuft)');
|
||||
this.lock = true;
|
||||
if (this.restartTimer) { clearTimeout(this.restartTimer); this.restartTimer = null; }
|
||||
this.state = 'grabbing';
|
||||
try {
|
||||
const w = parseInt(this.liveSize.split('x')[0], 10) || 0;
|
||||
return await this._captureAt(this.liveSize, this.liveFps, this.encode, {
|
||||
minSize: 1000,
|
||||
minWidth: w ? Math.floor(w * 0.8) : 0,
|
||||
settleFrames: 3,
|
||||
maxWaitMs: 6000,
|
||||
});
|
||||
} finally {
|
||||
this.state = 'stopped';
|
||||
this.lock = false;
|
||||
// Falls inzwischen wieder Live gewünscht ist (stream:true + Verbraucher):
|
||||
if (this.streamEnabled && (!this.onDemand || this.subscribers > 0)) this._spawnLive();
|
||||
}
|
||||
}
|
||||
|
||||
// Beendet den aktuellen Prozess und resolved erst nach dessen 'close' (FD frei).
|
||||
_killCurrentAndWait(timeoutMs = 4000) {
|
||||
return new Promise((resolve) => {
|
||||
@@ -306,7 +336,9 @@ class CameraSwitch extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
_captureHires({ minSize, minWidth, settleFrames, maxWaitMs }) {
|
||||
// Generischer one-shot Capture an beliebiger Auflösung (open → settle → grab → close).
|
||||
// Genutzt von grabHires (hiresSize) und grabSnapshot (liveSize, Snapshot-Modus).
|
||||
_captureAt(size, fps, encode, { minSize, minWidth, settleFrames, maxWaitMs }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const args = [
|
||||
'-hide_banner', '-loglevel', 'warning',
|
||||
@@ -314,9 +346,9 @@ class CameraSwitch extends EventEmitter {
|
||||
'-probesize', '5000000',
|
||||
'-analyzeduration', '1000000',
|
||||
'-f', 'v4l2', '-input_format', 'mjpeg',
|
||||
'-video_size', this.hiresSize, '-framerate', String(this.hiresFps),
|
||||
'-video_size', size, '-framerate', String(fps),
|
||||
'-i', this.device,
|
||||
...videoOutArgs(this.hiresEncode), '-f', 'mpjpeg', 'pipe:1',
|
||||
...videoOutArgs(encode), '-f', 'mpjpeg', 'pipe:1',
|
||||
];
|
||||
let p;
|
||||
try {
|
||||
|
||||
@@ -54,8 +54,9 @@ function createSnapshotRouter(switches, cameras) {
|
||||
const sw = switches[req.params.id];
|
||||
if (!sw) return res.status(404).json({ error: `Unbekannte Kamera: ${req.params.id}` });
|
||||
try {
|
||||
// getFrame() startet die Kamera bei Bedarf on-demand und wartet auf ein frisches Bild
|
||||
const frame = await sw.getFrame();
|
||||
// grabSnapshot(): liefert das Live-Frame falls vorhanden, sonst (Snapshot-Modus,
|
||||
// stream:false) ein one-shot Bild – öffnet das Gerät kurz und schliesst es wieder.
|
||||
const frame = await sw.grabSnapshot();
|
||||
res.set({
|
||||
'Content-Type': 'image/jpeg',
|
||||
'Content-Length': frame.length,
|
||||
|
||||
Reference in New Issue
Block a user