Cloud gaming

Last updated Sep 22, 2026 Run Freedoom in a Cloudflare Container, stream its audio and video through Realtime SFU, and send browser input back over DataChannels. Multiple viewers can watch while one browser tab controls the game.

This experimental example demonstrates a native publisher and browser receivers. The same media-and-control topology can inform remote application and device interfaces.

Open the example

How it works

The Container captures H.264 video and Opus audio. Its native publisher sends both tracks through a WebRTC connection to Realtime SFU. Each browser uses one SFU session and PeerConnection for received media and input DataChannels.

Application boundary

Browser tab

Worker

Game state

Realtime SFU

Container publisher

Access identity + per-tab capability · One SFU session and PeerConnection per browser tab

App request

Typed RPC

Start or stop

Register publisher

Create SFU resources

Publish H.264 + Opus

Publish two input channels

Subscribe to media + channels

Enable controller replies

Reliable keyboard + buttons

Replaceable pointer movement

Forward controller input

A Worker authenticates browser requests and holds the SFU credentials. A Durable Object owns the game run, viewer membership, controller selection, and resource cleanup. The Container's publisher and the browser exchange media and input through the SFU.

Requirements

Run and verify

  1. Prepare the example. Run:

sh git clone https://github.com/cloudflare/realtime-examples.git cd realtime-examples/cloud-gaming

npmyarnpnpmbun

npm install

yarn install

pnpm install

bun install

Create the ignored configuration file:

sh cp .dev.vars.example .dev.vars chmod 600 .dev.vars

Set REALTIME_SFU_APP_ID and REALTIME_SFU_BEARER_TOKEN to your SFU app values. 2. Start the application. With your container engine running, start development:npmyarnpnpm

npm run dev

yarn run dev

pnpm run dev

Open http://localhost:8787/. The local identity path works on loopback hosts only. 3. Start the game. Select Start and wait for Running. Open a second browser tab and confirm both tabs receive video and audio. 4. Take control. In one tab, select Take control. Once control is ready, select the game to capture the pointer, then use the keyboard and mouse. The other tab should remain view-only.

Use Send Esc for the game menu. The browser's Escape action releases pointer lock. Select Release control to allow another tab to take control.

For deployment, follow the example's deployment instructions ↗︎. They install the SFU values as Worker secrets and configure the Access team domain and audience. Protect the hostname with the matching Access policy before inviting viewers.

Choose delivery settings for input

The example uses separate named channels for discrete actions and replaceable input:

Input Delivery Reason
Keyboard, buttons, wheel, and reset Reliable and ordered Preserve discrete actions and their order
Pointer movement Unordered, maxRetransmits: 0 New movement replaces older movement

The publisher chooses each channel's policy, and every subscriber mirrors it. Each browser uses the matching delivery settings with its own allocated channel ID. Refer to DataChannel delivery settings.

Control ownership

Viewers initially subscribe with canReply: false and acknowledge channel readiness with waitForAck. The backend grants reply access by updating the selected viewer's existing subscriptions.

The application identifies each controller selection with a generation. The browser sends input only after the publisher confirms its viewer and generation. The application releases held input when control changes, pointer lock is lost, or the run ends.

canReply restricts the SFU return path. The backend still authenticates and authorizes the operator. Refer to reply access and the example's architecture ↗︎.

Stop and clean up

Select Stop game and wait for Offline before deleting a deployment. The application stops the Container and closes known SFU tracks and DataChannels. Follow the cleanup instructions ↗︎ to remove the Worker and Access application.

A failed media pipeline ends the run; transparent publisher restart is not implemented.

Adapt the example

The example implements one fixed game slot and one controller. Admission limits, rate limiting, and Access policy provisioning are deployment responsibilities.

For a shared CAD viewer or simulation, replace Freedoom and adapt application startup, capture, and input handling. Preserve viewer authorization, controller ownership, and cleanup. Add the file ownership and save/export behavior your application needs. Refer to production integration ↗︎ for the application policies and controls to add.

Inspect the implementation

Inspect the example's input channel definitions ↗︎ to compare reliable controls with transient pointer movement.

Troubleshooting

Use the troubleshooting guide ↗︎ to diagnose Container startup, media, and control failures.

Try another example

Pocket Radio applies a related observer/controller pattern on embedded hardware.

Was this helpful?

YesNo

On this page

Cloud gaming - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/realtime/sfu/examples/cloud-gaming/#page","headline":"Cloud gaming","description":"Stream a Container's audio and video to browsers and return authorized keyboard and pointer input through Realtime SFU.","url":"https://developers.cloudflare.com/realtime/sfu/examples/cloud-gaming/","inLanguage":"en","image":"https://developers.cloudflare.com/realtime/sfu/examples/cloud-gaming/og.png?v=469bafc757092260","dateModified":"2026-09-22","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}


description: Connect embedded firmware to Realtime SFU for audio, telemetry, and browser control using the ESP32 Pocket Radio example. title: Embedded devices and remote control image: https://developers.cloudflare.com/realtime/sfu/examples/embedded-devices/og.png?v=39e1a846a14cc8cf


Skip to content

Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/realtime/llms.txt
Use this file to discover all available pages before exploring further.