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.

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

Use gatewayUrl + streamKey only when the resolve request runs in the publisher’s browser or device. For backend or encoder-side publishing, prefer 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-1)
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
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
destroy()() => voidCleanup and tear down

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
sourcesMediaSource[]sourceAdded, sourceRemoved, sourceUpdated
primaryVideoMediaSource | nullsourceUpdated
masterVolumenumberstateChange
profileQualityProfilequalityChanged
errorstring | nullerror, stateChange
compositorEnabledbooleanstateChange
webCodecsActivebooleanwebCodecsActive
OptionTypeDefaultDescription
targetstring | HTMLElementContainer element or selector
whipUrlstringDirect WHIP ingest endpoint
gatewayUrlstringGateway GraphQL endpoint for resolver-backed ingest environments
streamKeystringStream key used with gatewayUrl
profilestring"broadcast"Quality preset name
themestring"default"Theme preset
localestring"en"UI language
keyMapKeyMapCustom hotkey bindings
reconnectionReconnectionConfig{ enabled: true }Reconnection settings
debugbooleanfalseDebug logging
enableCompositorbooleantrueEnable compositor
useWebCodecsbooleanautoWebCodecs encoder (auto-enabled on Chromium)

Omit target to run without any UI — useful for building entirely custom interfaces:

const studio = createStreamCrafter({
whipUrl: "https://edge-ingest.frameworks.network/webrtc/key",
profile: "broadcast",
});
// No UI rendered — control everything programmatically
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
await studio.enableCompositor({ renderer: "webgl" });
// Stats
const stats = await studio.stats;