Implementación Proxy — Node.js
GET /video — MJPEG proxy
const GO2RTC = 'http://127.0.0.1:1984';
app.get('/video', (req, res) => {
const up = http.get(
`${GO2RTC}/api/frame.mjpeg?src=solar-cam`,
(mjpeg) => {
res.writeHead(200, {
'Content-Type': mjpeg.headers['content-type'],
'Cache-Control': 'no-store',
'X-Content-Type-Options': 'nosniff'
});
mjpeg.pipe(res);
}
);
up.on('error', () => res.status(502).end('upstream down'));
req.on('close', () => up.destroy());
});
GET /video.m3u8 — HLS proxy (para remoto)
app.get('/video.m3u8', (req, res) => {
const up = http.get(
`${GO2RTC}/api/stream.m3u8?src=solar-cam`,
(hls) => {
res.writeHead(200, {
'Content-Type': 'application/vnd.apple.mpegurl',
'Cache-Control': 'no-store'
});
hls.pipe(res);
}
);
up.on('error', () => res.status(502).end());
req.on('close', () => up.destroy());
});
GET /snapshot — JPEG
app.get('/snapshot', (req, res) => {
const up = http.get(
`${GO2RTC}/api/frame.jpeg?src=solar-cam`,
(snap) => {
res.writeHead(200, {
'Content-Type': 'image/jpeg',
'Cache-Control': 'no-store'
});
snap.pipe(res);
}
);
up.on('error', () => res.status(502).end());
req.on('close', () => up.destroy());
});
Templates HTML
Vista LAN — /cam (cero JS)
<img src="/video" alt="MJPEG stream">
<audio src="/audio" controls autoplay></audio>
<p>MJPEG · Mumble OGG · cero JavaScript</p>
Vista remota — /remote (HLS)
<video src="/video.m3u8" controls autoplay></video>
<audio src="/audio" controls autoplay></audio>
<p>HLS H.264 · Mumble OGG</p>
HLS nativo solo en Safari. Para Firefox/Chrome añadir hls.js en /remote (afecta al CSP de esa ruta).
Security Headers — Doble CSP
CSP para /cam (LAN, cero JS)
Content-Security-Policy:
default-src 'self';
img-src 'self';
media-src 'self';
style-src 'self' 'unsafe-inline';
script-src 'none'
CSP para /remote (si hls.js necesario)
Content-Security-Policy:
default-src 'self';
img-src 'self';
media-src 'self' blob:;
style-src 'self' 'unsafe-inline';
script-src 'self'
DRY Rules — Proxy Oasis
1. const GO2RTC = URL centralizada. Nunca hardcodear localhost:1984 en cada ruta.
2. req.on('close', () => up.destroy()) — siempre. Evitar socket leaks.
3. Cache-Control: no-store — en todo stream. No cachear datos en vuelo.
4. Un solo puerto público: :3000. go2rtc (:1984) y murmurd (:64738) son internos.
5. Dos CSP: /cam (script-src 'none') y /remote (script-src 'self' si hls.js).
6. up.on('error') — siempre manejar caída del upstream con 502.