Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.streampixel.io/llms.txt

Use this file to discover all available pages before exploring further.

The outcome: one Unreal Engine instance runs on a Streampixel worker; many viewers all see and hear the same scene in real time. One host with input authority, the rest along for the ride.

Standard streaming vs. SFU

In standard mode, Streampixel allocates one worker per user. Two users on the same project means two separate Unreal instances, two separate worlds, two separate save files. Useful for solo experiences (configurators, walkthroughs, tutorials). In SFU mode (Selective Forwarding Unit), Streampixel allocates one worker. The worker streams its video and audio to a media server, which fans the same stream out to every connected viewer. There is one Unreal instance, one world state, one set of game logic running.
StandardSFU
UE instancesOne per userOne total
Cost per concurrent userHighLow
Input authorityEach user controls their ownHost only by default
Use caseConfigurators, single-player demosWatch parties, classrooms, presentations, co-viewing
ScaleLimited by worker fleetTens of viewers per host
SFU is best when most viewers are passively watching what the host is doing. If every user needs independent control of their own avatar in a shared world, that is a true multiplayer game and belongs in a different architecture (dedicated UE server, players connect as Pixel Streaming clients with their own workers).

Roles

  • Host — connects with sfuHost: true. Their input drives the Unreal instance. Exactly one host per session.
  • Viewer — connects with sfuPlayer: true. Receives video and audio. Input is ignored by default.
Host and viewer use the same appId (project ID). They are distinguished by the SDK init flags and by the streamerId query param when present.

Step 1 — Prerequisites

RequirementNotes
Streampixel projectActive build distributed. See Uploading your build.
Web SDK installednpm install git+https://github.com/infinity-void-metaverse/Streampixel-Web-SDK.git#latest
Unreal project that handles multiple players gracefullySee UE-side notes below.

Step 2 — Host page

