From c5198b70bda892a74e833606cc1f0e9969fcf8b9 Mon Sep 17 00:00:00 2001 From: chk <79915315+ChKendel@users.noreply.github.com> Date: Thu, 4 Jun 2026 20:43:19 +0200 Subject: [PATCH] Claude: Phase 1 ScreenShot OK --- doc/05_screenShot_roadmap.md | 90 +++++++++++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 6 deletions(-) diff --git a/doc/05_screenShot_roadmap.md b/doc/05_screenShot_roadmap.md index 25782b1..d4362f6 100644 --- a/doc/05_screenShot_roadmap.md +++ b/doc/05_screenShot_roadmap.md @@ -1,7 +1,9 @@ # AppRobotWebcam – Hi-Res-Snapshot via Consumer-Umhängen -> Status: **Phase 1 implementiert** (Code steht, Messung an der Live-Instanz steht -> noch aus). Phase 2 weiterhin Konzept. +> Status: **Phase 1 abgeschlossen** (2026-06-04): +> **Linchpin beantwortet: `freed: true`.** go2rtc gibt das Gerät frei, sobald der letzte +> Consumer weg ist. Zwei Folgeprobleme blockieren Phase 2: Stream erholt sich nicht +> (bleibt schwarz), und go2rtc läuft nach dem Test auf 106 % (erfordert Container-Recreate). > Vorgeschichte & gescheiterte Ansätze: siehe `04_Delay_roadmap.md` (Abschnitt > „KONSOLIDIERT"). Diese Datei beschreibt den Ansatz, der die dort dokumentierten > Fehler **strukturell** umgeht. @@ -139,9 +141,12 @@ im schlimmsten Fall ist es ein Reconnect von cam0. ### Was wir daraus lernen - `msUntilFree` → der reale Pausenwert für Schritt 3/5 (statt der geratenen 4 s). -- Wird der Producer **nicht** gestoppt (`freed: false`): go2rtc hält das Gerät warm → - Ansatz so nicht tragfähig → prüfen, ob ein go2rtc-Setting das Verhalten ändert, - sonst zurück zu Weg A (separate Kamera, siehe `04_*`). +- **Gemessen (2026-06-04):** `freed: true`, `msUntilFree: 0`, `zeroConsumerAt: 4850 ms`. + go2rtc hält das Gerät **nicht** warm — es stoppt den Producer sofort wenn der letzte + Consumer weg ist. Der Ansatz ist bestätigt. +- Würde der Producer nicht gestoppt (`freed: false`): go2rtc hält das Gerät warm → + Ansatz so nicht tragfähig → zurück zu Weg A (separate Kamera, siehe `04_*`). Trat + **nicht** ein. > ⚠ Die genaue JSON-Form von `/api/streams` (Felder `producers`/`consumers`/`state`) > vor dem Bauen kurz an der echten Instanz ansehen (`curl -s localhost:1984/api/streams`) @@ -149,9 +154,82 @@ im schlimmsten Fall ist es ein Reconnect von cam0. --- +## Erster Live-Test (2026-06-04) — INCONCLUSIVE (nicht widerlegt, ersetzt durch zweiten) + +Antwort: `{ freed: false, zeroConsumerAt: null, producerStoppedAt: null }`. + +**Richtig gelesen:** `zeroConsumerAt: null` = cam0 hatte während der vollen 10 s Messung +**nie 0 Consumer**. Damit wurde die eigentliche Linchpin-Frage (*stoppt der Producer bei +0 Consumern?*) **gar nicht getestet** — der Vorzustand „0 Consumer" wurde nie erreicht. +Der Test ist **ergebnislos, nicht widerlegend.** (Frühere Notiz „Producer warm gehalten → +Ansatz tot" war voreilig und ist zurückgezogen.) + +**CPU-Spike 106 % war transient.** Direkt nach Disconnect→Reconnect kurz 106 %, danach +von selbst auf **23 %** zurück (`docker stats AppRobotGo2RTC`). `curl /api/streams` direkt +danach zeigte einen **sauberen Zustand**: je **1** mjpeg-Producer (`-c:v mjpeg`, 640×480) ++ **1** Consumer pro Kamera — **kein** doppelter Producer, **kein** H.264. Der Test hat +**keinen kaputten State hinterlassen**; cam0/cam1 unangetastet. Read-only-Pfad bestätigt. + +**Warum nie 0 Consumer? — vor dem Weiterbauen klären:** +- **Verdacht A — zweiter Consumer:** weiterer Browser-Tab, oder die go2rtc-Debug-UI auf + `:1984` zeigte cam0 (= persistenter Consumer). Frühes Monitor-Log zeigte `consumers → 2`. +- **Verdacht B — Abmelde-Lag:** `el.remove()` schließt die WS (Browser-Log zeigt + `stream.onclose`), go2rtc meldet den Consumer aber nicht (rechtzeitig) als entfernt. +- **Datenquelle:** das `samples`-Array der Antwort (Consumer-Zahl alle 200 ms) zeigt es + exakt — beim nächsten Lauf auswerten (F12-Console: geloggtes `release-test JSON` aufklappen). + +**Nächster Schritt (tragfähig):** sicherstellen, dass **nur ein** Consumer total existiert +(alle anderen Tabs + go2rtc-UI schließen) und der Feed auf Klick **vollständig** beendet +wird, dann neu messen. Erst wenn die Consumer-Zahl nachweislich auf 0 fällt (`samples` +zeigt `consumers: 0`), ist die Linchpin-Frage beantwortbar. Fällt sie dann auf 0 und der +Producer stoppt → `freed: true` → Phase 2. Stoppt der Producer trotz 0 Consumern nicht → +*dann erst* ist der Ansatz widerlegt → Weg A (separate Kamera, `04_*`). + +## Zweiter Live-Test (2026-06-04) — ✅ Phase 1 abgeschlossen + +Kamera: cam1. Alle anderen Consumer geschlossen (offener Tab an unerwartetem Ort gefunden +und geschlossen). Test mit einem einzigen Browser-Tab durchgeführt. + +**Ergebnis:** +```json +{ "id": "cam1", "freed": true, "msUntilFree": 0, + "zeroConsumerAt": 4850, "producerStoppedAt": 4850 } +``` + +**Was das bedeutet:** +- **`freed: true` → Linchpin hält.** go2rtc gibt das Gerät frei, sobald der letzte + Consumer weg ist. Der Grundansatz ist tragfähig. +- **`msUntilFree: 0`** — Producer stoppt **gleichzeitig** mit dem letzten Consumer-Abgang + (kein „warm halten"). Die Pause für Phase 2 kann sehr kurz gewählt werden. +- **`zeroConsumerAt: 4850ms`** — es dauert ~5 s, bis go2rtc den WS-Consumer nach + `el.remove()` als entfernt registriert. Das ist der Wert, der in Schritt 3 als Pause + gebraucht wird: **Browser muss cam0 loslassen und dann ~5 s warten**, bevor der + Hi-Res-Grab starten kann. (Nicht 4 s wie geraten — real ~5 s, vermutlich ein go2rtc + internen Timeout.) + +**Bug 1 — Stream blieb schwarz (behoben, Ursache: zweiter Tab):** +`startStream()` im `finally` feuerte `stream.onopen`, danach sofort `ondisconnect → +Video-Fehler: MEDIA_ELEMENT_ERROR: Empty src attribute → onclose`. Ursache war ein +**zweiter offener Browser-Tab** an unerwartetem Ort. Mit genau einem Tab **kommt der +Stream korrekt in der 640er-Auflösung zurück.** Kein Code-Fix nötig. + +**Bug 2 — CPU 106 % nach Test (behoben, Ursache: zweiter Tab):** +Direkt nach dem ersten Test 106 %, erforderte Container-Recreate. Nach Schließen des +zweiten Tabs und erneutem Test: CPU geht danach auf ~40 % zurück, stabil. Ursache war +der zweite Consumer, der go2rtc in einen unklaren Zustand beim Reconnect trieb. +**Mit einem Tab: kein CPU-Problem. Kein Code-Fix nötig für Single-Operator-Betrieb.** + +> ⚠ Einschränkung bleibt: **Immer nur ein Tab/Client pro Kamera**, wenn der HD-Test +> läuft — sonst fällt der Consumer-Count nie auf 0 und der Test schlägt fehl. Für +> Single-Operator-Betrieb (Button auf Anforderung) ist das akzeptiert. + +--- + ## PHASE 2 — Den Hi-Res-Grab ergänzen (Schritt 4 + 5) -Nur starten, **wenn Phase 1 `freed: true` geliefert hat.** +Phase 1 hat `freed: true` geliefert. **Phase 2 kann gestartet werden.** +Realer Pausenwert aus der Messung: `zeroConsumerAt: 4850 ms` → Schritt 3/5 mit **5 s** +planen (statt der geratenen 4 s). ### Vorbereitung (Config, per Redeploy – nicht zur Laufzeit) `docker-compose.yaml`, go2rtc-`streams` ergänzen: