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: a clean stream that loads fast on cellular, responds to touch, doesn’t bake the device, and survives the iOS Safari quirks that break naive integrations. Streampixel works on mobile via the device browser — Safari on iOS and Chrome on Android. There is no native mobile SDK; everything is the same Web SDK with a different config profile.

Step 1 — Detect mobile, don’t guess

User-agent sniffing is fragile, but for switching between desktop and mobile config it’s good enough:
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);
Use this to feed different values into the SDK — not to gate features. A user on a Pixel Tablet wants the desktop experience; a user on an iPhone Mini wants tighter bitrate caps. Detect, then choose.

Step 2 — Mobile-tuned SDK init

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport"
      content="width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=no" />
    <title>Streampixel Mobile</title>
    <style>
      html, body { margin: 0; height: 100%; background: #000;
        overscroll-behavior: none; }
      #stream { width: 100vw; height: 100dvh; touch-action: none; }
      #unmute {
        position: fixed; inset: 0; display: flex;
        align-items: center; justify-content: center;
        background: rgba(0,0,0,0.7); color: #fff;
        font-family: ui-sans-serif, system-ui; font-size: 18px;
        z-index: 10;
      }
    </style>
  </head>
  <body>
    <div id="stream"></div>
    <div id="unmute">Tap to start</div>

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

      const PROJECT_ID = 'YOUR_PROJECT_ID';
      const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);

      const { appStream, pixelStreaming, UIControl } =
        await StreamPixelApplication({
          appId: PROJECT_ID,
          AutoConnect: true,
          forceTurn: true,
          touchInput: true,
          startResolutionMobile: '480p',  // mobile-only opener
          preferredCodec: 'H264',          // universal mobile decoder
          maxBitrate: isMobile ? 4_000_000 : 10_000_000,
        });

      appStream.onVideoInitialized = () => {
        document.getElementById('stream').append(appStream.rootElement);
      };

      // iOS requires a user gesture to play audio.
      document.getElementById('unmute').addEventListener('click', () => {
        UIControl.toggleAudio?.();
        document.getElementById('unmute').remove();
      }, { once: true });
    </script>
  </body>
</html>
What each option buys you:
  • touchInput: true — translate browser touch events into Pixel Streaming touch input so Unreal sees taps as taps, not as mouse clicks at (0, 0).
  • startResolutionMobile: '480p' — load fast. Adaptive bitrate negotiates upward as the connection allows.
  • preferredCodec: 'H264' — every mobile decoder has H264 silicon. VP9 software-decodes on most phones and burns CPU.
  • maxBitrate — cap the stream so an over-eager adaptive controller doesn’t try to push 12 Mbps over LTE.
  • forceTurn: true — most carrier NATs block direct WebRTC; TURN relays just work.

Step 3 — Touch UI in Unreal

The browser-side touchInput: true flag delivers touch events to Unreal. Whether your Unreal project does anything useful with them depends on what touch UI is built into the project itself. For projects packaged with on-screen joysticks, on-screen buttons, or other touch HUD, see Hide / unhide touch controls for runtime control of those overlays. Show them when touchInput is active and hide them on desktop.

Step 4 — Bandwidth budget

Plan for the network the user actually has, not the network you wish they had:
ConnectionRealistic sustained bitrateRecommended maxBitrate
LTE (good signal)10–20 Mbps4 Mbps
LTE (weak / congested)1–3 Mbps2 Mbps
5G (sub-6)50–200 Mbps6 Mbps
5G (mmWave)500+ Mbps8 Mbps
Hotel / cafe Wi-Fi5–20 Mbps shared4 Mbps
maxBitrate is in bits per second. The 4 Mbps cap above is 4_000_000. Adaptive bitrate will scale down further on bad networks; it will not scale up beyond the cap.
If your user base skews toward mobile data plans with hard caps, default to 2–3 Mbps and let users opt into higher quality from a settings menu. The bandwidth saved over a 30-minute session adds up.

