A Riverside-style video calling app with local high-quality recording, cloud upload, and server-side demuxing into playable MKV files.
Live demo: https://shitalks-worker.roman01la.workers.dev
- Live video calls between two participants via Cloudflare Realtime SFU
- High-quality local recording using WebCodecs (VP9/H.264 + Opus) — independent of the live stream
- Reliable upload with crash recovery — pending epochs survive page reload via OPFS + localStorage manifest
- Server-side demux in Cloudflare Containers (Rust + ffmpeg) → playable MKV per participant
- Multi-cam timeline generated as Final Cut Pro XML for editing
| Layer | Tech |
|---|---|
| Frontend | Vue 3, Vite, TypeScript |
| Backend | Cloudflare Workers + Durable Objects |
| WebRTC | Cloudflare Realtime SFU |
| Recording | WebCodecs (VideoEncoder/AudioEncoder) → OPFS |
| Storage | Cloudflare R2 |
| Demux | Cloudflare Containers (Rust + ffmpeg) |
| Auth | HMAC-SHA256 short-lived tokens |
Browser Cloudflare
┌─────────────────────────────┐ ┌──────────────────────┐
│ Camera/Mic │ │ Realtime SFU │
│ ↓ │ WebRTC │ │
│ Capture → LiveScaler ───────┼────────▶│ Forwards tracks │
│ │ │ │
│ ↓ │ WS │ RoomDO │
│ ABR (1080p reactive) │◀────────│ - Signaling │
│ │ │ - Token issuance │
│ │ │ - Demux orchestration│
│ Recorder (WebCodecs) │ │ │
│ ↓ │ │ R2 Bucket │
│ OPFS epochs (every 2s) ─────┼─PUT────▶│ recordings/ │
│ ↓ │ │ {room}/{p}/{src}/ │
│ Upload manifest │ │ epoch-NNNN.bin │
│ (localStorage) │ │ │
│ │ │ DemuxContainer │
│ │ │ (Rust + ffmpeg) │
│ │ │ ↓ │
│ │ │ demuxed/*.mkv │
│ │ │ + timeline.fcpxml │
└─────────────────────────────┘ └──────────────────────┘
- ABR is reactive, not proactive — starts at 1080p and lets WebRTC's congestion controller handle bitrate. Only drops resolution after sustained congestion. Mirrors how Google Meet/Zoom work.
- Recording is independent of live stream — captures at full camera resolution regardless of ABR rung.
- Crash recovery — every epoch is recorded to a localStorage manifest before upload. On reload, orphaned epochs in OPFS are re-enqueued.
- Token refresh on 401 — auth tokens auto-refresh when they expire mid-upload.
- Server validates epoch completeness — demux only triggers when all expected epochs exist in R2.
npm installYou need:
- Node 20.19+ or 22.12+
- Docker Desktop running (for the demux container)
- A
.dev.varsfile withCALLS_APP_ID,CALLS_APP_SECRET, and optionallyTURN_SERVICE_ID/TURN_SERVICE_TOKEN
Start the dev servers (two terminals):
# Terminal 1 — Vite frontend (port 5173)
npm run dev
# Terminal 2 — Wrangler backend (port 8787)
npx wrangler devOpen http://localhost:8787 — the backend serves the frontend assets and proxies API/WS routes to the Durable Object.
For testing without Docker:
npx wrangler dev --enable-containers=false(Uploads work, demux is disabled — use node demux-recording.mjs for local demuxing.)
npm test # 177 unit tests
npm run type-check # vue-tscEnd-to-end test procedure: tests/e2e-call-test.md
Local demux from R2:
node demux-recording.mjs <dir> # demux a directory of epoch-*.bin files
node demux-recording.mjs --opfs # pull from browser OPFS via CDPnpm run deployBuilds the frontend, deploys the worker, and pushes the demux container image to Cloudflare's registry.
src/
app/ Vue 3 frontend
views/ LobbyView, CallStageView, DownloadView
components/ VideoTile, CallControls, DebugPanel
composables/ useRoom, useCapture, useDebugStats
lib/ Core controllers (framework-agnostic)
room/ RoomController — WebRTC + signaling
capture/ CaptureController — camera/mic/screen
live/ AbrController — adaptive bitrate
recorder/ WebCodecs recording, OPFS, crash recovery
uploader/ UploaderController, TokenBucket, manifest
sync/ SyncController — NTP-style clock alignment
audio/ AudioProcessor
worker/ Cloudflare Worker backend
index.ts HTTP router, CORS, auth gate
room.ts RoomDO Durable Object
sfu.ts Cloudflare Realtime SFU client
upload.ts Epoch upload handlers
download.ts MKV/FCPXML download
mux.ts Server-side demux handlers
fcpxml.ts Final Cut Pro XML builder
auth.ts HMAC token signing/verification
demux-container.ts DemuxContainer config
worker/container/ Rust demux server (Dockerfile + Cargo)
demux-recording.mjs Local demux CLI (Node)
tests/ E2E test docs
- Recording duration is limited only by browser OPFS quota (~half of free disk space).
- Demux runs in a Cloudflare Container with 15-minute idle timeout.
- Recordings auto-delete after 7 days.
- Max participants per room: 20.
- Max upload payload: 50 MB per epoch.
- WebSocket message size limit: 64 KB.