Skip to content

StreamCrafter — Vanilla JS

Terminal window
npm install @livepeer-frameworks/streamcrafter-core
import "@livepeer-frameworks/streamcrafter-core/streamcrafter.css";

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; // false
studio.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";
});
// Cleanup
studio.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.

All queries are synchronous getters on the instance.

PropertyTypeDescription
stateIngestStateCurrent lifecycle state
streamingbooleanWhether currently live
capturingbooleanWhether media is captured
reconnectingbooleanWhether reconnecting
sourcesMediaSource[]Active media sources
primaryVideoMediaSource | nullPrimary video source
masterVolumenumberMaster volume (0-2)
profileQualityProfileCurrent quality profile
compositorEnabledbooleanWhether compositor is active
webCodecsActivebooleanWhether WebCodecs encoder is active
MutationSignatureDescription
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) => voidRemove a source
setSourceVolume(id, vol)(id, number) => voidSet source volume (0-2)
setSourceMuted(id, muted)(id, boolean) => voidMute/unmute source
setSourceActive(id, active)(id, boolean) => voidSet active source
setPrimaryVideo(id)(id: string) => voidSet 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) => voidOverride encoder settings
enableCompositor(config?)(config?) => Promise<void>Start the compositor
disableCompositor()() => voidStop the compositor
destroy()() => voidCleanup 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.

Event-based subscriptions using on(event, listener). Returns an unsubscribe function.

const unsub = studio.on("stateChange", ({ state, context }) => {
console.log("New state:", state);
});
unsub();
EventPayloadDescription
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
statsUpdateIngestStatsStreaming statistics update
deviceChange{ devices }Available devices changed
error{ error, recoverable }Error occurred
reconnectionAttempt{ attempt, maxAttempts }Reconnection attempt started
reconnectionSuccessvoidReconnection succeeded
reconnectionFailed{ error }All reconnection attempts failed
webCodecsActive{ active }WebCodecs encoder state changed

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 subscribing
const isLive = studio.reactiveState.get("streaming");
PropertyTypeTrigger Events
stateIngestStatestateChange
stateContextIngestStateContextV2stateChange
streamingbooleanstateChange
capturingbooleanstateChange
reconnectingbooleanstateChange, reconnection* events
sourcesMediaSource[]sourceAdded, sourceRemoved, sourceUpdated
primaryVideoMediaSource | nullsourceAdded, sourceRemoved, sourceUpdated
masterVolumenumberstateChange
profileQualityProfilequalityChanged
error{ error, recoverable } | nullerror, stateChange
compositorEnabledbooleanstateChange
webCodecsActivebooleanwebCodecsActive
OptionTypeDefaultDescription
whipUrlstring— (required)WHIP ingest endpoint
whipUrlsstring[]—Additional WHIP endpoints tried in order after whipUrl on reconnect
resolveWhipUrl(signal) => Promise<string>—Resolve the destination before each connection; takes precedence over whipUrl
targetstring | HTMLElement—Element that receives theme variables and keyboard shortcuts (renders no UI)
profileQualityProfile"broadcast"Quality preset name
audioMixingbooleanfalseMix source audio through the Web Audio mixer (required for masterVolume)
compositorPartial<CompositorConfig>—Defaults applied when enableCompositor() is called
themeFwThemePreset | StudioThemeOverrides"default"Theme preset or token overrides
localeStudioLocale"en"Language for t()
translationsPartial<StudioTranslationStrings>—Custom translation overrides
keyMapPartial<StudioKeyMap>—Custom hotkey bindings
reconnectionPartial<ReconnectionConfig>{ enabled: true, maxAttempts: 5 }Reconnection settings
iceServersRTCIceServer[]—STUN/TURN servers for the WHIP connection
debugbooleanfalseDebug logging
useWebCodecsbooleanautoWebCodecs 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.

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();

The vanilla facade already exposes advanced operations directly:

// Device enumeration
const devices = await studio.devices;
// Compositor (off by default in the vanilla facade)
await studio.enableCompositor({ renderer: "webgl" });
// Stats
const stats = await studio.stats;