4.2 KiB
4.2 KiB
Single-Server Streaming Implementation Plan (Prototype)
Scope
Build live camera streaming and simultaneous recording on the current web server for low-to-moderate load testing, with explicit non-scale assumptions.
Constraints
- Keep existing backend as the control API (
/streams/*, device auth, command lifecycle). - Add media transport and recording in the same deployment for now.
- Prefer solutions that can later be split into a dedicated media service.
Recommended Stack (Current Server)
- SFU:
mediasoup(Node.js SFU library). - TURN/STUN:
coturn(external process/service, mandatory for NAT traversal reliability). - Recording worker:
ffmpegprocess consuming RTP from SFU plain transports. - Signaling: keep existing Socket.IO channel (
webrtc:signal) or migrate to REST+WS messages while preserving auth. - Storage: keep MinIO upload path and reuse current recordings finalize flow.
Why this stack
mediasoupgives server-side fan-out (camera publishes once, multiple subscribers).ffmpegcan write MP4/HLS outputs from server-side RTP.coturnis required for real-world networks where direct ICE paths fail.- This minimizes changes to existing route structure and DB entities.
Candidate Library Check
mediasoup: mature SFU for Node, suitable for self-hosted media routing.@roamhq/wrtc/node-webrtcstyle bindings: useful for peer/bot use-cases, but not a full SFU architecture by itself.werift: pure TypeScript WebRTC stack; possible for custom flows, but higher implementation risk than mediasoup for production-like behavior.- Managed alternatives (LiveKit/Twilio/Agora/100ms/Mux/Cloudflare): faster and more reliable, but outside strict single-server-in-process scope.
Implementation Phases
Phase 0: Environment + Guardrails
- Add env vars:
TURN_URLS,TURN_USERNAME,TURN_CREDENTIALMEDIA_RECORDINGS_DIRMEDIA_MAX_PUBLISHERS,MEDIA_MAX_SUBSCRIBERS_PER_ROOM
- Add explicit README warning that this mode is prototype-only.
- Add metrics baseline (CPU, RAM, event loop lag, outbound bitrate, active sessions).
Phase 1: Media Plane Skeleton
- Add
media/sfu/module:- worker bootstrap
- router lifecycle per stream session
- transport creation helpers
- Extend
media/types.tsprovider contracts:- publish transport params
- subscribe transport params
- producer/consumer lifecycle ops
- Add stream session registry in memory + DB mapping (
streamSessionId -> router/producer state).
Phase 2: Publish/Subscribe Handshake
- Camera flow:
- request publish transport params
- connect DTLS
- produce video track
- Client flow:
- request subscribe transport params
- connect DTLS
- consume producer track
- Use existing device auth checks and stream ownership checks.
- Keep
stream:started/stream:endedevents for UI state updates.
Phase 3: Recording on Server
- On first producer for a stream, start
ffmpegrecording worker. - Record strategy:
- start with single-track MP4 for simplicity
- optionally add HLS segment output later
- On
/streams/:id/end:- stop recorder
- upload result to MinIO
- call existing recording finalize path
- Add retry and orphan cleanup worker for interrupted recordings.
Phase 4: Reliability + Backpressure
- Remove JPEG
stream:framefallback from simulator once SFU path is stable. - Add connection timeout, ICE restart, and stream health checks.
- Add admission limits per account and global concurrent stream caps.
- Add stale session cleanup and worker crash recovery.
Phase 5: Load Test + Exit Criteria
- Target load test:
- 1 publisher + N viewers per stream
- multiple concurrent streams
- Capture:
- startup latency (request -> first frame)
- packet loss behavior
- server CPU/RAM/network saturation points
- Define threshold to migrate to dedicated media service when limits are hit.
Immediate Code Changes (Low-Risk First)
- Add
docsand env scaffolding for TURN and recording worker. - Add
media/sfuinterfaces with no routing behavior yet (feature-flagged). - Implement one end-to-end stream path behind a flag (
MEDIA_MODE=single_server_sfu). - Deprecate frame relay fallback after validation.