Skip to content

feat(packages): add mux media with src parsing, structured source, and storyboards - #1850

Merged
luwes merged 21 commits into
mainfrom
feat/mux-media-playback-id
Jul 27, 2026
Merged

feat(packages): add mux media with src parsing, structured source, and storyboards#1850
luwes merged 21 commits into
mainfrom
feat/mux-media-playback-id

Conversation

@luwes

@luwes luwes commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Refs #1797

Summary

Reworks mux-video / mux-audio around two ways to load Mux content: a plain src stream URL (the playback ID is parsed out automatically) or a structured source object. Thumbnail and storyboard URLs are derived from the same source, so the storyboard track auto-configures itself.

HTML

<!-- src stream URL — playback ID, domain, and params are parsed into `source` -->
<mux-video src="https://stream.mux.com/{PLAYBACK_ID}.m3u8?max_resolution=1080p"></mux-video>

<!-- explicit thumbnail/storyboard attributes override the derived URLs -->
<mux-video
  src="https://stream.mux.com/{PLAYBACK_ID}.m3u8"
  thumbnail="https://image.mux.com/{OTHER_ID}/thumbnail.webp"
  storyboard="https://image.mux.com/{OTHER_ID}/storyboard.vtt"
></mux-video>

<mux-audio src="https://stream.mux.com/{PLAYBACK_ID}.m3u8"></mux-audio>
// structured source (JS-only property) — `src` is derived from it
const video = document.querySelector('mux-video');

video.source = {
  playbackId: '{PLAYBACK_ID}',
  customDomain: 'media.example.com', // stream.media.example.com / image.media.example.com
  playback: { maxResolution: '1080p', assetStartTime: 10 }, // → ?max_resolution=1080p&asset_start_time=10
};

// signed playback — the token replaces all other playback params,
// and image URLs require their own audience-checked tokens
video.source = {
  playbackId: '{PLAYBACK_ID}',
  playback: { token: playbackToken },     // aud 'v'
  thumbnail: { token: thumbnailToken },   // aud 't'
  storyboard: { token: storyboardToken }, // aud 's'
};

video.thumbnail; // explicit attribute, or derived from `source`

A storyboard <track> is injected automatically (skipped for live streams):

<mux-video src="https://stream.mux.com/{PLAYBACK_ID}.m3u8">
  <!-- rendered -->
  <track data-storyboard kind="metadata" label="thumbnails" default
         src="https://image.mux.com/{PLAYBACK_ID}/storyboard.vtt?format=webp" />
</mux-video>

React

import { MuxVideo } from '@videojs/react/media/mux-video';
import { MuxAudio } from '@videojs/react/media/mux-audio';

// src stream URL — parsed into `source`, storyboard track rendered automatically
<MuxVideo src="https://stream.mux.com/{PLAYBACK_ID}.m3u8" />

// structured source
<MuxVideo
  source={{
    playbackId: '{PLAYBACK_ID}',
    playback: { maxResolution: '1080p' },
    thumbnail: { time: 5, ext: 'jpg' },
  }}
/>

// explicit URLs override the derived ones
<MuxVideo source={{ playbackId: '{PLAYBACK_ID}' }} storyboard="https://image.mux.com/{OTHER_ID}/storyboard.vtt" />

<MuxAudio source={{ playbackId: '{PLAYBACK_ID}' }} />

API Reference

Props — HTML <mux-video> / <mux-audio>, React MuxVideo / MuxAudio

