OASIS

Media Proxy

Consumer WebRTC → MJPEG/OGG — sirve media sin JS al browser del SNH

Identidad del Componente

CampoValor
Ficheromedia_proxy.js (~200 líneas)
Corre dentro deOasis (Node.js :3000)
Dependencia principalnode-datachannel (consumer WebRTC)
Dependencia decodeffmpeg (pipe: H.264→MJPEG, Opus→OGG)
RAM adicional~40–60 MB (ffmpeg decode + buffers)
EntradaWebRTC tracks desde RPi-WebRTC (localhost:8889)
SalidaGET /video (MJPEG), GET /audio (OGG)

Key Concept

RPi-WebRTC sirve WebRTC nativo → el browser necesita JS. El proxy consume ese stream WebRTC dentro de Node.js (vía node-datachannel) y reconvierte a MJPEG/OGG servido como HTTP puro. Así el browser del SNH recibe <img src="/video"> y <audio src="/audio"> sin JS.
Es el mismo principio que Arq. 3 (go2rtc sirve MJPEG/HLS sin JS), pero aquí la fuente es WebRTC nativo en vez de V4L2 directo.

Pipeline de Conversión

  RPi-WebRTC (:8889)
       │
       │  WebRTC: H.264 RTP + Opus RTP
       │
  ┌────┴──────────────────────────────── media_proxy.js ──────────┐
  │                                                                │
  │  node-datachannel                                              │
  │  ├─ PeerConnection (localhost)                                 │
  │  ├─ onTrack('video') ──▷ ffmpeg -f h264 -i pipe:              │
  │  │                          -f mjpeg -q:v 5 pipe:1             │
  │  │                       ──▷ HTTP chunked → GET /video         │
  │  │                                                             │
  │  └─ onTrack('audio') ──▷ ffmpeg -f opus -i pipe:              │
  │                             -c:a libvorbis -f ogg pipe:1       │
  │                          ──▷ HTTP chunked → GET /audio         │
  │                                                                │
  │  onDataChannel ──▷ relay a webrtc_model.js (chat/files)        │
  └────────────────────────────────────────────────────────────────┘

Rutas HTTP (dentro de Oasis)

RutaMétodoContent-TypeDescripción
/video GET multipart/x-mixed-replace (MJPEG) Stream MJPEG continuo, ffmpeg decode H.264→MJPEG
/audio GET audio/ogg Stream OGG/Vorbis, ffmpeg decode Opus→OGG
/snapshot GET image/jpeg Último frame JPEG capturado
Los templates EJS de Oasis renderizan HTML con:
<img src="/video" alt="cam"> <audio src="/audio" autoplay controls></audio>

Handshake: Proxy ↔ RPi-WebRTC

1. Crear PeerConnection
// media_proxy.js — inicialización const { PeerConnection } = require('node-datachannel'); const pc = new PeerConnection('proxy', { iceServers: [] });
2. Intercambiar SDP con RPi-WebRTC
// Crear offer y enviar a RPi-WebRTC HTTP API const offer = pc.createOffer(); pc.setLocalDescription(offer); // POST a localhost:8889/offer const resp = await fetch('http://127.0.0.1:8889/offer', { method: 'POST', headers: { 'Content-Type': 'application/sdp' }, body: offer.sdp }); const answerSdp = await resp.text(); pc.setRemoteDescription({ type: 'answer', sdp: answerSdp });
3. Recibir tracks y decodificar
// Pipe tracks a ffmpeg para conversión pc.onTrack((track) => { if (track.kind === 'video') { spawnFfmpeg(track, 'mjpeg', videoOutputStream); } else if (track.kind === 'audio') { spawnFfmpeg(track, 'ogg', audioOutputStream); } });
El handshake es en localhost — no necesita STUN/TURN. ICE candidates serán solo host candidates (127.0.0.1).

Comandos ffmpeg Internos

Vídeo: H.264 → MJPEG
ffmpeg -f h264 -i pipe:0 \ -f mjpeg -q:v 5 -r 15 pipe:1
-q:v 5 balancea calidad/BW. -r 15 limita fps para ahorrar CPU.
Audio: Opus → OGG/Vorbis
ffmpeg -f opus -i pipe:0 \ -c:a libvorbis -q:a 3 -f ogg pipe:1
Snapshot: H.264 → JPEG (un frame)
ffmpeg -f h264 -i pipe:0 \ -frames:v 1 -f image2 pipe:1

CSP & Seguridad

HeaderValorMotivo
Content-Security-Policy img-src 'self'; media-src 'self' Solo carga media de Oasis mismo
X-Content-Type-Options nosniff Prevenir MIME sniffing
Cache-Control no-store Stream en vivo, no cachear
El proxy escucha solo en localhost (127.0.0.1). RPi-WebRTC también debe bindearse a localhost. Las rutas /video y /audio se exponen a través de Oasis (:3000), que ya gestiona autenticación SSB. → Poster de seguridad canónico

Troubleshooting

SíntomaCausaFix
GET /video devuelve 502 RPi-WebRTC no está corriendo Verificar systemctl status rpi-webrtc
Vídeo negro / frames corruptos ffmpeg no recibe H.264 válido Verificar que RPi-WebRTC negoció H.264 (no VP8/VP9)
Audio entrecortado Buffer underrun en pipe Aumentar buffer size en spawn_ffmpeg. Verificar CPU < 80%
PeerConnection falla (ICE failed) RPi-WebRTC rechaza offer Verificar formato SDP. Probar con browser estándar primero
Alta CPU en ffmpeg decode H.264 High profile = CPU-intensive decode Configurar RPi-WebRTC para Baseline profile. Reducir resolución
media_proxy.js node-datachannel ffmpeg pipe MJPEG OGG H.264→MJPEG Opus→OGG GET /video