Skip to content
Breaking changes

v10.0.0-beta.29

Compare changes on GitHub

Your media knows its own poster and title. Beta.29 teaches the player to ask for them, retires the store setters that used to stand in the way, and finishes the canonical source the shipped skins will eventually be generated from.

Breaking changes

  • Poster is player metadata (#2039). setPoster and setDefaultPoster are gone from the store, and defaultPoster is gone as a provider input. Pass poster on the player instead: the poster attribute on <video-player>, or the poster prop on a preset’s VideoPlayer or the Player your createPlayer returns. React skins take a renderPoster function in place of the poster prop; the player owns the URL, so there is no string form.
  • contentTitle is now title (#2176). Read store.title rather than store.contentTitle, and pass title rather than contentTitle to a React player. setContentTitle, setDefaultContentTitle, and the defaultContentTitle input are all removed. Markup is untouched: title on an HTML element already means the tooltip, so the attribute stays content-title and the element property stays contentTitle.
  • SPF track selection takes rules, not pickers (#2135). SelectVideoTrackConfig.picker, SelectAudioTrackConfig.picker, and BackgroundVideoEngineConfig.picker are replaced by rules plus an optional constraints pre-pass, which is the chain switchVideoTrack already ran. A rule composes into either behavior unchanged, and it picks among candidates the manifest actually offers rather than returning any id. preferHighestResolution ships as a shared rule.

Poster and title come from the media

The player resolves both values from the same two tiers now: what you pass, then what the media reports, then the empty string. Nothing else can write them.

Mux and Vimeo hold up their end of that bargain first (#1998). MediaContentData gained named keys for the vocabulary features read — title, poster, and storyboard — and both hosts donate a cached bag that fires contentdatachange only when the bag genuinely changed. Mux refreshes its derived poster and storyboard URLs before sourcechange, skips the event when playback parameters move but the image URLs don’t, and clears the bag when the source is dropped. Vimeo sets title once the embed loads and treats a blank title as an absent key. The SPF-backed Mux Medias share one MuxMediaMixin with their hls.js counterparts, so a source change no longer leaves the store stale. See the elements for HTML and React, and the contentData contract in the media sources concept for HTML and React.

The title landed first (#2176), then the poster (#2039). Both are breaking; see Breaking changes. The interesting half of the poster work is what <media-poster> does with the resolved URL: it fills src on an image you already supplied rather than rendering one of its own. The packaged skins carry a plain <img> as slot fallback content, so slotting your own image displaces it, and an image that brings its own src, srcset, or <picture> candidates is left alone. The host reports data-loading, data-loaded, and data-error beside data-visible. In React the component is the image, so image attributes go straight on it and render swaps the element while keeping the resolved src. Full details in the poster reference for HTML and React.

Sliders that hold their preview

Drag the time slider with a mouse, release inside the control, and the scrub preview used to vanish. The slider keeps its data-pointing state through a release inside its bounds now, while a release outside, a touch, and keyboard adjustment all clear it (#2257). A follow-up routed endDrag through one path so a stale pointermove arriving before lostpointercapture can’t drop hover either (#2259).

The rest of the pass is about small players. Preview thumbnails size from the container’s aspect ratio against a shared --max-size cap, so they stay inside the player instead of spilling out of it, and the Minimal skin clamps them at both slider edges. Primary controls sit above secondary ones in the stacking order, so a preview no longer renders behind the cast and fullscreen cluster. Thumbs animate with opacity and scale rather than resizing, fill and buffer transitions line up, and Minimal keeps its volume thumb visible on :focus-visible. The Default skin hides the captions toggle on narrow players. Covered in the slider references for HTML and React, volume for HTML and React, and captions for HTML and React.

Canonical skins reach parity

The canonical skin source that started in beta.27 now covers a complete Default and Minimal video skin, authored once and projected to HTML, React, vanilla CSS, and a shadcn-style registry. It arrived one component at a time: the buffering indicator (#2189), error dialog (#2190), captions button (#2191), the cast, AirPlay, and picture-in-picture cluster (#2192), the input indicator overlay (#2193), the volume popover with side and orientation variants (#2194), time slider chapters and the thumbnail preview (#2195), and the settings menu with its quality, audio, speed, and captions submenus (#2196).

Keyboard and pointer defaults became declarable too (#2197). Hotkey and Gesture are framework-neutral core components wired to InputAction, and a reusable composition declares the standard video shortcuts — play, seek, volume, fullscreen, captions, picture-in-picture, speed — alongside center-click to pause and double-tap to seek or go fullscreen by region. HTML gained the standalone <media-gesture> registration it was missing. With all of that in place, the Default skin got its responsive control layout (#2198) and a compact Minimal skin joined it (#2199).

Styling followed the same route. A validated --media-* token contract runs across themes, Tailwind, and emitted CSS, and the registry output composes with cn and opt-in prop forwarding so it stays editable (#2202). Four near-duplicate settings submenus collapsed behind shared Submenu and RadioItem wrappers, leaving QualityMenu, AudioTrackMenu, PlaybackRateMenu, and CaptionsMenu to supply domain behavior only (#2203). A poster styling gate that kept generated HTML posters transparent while visible is fixed, and the canonical Default skin dropped its seek buttons while SeekButton stays published as a standalone registry component (#2181).

None of this changes what you install yet. The packages still ship the hand-authored skins, and the generated output is checked against one reviewed snapshot and driven by the E2E suite while parity work finishes. Start from the skins concept for HTML and React, or the customization guide for HTML and React.

The compiler behind it

Four refactors turned skin-specific machinery into a general pipeline. Template and text transforms gave way to composable JSX edit primitives, and generated projections came out of version control in favor of build-time generation (#2206). Style authoring moved into a typed styles() API with explicit output files, layers, semantic class names, and variants (#2207). Catalog resolution and emission became compiler APIs, with shadcn reduced to an adapter over them (#2213). And canonical components are now described by typed schemas, with the React and HTML registries owning their own mappings — the compiler package renamed to vjsc along the way, and picked up a generate CLI (#2234). That rename briefly broke preview package publishing, since vjsc shipped without a version field for pnpm pack to resolve; it has one now (#2272).

Under the hood: SPF

SPF background video picks a rendition that fits the screen it will be shown on (#2135). screenResolutionCap narrows candidates to renditions whose pixel area fits, composed ahead of the ranker in the default chain, and it measures area rather than a "1080p"-style tier, because a tier only describes a rendition once you assume its aspect ratio. It’s a soft filter: an over-cap rendition is wasteful, not unplayable, so no screen reading and nothing fitting both mean “don’t cap” rather than a cap of zero. Player-size capping is a different signal and a different layer, and stays out of this.

A source the engine can’t play also stops failing silently. An unsupported container, encryption with no EME, and an undecodable codec all leave HTMLMediaElement.error null with the element stalled at readyState 0, because nothing in MSE reports them. The engine now collects SVTA-coded conditions per source, promotes the first fatal one to error and an 'error' event, and explains it on the console; <hls-background-video> re-fires it, and the React component re-dispatches on the inner <video> so onError runs. The selection rewrite that made this composable is breaking; see Breaking changes.