Files
appRobotWebcam/server.js
2026-06-04 05:46:59 +02:00

136 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
const express = require('express');
const http = require('http');
const path = require('path');
const { createProxyMiddleware } = require('http-proxy-middleware');
const { createSnapshotRouter } = require('./src/snapshotService');
const PORT = parseInt(process.env.PORT ?? '8444', 10);
const GO2RTC_URL = process.env.GO2RTC_URL ?? 'http://localhost:1984';
const GO2RTC_PORT = parseInt(process.env.GO2RTC_PORT ?? '1984', 10);
const app = express();
// ── 1. Eigene Endpunkte (vor dem Proxy registrieren) ─────────────────────────
app.use('/api/snapshot', createSnapshotRouter(GO2RTC_URL));
app.get('/health', async (_req, res) => {
try {
const r = await fetch(`${GO2RTC_URL}/api/streams`);
const streams = r.ok ? await r.json() : {};
res.json({ status: r.ok ? 'ok' : 'degraded', cameras: Object.keys(streams) });
} catch (err) {
res.status(503).json({ status: 'down', error: err.message });
}
});
app.get('/config.json', (_req, res) => {
res.json({ go2rtcPort: GO2RTC_PORT });
});
// ── 2. HTTP-Proxy zu go2rtc ───────────────────────────────────────────────────
const go2rtcProxy = createProxyMiddleware({
target: GO2RTC_URL,
changeOrigin: true,
pathFilter: ['/api', '/video-rtc.js', '/video-stream.js'],
logger: console,
on: {
error: (err, _req, res) => {
console.error('[HPM] proxy error:', err.message);
if (!res.headersSent) res.status(502).json({ error: 'go2rtc nicht erreichbar' });
},
},
});
app.use(go2rtcProxy);
// ── 3. Statische Dateien ──────────────────────────────────────────────────────
app.use(express.static(path.join(__dirname, 'public')));
// ── 4. go2rtc Stream-Monitor (server-seitiges Logging) ───────────────────────
// Pollt alle 5 s go2rtc /api/streams und loggt Änderungen.
// Sichtbar im Portainer-Log von AppRobotWebcam.
// Logt: Producer-Starts/-Stops, Consumer-Anzahl, Timeouts/Restarts.
//
// go2rtc /api/streams liefert z.B.:
// { "cam0": { "producers": [{"url":"...","state":"running"}], "consumers": [...] } }
//
const STREAM_POLL_MS = 5000;
let prevStreamState = {};
async function pollGo2rtcStreams() {
try {
const r = await fetch(`${GO2RTC_URL}/api/streams`);
if (!r.ok) { console.warn(`[monitor] /api/streams → HTTP ${r.status}`); return; }
const streams = await r.json();
for (const [name, data] of Object.entries(streams)) {
const producers = data.producers ?? [];
const consumers = data.consumers ?? [];
const nConsumers = consumers.length;
const prev = prevStreamState[name] ?? {};
// Producer-Status
for (let i = 0; i < producers.length; i++) {
const p = producers[i];
const state = p.state ?? 'unknown';
const key = `${name}.p${i}`;
const pPrev = prevStreamState[key];
if (pPrev !== state) {
if (state === 'running') console.log(`[monitor][${name}] producer #${i} LÄUFT (${p.url ?? ''})`);
if (state === 'error') console.error(`[monitor][${name}] producer #${i} FEHLER (${p.url ?? ''})`);
if (state === 'stop') console.warn(`[monitor][${name}] producer #${i} GESTOPPT`);
if (!['running','error','stop'].includes(state))
console.log(`[monitor][${name}] producer #${i} state="${state}"`);
prevStreamState[key] = state;
}
}
// Consumer-Anzahl — nur loggen wenn sie sich ändert
if (prev.nConsumers !== nConsumers) {
console.log(`[monitor][${name}] consumers: ${prev.nConsumers ?? '?'}${nConsumers}`);
prevStreamState[name] = { ...prev, nConsumers };
}
}
// Streams die verschwunden sind (Timeout/Restart)
for (const name of Object.keys(prevStreamState)) {
if (name.includes('.')) continue; // skip producer-state keys
if (!streams[name]) {
console.warn(`[monitor][${name}] Stream verschwunden aus go2rtc`);
delete prevStreamState[name];
}
}
} catch (err) {
console.error('[monitor] go2rtc nicht erreichbar:', err.message);
}
}
// ── Start ─────────────────────────────────────────────────────────────────────
const server = http.createServer(app);
server.listen(PORT, '0.0.0.0', () => {
console.log(`AppRobotWebcam http://0.0.0.0:${PORT}`);
console.log(` go2rtc HTTP: ${GO2RTC_URL}`);
console.log(` go2rtc WS: ws://[host]:${GO2RTC_PORT} (Browser verbindet direkt)`);
console.log(` Viewer: http://0.0.0.0:${PORT}/`);
console.log(` Snapshot API: http://0.0.0.0:${PORT}/api/snapshot/cam0`);
console.log(` Stream-Monitor: alle ${STREAM_POLL_MS / 1000}s → Portainer-Log`);
// Ersten Poll nach 3 s (go2rtc braucht einen Moment zum Starten)
setTimeout(() => {
pollGo2rtcStreams();
setInterval(pollGo2rtcStreams, STREAM_POLL_MS);
}, 3000);
});
const shutdown = (sig) => {
console.log(`\n${sig} shutting down`);
server.close(() => process.exit(0));
};
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('SIGTERM', () => shutdown('SIGTERM'));