Skip to content
Breaking changes

v10.0.0-beta.32

Compare changes on GitHub

The player’s chrome grew a full anatomy: dialogs, controls, menus, and their backdrops are now separate parts with their own state attributes, so you can style and animate each surface without selector gymnastics. A new Dialog component puts your own content in a modal over the player, and a set of task-based how-to guides landed alongside a wave of reference pages. Six breaking changes ship with it, all in the HTML package’s public API or the shared component anatomy.

Breaking changes

  • createPlayer returns the player element (#2180). The result is now PlayerElement, PlayerController, and playerContext: a configured element class you register with customElements.define, a controller already bound to that player’s context, and the lower-level context as an escape hatch. ProviderMixin, context, and the standalone create store factory are gone. Where you wrote class CustomPlayer extends ProviderMixin(UIElement), define the returned PlayerElement directly; where controllers took a context argument, they now take only the host and an optional selector.
  • Preset and UI registration is explicit (#2247). Preset roots like @videojs/html/video are side-effect-free value modules, and registration paths only register: @videojs/html/video/player defines the player element, @videojs/html/video/skin registers the skin, container, and its UI, and @videojs/html/ui/play-button registers only its element. UI registration paths no longer export constructors, so import those from @videojs/html. Media paths such as @videojs/html/media/hls-video are unchanged: they still export and register.
  • ContainerMixin is now ContainerElement (#2280). Subclass the concrete class instead of composing the mixin, and update the registration import from @videojs/html/media/container to @videojs/html/ui/container. The media-container tag name is unchanged, and attachment now survives elements connecting, disconnecting, reordering, or upgrading late.
  • MediaElement is now UIElement (#2245). The HTML reactive UI base is renamed so it stops colliding with playback media concepts. Extend UIElement for custom controls; playback hosts like CustomMediaElement keep their names.
  • Dialogs split into popup and backdrop parts (#2435). media-dialog, media-alert-dialog, and media-error-dialog are display: contents state roots now; dialog semantics, focus registration, and transition completion live on a media-dialog-popup child, with media-dialog-backdrop as an independently styled sibling. In React, Dialog.Root is provider-only with Backdrop and Popup as sibling parts.
  • Controls split into content and backdrop parts (#2436). React Controls.Root is provider-only: move DOM props, refs, and styling onto Controls.Content. In HTML, media-controls hosts context while media-controls-content carries the layout, next to the optional media-controls-backdrop.

A real anatomy for player chrome

Dialog is a generic modal for your own content, like opening a player from a thumbnail (#2379). It handles focus trapping and restoration, background isolation, transitions, trigger wiring, and Escape dismissal, and dismissal stays explicit so a play button inside a dialog doesn’t close it. AlertDialog and ErrorDialog now compose the same primitives while keeping their specialized semantics, the Video.js 8 ModalDialog migration path is documented in the migration guide for HTML and React, and Dialog has reference pages with player-modal demos for HTML and React (#2375).

The rest of the chrome follows the same shape: state owners are renderless, and visual surfaces are explicit sibling parts. Backdrops arrived as optional anatomy for alert dialogs and controls (#2343), menus separated their popup from their content (#2347), dialogs and controls made the same split (breaking, above), and the Default and Minimal skins were aligned so CSS and Tailwind render the same structure, including reduced-motion and forced-colors behavior (#2437). A backdrop receives the same transition state attributes as its surface, so a scrim and its content can animate on independent curves without cross-component selectors.

Volume UI that respects the device

VolumePopover is a new compound of Root, Trigger, and Popup parts that follows volume availability instead of skin heuristics (#2378). On devices that don’t expose volume control, it keeps the mute button, drops the popup and its ARIA wiring, and closes an open popup if availability changes mid-session. Read the reference for HTML and React. Nearby: the container now reflects controls visibility through data-controls-visible, so skins key cursor, captions offset, and transition timing off the owning boundary (#2376), the Minimal audio skins got their volume controls back (#2386), and audio playback rate controls gained tooltips (#2389, #2388).

Playback stays in one codec family

On mixed-codec HLS sources, the playback engine now keeps rendition selection within the codec family of the initial pick (#2289). The engine implements no SourceBuffer.changeType(), so a mid-stream hop from AVC to HEVC appended undecodable data; now the initial pick prefers AVC and AAC by default, later picks stay in that family, and a cross-family user pick is ignored instead of killing playback. Configure or disable the preference with preferredCodecs. The same PR fixed a signals-engine bug where an effect that writes through its own dependencies stopped seeing later changes.

Guides and reference coverage

A set of task-oriented how-to guides landed, each solving one job with copy-pasteable code for HTML and React, and every example was QA’d by running it in a real browser (#1945): autoplay for HTML and React, live streams for HTML and React, remembering user preferences, error handling, casting, seek previews, and more. A background video guide followed for HTML and React (#2339), plus Vue and Svelte integration guides (#2333, #2374).

Reference coverage widened too:

Assorted fixes

  • Anchored popovers stay attached to their triggers while the page scrolls (#2387).
  • Menus size to their available space, keep item labels on one line, and clear the parent highlight while a submenu is open (#2440).
  • Registered translation overrides survive re-registration (#2354), and durations format through Intl.NumberFormat and Intl.ListFormat instead of Intl.DurationFormat, which broadens browser support (#2336).
  • @videojs/html and engine entries import cleanly on the server, so SSR frameworks can share modules with the client (#2428, #2429).
  • Props set on an element before its definition registers survive the late upgrade (#2400), and popup roots handle being detached (#2348).
  • Skin-internal CSS custom properties carry a prefix so they can’t collide with yours (#2391).
  • The vjsc skin pipeline reports style diagnostics (#2345), hardens its Vite workflow (#2355), and matches packaged skin visuals again (#2344).

Thanks to @dylanjha for a first contribution: the how-to guide series in #1945.