Claude: Fix
This commit is contained in:
0
doc/02_Measurements.md
Normal file
0
doc/02_Measurements.md
Normal file
@@ -25,8 +25,12 @@ configs:
|
|||||||
# Komplette go2rtc-Config eingebettet – keine separate Datei nötig.
|
# Komplette go2rtc-Config eingebettet – keine separate Datei nötig.
|
||||||
content: |
|
content: |
|
||||||
streams:
|
streams:
|
||||||
cam0: "ffmpeg:device?video=/dev/video0&video_size=640x480#video=h264#video=mjpeg"
|
# Einfache Form: von go2rtc bestätigt funktionsfähig für beide Kameras.
|
||||||
cam1: "ffmpeg:device?video=/dev/video2&video_size=640x480#video=h264#video=mjpeg"
|
# device? wurde verworfen (generiert FFmpeg ohne -input_format mjpeg → Timeout).
|
||||||
|
# #video=h264 → für WebRTC (transcodiert)
|
||||||
|
# #video=mjpeg → für MJPEG-Fallback + /api/frame.jpeg (Snapshot)
|
||||||
|
cam0: "ffmpeg:/dev/video0#video=h264#video=mjpeg"
|
||||||
|
cam1: "ffmpeg:/dev/video2#video=h264#video=mjpeg"
|
||||||
webrtc:
|
webrtc:
|
||||||
listen: ":8555"
|
listen: ":8555"
|
||||||
candidates:
|
candidates:
|
||||||
|
|||||||
@@ -1,18 +1,39 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// go2rtc-Player-Modi in Fallback-Reihenfolge.
|
// go2rtc Player-Modi – Fallback-Reihenfolge: WebRTC → MSE → MJPEG
|
||||||
// webrtc zuerst (niedrigste Latenz), dann MSE, dann MJPEG – das verhindert
|
// Schlägt WebRTC fehl, springt der go2rtc-Player automatisch weiter → kein schwarzes Bild.
|
||||||
// schwarze Seiten: schlägt WebRTC fehl, springt der Player automatisch weiter.
|
|
||||||
const MODE = 'webrtc,mse,mjpeg';
|
const MODE = 'webrtc,mse,mjpeg';
|
||||||
|
|
||||||
|
// ── Logging-Hilfe (sichtbar in Browser DevTools → Console) ──────────────────
|
||||||
|
const LOG_PREFIX = '[AppRobotWebcam]';
|
||||||
|
function log(cam, msg) { console.log(`${LOG_PREFIX}[${cam}] ${msg}`); }
|
||||||
|
function warn(cam, msg) { console.warn(`${LOG_PREFIX}[${cam}] ⚠ ${msg}`); }
|
||||||
|
function logErr(cam, msg, e) { console.error(`${LOG_PREFIX}[${cam}] ✗ ${msg}`, e ?? ''); }
|
||||||
|
|
||||||
|
// ── Kamera-View aufbauen ─────────────────────────────────────────────────────
|
||||||
function buildCamera(camId, container) {
|
function buildCamera(camId, container) {
|
||||||
|
const wsUrl = `/api/ws?src=${encodeURIComponent(camId)}`;
|
||||||
|
log(camId, `View erstellt mode=${MODE} ws=${wsUrl}`);
|
||||||
|
|
||||||
const box = document.createElement('div');
|
const box = document.createElement('div');
|
||||||
box.className = 'cam-box';
|
box.className = 'cam-box';
|
||||||
|
|
||||||
// go2rtc Web-Component – verbindet sich relativ auf /api/ws (→ Node-Proxy)
|
// go2rtc Web-Component: verbindet sich via WebSocket zu /api/ws (→ Node-Proxy → go2rtc)
|
||||||
const stream = document.createElement('video-stream');
|
const stream = document.createElement('video-stream');
|
||||||
stream.mode = MODE;
|
stream.mode = MODE;
|
||||||
stream.src = `/api/ws?src=${encodeURIComponent(camId)}`;
|
|
||||||
|
// Events vom inneren <video>-Element abfangen (capture-Phase = vor dem Element selbst)
|
||||||
|
stream.addEventListener('play', () => log(camId, '▶ spielt'), true);
|
||||||
|
stream.addEventListener('playing', () => log(camId, '▶ Bild läuft'), true);
|
||||||
|
stream.addEventListener('pause', () => warn(camId, 'pausiert'), true);
|
||||||
|
stream.addEventListener('stalled', () => warn(camId, 'stalled (kein Daten)'), true);
|
||||||
|
stream.addEventListener('waiting', () => warn(camId, 'waiting (Buffer leer)'), true);
|
||||||
|
stream.addEventListener('error', (e) => logErr(camId, 'Video-Fehler', e), true);
|
||||||
|
|
||||||
|
// src setzen startet die Verbindung
|
||||||
|
log(camId, `Verbinde → ${wsUrl}`);
|
||||||
|
stream.src = wsUrl;
|
||||||
|
|
||||||
box.appendChild(stream);
|
box.appendChild(stream);
|
||||||
|
|
||||||
const label = document.createElement('div');
|
const label = document.createElement('div');
|
||||||
@@ -25,6 +46,7 @@ function buildCamera(camId, container) {
|
|||||||
const snapBtn = document.createElement('button');
|
const snapBtn = document.createElement('button');
|
||||||
snapBtn.textContent = 'Snapshot';
|
snapBtn.textContent = 'Snapshot';
|
||||||
snapBtn.onclick = () => {
|
snapBtn.onclick = () => {
|
||||||
|
log(camId, 'Snapshot download → /api/snapshot/' + camId);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = `/api/snapshot/${camId}`;
|
a.href = `/api/snapshot/${camId}`;
|
||||||
a.download = `${camId}_${Date.now()}.jpg`;
|
a.download = `${camId}_${Date.now()}.jpg`;
|
||||||
@@ -34,28 +56,63 @@ function buildCamera(camId, container) {
|
|||||||
box.appendChild(actions);
|
box.appendChild(actions);
|
||||||
|
|
||||||
container.appendChild(box);
|
container.appendChild(box);
|
||||||
|
|
||||||
|
// Connectivity-Check nach 5 s: ist die WebSocket-Verbindung zu /api/ws nutzbar?
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
const r = await fetch('/api/streams');
|
||||||
|
if (r.ok) {
|
||||||
|
const d = await r.json();
|
||||||
|
log(camId, `go2rtc streams: ${Object.keys(d).join(', ')}`);
|
||||||
|
} else {
|
||||||
|
warn(camId, `/api/streams → HTTP ${r.status}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logErr(camId, '/api/streams nicht erreichbar (Proxy defekt?)', err);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Init ─────────────────────────────────────────────────────────────────────
|
||||||
async function init() {
|
async function init() {
|
||||||
// Warten bis die go2rtc-Web-Component definiert ist (sonst greift der .src-Setter nicht)
|
log('init', 'Starte...');
|
||||||
await customElements.whenDefined('video-stream');
|
|
||||||
|
// go2rtc Web-Component muss geladen sein bevor .src gesetzt wird
|
||||||
|
try {
|
||||||
|
await customElements.whenDefined('video-stream');
|
||||||
|
log('init', '<video-stream> definiert');
|
||||||
|
} catch (err) {
|
||||||
|
logErr('init', '<video-stream> nicht geladen – /video-stream.js erreichbar?', err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const container = document.getElementById('cameras');
|
const container = document.getElementById('cameras');
|
||||||
const statusText = document.getElementById('statusText');
|
const statusText = document.getElementById('statusText');
|
||||||
|
|
||||||
let cams = ['cam0', 'cam1']; // Fallback
|
// Kamera-Liste von go2rtc (via Node-Proxy /api/snapshot → /api/streams)
|
||||||
|
let cams = [];
|
||||||
try {
|
try {
|
||||||
const r = await fetch('/api/snapshot');
|
const r = await fetch('/api/snapshot');
|
||||||
|
log('init', `/api/snapshot → HTTP ${r.status}`);
|
||||||
const d = await r.json();
|
const d = await r.json();
|
||||||
if (Array.isArray(d.cameras) && d.cameras.length) {
|
if (Array.isArray(d.cameras) && d.cameras.length) {
|
||||||
cams = d.cameras.map(c => c.id);
|
cams = d.cameras.map(c => c.id);
|
||||||
|
log('init', `Kameras: ${cams.join(', ')}`);
|
||||||
|
} else {
|
||||||
|
warn('init', 'Keine Kameras in go2rtc – Config OK? go2rtc läuft?');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Kamera-Liste nicht abrufbar, nutze Fallback:', err.message);
|
logErr('init', '/api/snapshot nicht erreichbar – Fallback cam0/cam1', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cams.length === 0) {
|
||||||
|
warn('init', 'Fallback: nehme cam0 und cam1 an');
|
||||||
|
cams = ['cam0', 'cam1'];
|
||||||
}
|
}
|
||||||
|
|
||||||
cams.forEach(id => buildCamera(id, container));
|
cams.forEach(id => buildCamera(id, container));
|
||||||
statusText.textContent = `${cams.length} Kamera${cams.length !== 1 ? 's' : ''} · WebRTC`;
|
statusText.textContent = `${cams.length} Kamera${cams.length !== 1 ? 's' : ''} · WebRTC`;
|
||||||
|
log('init', 'Fertig');
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ const go2rtcProxy = createProxyMiddleware({
|
|||||||
target: GO2RTC_URL,
|
target: GO2RTC_URL,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
ws: true,
|
ws: true,
|
||||||
pathFilter: ['/api/**', '/video-rtc.js', '/video-stream.js'],
|
// Plain-Pfade: dürfen NICHT mit Globs (/api/**) gemischt werden (HPM v3)
|
||||||
|
// '/api' matcht alles ab /api/... — kein ** nötig
|
||||||
|
pathFilter: ['/api', '/video-rtc.js', '/video-stream.js'],
|
||||||
logger: console,
|
logger: console,
|
||||||
});
|
});
|
||||||
app.use(go2rtcProxy);
|
app.use(go2rtcProxy);
|
||||||
|
|||||||
Reference in New Issue
Block a user