Claude: Korrekturen nach WebRecherche
This commit is contained in:
@@ -373,3 +373,38 @@ GET /api/snapshot/cam0/hires
|
|||||||
```
|
```
|
||||||
|
|
||||||
Umgesetzt in `src/snapshotService.js` und `docker-compose.yaml`.
|
Umgesetzt in `src/snapshotService.js` und `docker-compose.yaml`.
|
||||||
|
|
||||||
|
### Erster Live-Test (2026-06-04): erfolgreich + 2 Bugs behoben
|
||||||
|
|
||||||
|
Live-Stream nahezu Echtzeit, stabil. Hi-Res-Bild 1280×960 über `/hires` da.
|
||||||
|
Zwei Bugs gefunden und sofort behoben:
|
||||||
|
|
||||||
|
1. **Schwarzer Player nach Reload** ✓ behoben
|
||||||
|
Ursache: Stream-Restore rief die go2rtc-API falsch auf. Verifiziert gegen die
|
||||||
|
go2rtc-OpenAPI-Spec: `PUT /api/streams` erwartet `src` = **Quelle (URI)** und
|
||||||
|
`name` = Stream-Name, beide als Query-Param. Der Code schickte aber `src=cam0`
|
||||||
|
(den Namen) und die Quelle im **Body** (den go2rtc ignoriert). Folge: `cam0` wurde
|
||||||
|
mit Quelle „cam0" = Selbstreferenz neu angelegt → kaputt → beim nächsten
|
||||||
|
Verbindungsaufbau (Reload) schwarz. Fix: `buildPutUrl()` →
|
||||||
|
`PUT /api/streams?name=cam0&src=<url-encoded-quelle>`, kein Body.
|
||||||
|
(DELETE `?src=cam0` war korrekt — DELETE nutzt `src` als Namen, API-Asymmetrie.)
|
||||||
|
|
||||||
|
2. **Hi-Res-Bild manchmal leer (~1KB schwarz)** ✓ behoben
|
||||||
|
Ursache: USB-Kamera liefert direkt nach Geräte-Öffnen unbelichtete Frames
|
||||||
|
(Auto-Belichtung/Weissabgleich brauchen einen Moment). `-frames:v 1` griff den
|
||||||
|
ersten, schwarzen Frame. Fix: erste 15 Frames verwerfen
|
||||||
|
(`-vf select=gte(n,15)`), dann einen greifen. Kostet ~1 s mehr Blackout.
|
||||||
|
|
||||||
|
### Offene Punkte (ToDo)
|
||||||
|
|
||||||
|
- **go2rtc-CPU ~53% bei 2 aktiven Live-Streams.** Besser als H.264-Transcode (~127%),
|
||||||
|
aber kein echtes Null. go2rtc re-encodiert MJPEG→MJPEG (kein `-c:v copy`) statt
|
||||||
|
reinem Durchreichen. Das sind ~0,5 CPU-Kerne für 2 Kameras → stabil und unkritisch
|
||||||
|
auf dieser Maschine. **Optionaler Hebel falls je nötig:** prüfen ob go2rtc-Quelle
|
||||||
|
auf echtes Copy/Passthrough umstellbar ist. Risiko: Stabilität des laufenden,
|
||||||
|
funktionierenden Streams — daher nur anfassen wenn CPU real zum Problem wird.
|
||||||
|
- **Geräte-Race bei Hi-Res mit gleichzeitig offenem Live-Tab.** Ist ein Live-Consumer
|
||||||
|
aktiv, kann go2rtc das Gerät nach dem DELETE per on-demand-Reconnect sofort wieder
|
||||||
|
greifen und mit dem Hi-Res-Grab kollidieren. Warmup + Frame-Verwerfen fängt das
|
||||||
|
meist ab. Falls doch leere Bilder auftreten: kurzer Retry im Grab, oder Live-Tab
|
||||||
|
vor dem Hi-Res-Klick kurz pausieren.
|
||||||
|
|||||||
@@ -105,15 +105,10 @@ function createSnapshotRouter(go2rtcUrl) {
|
|||||||
const jpeg = await captureOneFrame(cfg.device, cfg.hiresSize);
|
const jpeg = await captureOneFrame(cfg.device, cfg.hiresSize);
|
||||||
console.log(`[snapshot][${id}] Frame captured (${jpeg.length} bytes)`);
|
console.log(`[snapshot][${id}] Frame captured (${jpeg.length} bytes)`);
|
||||||
|
|
||||||
// 3. go2rtc-Stream wiederherstellen
|
// 3. go2rtc-Stream wiederherstellen.
|
||||||
const putRes = await fetch(
|
// go2rtc-API: PUT /api/streams?name=<stream>&src=<quelle-url-encoded>
|
||||||
`${go2rtcUrl}/api/streams?src=${encodeURIComponent(id)}`,
|
// Quelle steht im `src`-Query-Param (URL-encoded), NICHT im Body.
|
||||||
{
|
const putRes = await fetch(buildPutUrl(go2rtcUrl, id, cfg.streamUrl), { method: 'PUT' });
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
|
||||||
body: cfg.streamUrl,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
console.log(`[snapshot][${id}] go2rtc PUT stream → HTTP ${putRes.status}`);
|
console.log(`[snapshot][${id}] go2rtc PUT stream → HTTP ${putRes.status}`);
|
||||||
|
|
||||||
res.set({
|
res.set({
|
||||||
@@ -131,11 +126,7 @@ function createSnapshotRouter(go2rtcUrl) {
|
|||||||
|
|
||||||
// Stream auf jeden Fall wiederherstellen, auch im Fehlerfall
|
// Stream auf jeden Fall wiederherstellen, auch im Fehlerfall
|
||||||
try {
|
try {
|
||||||
await fetch(`${go2rtcUrl}/api/streams?src=${encodeURIComponent(id)}`, {
|
await fetch(buildPutUrl(go2rtcUrl, id, cfg.streamUrl), { method: 'PUT' });
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
|
||||||
body: cfg.streamUrl,
|
|
||||||
});
|
|
||||||
console.log(`[snapshot][${id}] Stream nach Fehler wiederhergestellt`);
|
console.log(`[snapshot][${id}] Stream nach Fehler wiederhergestellt`);
|
||||||
} catch (restoreErr) {
|
} catch (restoreErr) {
|
||||||
console.error(`[snapshot][${id}] Stream-Wiederherstellung fehlgeschlagen:`, restoreErr.message);
|
console.error(`[snapshot][${id}] Stream-Wiederherstellung fehlgeschlagen:`, restoreErr.message);
|
||||||
@@ -158,6 +149,16 @@ function sleep(ms) {
|
|||||||
return new Promise(r => setTimeout(r, ms));
|
return new Promise(r => setTimeout(r, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// go2rtc-API zum (Wieder-)Anlegen eines Streams:
|
||||||
|
// PUT /api/streams?name=<stream-name>&src=<quelle-uri-url-encoded>
|
||||||
|
// Beide Werte als Query-Param. `src` ist die QUELLE (nicht der Name) — go2rtc
|
||||||
|
// liest sie NICHT aus dem Body.
|
||||||
|
function buildPutUrl(go2rtcUrl, name, streamUrl) {
|
||||||
|
return `${go2rtcUrl}/api/streams`
|
||||||
|
+ `?name=${encodeURIComponent(name)}`
|
||||||
|
+ `&src=${encodeURIComponent(streamUrl)}`;
|
||||||
|
}
|
||||||
|
|
||||||
// Startet FFmpeg, liest genau einen MJPEG-Frame aus stdout, gibt ihn als Buffer zurück.
|
// Startet FFmpeg, liest genau einen MJPEG-Frame aus stdout, gibt ihn als Buffer zurück.
|
||||||
function captureOneFrame(device, size, timeoutMs = 8000) {
|
function captureOneFrame(device, size, timeoutMs = 8000) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -166,8 +167,12 @@ function captureOneFrame(device, size, timeoutMs = 8000) {
|
|||||||
'-f', 'v4l2',
|
'-f', 'v4l2',
|
||||||
'-input_format', 'mjpeg',
|
'-input_format', 'mjpeg',
|
||||||
'-video_size', size,
|
'-video_size', size,
|
||||||
'-framerate', '10', // niedrige FPS → schnellerer erster Frame
|
'-framerate', '15',
|
||||||
'-i', device,
|
'-i', device,
|
||||||
|
// Erste ~15 Frames verwerfen: die USB-Kamera liefert direkt nach dem Öffnen
|
||||||
|
// noch unbelichtete (schwarze) Frames – Auto-Belichtung/Weissabgleich brauchen
|
||||||
|
// einen Moment. Ohne das kommt das "1KB leer/schwarz"-Bild.
|
||||||
|
'-vf', 'select=gte(n\\,15)',
|
||||||
'-frames:v', '1',
|
'-frames:v', '1',
|
||||||
'-q:v', '1', // beste JPEG-Qualität
|
'-q:v', '1', // beste JPEG-Qualität
|
||||||
'-f', 'mjpeg',
|
'-f', 'mjpeg',
|
||||||
|
|||||||
Reference in New Issue
Block a user