Migrate from Mux
Mux pairs a video API with the Mux Data QoE product. Mux and FrameWorks overlap on the live-streaming basics, but the API shape, the playback codec/protocol surface, and a few access-control features differ enough that an endpoint-by-endpoint swap won’t get you all the way there. Some Mux pieces (Mux Data, broad SDK coverage, productized DRM) are worth keeping during the cut-over.
- What stays: RTMP and SRT ingest, HLS playback, signed JWT playback, simulcast targets, live-to-VOD recording, clips.
- What changes: REST → GraphQL · API access token (HTTP Basic) → developer token (Bearer)
or wallet sign-in · Mux webhooks → FrameWorks webhooks with Standard Webhooks signatures (or
the
tenantEventssubscription) ·?token=JWT →?jwt=JWT, signed with ES256 instead of RS256 ·aud-scoped tokens (per asset type) → one signed URL gates HLS / LL-HLS / DASH / WHEP / MP4 ·@mux/mux-player-react→@livepeer-frameworks/player-*· built-in referrer/UA playback restrictions → webhook playback policy. - Capabilities to evaluate: E-RTMP ingest (HEVC/AV1 contribution over RTMP) · WHIP ingest · LL-HLS / DASH / WebRTC-WHEP delivery alongside HLS · pull-input streams (RTSP / SRT / RIST / HLS / DTSC / MKV / TS-over-UDP) · 24/7 streams with no 12-hour disconnect · AV1 in delivery · sprite-sheet seek previews (≈ Mux storyboard) · self-hosting and hybrid hosting · MCP, wallet auth, and x402 for agents · Skipper · experimental device-discovery productization.
- What’s not yet at parity: Mux Data-class QoE breadth — FrameWorks now has first-party view-level QoE (rebuffering, frame drops, startup, VOD retention) for its own player, but cross-player coverage and ad observability are still maturing · DRM · live auto-captions · productized custom-domain SKU · number of official server-side SDKs (Mux ships Node/Python/Ruby/Go/PHP/Elixir/ Java/.NET; FrameWorks ships TypeScript, Go, and Python) · built-in referrer/UA playback restrictions as a no-code policy · Master Access download. See the gap list before cutting over production traffic.
Translation table
Section titled “Translation table”The Mux shapes you know, mapped to FrameWorks:
Streams
Section titled “Streams”| Mux | FrameWorks |
|---|---|
POST /video/v1/live-streams | createStream(input) mutation |
GET /video/v1/live-streams/{id} | stream(id) query |
PATCH /video/v1/live-streams/{id} | updateStream(id, input) mutation |
DELETE /video/v1/live-streams/{id} | deleteStream(id) mutation |
PUT /video/v1/live-streams/{id}/disable and /enable | No direct equivalent; manage at the encoder side |
latency_mode: "standard" | "reduced" | "low" | Choose the protocol/profile at the player — HLS for VOD/standard, LL-HLS for reduced, WHEP for low — see Player (Playback) |
new_asset_settings for live-to-VOD | record: true at create, or startDVR(streamId) post-create |
| 12-hour continuous-stream auto-disconnect | No fixed cap; 24/7 streams supported, including continuous DVR archive replay through chapters |
| URL pull is VOD-only; no live pull-source ingest | createStream(input: { ingestMode: PULL, pullSource: { sourceUri }, sourceLocation }) for live URL pull from RTSP / SRT / RIST / HLS / DTSC / MKV / TS-over-UDP — see Pull-input streams |
| (RTMP pull is unsupported on Mux) | RTMP pull is not supported on FrameWorks either; bridge via FFmpeg, MistServer, or srt-live-transmit to SRT / RIST / RTSP / HLS |
Recordings, clips, and VOD
Section titled “Recordings, clips, and VOD”| Mux | FrameWorks |
|---|---|
POST /video/v1/assets with input: { url: "mux://assets/{asset_id}", start_time, end_time } | createClip(input) mutation |
| Instant clipping (segment-level) vs asset clips (frame-accurate) | FrameWorks clips are GOP-aligned today; frame-accurate trimming is not exposed |
POST /video/v1/uploads (one-shot HTTP PUT URL) | createVodUpload(input) → S3 multipart upload |
POST /video/v1/assets with input.url: "https://..." (URL ingest) | importVodAsset(input: { url, filename, title }) — see Import a video from a URL |
GET /video/v1/assets/{id} | vodAsset(id) query |
DELETE /video/v1/assets/{id} | deleteVodAsset(id) mutation |
Storyboard .vtt + JPG sprite | Sprite-sheet (10×10) + WebVTT cue file, generated on every recording |
mp4_support: "standard" | "high" for static MP4 renditions | /play/{id}.mp4 progressive — always available, no gated tier |
| Master Access (downloadable original) | Not exposed today |
FrameWorks DVR is not just live-to-VOD asset creation. A long-running stream can keep one DVR archive for the full stream session: live viewers use the bounded DVR window, and replay viewers play finalized chapters as canonical VOD artifacts (each chapter is published with its own playback ID). See Recordings, Clips & VOD.
Simulcast / multistream
Section titled “Simulcast / multistream”| Mux | FrameWorks |
|---|---|
POST /video/v1/live-streams/{id}/simulcast-targets | createPushTarget(streamId, input) mutation |
DELETE /video/v1/live-streams/{id}/simulcast-targets/{target_id} | deletePushTarget(id) mutation |
| Up to 6 simultaneous targets per stream | No documented hard cap; tested across Twitch / YouTube / Facebook / X / Kick |
| Per-target rendition selection | Not exposed today (default output is pushed) |
Simulcast events on webhooks (.starting / .broadcasting / .errored / .idle) | multistream.status_changed on webhooks or tenantEvents |
Access control
Section titled “Access control”Mux’s “access control” covers two separate concerns: authenticating your application to the API, and gating viewer playback. FrameWorks splits the same way — developer tokens for the API, playback policies for viewers.
API and dashboard access
| Mux | FrameWorks |
|---|---|
HTTP Basic with <token_id>:<token_secret> (per-environment) | Developer token (Bearer) — see Agent Access for wallet/x402 alternatives |
| Team login | Email/password login |
| — | Wallet sign-in (EIP-191) |
Viewer-side playback access
| Mux | FrameWorks |
|---|---|
playback_policy: "public" | Default public playback ID |
playback_policy: "signed" | setPlaybackPolicy({ streamId, policy: { type: JWT, jwt: { allowedKids } } }) — see Playback Access Control |
POST /system/v1/signing-keys — RSA-2048, RS256 | createSigningKey(input: { name }) — ES256, ECDSA P-256 (asymmetric, customer holds private key) |
JWT claims sub (playback ID) · aud (v / t / g / s / d) · exp · kid | kid header and exp claim required; aud only if your policy requires it; sub can hold a viewer ID. One signed URL covers HLS, LL-HLS, DASH, WHEP, MP4 |
?token=<jwt> query param | ?jwt=<token> universally; Authorization: Bearer where playback paths can carry headers |
Built-in playback restrictions (referrer-domain allowlist, user-agent rules) via playback_restriction_id | WEBHOOK policy: HMAC-signed POST to your URL; return 200 to allow, anything else denies. Implement referrer/UA logic in your endpoint |
| DRM (Widevine + PlayReady + FairPlay) | DRM in development — not GA |
Real-time events
Section titled “Real-time events”FrameWorks delivers the events below as webhooks and over the
tenantEvents subscription, with the same payloads.
| Mux webhook | FrameWorks event |
|---|---|
video.live_stream.connected | stream.connected |
video.live_stream.active | stream.live |
video.live_stream.disconnected / .idle | stream.idle |
video.live_stream.recording | No event; liveStreamEvents(streamId) carries detailed stream state |
video.live_stream.created / .updated / .deleted | stream.created / stream.updated / stream.deleted |
video.asset.ready / .errored | upload.ready / upload.failed for uploads, recording.* for recordings, clip.* for clips |
video.asset.created / .updated / .deleted | upload.created for uploads; no asset update or delete event |
video.upload.created / .asset_created / .errored / .cancelled | upload.created / upload.completed / upload.failed / upload.aborted |
video.live_stream.simulcast_target.starting / .broadcasting / .errored / .idle | multistream.status_changed |
video.asset.track.* (subtitles/captions ready) | Live captions / transcription not GA on FrameWorks |
| Mux CLI webhook forwarding for local dev | Subscribe to tenantEvents on wss://bridge.frameworks.network/graphql/ws from your dev machine, or expose a public HTTPS URL |
Webhook endpoints must be public HTTPS URLs; FrameWorks refuses private and loopback addresses.
For local development, subscribe to tenantEvents or use a tunnel that gives you a public URL.
Step-by-step
Section titled “Step-by-step”1. Create a FrameWorks account and a stream
Section titled “1. Create a FrameWorks account and a stream”Sign up at https://chartroom.frameworks.network — email + password, or sign in with an EVM wallet if you’d rather skip email entirely. Then:
mutation CreateStream { createStream(input: { name: "my-first-stream" }) { ... on Stream { id streamKey playbackId } }}You get back a streamKey (secret — for your encoder) and a playbackId (public — for
viewers).
2. Repoint your encoder
Section titled “2. Repoint your encoder”Mux’s RTMP ingest is on rtmp://global-live.mux.com:5222/app (note the non-standard port
5222), RTMPS on rtmps://global-live.mux.com:443/app, and Mux also accepts SRT. FrameWorks
publishes its equivalents via the dashboard or resolveIngestEndpoint(streamKey). FrameWorks
RTMP ingest is plain RTMP on port 1935 with no RTMPS listener, so an encoder using Mux’s RTMPS
URL moves to RTMP, or to SRT or WHIP when the link must be encrypted. RTMP and SRT remain the
low-effort path; FrameWorks additionally accepts E-RTMP (HEVC/AV1
contribution over RTMP) and WHIP (WebRTC ingest) where your encoder supports them.
OBS / FFmpeg / hardware encoder → swap the URL, keep the rest. Watch the port — Mux’s 5222
won’t carry over.
3. Swap the player
Section titled “3. Swap the player”If you were using @mux/mux-player-react:
import MuxPlayer from "@mux/mux-player-react";<MuxPlayer playbackId={playbackId} streamType="on-demand" />import { Player } from "@livepeer-frameworks/player-react";<Player contentType="vod" contentId={playbackId} options={{ gatewayUrl: "https://bridge.frameworks.network/graphql" }} />The player also ships as:
@livepeer-frameworks/player-svelte@livepeer-frameworks/player-wc(<fw-player>)@livepeer-frameworks/player-core(headless / vanilla)
The component prop names and theming model differ — Mux uses kebab-case attributes
(playback-id, stream-type) and a Media Chrome theming surface; the FrameWorks player uses
camelCase props and its own theming primitives. See Player (Playback)
for the full surface.
4. Rewire REST as GraphQL
Section titled “4. Rewire REST as GraphQL”Pick your client:
- Houdini (SvelteKit-native GraphQL codegen)
- urql or Apollo for React
- graphql-request for plain Node/Bun scripts
Codegen against the schema gives you typed queries:
# example with graphql-codegenpnpm dlx graphql-codegen \ --schema https://bridge.frameworks.network/graphql \ --documents 'src/**/*.graphql' \ --generates ./src/gql/generated.ts:typescript,typescript-operationsIf you want to read the schema directly, it lives at
pkg/graphql/schema.graphql
on GitHub, and the playground is at
https://bridge.frameworks.network/graphql/playground.
Mux’s REST shape is the closer of the two big migrations to translate (Studio’s REST is more ad-hoc); most Mux endpoints have a one-to-one GraphQL operation in the Streams translation table above.
5. Point your webhook receiver at FrameWorks
Section titled “5. Point your webhook receiver at FrameWorks”Create an endpoint under Developer → Webhooks (or with createWebhookEndpoint) for the event
types you handle, and store the whsec_... secret it returns. Then swap the signature check and
the event names in your receiver.
Before (Mux):
// Express endpoint receiving Mux webhooksapp.post("/mux-webhook", verifyMuxSignature, (req, res) => { if (req.body.type === "video.live_stream.active") { notifyDashboard(req.body.data.id); } res.sendStatus(200);});After (FrameWorks):
import { Webhook } from "standardwebhooks";
const wh = new Webhook(process.env.FRAMEWORKS_WEBHOOK_SECRET);
app.post("/frameworks-webhook", express.raw({ type: "application/json" }), (req, res) => { let event; try { event = wh.verify(req.body.toString("utf8"), { "webhook-id": req.header("webhook-id"), "webhook-timestamp": req.header("webhook-timestamp"), "webhook-signature": req.header("webhook-signature"), }); } catch { return res.sendStatus(401); } if (event.type === "stream.live") { notifyDashboard(event.data.streamId); } res.sendStatus(204);});Deduplicate on event.id as you would on Mux’s event ID. The webhooks guide
covers the body shape, retries, and replay. If a server-side process holds a connection anyway,
the tenantEvents subscription delivers the same events without an endpoint.
6. Plan playback access control
Section titled “6. Plan playback access control”If your Mux assets and streams are public, this step is short: move to the new playbackId
and update embeds.
If you use Mux signing keys, playback_policy: "signed", or playback_restriction_id for
referrer/UA gating, FrameWorks ships an equivalent surface — see
Playback Access Control. The translation:
POST /system/v1/signing-keys→createSigningKeymutation. The algorithm changes from RS256 (RSA-2048) to ES256 (ECDSA P-256) — re-mint with a new private key and update your signing library.playback_policy: "signed"→setPlaybackPolicywithtype: JWT.?token=<jwt>query param →?jwt=<token>query param. Universal across HLS, LL-HLS, DASH, WHEP, and MP4 — no per-asset-typeaudswap.Authorization: Bearerworks on playback request paths where headers are available.- Built-in referrer / user-agent restrictions (
playback_restriction_idclaim) → implement viaWEBHOOKpolicy. FrameWorks POSTs an HMAC-signed body; your endpoint inspects theRefererandUser-Agentheaders and returns 200 to allow or 403 to deny.
Signing keys do not transfer between platforms — reissue on FrameWorks. Migration concierge available via in-app ticket if you have a large library of pre-signed JWTs to roll over.
7. Re-attach simulcast targets
Section titled “7. Re-attach simulcast targets”Targets are stream-scoped on both platforms — they don’t carry over when a stream is recreated. After your stream is up:
mutation Restream { createPushTarget( streamId: "stream_xxx" input: { platform: "twitch" name: "Twitch — main channel" targetUri: "rtmp://live.twitch.tv/app/live_xxxxxxxxxx_xxx" } ) { id status }}8. Validate side by side
Section titled “8. Validate side by side”Run a low-traffic test stream through both platforms in parallel for a day. Watch the
FrameWorks side via
liveStreamEvents
and liveViewerMetrics, compare against Mux’s dashboard, and only point production traffic
over once you trust the numbers.
What you gain on FrameWorks
Section titled “What you gain on FrameWorks”- LL-HLS, DASH, and WebRTC/WHEP delivery alongside HLS — Mux documents HLS as its standard live playback path. WHEP gives you ~sub-second one-to-many playback (this is playback, not multi-publisher conferencing).
- WHIP ingest for sub-second contribution from browsers and OBS WebRTC out.
- E-RTMP ingest — HEVC and AV1 contribution over RTMP, beyond H.264.
- SRT both directions — Mux receives SRT but doesn’t deliver it. FrameWorks does both.
- AV1 in delivery where the selected cluster supports it.
- 24/7 streams with DVR replay. No 12-hour auto-disconnect; long-running broadcasts and infinite live channels are supported natively. DVR archive replay is chapter-based: live seekback stays bounded for performance, while historical viewers request bounded chapter playlists over the same continuous archive.
- Pull-input streams for live:
createStream(input: { ingestMode: PULL, pullSource })pulls from RTSP, SRT, RIST, HLS, DTSC, MKV/WebM, or TS-over-UDP (unicast or multicast). Self-hosted edges can pull from RFC1918 / multicast sources only when the pull source is pinned to clusters withallow_private_pull_sources=true. Mux’s URL pull is VOD-only. RTMP pull is not supported on either platform — bridge through FFmpeg/MistServer/srt-live-transmitif you need it. See Pull-input streams. Experimental media-engine discovery exists locally today; FrameWorks packaging through Edge, Tray, CLI, API, and dashboard workflows is tracked on the roadmap. - Sprite-sheet seek previews (Mux’s storyboard analogue) — a 10×10 grid plus a WebVTT cue file, generated for every recording, ready for hover-scrub UIs.
- Self-hosted edge and hybrid hosting. Run MistServer and Helmsman edge capacity on your own hardware while FrameWorks operates Foghorn, federation, DNS/TLS, storage, analytics, and the control plane. To run Foghorn and the control plane yourself, self-host the entire stack as an independent platform; see Deployment.
- Cross-cluster federation — one viewer pool routed across clusters; the closer edge wins automatically.
- Agent-first surface. MCP server at
/.well-known/mcp.json, wallet auth (EIP-191), and gasless USDC payments via x402 (EIP-3009). See Agent Access. - Skipper — an in-product AI consultant for API discovery and diagnostics. Reach it from
the dashboard, the docs (
⌘J), or MCP. - Player packages for React, Svelte, Web Components, and vanilla JS.
What’s not yet at parity, and what’s coming
Section titled “What’s not yet at parity, and what’s coming”Mux ships several things FrameWorks doesn’t have a one-for-one answer for yet. Check which of these you depend on before cutting over.
-
Mux Data-class view-level QoE analytics. Mux Data is best-in-class here — view-level traces, ad observability, and a player-agnostic SDK that spans Video.js, hls.js, Shaka, native HTML5, Roku, Chromecast, and OTT surfaces. FrameWorks has closed a good chunk of this: the FrameWorks player now reports view-level QoE — rebuffering ratio, frame drops, startup time-to-first-frame, bitrate/rendition switches, exit-before-start, and per-asset VOD retention/watch-density — surfaced in the dashboard at
/analytics/qoe. What’s still on the roadmap is the breadth Mux Data has: QoE from third-party / non-FrameWorks players and ad-specific observability. If your stack leans on those today, weigh that gap before cutting over. -
DRM — Widevine, PlayReady, and FairPlay are in development; not GA. Mux ships productized DRM today.
-
Live auto-captions / transcription — on the roadmap, not shipping. Mux ships live auto-captions today.
-
Custom-domain availability depends on billing entitlement. Mux offers productized custom-domain delivery. FrameWorks materializes explicit platform-subdomain and BYO-domain entitlements from the active subscription; downgrade tears down served DNS/TLS state while retaining the requested name for later re-entitlement.
-
Number of official server-side SDKs. Mux ships official SDKs for Node, Python, Ruby, Go, PHP, Elixir, Java, and .NET, all covering both Mux Video and Mux Data. FrameWorks ships SDKs for TypeScript (Node and browsers), Go, and Python, plus the player suite (
@livepeer-frameworks/player-*); for Ruby, PHP, Elixir, Java, and .NET, use GraphQL codegen against the public schema. -
Built-in referrer / user-agent playback restrictions. Mux exposes them as a no-code policy. The webhook playback policy can express the same rules, but it’s not a toggle.
-
Master Access — the downloadable original of an asset.
-
Frame-accurate clip trimming. FrameWorks clips today are GOP-aligned; Mux offers both asset-clips (frame-accurate) and instant clips (segment-level).
-
Live captions / subtitle track lifecycle webhooks. Tracked alongside the captions feature.
Beta and tricky bits
Section titled “Beta and tricky bits”- Billing rates are still maturing. Rates may adjust as we scale, but you keep the allowances you signed up for.
- Self-hosted and hybrid production cutovers need operator validation. Test DNS, TLS, firewall policy, capacity, node enrollment, and upgrade runbooks before moving production traffic.
- Marketplace primitives ship; the vetted public marketplace program is on the roadmap. Validate operator vetting, pricing, approval flow, and support boundaries before relying on third-party clusters for production traffic.
- Encoder port gotcha. Mux’s RTMP endpoint uses non-standard port
5222; FrameWorks uses the conventional1935. Update any hard-coded encoder configs and firewall allowlists. - JWT algorithm change: RS256 → ES256. Re-mint your tokens with a new private key. Your
signing library import changes (e.g.
RS256→ES256injsonwebtoken). - Single-token, single transport. Mux scopes JWTs by
aud(vfor video,tfor thumbnail,gfor GIF,sfor storyboard,dfor DRM). FrameWorks treats one signed URL as the auth boundary across HLS / LL-HLS / DASH / WHEP / MP4 — no per-asset-type token swap in your client. - GraphQL learning curve. If you’ve never written a query, the docs playground is the quickest way to inspect available fields. Skipper can also draft queries.
- Webhook bodies render 64-bit integers as strings.
sizeBytes,durationMs, and money amounts arrive as"7340032", not7340032; parse them explicitly. - The player swap is not API-compatible. Props, attribute casing, and theming differ — budget a few hours to walk through Player (Playback) examples.
- Stream keys, playback IDs, and asset IDs are not transferable. Reissue stream keys and update embeds.
- Playback signing is not key-portable. Re-create signing keys in FrameWorks and update
your token transport from
?token=to?jwt=(orAuthorization: Bearer). - No webhook history backfill. Past Mux events stay on Mux. A FrameWorks webhook endpoint receives events from the moment it is created, so create it before you move traffic.
Get help
Section titled “Get help”| Channel | Best for | Where |
|---|---|---|
| Skipper (AI consultant) | Quick troubleshooting, API discovery, diagnostics, after-hours, non-human callers | Dashboard /skipper · ⌘J in docs · MCP at /mcp |
| In-app ticketing | Account-specific issues, billing, anything tied to your tenant data, library-migration help | Dashboard /support |
| Forum | Long-form Q&A, recipes, searchable community knowledge | forum.frameworks.network |
| Discord | Real-time chat, casual questions | Join |
| GitHub | Bugs, feature requests, deeper technical threads | Livepeer-FrameWorks/monorepo |