Claude Multicam (a)

This commit is contained in:
chk
2026-06-06 07:21:38 +02:00
parent 45d9837f4f
commit e13ec59a2f
5 changed files with 142 additions and 49 deletions

View File

@@ -1,11 +1,11 @@
'use strict';
const express = require('express');
const fs = require('fs');
const http = require('http');
const path = require('path');
const { CameraSwitch } = require('./src/cameraSwitch');
const { detectDevices } = require('./src/deviceDetect');
const { createSnapshotRouter, createStreamRouter } = require('./src/snapshotService');
const { createSnapshotRouter, createStreamRouter, createCamerasRouter } = require('./src/snapshotService');
const PORT = parseInt(process.env.PORT ?? '8444', 10);
const LIVE_SIZE = process.env.LIVE_SIZE ?? '640x480';
@@ -16,31 +16,60 @@ const ENCODE_MODE = process.env.ENCODE_MODE ?? 'copybsf'; // 'copybsf' (niedrige
const ON_DEMAND = (process.env.ON_DEMAND ?? 'true') !== 'false'; // Live nur bei Verbrauchern (spart idle-CPU)
const IDLE_GRACE_MS = parseInt(process.env.IDLE_GRACE_MS ?? '15000', 10);
// ── Kameras: cam0 = erstes Gerät, cam1 = zweites … (DEV0/DEV1-Env überschreibt)
const devices = detectDevices();
// ── cameras.json → CameraSwitch-Instanzen ────────────────────────────────────
let camerasJson;
try {
camerasJson = JSON.parse(fs.readFileSync(path.join(__dirname, 'cameras.json'), 'utf8'));
} catch (e) {
console.error('cameras.json fehlt oder ungültig:', e.message); process.exit(1);
}
const camsConfig = camerasJson.cameras;
if (!Array.isArray(camsConfig) || camsConfig.length === 0) {
console.error('cameras.json: "cameras" muss ein nicht-leeres Array sein'); process.exit(1);
}
const switches = {};
devices.forEach((device, i) => {
const id = `cam${i}`;
switches[id] = new CameraSwitch({ id, device, liveSize: LIVE_SIZE, liveFps: LIVE_FPS, hiresSize: HIRES_SIZE, hiresFps: HIRES_FPS, encode: ENCODE_MODE, onDemand: ON_DEMAND, idleGraceMs: IDLE_GRACE_MS });
});
const camsMeta = []; // { id, device, name, position, stream, hires, note }
for (const cam of camsConfig) {
if (!cam.id || !cam.device) {
console.error(`cameras.json: Eintrag ohne id/device: ${JSON.stringify(cam)}`); process.exit(1);
}
switches[cam.id] = new CameraSwitch({
id: cam.id, device: cam.device,
liveSize: LIVE_SIZE, liveFps: LIVE_FPS,
hiresSize: HIRES_SIZE, hiresFps: HIRES_FPS,
encode: ENCODE_MODE, onDemand: ON_DEMAND, idleGraceMs: IDLE_GRACE_MS,
});
camsMeta.push({
id: cam.id,
device: cam.device,
name: cam.name ?? cam.id,
position: cam.position ?? '',
stream: cam.stream !== false,
hires: cam.hires !== false,
note: cam.note ?? '',
});
}
const app = express();
// ── 1. Eigene Endpunkte ───────────────────────────────────────────────────────
app.use('/api/snapshot', createSnapshotRouter(switches));
app.use('/api/snapshot', createSnapshotRouter(switches, camsMeta));
app.use('/api/stream', createStreamRouter(switches));
app.use('/api/cameras', createCamerasRouter(camsMeta));
app.get('/health', (_req, res) => {
res.json({
status: 'ok',
cameras: Object.values(switches).map((sw) => ({
id: sw.id, device: sw.device, state: sw.state, hasFrame: !!sw.latest,
})),
cameras: camsMeta.map((c) => {
const sw = switches[c.id];
return { id: c.id, name: c.name, device: c.device, state: sw?.state, hasFrame: !!sw?.latest };
}),
});
});
app.get('/config.json', (_req, res) => {
res.json({ cameras: Object.keys(switches) });
res.json({ cameras: camsMeta.map((c) => ({ id: c.id, name: c.name, stream: c.stream })) });
});
// ── 2. Statische Dateien ──────────────────────────────────────────────────────
@@ -54,11 +83,9 @@ const server = http.createServer(app);
server.listen(PORT, '0.0.0.0', () => {
console.log(`AppRobotWebcam http://0.0.0.0:${PORT}`);
console.log(` Kameras: ${Object.entries(switches).map(([id, sw]) => `${id}=${sw.device}`).join(', ')}`);
console.log(` Kameras: ${camsMeta.map((c) => `${c.id}=${c.device} "${c.name}" stream=${c.stream}`).join(', ')}`);
console.log(` Live: ${LIVE_SIZE}@${LIVE_FPS} · HD-Grab: ${HIRES_SIZE}@${HIRES_FPS} · Encode: ${ENCODE_MODE} · On-Demand: ${ON_DEMAND}`);
console.log(` Viewer: http://0.0.0.0:${PORT}/`);
console.log(` Live-Stream: http://0.0.0.0:${PORT}/api/stream/cam0`);
console.log(` Snapshot API: http://0.0.0.0:${PORT}/api/snapshot/cam0 (+ /hires)`);
// Live-Producer starten (Dauerbetrieb)
Object.values(switches).forEach((sw) => sw.start());