From 0fab0ab5236c6da8cad112a2bc5aabf9fd95d80d Mon Sep 17 00:00:00 2001
From: chk <79915315+ChKendel@users.noreply.github.com>
Date: Wed, 3 Jun 2026 21:26:44 +0200
Subject: [PATCH] Claude: Button
---
doc/01_WebcamRoadmap.md | 10 +++----
doc/02_Measurements.md | 0
doc/03_Protocoll_roadmap.md | 13 +++++----
public/index.html | 22 +++++++-------
public/viewer.js | 58 ++++++++++++++++++++-----------------
5 files changed, 56 insertions(+), 47 deletions(-)
delete mode 100644 doc/02_Measurements.md
diff --git a/doc/01_WebcamRoadmap.md b/doc/01_WebcamRoadmap.md
index a61d288..0da420b 100644
--- a/doc/01_WebcamRoadmap.md
+++ b/doc/01_WebcamRoadmap.md
@@ -65,11 +65,11 @@ Encoding, ICE-Negotiation, robuster Client mit Auto-Fallback (WebRTC→MSE→MJP
- [x] Stabile Snapshot-API `/api/snapshot/cam{n}`
- [x] Auflösung fest 640×480 → Latenz „akzeptabel" (war vorher das Hauptproblem)
-### Phase 3 – Latenz final tunen (offen)
-- [ ] Messvergleich WebRTC ⟷ MJPEG durchführen → siehe `03_Protocoll_roadmap.md`
-- [ ] Falls nötig: Auflösung 320×240 testen (kleiner = weniger Browser-Last)
-- [ ] Falls nötig: Keyframe-Intervall senken (`-g 15`), zerolatency-Tuning
-- [ ] Prüfen ob Kamera natives H.264 liefert (`v4l2-ctl --list-formats`) → kein Re-Encode
+### Phase 3 – Latenz final tunen ✅
+- [x] Messvergleich WebRTC ⟷ MJPEG: **WebRTC ~130 ms, MJPEG ~200 ms** → WebRTC gewinnt
+- [x] Entscheid: bei WebRTC bleiben (niedrigere Latenz + besser für Internet)
+- [ ] Optional: Prüfen ob Kamera natives H.264 liefert (`v4l2-ctl --list-formats`) → kein Re-Encode
+- [ ] Optional: Keyframe-Intervall / Encoder-Preset tunen wenn <100 ms gefordert
### Phase 4 – Internet-Härtung (offen, vor Produktiv-Schaltung)
- [ ] **TLS via Caddy** – empfohlen, weil Caddy WebSocket-Proxy nativ und zuverlässig kann.
diff --git a/doc/02_Measurements.md b/doc/02_Measurements.md
deleted file mode 100644
index e69de29..0000000
diff --git a/doc/03_Protocoll_roadmap.md b/doc/03_Protocoll_roadmap.md
index dac9044..5a5c14b 100644
--- a/doc/03_Protocoll_roadmap.md
+++ b/doc/03_Protocoll_roadmap.md
@@ -56,12 +56,15 @@ direkt im Browser öffnen (für cam1: `cam0` → `cam1` ersetzen):
gleichzeitig zeigt (Handy + Monitor zusammen abfotografieren ist am einfachsten).
4. Differenz „echte Zeit ↔ Bild im Stream" ablesen = Gesamt-Latenz pro Protokoll.
-### Ergebnis-Tabelle (später ausfüllen)
+### Ergebnis-Tabelle ✅ gemessen 2026-06-03
-| Kamera | MJPEG roh | WebRTC | MSE | Sieger |
-|--------|-----------|--------|-----|--------|
-| cam0 | ? ms | ? ms | ? ms | ? |
-| cam1 | ? ms | ? ms | ? ms | ? |
+Methode: Handy-Stoppuhr (ms) vor Kamera, Foto von Monitor + Stoppuhr.
+
+| Kamera | MJPEG | WebRTC | MSE | Sieger |
+|--------|-------|--------|-----|--------|
+| cam0+1 | ~200 ms | ~130 ms | — | **WebRTC** |
+
+MSE nicht gemessen (erwartet schlechter als beide, für Live nicht relevant).
---
diff --git a/public/index.html b/public/index.html
index 6c674b0..5afeb5d 100644
--- a/public/index.html
+++ b/public/index.html
@@ -13,13 +13,21 @@
padding: 10px 16px; background: #1a1a1a; border-bottom: 1px solid #333;
}
h1 { font-size: 1rem; font-weight: normal; letter-spacing: 0.05em; }
- #statusText { font-size: 0.8rem; color: #888; margin-left: auto; }
+ #statusText { font-size: 0.8rem; color: #888; }
+
+ #snapAllBtn {
+ margin-left: auto;
+ background: #2a4a2a; color: #8f8; border: 1px solid #4a8;
+ padding: 5px 14px; font-family: monospace; font-size: 0.82rem;
+ cursor: pointer; border-radius: 3px; letter-spacing: 0.03em;
+ }
+ #snapAllBtn:hover:not(:disabled) { background: #3a6a3a; }
+ #snapAllBtn:disabled { opacity: 0.4; cursor: default; }
#cameras { display: flex; flex-wrap: wrap; gap: 12px; padding: 12px; }
.cam-box { position: relative; background: #000; border: 1px solid #2a2a2a; }
- /* go2rtc Web-Component */
video-stream { display: block; width: 640px; height: 480px; background: #111; }
video-stream video { width: 100%; height: 100%; object-fit: contain; }
@@ -28,23 +36,17 @@
background: rgba(0,0,0,.65); padding: 2px 7px; border-radius: 3px;
font-size: 0.72rem; color: #ccc;
}
- .cam-actions { position: absolute; top: 5px; right: 8px; display: flex; gap: 4px; }
- .cam-actions button {
- background: rgba(0,0,0,.65); color: #ccc; border: 1px solid #444;
- padding: 2px 8px; font-family: monospace; font-size: 0.7rem;
- cursor: pointer; border-radius: 3px;
- }
- .cam-actions button:hover { background: rgba(60,60,60,.8); }
AppRobotWebcam
Verbinde...
+
+
-
diff --git a/public/viewer.js b/public/viewer.js
index ddc3e73..3dd8541 100644
--- a/public/viewer.js
+++ b/public/viewer.js
@@ -3,16 +3,31 @@
// go2rtc Player-Modi – Fallback-Reihenfolge: WebRTC → MSE → MJPEG
const MODE = 'webrtc,mse,mjpeg';
-// ── Logging (sichtbar in Browser DevTools → Console → F12) ──────────────────
+// ── Logging (Browser DevTools → Console → F12) ───────────────────────────────
const P = '[WebcamViewer]';
const log = (c, m) => console.log(`${P}[${c}] ${m}`);
const warn = (c, m) => console.warn(`${P}[${c}] ⚠ ${m}`);
-const err = (c, m, e) => console.error(`${P}[${c}] ✗ ${m}`, e ?? '');
+const logErr = (c, m, e) => console.error(`${P}[${c}] ✗ ${m}`, e ?? '');
+
+// ── Snapshot aller Kameras gleichzeitig ──────────────────────────────────────
+// Auflösung = was go2rtc im MJPEG-Stream hält (aktuell 640×480 gemäss Config).
+// Für höhere Auflösung: in go2rtc.yaml einen separaten Hi-Res-Stream definieren
+// und hier auf dessen /api/frame.jpeg?src=cam0_hires zeigen.
+function snapshotAll(camIds) {
+ const ts = Date.now();
+ log('snap', `Snapshot alle Kameras: ${camIds.join(', ')}`);
+ camIds.forEach(id => {
+ const a = document.createElement('a');
+ a.href = `/api/snapshot/${id}`;
+ a.download = `${id}_${ts}.jpg`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ });
+}
// ── Kamera-View aufbauen ─────────────────────────────────────────────────────
function buildCamera(camId, go2rtcPort, container) {
- // WebSocket direkt zu go2rtc – kein Proxy-Zwischenschritt, garantiert stabil.
- // Protokoll: ws:// auf LAN (http). Für Internet mit TLS wird aus ws: wss: (Caddy).
const wsUrl = `ws://${location.hostname}:${go2rtcPort}/api/ws?src=${encodeURIComponent(camId)}`;
log(camId, `View erstellt mode="${MODE}" ws=${wsUrl}`);
@@ -22,16 +37,15 @@ function buildCamera(camId, go2rtcPort, container) {
const stream = document.createElement('video-stream');
stream.mode = MODE;
- // Events vom inneren