GuideMigrate
Migrate from Media Chrome
Map Media Chrome's controller, elements, and attributes onto Video.js v10, where most of the work is renaming and reshaping rather than rewriting
Video.js v10’s HTML player uses the same media-* custom-element convention as Media Chrome, so most of the work is renaming and reshaping, not rewriting.
Before you begin
Install Video.js and choose a preset (see Installation). Start with its ready-made skin if those controls fit your player. If you need to keep a custom control bar, build it from the individual Video.js UI components instead.
Map the controller
Media Chrome wraps a slotted <video slot="media"> in a single <media-controller>. Video.js splits that into two elements. The player owns state and draws nothing. The <media-container> is the box everything lives in, with the media as a plain child.
Map controller attributes
Media Chrome configures behavior through <media-controller> attributes. Video.js v10 has no single controller element, so these settle into three places:
- Player features — playback behavior that comes from the player’s feature set. A preset picks one for you;
createPlayerlets you name your own. <media-container>and<media-controls>— layout, focus, and autohide.- Dedicated elements —
<media-hotkey>,<media-gesture>, and friends, declared in markup.
Hotkeys
Media Chrome toggles keyboard shortcuts with nohotkeys and hotkeys. Video.js declares each shortcut as its own element, so you opt in to exactly the keys you want and set seek offsets inline:
To drop a shortcut, remove its element or add disabled. There’s no global “all hotkeys off” switch; omit the elements you don’t need.
Gestures
gesturesdisabled becomes per-element <media-gesture> controls. Tap and double-tap behavior — click to toggle play, double-tap to seek or go fullscreen — is declared explicitly:
Remove an element or set disabled to turn a gesture off.
Map the elements
Most names match. These are the renames that bite:
Unchanged names: media-play-button, media-mute-button, media-fullscreen-button, media-pip-button, media-airplay-button, media-cast-button, media-captions-button, media-playback-rate-button, media-tooltip, and media-thumbnail.
Video.js sliders use media-slider-track, media-slider-fill, and media-slider-thumb. See media-time-slider and media-volume-slider for copy-paste markup and CSS.
Media Chrome’s named icon slots do not carry over. Remove attributes such as slot="play" and put the icon directly inside the Video.js button. Import @videojs/html/icons/element to use <media-icon>, as shown in Map the controller. Set family="minimal" to use the Minimal skin’s icon designs. You can also use your own text or SVG.
media-poster is the one rename that changes shape. Where media-poster-image took its own src, media-poster wraps the image you bring:
Rewrite your styles
Media Chrome reflects state as media* attributes such as mediapaused; Video.js uses data-*.
Continuous values use CSS custom properties: sliders expose --media-slider-fill and --media-slider-pointer.
Map theme variables by meaning
Media Chrome and Video.js both use names beginning with --media-, but that shared prefix is a naming convention, not a compatibility layer. Keep a variable only when its documented meaning matches.
The packaged Video.js skins expose --media-accent-color, --media-accent-text-color, --media-border-radius, and --media-scale-unit for broad customization. Do not assume another Media Chrome variable will work because its name starts with --media-.
Themes become skins
Media Chrome’s <template>-based themes (media-theme) become Video.js skins and presets. Start from a preset, then add its skin source to your project, rather than authoring a template.
Ready-made HTML skins keep their controls inside Shadow DOM. Code outside the skin sees a click as coming from the skin, not the button inside it. Some control events also stay inside the skin. Do not use an outside listener to identify which built-in button was clicked.
Keep playback event handlers on your <video> or <audio>. When you build a custom control bar, attach click and input handlers directly to those controls. With a ready-made skin, read media or player state instead of listening for clicks inside the skin.
Keep the skin when its CSS variables and content slots cover your changes. Build a custom control bar when you want to start from scratch. Add the skin source to your project when you want its existing layout and styles but need to change the controls inside it.
Control the player imperatively
Control everything through the player’s store. Media Chrome had no player object; Video.js gives you one, with an action for every operation — play, togglePaused, seek, setVolume, toggleFullscreen — so one mental model covers playback and the state the browser doesn’t own, such as fullscreen, captions, and quality.
Hold a reference to the player element and call actions on its store:
The Media Chrome habit of scripting the media element directly still works, unchanged. The media is a plain child of the player, and Video.js derives player state from the native media events, so video.play() or video.currentTime = 30 never leaves the controls out of sync. The media element also stays the way to swap src, replace the media component, and listen for native events.
Known gaps
These Media Chrome features have no direct equivalent yet. Several can be approximated; see Workarounds.
- No configurable
autohidedelay and noautohideovercontrols(#1728). Disabling autohide (autohide="-1") is covered byvisibility="always"on the controls component; see Workarounds. - No
defaultdurationplaceholder before the media loads (#1729) - Volume and muted preferences are not persisted across sessions, so Media Chrome’s
novolumeprefandnomutedprefopt-outs have nothing to opt out of (#944). Video.js can choose an initial subtitle track from the locale, but it does not yet remember the viewer’s later language choice (#1786). - No
breakpointsor container-breakpoint attributes; use CSS container queries instead - No
seektoliveoffsetornoautoseektolivecontrols, and live-edge offset and tolerance aren’t configurable (#1730) - Chapters render and each time-slider segment reflects
data-active, but there’s no player-level active chapter value,chapterchangeevent, or menu to jump between them (#1873) - Cue points are not supported (#1442)
Chapters themselves do work. Add a default <track kind="chapters"> and the packaged skins segment the time slider and show the chapter title on hover, via media-time-slider-chapters and media-time-slider-chapter-title.
Workarounds
Disable autohide (autohide="-1")
Set visibility="always" on media-controls and the controls stay visible regardless of activity. Packaged skins don’t expose that attribute; to keep a preset skin’s look, add the skin source to your project and set it on the skin’s controls element rather than overriding private selectors.
Breakpoints
The skin root is an inline-size container named media-root, so write responsive styles with container queries instead of breakpoints attributes. This is exactly how the built-in skins adapt:
Default duration
There’s no player input for a pre-load duration. Use preload="metadata", the default, so the real duration is known almost immediately; only preload="none" defers it. If you must defer loading, render your own static placeholder in markup.
Preference persistence
Media Chrome remembers volume, muted state, and language across sessions. Video.js v10 persists nothing, so restore and save the values yourself.
Work against the store directly: selectVolume(store.state).setVolume(…) on init, and store.subscribe(…) to save on change.
Live edge offsets
seektoliveoffset and noautoseektolive are governed by the playback engine rather than the UI layer, so there’s no attribute to tune them today. The escape hatch is a custom live control built against the selectLive and selectTime player state.