moqbox#
Shared media rooms. Paste a URL, everyone watches together.
Requires ffmpeg and yt-dlp on PATH. Run with cargo run, open localhost:3000.
Architecture#
One Axum binary serving SSR pages, REST API, and a custom media streaming protocol over WebSocket on the same port. Per-room mpsc actor processes commands (queue, skip, chat, register, metadata) sequentially -- no locks. Room state persisted to SQLite (WAL mode).
Async metadata extraction#
The ingest HTTP handler returns immediately after queuing a URL. Metadata extraction (yt-dlp) runs in a spawned task within the actor. When extraction completes, the result is sent back to the actor which updates the DB and pushes the resolved metadata to clients via the state broadcast channel.
sequenceDiagram
participant Client
participant HTTP as HTTP Handler
participant Actor as Room Actor
participant Extract as Extraction Task
Client->>HTTP: POST /api/ingest
HTTP->>Actor: QueueUrl(url)
HTTP-->>Client: {ok: true}
Actor->>Actor: insert placeholder
Actor->>Extract: spawn extraction
Extract->>Actor: MetadataReady
Actor->>Client: PublishSnapshot via WS
Playback pipeline#
yt-dlp resolves URLs to direct stream addresses. FFmpeg transcodes to fragmented
MP4 at real-time speed (-re). fMP4 boxes are read from stdout as moof+mdat
pairs and published over per-room broadcast channels. The video init segment
(ftyp+moov) is cached for late-joining clients.
State machine#
PlaybackState is pure -- all transitions are deterministic. The actor follows
the impure-pure-impure sandwich:
- Gather -- DB queries, wall clock
- Transition --
PlaybackState::transition(event, now)-- returnsEffects - Commit -- actor executes effects
Events: TrackQueued, Skip, TrackEnded, MetadataUpdated.
Effects: AbortPipeline, StartPipeline, PersistStarted, PersistFinished, PublishSnapshot.
Client delivery#
Clients use MSE with a single SourceBuffer in segments mode. A state track
(watch channel) delivers current playback position, queue, and history as JSON.
Queue items carry a pending flag while metadata is unresolved. Chat is
persisted to SQLite, replayed on subscribe, then live.