StreamCrafter — Vanilla JS
Installation
Section titled “Installation”npm install @livepeer-frameworks/streamcrafter-coreimport "@livepeer-frameworks/streamcrafter-core/streamcrafter.css";createStreamCrafter()
Section titled “createStreamCrafter()”The primary API for vanilla and headless usage. Returns a property-based facade with queries, mutations, and subscriptions.
The vanilla facade is headless: it renders no UI. The optional target element scopes theme CSS
variables and keyboard shortcuts; your own markup provides the preview and controls. For a built-in
UI without a framework, use the Web Component.
import { createStreamCrafter } from "@livepeer-frameworks/streamcrafter-core";import "@livepeer-frameworks/streamcrafter-core/streamcrafter.css";
const studio = createStreamCrafter({ target: "#studio-container", whipUrl: "https://edge-ingest.frameworks.network/webrtc/stream-key", profile: "broadcast", audioMixing: true, debug: true,});
// Read state (queries)studio.state; // "idle"studio.streaming; // falsestudio.sources; // []studio.profile; // "broadcast"
// Change state (mutations)await studio.startCamera();await studio.startScreenShare({ audio: true });await studio.goLive();
// React to changes (subscriptions)const unsub = studio.reactiveState.on("streaming", (isLive) => { goLiveBtn.textContent = isLive ? "Stop" : "Go Live";});
// Cleanupstudio.destroy();whipUrl is required. createStreamCrafter() does not accept gatewayUrl or streamKey; to
resolve a WHIP URL from a stream key through the Gateway’s resolveIngestEndpoint field, run
IngestClient first and pass the result:
import { IngestClient, createStreamCrafter } from "@livepeer-frameworks/streamcrafter-core";
const client = new IngestClient({ gatewayUrl: "https://bridge.frameworks.network/graphql", streamKey: "<YOUR_STREAM_KEY>",});const endpoints = await client.resolve();const whipUrl = endpoints.primary.whipUrl;if (!whipUrl) throw new Error("No WHIP endpoint resolved");
const studio = createStreamCrafter({ whipUrl, audioMixing: true });Run the resolve in the publisher’s browser so ingest routing uses the publisher’s location. For backend or encoder-side publishing, use the dashboard DNS ingest URL directly.
Queries (Read State)
Section titled “Queries (Read State)”All queries are synchronous getters on the instance.
| Property | Type | Description |
|---|---|---|
state | IngestState | Current lifecycle state |
streaming | boolean | Whether currently live |
capturing | boolean | Whether media is captured |
reconnecting | boolean | Whether reconnecting |
sources | MediaSource[] | Active media sources |
primaryVideo | MediaSource | null | Primary video source |
masterVolume | number | Master volume (0-2) |
profile | QualityProfile | Current quality profile |
compositorEnabled | boolean | Whether compositor is active |
webCodecsActive | boolean | Whether WebCodecs encoder is active |
Mutations (Change State)
Section titled “Mutations (Change State)”| Mutation | Signature | Description |
|---|---|---|
startCamera(opts?) | (opts?) => Promise<MediaSource> | Add camera source |
startScreenShare(opts?) | (opts?) => Promise<MediaSource | null> | Add screen share source |
goLive() | () => Promise<void> | Start streaming |
stop() | () => Promise<void> | Stop streaming |
removeSource(id) | (id: string) => void | Remove a source |
setSourceVolume(id, vol) | (id, number) => void | Set source volume (0-2) |
setSourceMuted(id, muted) | (id, boolean) => void | Mute/unmute source |
setSourceActive(id, active) | (id, boolean) => void | Set active source |
setPrimaryVideo(id) | (id: string) => void | Set primary video |
profile = | set profile(p) | Change quality profile |
masterVolume = | set masterVolume(v) | Set master volume |
theme = | set theme(t) | Switch theme preset |
setEncoderOverrides(o) | (overrides) => void | Override encoder settings |
enableCompositor(config?) | (config?) => Promise<void> | Start the compositor |
disableCompositor() | () => void | Stop the compositor |
destroy() | () => void | Cleanup and tear down |
masterVolume only takes effect when the instance was created with audioMixing: true. Without
audio mixing, setting it does nothing and reading it returns 1.
Subscriptions — Events
Section titled “Subscriptions — Events”Event-based subscriptions using on(event, listener). Returns an unsubscribe function.
const unsub = studio.on("stateChange", ({ state, context }) => { console.log("New state:", state);});unsub();| Event | Payload | Description |
|---|---|---|
stateChange | { state, context } | Lifecycle state transition |
sourceAdded | { source } | New source added |
sourceRemoved | { sourceId } | Source removed |
sourceUpdated | { source, changes } | Source properties changed |
qualityChanged | { profile, previousProfile } | Quality profile changed |
statsUpdate | IngestStats | Streaming statistics update |
deviceChange | { devices } | Available devices changed |
error | { error, recoverable } | Error occurred |
reconnectionAttempt | { attempt, maxAttempts } | Reconnection attempt started |
reconnectionSuccess | void | Reconnection succeeded |
reconnectionFailed | { error } | All reconnection attempts failed |
webCodecsActive | { active } | WebCodecs encoder state changed |
Subscriptions — Reactive State
Section titled “Subscriptions — Reactive State”Per-property reactive subscriptions with immediate invocation. The callback fires immediately with the current value, then on every change.
const unsub = studio.reactiveState.on("streaming", (isLive) => { goLiveBtn.textContent = isLive ? "Stop" : "Go Live";});
const unsub2 = studio.reactiveState.on("sources", (sources) => { sourceList.innerHTML = sources.map((s) => `<li>${s.label}</li>`).join("");});
// Read current value without subscribingconst isLive = studio.reactiveState.get("streaming");| Property | Type | Trigger Events |
|---|---|---|
state | IngestState | stateChange |
stateContext | IngestStateContextV2 | stateChange |
streaming | boolean | stateChange |
capturing | boolean | stateChange |
reconnecting | boolean | stateChange, reconnection* events |
sources | MediaSource[] | sourceAdded, sourceRemoved, sourceUpdated |
primaryVideo | MediaSource | null | sourceAdded, sourceRemoved, sourceUpdated |
masterVolume | number | stateChange |
profile | QualityProfile | qualityChanged |
error | { error, recoverable } | null | error, stateChange |
compositorEnabled | boolean | stateChange |
webCodecsActive | boolean | webCodecsActive |
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
whipUrl | string | — (required) | WHIP ingest endpoint |
whipUrls | string[] | — | Additional WHIP endpoints tried in order after whipUrl on reconnect |
resolveWhipUrl | (signal) => Promise<string> | — | Resolve the destination before each connection; takes precedence over whipUrl |
target | string | HTMLElement | — | Element that receives theme variables and keyboard shortcuts (renders no UI) |
profile | QualityProfile | "broadcast" | Quality preset name |
audioMixing | boolean | false | Mix source audio through the Web Audio mixer (required for masterVolume) |
compositor | Partial<CompositorConfig> | — | Defaults applied when enableCompositor() is called |
theme | FwThemePreset | StudioThemeOverrides | "default" | Theme preset or token overrides |
locale | StudioLocale | "en" | Language for t() |
translations | Partial<StudioTranslationStrings> | — | Custom translation overrides |
keyMap | Partial<StudioKeyMap> | — | Custom hotkey bindings |
reconnection | Partial<ReconnectionConfig> | { enabled: true, maxAttempts: 5 } | Reconnection settings |
iceServers | RTCIceServer[] | — | STUN/TURN servers for the WHIP connection |
debug | boolean | false | Debug logging |
useWebCodecs | boolean | auto | WebCodecs encoder (auto-enabled on Chromium) |
The compositor is off until you call enableCompositor(). The React, Svelte, and Web Component
drop-in components enable it by default; the vanilla facade does not.
Headless Mode
Section titled “Headless Mode”The facade never renders UI, with or without target. Attach the output stream to your own
elements:
const studio = createStreamCrafter({ whipUrl: "https://edge-ingest.frameworks.network/webrtc/key", profile: "broadcast",});
await studio.startCamera();const stream = studio.mediaStream;myVideoElement.srcObject = stream;
studio.reactiveState.on("state", (s) => updateUI(s));await studio.goLive();Advanced Controls
Section titled “Advanced Controls”The vanilla facade already exposes advanced operations directly:
// Device enumerationconst devices = await studio.devices;
// Compositor (off by default in the vanilla facade)await studio.enableCompositor({ renderer: "webgl" });
// Statsconst stats = await studio.stats;