feat(packages): add mux media with src parsing, structured source, and storyboards - #1850
Conversation
✅ Deploy Preview for vjs10-site ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📦 Bundle Size Report🎨 @videojs/html
Presets (7)
Media (10)
Players (5)
Skins (30)
UI Components (39)
Sizes are marginal over the root entry point. ⚛️ @videojs/react
Presets (7)
Media (9)
Skins (27)
UI Components (33)
Sizes are marginal over the root entry point. 🧩 @videojs/core
Entries (68)
🏷️ @videojs/element — no changesEntries (2)
📦 @videojs/store — no changesEntries (3)
🔧 @videojs/utils
Entries (12)
📦 @videojs/spf — no changesEntries (4)
ℹ️ How to interpretJS sizes are initial static graph totals (minified + brotli). Lazy dynamic chunks are shown separately when present.
Run |
mihar-22
left a comment
There was a problem hiding this comment.
As usual, awesome work! Left a bunch of comments/feedback and approved so you can decide what is worth addressing and what could be a fast follow. Thank you!
|
Looks like this breaks our |
…media element types
Compare sources with deepEqual instead of reference equality so idiomatic
React usage (source={{ playbackId }}) does not re-dispatch sourcechange on
every render. Also keep the playback token a string when parsing a src URL
so numeric-looking tokens are not coerced.
mihar-22
left a comment
There was a problem hiding this comment.
This is beautiful work, Wes! Amazing 👏
| function MuxStoryboard({ media }: { media: MuxMedia }) { | ||
| const subscribe = useCallback( | ||
| (onChange: () => void) => { | ||
| media.addEventListener('streamtypechange', onChange); | ||
| media.addEventListener('sourcechange', onChange); | ||
| return () => { | ||
| media.removeEventListener('streamtypechange', onChange); | ||
| media.removeEventListener('sourcechange', onChange); | ||
| }; | ||
| }, | ||
| [media] | ||
| ); | ||
|
|
||
| // The stream type is detected at runtime and live streams have no storyboard. | ||
| const getSnapshot = () => (media.streamType === StreamTypes.LIVE ? '' : media.storyboard); | ||
| const src = useSyncExternalStore(subscribe, getSnapshot, getSnapshot); | ||
|
|
||
| if (!src) return null; | ||
| return <track kind="metadata" label="thumbnails" src={src} default />; | ||
| } |
useSyncProps only synced keys present on props, so removing a prop (e.g. a storyboard override) on a later render left the old value stuck on the media. Track previously synced keys and reset removed ones to their defaults before applying the current props.
Prop syncing writes src/source during render and those setters dispatch sourcechange synchronously, notifying the storyboard subscription while another component renders. Defer and coalesce notifications with a microtask; render-driven changes are still picked up by the child render in the same pass.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 7b1f07b. Configure here.

Refs #1797
Summary
Reworks
mux-video/mux-audioaround two ways to load Mux content: a plainsrcstream URL (the playback ID is parsed out automatically) or a structuredsourceobject. Thumbnail and storyboard URLs are derived from the same source, so the storyboard track auto-configures itself.HTML
A storyboard
<track>is injected automatically (skipped for live streams):React
API Reference
Props — HTML
<mux-video>/<mux-audio>, ReactMuxVideo/MuxAudiosrchttps://stream.<domain>/<playback-id>.m3u8?...) are parsed intosource; other URLs pass through. The HTML attribute reflects the active playback URL.sourceMuxSource. Setting it derivessrcfromplaybackId+customDomain+playbackparams.nullclearssrc.thumbnailmux-video/MuxVideoonly)source.storyboardmux-video/MuxVideoonly)source; drives the auto-injected/rendered storyboard<track>(skipped for live streams).MuxSource(core,@videojs/core/dom/media/mux)Params mirror the documented Mux URL modifiers as typed camelCase keys, serialized to
snake_casequery params — unknown keys pass through the same way. Parsing asrcURL round-trips them back to typed camelCase values.MuxPlaybackParamstoken,maxResolution,minResolution,renditionOrder,programStartTime,programEndTime,assetStartTime,assetEndTime,redundantStreams,rokuTrickPlay,defaultSubtitlesLang,excludePdt, customMuxThumbnailParamstoken,ext,time,width,height,rotate,fitMode,flipV,flipH,programTime,latest, customMuxStoryboardParamstoken,format, customMuxDrmParamstokenSigned playback rules:
playback.tokenreplaces every other playback param (they must be baked into the token).aud: 't'/aud: 's'); a mismatch yields no URL.Core utils (exported from
@videojs/core/dom/media/mux)createMuxVideoURLMuxSource→ stream URLparseMuxVideoURLMuxSource(typed params);undefinedfor non-Mux URLscreateMuxThumbnailURLMuxSource(+ optional explicit params) → thumbnail image URLcreateMuxStoryboardURLMuxSource→ storyboard VTT URLcreateMuxQuery?snake_case=...query string (token-only when a token is set)isSameMuxSourceMuxSourcevaluesEvents
sourcechangeMuxMedia(the media host)sourceor parsed from a newsrc. Readsourcefor the new value. Used internally to reflect thesrcattribute and re-sync the storyboard track. Setting an equivalent source (e.g. a new object with the same values on a React re-render) does not re-fire.Testing
pnpm -F @videojs/core test src/dom/media/mux, plus html/reactsrc/mediasuites. Covers URL building and parsing (round-trip with typed params), signed-playback param stripping, thumbnail/storyboard derivation and overrides,sourcechangedispatch (including structural-equality skips), and storyboard track injection (including the live-stream skip).Note
Medium Risk
Touches the core playback path for all Mux HTML/React elements (src derivation, signed URL rules, live storyboard skip); changes are broad but covered by extensive unit tests.
Overview
Mux playback is reworked around a new
MuxMediahost: set a Mux HLSsrcor a structuredsourceobject (playback ID, custom domain, typed playback params), with URLs built/parsed via shared utils and asourcechangeevent when the effective source changes structurally.HTML and React
mux-video/mux-audionow useMuxMediainstead of plain HLS. Video gains optionalthumbnail/storyboardprops and automatic storyboard<track>injection from the Mux source (skipped for live streams); sandboxes drop manual storyboard wiring. React adds a smallMuxStoryboardsubscriber (deferred updates) anduseSyncPropsresets so an omittedsrcdoes not clearsrcderived fromsource.Supporting pieces include JWT audience checks for signed thumbnail/storyboard URLs,
deepEqual/parseJwt/snakeCasein utils, sandboxgetMuxAssetIdviaparseMuxVideoURL, and API-docs handling for hoistedCustomMediaElementbases withascasts.Reviewed by Cursor Bugbot for commit 9183a31. Bugbot is set up for automated code reviews on this repo. Configure here.