API Kind Description
src attribute + prop Stream URL. Mux stream URLs (https://stream.<domain>/<playback-id>.m3u8?...) are parsed into source; other URLs pass through. The HTML attribute reflects the active playback URL.
source property / prop (JS-only, no attribute) Structured MuxSource. Setting it derives src from playbackId + customDomain + playback params. null clears src.
thumbnail attribute + prop (mux-video / MuxVideo only) Thumbnail image URL. Empty ⇒ derived from source.
storyboard attribute + prop (mux-video / MuxVideo only) Storyboard VTT URL. Empty ⇒ derived from source; drives the auto-injected/rendered storyboard <track> (skipped for live streams).

MuxSource (core, @videojs/core/dom/media/mux)

interface MuxSource {
  playbackId: string;
  customDomain?: string;                              // default 'mux.com'
  playback?: MuxPlaybackParams;                       // stream URL query params
  thumbnail?: MuxThumbnailParams | MuxThumbnailParams[]; // first entry used for the derived URL
  storyboard?: MuxStoryboardParams;
  drm?: MuxDrmParams;                                 // accepted, not consumed yet
}

Params mirror the documented Mux URL modifiers as typed camelCase keys, serialized to snake_case query params — unknown keys pass through the same way. Parsing a src URL round-trips them back to typed camelCase values.

Type Keys
MuxPlaybackParams token, maxResolution, minResolution, renditionOrder, programStartTime, programEndTime, assetStartTime, assetEndTime, redundantStreams, rokuTrickPlay, defaultSubtitlesLang, excludePdt, custom
MuxThumbnailParams token, ext, time, width, height, rotate, fitMode, flipV, flipH, programTime, latest, custom
MuxStoryboardParams token, format, custom
MuxDrmParams token

Signed playback rules:

  • A playback.token replaces every other playback param (they must be baked into the token).
  • Thumbnail/storyboard tokens are audience-checked (aud: 't' / aud: 's'); a mismatch yields no URL.
  • Signed playback without a matching image token yields no derived thumbnail/storyboard URL (unsigned requests would be rejected).
Core utils (exported from @videojs/core/dom/media/mux)
Export Description
createMuxVideoURL MuxSource → stream URL
parseMuxVideoURL Stream URL → MuxSource (typed params); undefined for non-Mux URLs
createMuxThumbnailURL MuxSource (+ optional explicit params) → thumbnail image URL
createMuxStoryboardURL MuxSource → storyboard VTT URL
createMuxQuery Params object → ?snake_case=... query string (token-only when a token is set)
isSameMuxSource Structural equality for MuxSource values

Events

Event Dispatched by When
sourcechange MuxMedia (the media host) The effective source changes structurally — set via source or parsed from a new src. Read source for the new value. Used internally to reflect the src attribute 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/react src/media suites. Covers URL building and parsing (round-trip with typed params), signed-playback param stripping, thumbnail/storyboard derivation and overrides, sourcechange dispatch (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 MuxMedia host: set a Mux HLS src or a structured source object (playback ID, custom domain, typed playback params), with URLs built/parsed via shared utils and a sourcechange event when the effective source changes structurally.

HTML and React mux-video / mux-audio now use MuxMedia instead of plain HLS. Video gains optional thumbnail / storyboard props and automatic storyboard <track> injection from the Mux source (skipped for live streams); sandboxes drop manual storyboard wiring. React adds a small MuxStoryboard subscriber (deferred updates) and useSyncProps resets so an omitted src does not clear src derived from source.

Supporting pieces include JWT audience checks for signed thumbnail/storyboard URLs, deepEqual / parseJwt / snakeCase in utils, sandbox getMuxAssetId via parseMuxVideoURL, and API-docs handling for hoisted CustomMediaElement bases with as casts.

Reviewed by Cursor Bugbot for commit 9183a31. Bugbot is set up for automated code reviews on this repo. Configure here.

@netlify

netlify Bot commented Jul 16, 2026

Copy link
Copy Markdown

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit 9183a31
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/6a67dde851e93e00080f2e13
😎 Deploy Preview https://deploy-preview-1850--vjs10-site.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
v10-sandbox Ready Ready Preview, Comment Jul 27, 2026 10:38pm

Request Review

Comment thread packages/core/src/dom/media/mux/utils.ts Outdated
Comment thread apps/sandbox/templates/react-mux-video/main.tsx Outdated
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Size Report

🎨 @videojs/html

Path Base initial PR initial Diff % Lazy
/media/mux-audio 164.00 kB 164.88 kB +894 B +0.5% 🔺
/media/mux-video 163.70 kB 165.04 kB +1.34 kB +0.8% 🔺
Presets (7)
Entry Initial Lazy
/video (default) 51.14 kB 54.24 kB
/video (default + hls) 190.40 kB 54.24 kB
/video (minimal) 51.15 kB 54.24 kB
/video (minimal + hls) 190.57 kB 54.24 kB
/audio (default) 44.42 kB 54.24 kB
/audio (minimal) 41.63 kB 54.24 kB
/background 4.20 kB
Media (10)
Entry Initial
/media/background-video 1.14 kB
/media/container 1.71 kB
/media/dash-video 214.67 kB
/media/hlsjs-video 141.52 kB
/media/mux-audio 164.88 kB
/media/mux-video 165.04 kB
/media/native-hls-video 9.06 kB
/media/simple-hls-audio-only 19.06 kB
/media/simple-hls-video 21.11 kB
/media/vimeo-video 12.33 kB
Players (5)
Entry Initial
/video/player 8.22 kB
/audio/player 5.38 kB
/background/player 3.92 kB
/live-video/player 7.63 kB
/live-audio/player 5.39 kB
Skins (30)
Entry Type Initial Lazy
/video/minimal-skin.css css 5.70 kB
/video/skin.css css 5.65 kB
/video/minimal-skin js 51.10 kB 54.24 kB
/video/minimal-skin.tailwind js 51.87 kB 54.24 kB
/video/skin js 51.12 kB 54.24 kB
/video/skin.tailwind js 51.87 kB 54.24 kB
/audio/minimal-skin.css css 3.97 kB
/audio/skin.css css 3.86 kB
/audio/minimal-skin js 41.62 kB 54.24 kB
/audio/minimal-skin.tailwind js 42.20 kB 54.24 kB
/audio/skin js 44.42 kB 54.24 kB
/audio/skin.tailwind js 44.91 kB 54.24 kB
/background/skin.css css 133 B
/background/skin js 1.14 kB
/live-video/minimal-skin.css css 5.70 kB
/live-video/skin.css css 5.65 kB
/live-video/minimal-skin js 48.06 kB 54.24 kB
/live-video/minimal-skin.tailwind js 48.56 kB 54.24 kB
/live-video/skin js 49.60 kB 54.24 kB
/live-video/skin.tailwind js 50.22 kB 54.24 kB
/live-audio/minimal-skin.css css 3.97 kB
/live-audio/skin.css css 3.86 kB
/live-audio/minimal-skin js 34.00 kB 54.24 kB
/live-audio/minimal-skin.tailwind js 33.42 kB 54.24 kB
/live-audio/skin js 36.91 kB 54.24 kB
/live-audio/skin.tailwind js 36.40 kB 54.24 kB
/global.css css 183 B
/shared.css css 153 B
/tailwind.css css 161 B
/skin-element js 1.51 kB
UI Components (39)
Entry Initial
/ui/airplay-button 2.57 kB
/ui/alert-dialog 2.95 kB
/ui/alert-dialog-close 2.39 kB
/ui/alert-dialog-description 2.39 kB
/ui/alert-dialog-title 2.34 kB
/ui/audio-track-radio-group 3.00 kB
/ui/buffering-indicator 2.68 kB
/ui/captions-button 2.69 kB
/ui/captions-radio-group 3.05 kB
/ui/cast-button 2.62 kB
/ui/compounds 3.22 kB
/ui/controls 2.93 kB
/ui/error-dialog 2.94 kB
/ui/fullscreen-button 2.60 kB
/ui/hotkey 2.45 kB
/ui/menu 2.97 kB
/ui/mute-button 2.61 kB
/ui/pip-button 2.59 kB
/ui/play-button 2.60 kB
/ui/playback-rate-button 2.66 kB
/ui/playback-rate-radio-group 2.89 kB
/ui/popover 3.14 kB
/ui/poster 2.50 kB
/ui/quality-radio-group 3.05 kB
/ui/seek-button 2.63 kB
/ui/seek-indicator 2.75 kB
/ui/seek-indicator-value 528 B
/ui/slider 2.95 kB
/ui/status-announcer 2.54 kB
/ui/status-indicator 2.68 kB
/ui/status-indicator-value 528 B
/ui/thumbnail 2.64 kB
/ui/time 2.93 kB
/ui/time-slider 2.96 kB
/ui/tooltip 2.94 kB
/ui/volume-indicator 2.59 kB
/ui/volume-indicator-fill 464 B
/ui/volume-indicator-value 463 B
/ui/volume-slider 2.95 kB

Sizes are marginal over the root entry point.

⚛️ @videojs/react

Path Base initial PR initial Diff % Lazy
/media/mux-audio 162.38 kB 163.21 kB +849 B +0.5% 🔺
/media/mux-video 162.41 kB 163.31 kB +928 B +0.6% 🔺
Presets (7)
Entry Initial Lazy
/video (default) 40.75 kB 54.24 kB
/video (default + hls) 178.89 kB 54.24 kB
/video (minimal) 41.12 kB 54.24 kB
/video (minimal + hls) 179.21 kB 54.24 kB
/audio (default) 33.91 kB 54.24 kB
/audio (minimal) 33.96 kB 54.24 kB
/background 579 B
Media (9)
Entry Initial
/media/background-video 403 B
/media/dash-video 213.00 kB
/media/hlsjs-video 139.83 kB
/media/mux-audio 163.21 kB
/media/mux-video 163.31 kB
/media/native-hls-video 7.29 kB
/media/simple-hls-audio-only 17.36 kB
/media/simple-hls-video 19.42 kB
/media/vimeo-video 10.49 kB
Skins (27)
Entry Type Initial Lazy
/tailwind.css css 161 B
/video/minimal-skin.css css 5.55 kB
/video/skin.css css 5.52 kB
/video/minimal-skin js 40.98 kB 54.24 kB
/video/minimal-skin.tailwind js 47.11 kB 54.24 kB
/video/skin js 40.65 kB 54.24 kB
/video/skin.tailwind js 46.79 kB 54.24 kB
/audio/minimal-skin.css css 3.79 kB
/audio/skin.css css 3.68 kB
/audio/minimal-skin js 33.88 kB 54.24 kB
/audio/minimal-skin.tailwind js 36.11 kB 54.24 kB
/audio/skin js 33.86 kB 54.24 kB
/audio/skin.tailwind js 38.06 kB 54.24 kB
/background/skin.css css 90 B
/background/skin js 272 B
/live-video/minimal-skin.css css 5.55 kB
/live-video/skin.css css 5.52 kB
/live-video/minimal-skin js 35.43 kB 54.24 kB
/live-video/minimal-skin.tailwind js 41.46 kB 54.24 kB
/live-video/skin js 35.44 kB 54.24 kB
/live-video/skin.tailwind js 41.55 kB 54.24 kB
/live-audio/minimal-skin.css css 3.79 kB
/live-audio/skin.css css 3.68 kB
/live-audio/minimal-skin js 24.25 kB 54.24 kB
/live-audio/minimal-skin.tailwind js 27.42 kB 54.24 kB
/live-audio/skin js 24.31 kB 54.24 kB
/live-audio/skin.tailwind js 27.45 kB 54.24 kB
UI Components (33)
Entry Initial
/ui/airplay-button 2.49 kB
/ui/alert-dialog 2.66 kB
/ui/audio-track 2.32 kB
/ui/buffering-indicator 2.51 kB
/ui/captions-button 2.44 kB
/ui/captions-radio-group 2.28 kB
/ui/cast-button 2.44 kB
/ui/controls 2.48 kB
/ui/error-dialog 2.47 kB
/ui/fullscreen-button 2.46 kB
/ui/gesture 2.31 kB
/ui/hotkey 2.27 kB
/ui/live-button 2.37 kB
/ui/menu 2.60 kB
/ui/mute-button 2.42 kB
/ui/pip-button 2.43 kB
/ui/play-button 2.52 kB
/ui/playback-rate 2.40 kB
/ui/playback-rate-button 2.44 kB
/ui/popover 3.06 kB
/ui/poster 2.42 kB
/ui/quality 2.27 kB
/ui/seek-button 2.42 kB
/ui/seek-indicator 2.60 kB
/ui/slider 2.69 kB
/ui/status-announcer 2.34 kB
/ui/status-indicator 2.38 kB
/ui/thumbnail 2.43 kB
/ui/time 2.27 kB
/ui/time-slider 2.58 kB
/ui/tooltip 3.04 kB
/ui/volume-indicator 2.32 kB
/ui/volume-slider 2.57 kB

Sizes are marginal over the root entry point.

🧩 @videojs/core

Path Base initial PR initial Diff % Lazy
/dom/media/mux 151.37 kB 159.47 kB +8.11 kB +5.4% 🔺
Entries (68)
Entry Initial Lazy
. 10.55 kB
/dom 17.70 kB
/dom/media/custom-media-element 2.09 kB
/dom/media/dash 208.97 kB
/dom/media/google-cast 4.03 kB
/dom/media/hls-js 135.87 kB
/dom/media/media-host 1.25 kB
/dom/media/media-played-ranges 576 B
/dom/media/mux 159.47 kB
/dom/media/native-hls 3.07 kB
/dom/media/simple-hls 18.91 kB
/dom/media/simple-hls-audio-only 16.85 kB
/dom/media/vimeo 9.87 kB
/media/predicate 563 B
/i18n 2.66 kB 54.24 kB
/i18n/locales/all 30.58 kB
/i18n/locales/ar 1.21 kB
/i18n/locales/az 1.08 kB
/i18n/locales/bg 1.25 kB
/i18n/locales/bn 1.27 kB
/i18n/locales/bs 1014 B
/i18n/locales/ca 1.05 kB
/i18n/locales/cs 1.04 kB
/i18n/locales/cy 1.00 kB
/i18n/locales/da 996 B
/i18n/locales/de 1.08 kB
/i18n/locales/el 1.45 kB
/i18n/locales/en 621 B
/i18n/locales/es 1010 B
/i18n/locales/et 1.05 kB
/i18n/locales/eu 1.01 kB
/i18n/locales/fa 1.20 kB
/i18n/locales/fi 1.03 kB
/i18n/locales/fr 1.07 kB
/i18n/locales/gd 1.10 kB
/i18n/locales/gl 1014 B
/i18n/locales/he 1.12 kB
/i18n/locales/hi 1.28 kB
/i18n/locales/hr 1.02 kB
/i18n/locales/hu 1.09 kB
/i18n/locales/it 1023 B
/i18n/locales/ja 1.17 kB
/i18n/locales/ko 1.11 kB
/i18n/locales/lv 1.08 kB
/i18n/locales/mr 1.28 kB
/i18n/locales/nb 1000 B
/i18n/locales/ne 1.28 kB
/i18n/locales/nl 1013 B
/i18n/locales/nn 996 B
/i18n/locales/oc 1.05 kB
/i18n/locales/pl 1.12 kB
/i18n/locales/pt 1.01 kB
/i18n/locales/pt-BR 1.01 kB
/i18n/locales/pt-PT 1009 B
/i18n/locales/ro 1.05 kB
/i18n/locales/ru 1.33 kB
/i18n/locales/sk 1.10 kB
/i18n/locales/sl 1.02 kB
/i18n/locales/sr 1.03 kB
/i18n/locales/sv 1022 B
/i18n/locales/te 1.31 kB
/i18n/locales/th 1.29 kB
/i18n/locales/tr 1.07 kB
/i18n/locales/uk 1.36 kB
/i18n/locales/vi 1.08 kB
/i18n/locales/zh 1.01 kB
/i18n/locales/zh-CN 1.01 kB
/i18n/locales/zh-TW 1.01 kB
🏷️ @videojs/element — no changes
Entries (2)
Entry Initial
. 996 B
/context 943 B
📦 @videojs/store — no changes
Entries (3)
Entry Initial
. 1.39 kB
/html 696 B
/react 361 B

🔧 @videojs/utils

Path Base initial PR initial Diff % Lazy
/jwt 176 B 🆕
Entries (12)
Entry Initial
/array 104 B
/dom 2.74 kB
/events 319 B
/function 327 B
/jwt 176 B
/object 407 B
/predicate 265 B
/percent 281 B
/string 239 B
/style 190 B
/time 813 B
/number 158 B
📦 @videojs/spf — no changes
Entries (4)
Entry Initial
. 4.46 kB
/dom 6.49 kB
/hls 17.88 kB
/background-video 13.27 kB

ℹ️ How to interpret

JS sizes are initial static graph totals (minified + brotli). Lazy dynamic chunks are shown separately when present.

Icon Meaning
No change
🔺 Increased ≤ 10%
🔴 Increased > 10%
🔽 Decreased
🆕 New (no baseline)

Run pnpm size locally to check current initial sizes.

@luwes luwes changed the title feat(packages): add mux media class with playback id and source props feat(packages): add mux media with playback id, source props, and storyboards Jul 16, 2026
Comment thread apps/sandbox/app/shared/mux.ts Outdated
Comment thread packages/core/src/dom/media/mux/utils.ts Outdated

@mihar-22 mihar-22 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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!

Comment thread packages/core/src/dom/media/mux/index.ts
Comment thread packages/core/src/dom/media/mux/index.ts
Comment thread packages/core/src/dom/media/mux/utils.ts Outdated
Comment thread packages/core/src/dom/media/mux/utils.ts Outdated
Comment thread packages/core/src/dom/media/mux/utils.ts Outdated
Comment thread packages/core/src/dom/media/mux/utils.ts Outdated
Comment thread packages/core/src/dom/media/mux/utils.ts Outdated
Comment thread packages/html/src/media/mux-audio/index.ts Outdated
Comment thread packages/html/src/media/mux-video/index.ts Outdated
Comment thread packages/react/src/media/mux-video/index.tsx Outdated
@decepulis

Copy link
Copy Markdown
Collaborator

Looks like this breaks our installation.mdx and api reference example code, and for some reason, fails to generate API reference tables. We should resolve the former and probably also the latter before we release.

@luwes luwes changed the title feat(packages): add mux media with playback id, source props, and storyboards feat(packages): add mux media with src parsing, structured source, and storyboards Jul 24, 2026
Comment thread packages/html/src/media/mux-video/index.ts Outdated
Comment thread packages/core/src/dom/media/mux/index.ts
Comment thread packages/react/src/media/mux-video/index.tsx
Comment thread packages/core/src/dom/media/mux/utils.ts
Comment thread apps/sandbox/app/shared/mux.ts Outdated
Comment thread packages/core/src/dom/media/mux/index.ts
luwes added 3 commits July 27, 2026 12:53
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.
Comment thread packages/react/src/media/mux-video/index.tsx
Comment thread packages/core/src/dom/media/mux/index.ts

@mihar-22 mihar-22 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is beautiful work, Wes! Amazing 👏

Comment on lines +47 to +66
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 />;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice 👌

luwes added 2 commits July 27, 2026 15:08
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.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread packages/react/src/utils/use-sync-props.ts Outdated
@luwes
luwes merged commit 409e7ef into main Jul 27, 2026
26 checks passed
@luwes
luwes deleted the feat/mux-media-playback-id branch July 27, 2026 23:55
@luwes luwes mentioned this pull request Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants