Skip to content

StreamCrafter — Advanced

WebCodecs encoding is auto-enabled on supported browsers (Chromium-family with RTCRtpScriptTransform). It provides background-safe encoding — the stream continues even when the browser tab loses focus.

The encoder runs in a Web Worker and uses RTCRtpScriptTransform to inject encoded frames into the WebRTC connection. This bypasses the browser’s default encoder, which pauses when the tab is backgrounded.

If you need to force standard WebRTC encoding:

// Vanilla / headless
const studio = createStreamCrafter({
whipUrl: "...",
useWebCodecs: false,
});

Override default encoding parameters for fine-grained control:

studio.setEncoderOverrides({
video: { width: 1920, height: 1080, bitrate: 4_000_000, framerate: 30 },
audio: { bitrate: 128_000, sampleRate: 48_000, numberOfChannels: 2 },
});
OverrideTypeDescription
video.widthnumberOutput width in pixels
video.heightnumberOutput height in pixels
video.bitratenumberTarget video bitrate in bps
video.frameratenumberTarget frame rate
audio.bitratenumberTarget audio bitrate in bps
audio.sampleRatenumberAudio sample rate in Hz
audio.numberOfChannelsnumberAudio channel count

Every field is optional; unset fields keep the quality profile’s value. There is no keyframe-interval override.

With audio mixing enabled, StreamCrafter mixes the audio of all active sources through the Web Audio API. The React, Svelte, and Web Component drop-in components enable it. createStreamCrafter() and the useStreamCrafterV2 / createStreamCrafterContextV2 controllers leave it off unless you pass audioMixing: true:

const studio = createStreamCrafter({
whipUrl: "...",
audioMixing: true,
});
// Volume ranges from 0 to 2 (1 = unchanged, 2 = 200% boost)
studio.setSourceVolume("camera-1", 0.8);
studio.setSourceVolume("screen-1", 0.3);
// Mute/unmute
studio.setSourceMuted("camera-1", true);
studio.masterVolume = 0.5; // 0 to 2; applies to the mixed output

Master volume requires audioMixing: true. Without it, setting masterVolume does nothing and reading it returns 1.

The mixed output passes through a fixed compressor (threshold -24 dB, ratio 4:1) followed by a limiter at -1 dB to prevent clipping. Levels are not normalized by the number of sources.

StreamCrafter automatically reconnects when the WHIP connection drops. Enabled by default.

const studio = createStreamCrafter({
whipUrl: "...",
reconnection: {
enabled: true,
maxAttempts: 5, // Maximum reconnection attempts
baseDelay: 1000, // Initial delay (ms)
maxDelay: 30000, // Maximum delay (ms)
backoffMultiplier: 2, // Exponential backoff factor
},
});
studio.on("reconnectionAttempt", ({ attempt, maxAttempts }) => {
console.log(`Reconnecting: attempt ${attempt} of ${maxAttempts}`);
});
studio.on("reconnectionSuccess", () => {
console.log("Reconnected!");
});
studio.on("reconnectionFailed", ({ error }) => {
console.log("Reconnection failed:", error);
});
studio.reactiveState.on("reconnecting", (isReconnecting) => {
reconnectBanner.hidden = !isReconnecting;
});

The SDK can call the public resolveIngestEndpoint GraphQL field to resolve a WHIP endpoint from a stream key. The React, Svelte, and Web Component drop-in components do this when given gatewayUrl and streamKey without a whipUrl. createStreamCrafter() requires a whipUrl, so resolve it first with IngestClient (see Manual Gateway Resolution).

The resolver chooses an ingest node based on:

  • Geographic proximity
  • Node load and capacity
  • Stream configuration

Run Gateway resolution from the publisher’s browser or device. If your application backend resolves the ingest endpoint, Foghorn scores the backend or proxy IP instead of the publisher location. For OBS, hardware encoders, or server-side publishing, use the dashboard DNS ingest URL (edge-ingest.{tenant}.cdn.{base} when present, then global or cluster-concrete fallback) or Foghorn’s /ingest/{streamKey} front door, which applies the same scoring without a GraphQL client.

For custom UIs that need to resolve a WHIP URL before initializing:

import { IngestClient } from "@livepeer-frameworks/streamcrafter-core";
const client = new IngestClient({
gatewayUrl: "https://bridge.frameworks.network/graphql",
streamKey: "my-stream-key",
});
const endpoints = await client.resolve();
const whipUrl = endpoints.primary.whipUrl;
if (!whipUrl) throw new Error("No WHIP endpoint resolved");
// Now use the resolved URL
const studio = createStreamCrafter({ whipUrl });
// Camera with a specific device and quality profile
await studio.startCamera({
videoDeviceId: "<VIDEO_DEVICE_ID>",
audioDeviceId: "<AUDIO_DEVICE_ID>",
profile: "professional",
});
// Or pass raw MediaStreamConstraints
await studio.startCamera({
customConstraints: {
video: { width: 1920, height: 1080, frameRate: 30 },
audio: true,
},
});
// Screen share with system audio
await studio.startScreenShare({ audio: true });
// Custom MediaStream
studio.addCustomSource(customStream, "External Source");
// List active sources
const sources = studio.sources;
// [
// { id: "camera-1", type: "camera", label: "FaceTime HD", muted: false, volume: 1 },
// { id: "screen-1", type: "screen", label: "Screen", muted: false, volume: 1 }
// ]
// Set primary video (determines which source is shown in non-compositor mode)
studio.setPrimaryVideo("camera-1");
// Remove a source
studio.removeSource("screen-1");
studio.on("sourceAdded", ({ source }) => {
console.log("New source:", source.label);
});
studio.on("sourceRemoved", ({ sourceId }) => {
console.log("Removed:", sourceId);
});
studio.on("sourceUpdated", ({ source, changes }) => {
console.log("Updated:", source.id, changes);
});

startCamera() accepts videoDeviceId, audioDeviceId, facingMode ("user" or "environment"), profile, and customConstraints. Device IDs come from await studio.devices.

StreamCrafter ships with 17 theme presets matching the player’s theme system.

// Switch theme at runtime
studio.theme = "tokyo-night";

All tokens use the --fw-sc-* prefix:

TokenPurpose
--fw-sc-bgBackground color
--fw-sc-surfacePanel surfaces
--fw-sc-borderBorder color
--fw-sc-textPrimary text
--fw-sc-text-mutedSecondary text
--fw-sc-accentInteractive elements
--fw-sc-successSuccess/live indicators
--fw-sc-dangerError/destructive actions
fw-streamcrafter,
.fw-sc-root {
--fw-sc-accent: 262 80% 60%;
--fw-sc-bg: 230 15% 12%;
}

Five built-in locales: en, es, fr, de, nl.

const studio = createStreamCrafter({
whipUrl: "...",
locale: "de",
});
// Or with custom translations
const studio = createStreamCrafter({
whipUrl: "...",
locale: "de",
translations: {
goLive: "Live gehen",
stopStreaming: "Stoppen",
addCamera: "Kamera hinzufugen",
},
});

Override default keyboard shortcuts:

const studio = createStreamCrafter({
whipUrl: "...",
keyMap: {
toggleStream: ["g"],
toggleMute: ["m"],
addCamera: ["c"],
shareScreen: ["s"],
},
});

Default hotkeys:

ActionDefault Key
Toggle streamingShift+Enter
Toggle mutem
Add camerac
Add screen shares
Toggle settings,