Claude: Phase 1 ScreenShot OK

This commit is contained in:
chk
2026-06-04 20:43:19 +02:00
parent 132e0ec597
commit c5198b70bd

View File

@@ -1,7 +1,9 @@
# AppRobotWebcam Hi-Res-Snapshot via Consumer-Umhängen # AppRobotWebcam Hi-Res-Snapshot via Consumer-Umhängen
> Status: **Phase 1 implementiert** (Code steht, Messung an der Live-Instanz steht > Status: **Phase 1 abgeschlossen** (2026-06-04):
> noch aus). Phase 2 weiterhin Konzept. > **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 > Vorgeschichte & gescheiterte Ansätze: siehe `04_Delay_roadmap.md` (Abschnitt
> „KONSOLIDIERT"). Diese Datei beschreibt den Ansatz, der die dort dokumentierten > „KONSOLIDIERT"). Diese Datei beschreibt den Ansatz, der die dort dokumentierten
> Fehler **strukturell** umgeht. > Fehler **strukturell** umgeht.
@@ -139,9 +141,12 @@ im schlimmsten Fall ist es ein Reconnect von cam0.
### Was wir daraus lernen ### Was wir daraus lernen
- `msUntilFree` → der reale Pausenwert für Schritt 3/5 (statt der geratenen 4 s). - `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 → - **Gemessen (2026-06-04):** `freed: true`, `msUntilFree: 0`, `zeroConsumerAt: 4850 ms`.
Ansatz so nicht tragfähig → prüfen, ob ein go2rtc-Setting das Verhalten ändert, go2rtc hält das Gerät **nicht** warm — es stoppt den Producer sofort wenn der letzte
sonst zurück zu Weg A (separate Kamera, siehe `04_*`). 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`) > ⚠ 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`) > 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) ## 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) ### Vorbereitung (Config, per Redeploy nicht zur Laufzeit)
`docker-compose.yaml`, go2rtc-`streams` ergänzen: `docker-compose.yaml`, go2rtc-`streams` ergänzen: