Section
Technology
The real Dueli stack in two layers: a plain-language explanation and the engineering view.
This page explains the technologies Dueli actually uses, in two layers: a plain-language view for everyone and an engineering view for specialists. No technology is listed unless the current code uses it.
In plain terms
Dueli runs in the browser on Cloudflare’s global network. Two competitors meet in a live video call between their devices (WebRTC peer-to-peer); when a network blocks the direct path, a TURN relay helps the connection through.
During a competition the browser combines both camera feeds into one picture and records it as short 5-second chunks. The chunks are uploaded as they are produced, can be played back in order, and are assembled into a full replay after the match ends.
Recorded competitions can be downloaded as an MP4 file. When a recording needs conversion, the conversion runs inside the visitor’s browser with FFmpeg.wasm, so the file is not sent to an external conversion service.
Platform data lives in D1, pages and APIs are built with Hono and TypeScript on Cloudflare, and the interface uses Tailwind CSS. Live updates arrive through SSE and polling.
For engineers
Runtime: Cloudflare Pages/Workers with Hono 4 on TypeScript. D1 (SQLite) is the database. Vite builds the Worker bundle, Tailwind CSS 4 styles the interface, and esbuild bundles the browser entry points.
Live media: a browser RTCPeerConnection carries WebRTC between host and opponent. ICE servers are issued per session from GET /api/signaling/ice-servers; Cloudflare Calls TURN (TURN_TOKEN_ID and TURN_API_TOKEN) or self-hosted coturn (TURN_URL and TURN_SECRET) can relay, with a STUN-only fallback when neither is configured.
Recording: a canvas compositor draws both feeds, and MediaRecorder emits a chunk every 5 seconds. The host registers each chunk at POST /api/chunks/register (a short-lived one-time key bound to the competition, sequence and byte ceiling), uploads it, verifies it with GET /verify and reports POST /receipt. Playback reads GET /api/chunks/playlist/:id, and the existing finalize step produces the VOD.
Downloads: @ffmpeg/core 0.12.10 runs in a self-hosted browser worker for MP4 conversion, with a structural remux path for recordings that are already MP4-compatible. Progress and memory limits are handled in the client service.
Realtime: SSE through GET /api/sse carries live updates, and signaling uses HTTP polling; the production path does not use WebSocket.
Quality: Vitest covers unit and UI contracts, and Playwright covers end-to-end browser flows. Markdown documents are rendered with marked, and PostCSS with Autoprefixer completes the CSS pipeline.
The stack in this repository
- Cloudflare Pages / Workers Hosting and server runtime
- Cloudflare D1 (SQLite) Application database
- Hono 4 HTTP routes, pages and API
- TypeScript 5 Typed source for server and client
- Tailwind CSS 4 + PostCSS + Autoprefixer Interface styling and CSS pipeline
- Vite 7 + esbuild Worker bundle and browser entry points
- WebRTC (RTCPeerConnection) + TURN/STUN Live audio and video between participants
- Canvas compositor + MediaRecorder 5-second recorded chunks in the browser
- FFmpeg.wasm (@ffmpeg/core 0.12.10) In-browser MP4 conversion and download
- marked 17 Markdown rendering for managed documents
- Vitest 5 Unit and UI test suite
- Playwright End-to-end browser tests
- Wrangler 4 Cloudflare development and deployment tooling
- SSE + HTTP polling Live updates outside the media path
The list is limited to what package.json and the repository actually contain. It is a technical description, not a partnership or endorsement claim.