86 lines
2.6 KiB
JavaScript
86 lines
2.6 KiB
JavaScript
'use strict';
|
||
|
||
const express = require('express');
|
||
const http = require('http');
|
||
const { WebSocketServer } = require('ws');
|
||
const path = require('path');
|
||
const { detectDevices } = require('./src/deviceDetect');
|
||
const { VideoStream } = require('./src/videoStream');
|
||
const { createSnapshotRouter } = require('./src/snapshotService');
|
||
|
||
const PORT = parseInt(process.env.PORT ?? '8080', 10);
|
||
|
||
function buildStreams() {
|
||
const devices = detectDevices();
|
||
console.log(`Detected ${devices.length} camera(s):`, devices);
|
||
|
||
return devices.map((device, idx) => new VideoStream(device, {
|
||
name: `cam${idx}`,
|
||
width: parseInt(process.env[`CAM${idx}_WIDTH`] ?? '640', 10),
|
||
height: parseInt(process.env[`CAM${idx}_HEIGHT`] ?? '480', 10),
|
||
fps: parseInt(process.env[`CAM${idx}_FPS`] ?? '30', 10),
|
||
quality: parseInt(process.env[`CAM${idx}_QUALITY`] ?? '5', 10),
|
||
}));
|
||
}
|
||
|
||
function main() {
|
||
const streams = buildStreams();
|
||
|
||
// Start all streams eagerly – first client gets immediate frame
|
||
streams.forEach(s => s.start());
|
||
|
||
const app = express();
|
||
|
||
app.use(express.static(path.join(__dirname, 'public')));
|
||
app.use('/api/snapshot', createSnapshotRouter(streams));
|
||
|
||
app.get('/health', (_req, res) => {
|
||
res.json({
|
||
status: 'ok',
|
||
cameras: streams.map((s, i) => ({
|
||
id: `cam${i}`,
|
||
device: s.device,
|
||
running: s.isRunning,
|
||
clients: s.clientCount,
|
||
hasFrame: s.latestFrame !== null,
|
||
})),
|
||
});
|
||
});
|
||
|
||
const server = http.createServer(app);
|
||
const wss = new WebSocketServer({ noServer: true });
|
||
|
||
server.on('upgrade', (req, socket, head) => {
|
||
const match = req.url?.match(/^\/ws\/cam(\d+)$/);
|
||
if (!match) { socket.destroy(); return; }
|
||
|
||
const idx = parseInt(match[1], 10);
|
||
if (!streams[idx]) { socket.destroy(); return; }
|
||
|
||
wss.handleUpgrade(req, socket, head, (ws) => {
|
||
const stream = streams[idx];
|
||
stream.addClient(ws);
|
||
ws.on('close', () => stream.removeClient(ws));
|
||
ws.on('error', () => stream.removeClient(ws));
|
||
});
|
||
});
|
||
|
||
server.listen(PORT, '0.0.0.0', () => {
|
||
console.log(`AppRobotWebcam on http://0.0.0.0:${PORT}`);
|
||
streams.forEach((s, i) => {
|
||
console.log(` ws://0.0.0.0:${PORT}/ws/cam${i} → ${s.device}`);
|
||
console.log(` http://0.0.0.0:${PORT}/api/snapshot/cam${i}`);
|
||
});
|
||
});
|
||
|
||
const shutdown = (signal) => {
|
||
console.log(`\n${signal} received – shutting down`);
|
||
streams.forEach(s => s.stop());
|
||
server.close(() => process.exit(0));
|
||
};
|
||
process.on('SIGINT', () => shutdown('SIGINT'));
|
||
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
||
}
|
||
|
||
main();
|