The host loads the project and asserts authority over the scene.
<!-- host.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Streampixel SFU Host</title>
    <style>
      html, body { margin: 0; height: 100%; background: #000; }
      #stream { width: 100vw; height: 100vh; }
      .badge {
        position: fixed; top: 12px; left: 12px;
        background: #b91c1c; color: white; padding: 6px 10px;
        border-radius: 4px; font-family: ui-sans-serif, system-ui;
        font-size: 12px; z-index: 10;
      }
    </style>
  </head>
  <body>
    <div class="badge">HOST</div>
    <div id="stream"></div>

    <script type="module">
      import { StreamPixelApplication } from 'streampixelsdk';

      const PROJECT_ID = 'YOUR_PROJECT_ID';
      const STREAMER_ID = 'host-1'; // viewers must use the same value

      const { appStream, pixelStreaming } = await StreamPixelApplication({
        appId: PROJECT_ID,
        AutoConnect: true,
        sfuHost: true,
        streamerId: STREAMER_ID,
        forceTurn: true,
      });

      appStream.onVideoInitialized = () => {
        document.getElementById('stream').append(appStream.rootElement);
      };
    </script>
  </body>
</html>
The host is a normal interactive stream: keyboard, mouse, gamepad input flows through to Unreal as usual.

Step 3 — Viewer page

Viewers connect with sfuPlayer: true and the same streamerId as the host.
<!-- viewer.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Streampixel SFU Viewer</title>
    <style>
      html, body { margin: 0; height: 100%; background: #000; }
      #stream { width: 100vw; height: 100vh; }
      .badge {
        position: fixed; top: 12px; left: 12px;
        background: #1d4ed8; color: white; padding: 6px 10px;
        border-radius: 4px; font-family: ui-sans-serif, system-ui;
        font-size: 12px; z-index: 10;
      }
    </style>
  </head>
  <body>
    <div class="badge">VIEWER</div>
    <div id="stream"></div>

    <script type="module">
      import { StreamPixelApplication } from 'streampixelsdk';

      const PROJECT_ID = 'YOUR_PROJECT_ID';
      const STREAMER_ID = 'host-1'; // must match the host

      const { appStream } = await StreamPixelApplication({
        appId: PROJECT_ID,
        AutoConnect: true,
        sfuPlayer: true,
        streamerId: STREAMER_ID,
        forceTurn: true,
      });

      appStream.onVideoInitialized = () => {
        document.getElementById('stream').append(appStream.rootElement);
      };
    </script>
  </body>
</html>
Viewers see what the host sees, frame-for-frame, with sub-second additional latency from the SFU hop.
In production, serve a single page that reads ?role=host or ?role=viewer from the URL and toggles the SDK flags accordingly. Keeping host and viewer logic on different bundles makes it harder for a viewer to flip themselves into host mode by editing localStorage.

Step 4 — Voice chat (optional)

Video and audio from the Unreal scene are already streamed. If you want viewers to talk to each other, layer in StreamPixelVoiceChat, which is backed by LiveKit:
import { StreamPixelVoiceChat } from 'streampixelsdk';

const voice = await StreamPixelVoiceChat({
  appId: PROJECT_ID,
  roomName: 'host-1-voice',  // every participant uses the same room
  identity: crypto.randomUUID(),
  enableMicrophone: true,
});

voice.on('participantConnected', (p) => {
  console.log('joined:', p.identity);
});
The roomName is the join key — give it the same value across host and all viewers in a session. A common pattern is ${STREAMER_ID}-voice.
Voice chat needs a microphone permission, which browsers only grant in response to a user gesture. Initialize StreamPixelVoiceChat from a button click, not on page load.
For UE-side voice (avatars speaking inside the game world), see Built-in voice and text chat.

UE-side considerations

The Unreal project does not magically become multiplayer-aware just because Streampixel is fanning out the stream. SFU streams whatever the one running UE instance renders. What this means in practice:
  • One PlayerController. The host’s input drives a single PlayerController. Viewers do not have their own pawns or cameras — they see exactly what the host’s camera is rendering.
  • No replication needed. Because there is only one UE instance, you don’t need network replication, dedicated servers, or any of the multiplayer plumbing. It’s single-player UE that happens to have many spectators.
  • Audio is mixed at the source. Anything played in the UE world is heard by all viewers. Spatial audio still works but the listener is the host’s audio listener, not each viewer’s.
  • If you want viewer input — for example, voting, polls, drawing on the screen — send it as JSON via emitUIInteraction() from the viewer page and handle it in Unreal Blueprint with a Pixel Streaming Input Component. The host remains in control of the camera and movement; viewers can only influence things you explicitly expose.

Limits

A single host can serve tens of viewers depending on bitrate, codec, and network conditions. Concrete planning numbers:
Bitrate per viewerApproximate viewers per host
8 Mbps (1080p high quality)10–20
4 Mbps (1080p standard)25–40
2 Mbps (720p)40–60
These are practical ceilings, not hard limits. For audiences in the hundreds, run multiple host sessions and load-balance.
Latency between the host and the furthest viewer is dominated by the SFU’s location and the viewer’s RTT. Pick a region close to the geographic center of your audience.

Gotchas

Mouse clicks, key presses, and gamepad input from viewer pages do not reach Unreal. This is intentional — otherwise a hundred viewers could fight over the camera. If you need viewers to interact, route those interactions through your own application code (e.g., a “raise hand” button in your UI), not through raw input.
There is no automatic host migration. If the host’s tab closes, the Unreal instance is reclaimed and viewers see a disconnect. Detect this in your viewer code by listening for the disconnect event and showing “host has left” UI.
Host and viewers must use identical streamerId values. A typo means viewers connect to a different worker (or none) and never see the host. Generate the value once in your application and pass it down to both pages.
Two pages connecting with sfuHost: true and the same streamerId is undefined behavior — typically the second connection is rejected. Enforce single-host on your side with a session lock.
SFU forwards the same encoded frames to every viewer. If the host encodes in AV1 (Chrome desktop only), viewers on Safari will not be able to decode. For broad audiences, lock the codec to H264. See Codec settings.

Next steps

Voice and text chat

UE-side voice and chat for in-game communication.

Meeting rooms

Pre-built room flows on top of LiveKit voice.

Performance tuning

Pick the right bitrate and codec for your viewer count.

Regions

Choose a region close to your audience.