Step 5 — Battery and thermals

A long mobile streaming session is a sustained workload: GPU decode, screen at full brightness, radio active. Phones throttle when they get hot, and a throttled phone produces a worse stream. Things you can do from your application:
  • Set a sensible AFK timeout. If the user puts the phone down for 30 seconds, Streampixel can disconnect them and free the worker. They can reconnect when they pick it back up.
  • Drop FPS targets if your UE project supports it. A console command via emitConsoleCommand('t.MaxFPS 30') halves render work and saves battery on long-form, low-motion content (configurators, tours).
  • Honour prefers-reduced-motion for any spinner / loader animations on your page.
There’s nothing the streaming side can do about a phone’s thermal envelope. Cap session length where appropriate or design for short bursts.

Step 6 — iOS Safari quirks

iOS has the most edge cases. The big ones:

Audio requires a user gesture

iOS will not start playing audio until a tap, click, or other input event. The “Tap to start” overlay in the example above exists to satisfy that requirement. Without it, the stream connects, video plays muted, and audio never starts even after audio.play() is called.
document.getElementById('unmute').addEventListener('click', () => {
  UIControl.toggleAudio?.();
}, { once: true });

Autoplay policies

Even muted autoplay can fail in some Safari configurations, especially with PiP enabled. The Web SDK handles the muted autoplay attribute correctly, but if you wrap it in your own <video> element, set playsinline muted autoplay explicitly.

Tab background

iOS pauses all media playback when the tab backgrounds. Streampixel disconnects after 60 seconds of background; if the user returns sooner, the stream resumes. Beyond 60 seconds, they reconnect (see Troubleshooting — mobile disconnects).

Viewport units

100vh does not behave the same on iOS Safari as it does in desktop browsers — the URL bar takes a slice and 100vh doesn’t account for it. Use 100dvh (dynamic viewport height) for full-screen layouts:
#stream { height: 100dvh; }

Step 7 — Orientation

Most streamed UE projects are designed for landscape. Lock orientation if your project depends on it:
<meta name="screen-orientation" content="landscape" />
That hint is non-binding. To force-rotate, listen for the orientation change and show a “Please rotate your device” overlay in portrait:
function checkOrientation() {
  const portrait = window.matchMedia('(orientation: portrait)').matches;
  document.getElementById('rotate-hint').style.display = portrait ? 'flex' : 'none';
}
window.addEventListener('orientationchange', checkOrientation);
window.addEventListener('resize', checkOrientation);
checkOrientation();
The Screen Orientation API’s lock() method works on Android Chrome but is not supported on iOS Safari, which is why a visual hint is the portable solution.

Gotchas

Without the viewport meta tag pinning user-scalable=no and CSS touch-action: none on the stream container, browser pinch-zoom and double-tap-to-zoom intercept touches before they reach Unreal. The example above sets both.
Streams launched from a saved-to-home-screen PWA on iOS run in a stripped-down WebView with stricter autoplay, no PiP, and quirky media handling. Test there explicitly if you support that install path.
Scrolling on Android Chrome shrinks/expands the URL bar, which resizes the viewport and can trigger video reflow. Using 100dvh and avoiding scroll on the stream page (overscroll-behavior: none) prevents this.
Some carriers and corporate cellular gateways block UDP entirely, which kills WebRTC media. forceTurn: true falls back to TCP via TURN, at the cost of a few ms of latency and slightly higher relay load. Worth it for reach.
If your stream UI includes a text input (chat, login), opening the soft keyboard reflows the page and can shift the video. Use position: fixed on the stream container and let the keyboard overlay it instead of resizing.

Next steps

Hide / unhide touch controls

Toggle the UE-side touch HUD from your web page.

Performance tuning

Bitrate and codec recipes for every device class.

Resolution settings

Set per-device opening resolution defaults.

Troubleshooting

Mobile disconnects, audio gating, and other common issues.