☞ Esquema de Arquitectura
┌──────────────────────────────────────────────────────────────────┐
│ RASPBERRY PI 3B · SOLAR NET HUB │
│ BCM2837 · Cortex-A53 · VideoCore IV │
│ │
│ ┌──────────┐ V4L2 ┌─────────────────────────┐ │
│ │ 🎥 CAM │────────▷│ go2rtc :1984 │ │
│ │ CSI/USB │ │ ┌───────────────────────┐│ │
│ └──────────┘ │ │MJPEG · HLS · RTSP ││ │
│ │ │WebRTC (WHEP) · snap ││ │
│ ┌──────────┐ ALSA │ │ALSA direct capture ││ │
│ │ 🎤 MIC │────────▷│ └───────────────────────┘│ │
│ └──────────┘ └──────────┬──────────────┘ │
│ │ localhost │
│ ┌──────────┐ ALSA ┌─────────┼──────────┐ │
│ │ 🎤 MIC │────────▷│ murmurd │ :64738 │ │
│ └──────────┘ │ (Mumble,│ya activo) │ │
│ │ audio │bidirecc. │ │
│ └─────────┼──────────┘ │
│ │ localhost │
│ ┌──────────────────────────────┴────────────────────────────┐ │
│ │ OASIS :3000 (Node.js · SSB · cero JS en browser) │ │
│ │ │ │
│ │ GET /video ──▷ proxy go2rtc MJPEG │ │
│ │ GET /video.m3u8 ─▷ proxy go2rtc HLS (remoto) │ │
│ │ GET /snapshot ──▷ proxy go2rtc JPEG │ │
│ │ GET /audio ──▷ bridge Mumble Opus→OGG │ │
│ │ │ │
│ │ HTML: <img src="/video"> + <audio src="/audio"> │ │
│ │ Remote: <video src="/video.m3u8"> (HLS, sin JS) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ node-datachannel (P2P DataChannels) │ │ │
│ │ │ texto · archivos · señalización │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌────────────┐ ┌─────────────┐ ┌────────────┐ ┌────────────┐
│ 📺 BROWSER │ │ 🔊 MUMBLE │ │ 📡 PEER │ │ 🌐 REMOTE │
│ LAN (no JS)│ │ (cliente │ │ P2P data │ │ HLS viewer │
│ <img>MJPEG │ │ nativo) │ │ node-dc │ │ <video>HLS │
│ <audio>OGG │ │ RTT ~20ms │ │ text/file │ │ via Tor OK │
└────────────┘ └─────────────┘ └────────────┘ └────────────┘
☞ Setup Técnico
1. INSTALAR go2rtc
wget -O /usr/local/bin/go2rtc \
https://github.com/AlexxIT/go2rtc/releases/latest/download/go2rtc_linux_arm
chmod +x /usr/local/bin/go2rtc
Alt: compile from source
git clone https://github.com/AlexxIT/go2rtc
cd go2rtc && go build -o go2rtc
sudo mv go2rtc /usr/local/bin/
Requiere Go 1.21+. En Bookworm ARM: apt install golang o usar binario precompilado.
2. CONFIGURAR go2rtc
streams:
solar-cam:
- v4l2:///dev/video0
solar-cam-csi:
- "exec:libcamerify v4l2-ctl --stream"
api:
listen: "127.0.0.1:1984"
Config avanzada: ALSA, RTSP in, multi-stream
streams:
solar-cam:
- v4l2:///dev/video0
solar-mic:
- alsa:///default
ext-cam:
- rtsp://192.168.1.50/stream1
api:
listen: "127.0.0.1:1984"
webrtc:
candidates:
- 192.168.1.10:8555
go2rtc puede agregar múltiples fuentes (V4L2 + RTSP + ALSA) y servirlas todas desde los mismos endpoints HTTP.
3. INICIAR go2rtc
go2rtc -config /etc/go2rtc/go2rtc.yaml
4. MUMBLE (ya corriendo)
ffmpeg -i mumble://localhost:64738 \
-c:a libvorbis -f ogg pipe:1 | \
node oasis-audio-bridge.js
5. HTML EN OASIS (cero JS)
<img src="/video" alt="stream">
<audio src="/audio" controls autoplay></audio>
<video src="/video.m3u8" controls autoplay></video>
6. PROXY EN OASIS (Node.js)
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'
});
mjpeg.pipe(res);
}
);
req.on('close', () => up.destroy());
});
app.get('/video.m3u8', (req, res) => {
const up = http.get(
`${GO2RTC}/api/stream.m3u8?src=solar-cam`,
(hls) => {
res.writeHead(200, hls.headers);
hls.pipe(res);
}
);
req.on('close', () => up.destroy());
});
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);
}
);
req.on('close', () => up.destroy());
});
Security headers middleware
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
res.setHeader('Referrer-Policy', 'no-referrer');
res.setHeader('Content-Security-Policy',
"default-src 'self'; img-src 'self'; " +
"media-src 'self'; style-src 'self' 'unsafe-inline'; " +
"script-src 'none'"
);
next();
});
script-src 'none': refuerza cero JS a nivel CSP. Si el remote necesita hls.js, crear una ruta /remote con CSP más permisiva. →
Ver poster de seguridad
DRY Rules — Arq. 3
1. go2rtc escucha en
127.0.0.1:1984 — nunca en 0.0.0.0. Oasis es la puerta única (:3000).
2. Audio siempre por
Mumble — no por go2rtc ALSA directo (Mumble = bidireccional + Tor).
3. Un solo
go2rtc.yaml — todas las fuentes declaradas ahí. No flags CLI sueltos.
4. Proxy con
upstream.destroy() on req.close — evitar socket leaks.
5. Cache-Control: no-store en todas las rutas de streaming.
6. CSP script-src 'none' en rutas LAN. Si remoto necesita hls.js → ruta /remote con CSP separada. →
poster de seguridad
7. Snapshots → guardar como
SSB blob (inmutable, content-addressed).
8. node-datachannel solo para datos P2P, nunca para media.