Build a browser room where participants publish camera and microphone tracks and receive each other's media. The example exposes the SFU operations and the application state that connects them.
Run the quickstartOpen the source
The example is experimental. It includes backend authentication and authorization, reconnect, and cleanup. Its production guide ↗ identifies the policies and controls to integrate for your application.
Each browser owns separate publishing and receiving PeerConnections. Each connection corresponds to an SFU session. This makes the two negotiation lifecycles independent.
The components have these responsibilities:
| Component | Responsibility |
|---|---|
| Browser | Capture media, apply SDP, receive tracks, and display room and connection state |
| Worker | Authenticate requests and route authorized application operations |
| Durable Object | Own room membership, track discovery, room permissions, reconnect state, and cleanup |
| Realtime SFU | Receive published audio/video and forward the tracks requested by each receiving session |
A hibernating WebSocket notifies browsers that a room revision changed. Browsers fetch the authoritative snapshot over HTTP. A periodic safety poll also checks for changes. Closing the notification socket does not by itself remove a participant.
The publisher sends an SDP offer through the backend to tracks/new. After publication succeeds, the Durable Object stores the track names and media kinds in the room's discovery state.
Other participants request the publications they want. The backend translates those requests into remote track subscriptions using publisher session IDs and track names. Any required SFU offer/answer exchange completes before another mutation starts on that session.
Refer to sessions and tracks and negotiation for the shared model.
A deployed room uses Cloudflare Access identity. A browser also receives application membership that owns its subsequent room operations. Room names, participant IDs, track names, and SFU session IDs are locators, not credentials.
The first successful participant becomes the room creator and can terminate the room. Other participants can leave their own membership. The example keeps provider secrets on the Worker and validates operations before calling the SFU.
Complete the local quickstart to prepare the checkout and .dev.vars file. A deployed room uses Workers, Durable Objects, and a Cloudflare Access application protecting the Worker hostname.
-
Complete the Access application setup for your Worker hostname. Keep the Access team domain and application audience for deployment.
-
From
realtime-examples/video-room, run the deployment script:npm run deploy -- --secrets-file .dev.vars --var "CF_ACCESS_TEAM_DOMAIN:<TEAM_NAME>.cloudflareaccess.com" --var "CF_ACCESS_AUD:<ACCESS_APPLICATION_AUDIENCE>"yarn run deploy -- --secrets-file .dev.vars --var "CF_ACCESS_TEAM_DOMAIN:<TEAM_NAME>.cloudflareaccess.com" --var "CF_ACCESS_AUD:<ACCESS_APPLICATION_AUDIENCE>"pnpm run deploy -- --secrets-file .dev.vars --var "CF_ACCESS_TEAM_DOMAIN:<TEAM_NAME>.cloudflareaccess.com" --var "CF_ACCESS_AUD:<ACCESS_APPLICATION_AUDIENCE>"Replace both Access placeholders. The script uploads the SFU values as Worker secrets. The Worker verifies the matching Access identity before allowing room operations.
-
On the protected hostname, open
/rooms/two-browser-check. Sign in and repeat the quickstart's two-participant verification before sharing the deployment.
The application defines these transitions:
| Action | Behavior |
|---|---|
| Join | Create membership plus publishing and receiving sessions |
| Refresh or replace a failed connection | Retain application identity, replace SFU sessions, republish, and rebuild subscriptions |
| Leave | Close known resources and confirm cleanup before clearing membership in the browser |
| Terminate room | The creator ends the room and starts cleanup for its participants |
| Abandoned tab | Heartbeat expiry schedules cleanup, with retries when resource closure fails |
A missing heartbeat can leave an abandoned participant visible for up to 45 seconds before it becomes eligible for cleanup. Backend errors can delay completion. Repeated reconnect setup failures can require a page reload.
End active rooms and wait for cleanup confirmation before removing the backend. From the example directory, delete the Worker:
npx wrangler deleteyarn wrangler deletepnpm wrangler deleteRemove the associated Access application separately. Delete .dev.vars when you no longer need the credentials. Delete a dedicated SFU app only after all applications using it have stopped.
Keep authentication, resource ownership, and the per-session negotiation queues when adapting the example. Replace display and presence behavior through the browser/backend contract rather than trusting browser-supplied SFU identifiers.
The example does not implement screen sharing, chat, recording, simulcast controls, or advanced layouts. Rate limiting, room quotas, and Access policy provisioning remain application responsibilities.
Trace the example's signaling and state flow ↗ and SDP serialization ↗.
Use the troubleshooting guide ↗ for media, authentication, and lifecycle symptoms.