GuideMigrate
Migrate from Video.js 8
Move a Video.js 8 integration to v10, mapping the options object, techs, plugins, and player API onto composed components
Move your Video.js 8 setup to v10 by replacing videojs() with a player, media component, and skin. This guide maps your options, techs, plugins, and imperative calls to those pieces.
Video.js 10 ships as scoped @videojs/* packages. The video.js npm package remains Video.js 8; its repository and docs remain available.
Before you migrate
- List the options, techs, plugins, custom controls, and event handlers your app uses.
- Check Known gaps, especially ads and playlists, before replacing a working player. Plan to rewrite plugins that have no equivalent.
- Keep your existing media URLs, source formats, tracks, and required behavior. The first example uses an MP4 file. For an adaptive stream or an embed, use the matching media component.
- Choose an installation method and playback engine that fit your app and supported browsers. See Installation and Techs become media components.
AI Quickstart
Paste this prompt into your coding agent:
Migrate this project's Video.js 8 player to Video.js v10 for HTML. Use @videojs/html or its CDN bundles for a page without a build step.
Run `npx @videojs/cli agents skills` to get the Video.js skill installation instructions. Follow the printed steps for your agent. If they require a session reload, reload the session and continue with this prompt. If you can't run commands, use the installation instructions at https://github.com/videojs/skills. Use the skill throughout this migration.
Read the migration guide at https://videojs.org/docs/framework/html/guides/migrate-from-video-js-8, including its Known gaps section. Inspect the existing player and list its media URLs, source formats, tracks, options, custom controls, event handlers, and integrations. Compare that list with the known gaps and report unsupported requirements before changing code. Keep the existing media URLs and required behavior throughout the migration.
For installation guidance, read https://videojs.org/docs/framework/html/llms.txt and follow its link to the installation guide. Keep using that online index for a CDN installation.
From the application root, run `npx @videojs/cli agents init` without flags to list installation options and their accepted values.
Choose values for these options:
- --media and --source-url: the existing content and playback requirements.
- --method: the installation method.
- --preset, --skin, and --extensions: the player UI and integrations.
Add those options to `npx @videojs/cli agents init --framework html --project existing` and run the command to print the installation plan. Follow any version-mismatch notice. Both CLI commands print instructions without changing project files. Use the printed plan to install Video.js and configure the player.
After installing a package, switch to node_modules/@videojs/html/docs/llms.txt for documentation matching its version.
Implement the migration. Then verify playback, captions, controls, and any analytics integration on every browser the project supports.Your first player
Here’s a standard v8 setup: one file, captions, a poster, and the default skin.
Load the video preset from the CDN and put your media inside its skin:
With a bundler, install the HTML package and import the player and skin:
Apply these changes to your existing markup:
- No
class="video-js", nodata-setup, novideojs()call. Importing the player and skin registers their custom elements. The browser activates these elements when it upgrades them. Video.js does not scan the page for players. - No
controlsattribute. The skin provides the controls. Include a skin to show controls. Leave it out for a player with no UI. - Move the poster URL to the player. Set
posteron<video-player>. The skin controls how the poster appears. To supply your own image element, see Add a poster and loading placeholder. - Keep your existing tracks. Captions and subtitles remain native tracks; the skin supplies their controls.
For the Neutral skin, replace <video-skin> with <video-neutral-skin>. Load video-neutral.js from the CDN, or replace the bundler skin import with @videojs/html/video/neutral-skin.
If you have chapter tracks or thumbnail tracks (kind="metadata" label="thumbnails"), add default to each track so the browser loads its cues. Keep each track’s existing src URL.
When enabling captions without a previous selection in this player instance, toggleSubtitles() prefers a track matching navigator.language, then falls back to the first available caption or subtitle track. For an authored track that should show on load, add default. See Show captions and subtitles.
Three pieces instead of one
Use these three pieces when deciding where a v8 option belongs:
The player is the outer custom element. It holds state and shares it with descendants. Its features determine the available state and actions.
The media plays the content and replaces the v8 tech. Choose a media component for your format or provider. Player controls use the capabilities that media exposes.
The skin supplies the controls, poster, menus, shortcuts, and gestures. Use a packaged skin as it is, or restyle it. To change its layout or interactions, install its source.
Where your options went
Move each option to the part of your v10 setup that owns it:
- Media settings. Set native attributes or props on the media.
- Player metadata. The title and poster belong to player state, which lets the skin or your custom UI render them.
- Your CSS. Set sizing, aspect ratio, and responsive behavior in your own CSS.
- Composition. Choose components to determine which controls appear, which shortcuts run, and which language the player uses.
Media attributes
Move native media settings onto the media component.
Video.js uses native autoplay. For v8’s string-valued autoplay options:
'muted': set bothautoplayandmutedon the media (autoPlayandmutedon video-backed React media).'play': to preserve v8’s explicit playback request, callplay()on the media when it firesloadstartand handle rejection.'any': onloadstart, callplay()on the media. If the returned promise rejects, mute the media and callplay()again. Implement this fallback in your app.
Keep a manual play control available if autoplay fails. See Handle autoplay.
Player metadata
If you do not set poster on the player, the skin uses a poster supplied by the media, such as a generated Mux poster. Set poster="" to turn off this fallback.
Set a poster URL on the player for the skin to display:
The packaged video and live-video skins already include the <media-title> element, so setting content-title on the player displays it. Audio skins do not include a title. In a custom layout or installed skin source, add <media-title> where you want the title to appear. For poster options such as placeholders and custom image elements, see Add a poster and loading placeholder and the <media-poster> reference.
Your CSS
Set sizing and aspect ratio on the skin with CSS:
Composition
These options need a migration decision. Each row links to the relevant guidance or identifies a gap.
Techs become media components
Choose a media component for each format or provider your v8 techs handled. Check its supported features before replacing a streaming engine.
html5.vhs.overrideNative: false lets v8 use native HLS where available while retaining VHS elsewhere. It does not mean native-only playback. Choose an HLS engine that supports your required browsers; native HLS has no fallback on browsers that cannot play the stream themselves. See Browser support.
For an existing VHS integration, start with hls.js video and test your streams on supported browsers. This is also the component selected by the installation CLI’s --media hls option.
The SPF HLS component is an alternative for fMP4/CMAF streams. It does not support MPEG-TS segments, raw ADTS AAC, or whole-segment AES-128 encryption. It supports DRM through source.drm. Verify your license configuration and browser support with the engine you choose. See HLS video and hls.js video.
Each media component needs its own import. For package-manager installations, install the matching adapter package:
SPF and native HLS are included as dependencies of the HTML and React packages.
For hls.js video from the CDN:
With a package manager, install the adapter and import its HTML registration:
Then swap the tag:
Your VHS tuning options don’t have direct equivalents. Settings such as rendition capping and bandwidth hints are engine-specific. Configure them through the engine you chose. Video.js has no shared options object for these settings.
The exception is html5.vhs.withCredentials. Cookie-gated streams on another origin (CloudFront signed cookies and the like) need the standard crossorigin="use-credentials" on the media element, which both HLS components honor for every manifest and segment request, the same way the browser does under native playback:
As with a plain <video>, the CDN has to answer with Access-Control-Allow-Credentials: true and a specific Access-Control-Allow-Origin, not *.
Customize your player
In v8, you customized the player with options, addChild, component subclasses, or CSS overrides on .vjs-* selectors. In v10, choose a skin, restyle it with CSS custom properties, or edit its source. Try these approaches in order.
Level 1: pick a skin
The Default skin groups settings in a menu. The Neutral skin has a compact layout like v8’s control bar. Both bring controls, tooltips, captions, keyboard shortcuts, touch gestures, and a settings menu that appears when there’s something to put in it. See Skins.
Both include AirPlay and Cast buttons. Follow Cast to AirPlay and Chromecast to add the Google Cast extension.
Level 2: restyle it
Replace your v8 selectors with the skin’s documented custom properties where they cover the change:
--media-accent-color reaches the sliders, the active buttons, and the accent surfaces at once. Video.js picks a readable text color for content on the accent color. To choose that text color yourself, set --media-accent-text-color. --media-border-radius rounds the player’s corners. --media-scale-unit scales the whole control bar, like v8’s font-size technique for sizing controls. Customize skins has the full list.
Level 3: edit skin source
To migrate controlBar, addChild, or custom component layouts, add the closest skin’s source to your project. The installed TypeScript and styles need a build step. A CDN-only page must add a build step before it can use this source. Follow Customize skins for installation and file locations.
Keep the player and media imports. Replace the packaged skin import with the installed skin.ts module, then use the markup from skin.html around your media. The module registers the elements and loads the local stylesheet.
To remove the picture-in-picture button, remove its markup from the installed controls layout. Keep the other controls’ contents, classes, and styles.
Move userActions.hotkeys customizations into the local hotkey declarations. Move click and double-click customizations into the gesture declarations. The installed skin includes both; edit those files to preserve the interactions you need.
For a control bar built from scratch, follow UI components. Individual controls supply behavior and accessible names. You supply their visible contents, layout, and CSS.
Plugins
v10 has no videojs.registerPlugin, player.myPlugin(), or plugin lifecycle. Audit each plugin and plan its replacement:
- Formats and techs (
videojs-contrib-*, YouTube, Vimeo, Mux). Replace them with the matching media component. - DRM (
videojs-contrib-eme,keySystems). Configure the media source’s license servers; see DRM. Plugin callbacks and custom license requests require engine-specific migration. - UI additions (extra buttons, overlays, custom control bars). Rebuild them in local skin source or with your own component.
- Workarounds for v8 limitations. Check whether the limitation still exists before porting the workaround.
- Missing features such as ads and playlists. Check Known gaps before scheduling the migration. An ads-dependent player has no built-in replacement today.
DRM
For protected DASH, use Shaka video. The dash.js component has no source.drm input. For protected HLS, choose an engine that supports your key systems and browsers; see DRM protected sources.
Move license URLs from v8’s keySystems configuration to source.drm, keyed by EME key system ID. FairPlay also needs serverCertificateUrl unless its CDM is pre-provisioned. Custom getLicense callbacks and license authentication do not transfer automatically. Reimplement them with your chosen engine’s APIs, then verify playback before you switch.
The examples configure Widevine. Replace both URL placeholders and add every key system your supported browsers need.
With a bundler, install @videojs/shaka-video and import @videojs/html/media/shaka-video. From the CDN, load https://cdn.jsdelivr.net/npm/@videojs/cdn@10.0.1/media/shaka-video.js in a <script type="module"> tag. Then replace the media component inside your player’s skin with:
Run this module after the player markup:
Read and control the player
Events
Your media component is a real media element, so every event you already listen for still fires on it. Keep your listeners, and attach them to the media component instead of the v8 player.
Map v8’s player-specific events separately:
Player state
Use PlayerController inside a custom element. Give it a selector for the feature you care about and it keeps your element in sync as that state changes:
Render it inside your existing <video-player> so it can read that player’s state:
Drive playback
Replace v8 accessor methods with player state for reads. Use store actions or media properties for writes. Video.js updates player state from media events, so native calls such as media.play() and assignments such as media.currentTime = 10 update the controls.
Every item the table labels “the player’s” is a property of the player element’s store. Call these actions only after player.store.target is set, which means the media has attached. Fullscreen requests must run from a user interaction:
setVolume(value) also unmutes when value is above zero. Use media.volume = value to preserve the current mute state. If the volume is 0, setMuted(false) also sets it to 0.25. To unmute and keep a zero volume, set media.muted directly.
To change sources, set src on the media component. If the new source needs a different playback engine, replace the media component. The UI follows the attached media.
Languages
Video.js defaults to English and ships locale packs that load on demand. Select the locale and move videojs.addLanguage overrides into the i18n registry or provider. See Internationalize the player.
To migrate language: 'ja', wrap your player in <media-i18n lang="ja">. The CDN preset registers this element. With a bundler, import @videojs/html/i18n to register it. If your v8 player used the nearest ancestor element’s lang attribute, omit lang from <media-i18n> to keep that behavior.
For a CDN page, register string overrides in a module script:
With a bundler, import registerI18n from @videojs/html/i18n instead. The CDN i18n.js module exports translation functions. The preset script registers the <media-i18n> element.
See Internationalize the player for scoped overrides, custom locales, and runtime switching.
Live and audio-only
Replace liveui or audioOnlyMode with the matching live or audio preset. Check its feature list when your app needs controls beyond those shown by the preset.
Load the live preset and the hls.js media component:
For audio, use the audio player and skin, or the live audio pair. For muted video without controls, use the background video preset.
You get targetLiveWindow and liveEdgeStart in state, plus a live button that jumps to the live edge. The live preset does not include streamType; add the stream type feature to a custom player when you need it. v8’s liveTracker tuning has no equivalent (#1730).
The live skins have no time slider. To preserve DVR scrubbing, add one to custom UI or installed skin source; follow the DVR setup.
The live composition is deliberately narrower than the video one. It leaves out playback rate, quality, and audio-track state, so those controls don’t appear in a live skin’s settings menu. If you need an omitted feature on a live player, build your own player from a feature list rather than taking the preset’s.
That’s what createPlayer is for. Hand it a feature list and you get back a PlayerElement class to register as your own player element, plus a PlayerController bound to that player’s features.
Replace v8 utilities
v8 put a handful of utilities on the videojs global for anyone building a custom component or plugin: videojs.time.formatTime, videojs.dom, videojs.fn.throttle, and a few others. v10 has no global and no public replacements, so use a platform API, a short helper of your own, or a utility library you already depend on.
For example, a clock-style string such as 1:05 or 1:02:03:
Known gaps
Check these limitations against your required features:
- No ads support. v8’s IMA and ad-plugin ecosystem has no v10 equivalent. If your player depends on ads, resolve that requirement before replacing it.
- No playlist support. No
videojs-playlistequivalent. - No plugin system, by design. Budget for rebuilding UI plugins as components. See Plugins.
- Playback rates are a fixed set —
0.2,0.5,0.7,1,1.2,1.5,1.7,2— so v8’splaybackRateshas no equivalent yet (#1404). - No text-track settings dialog equivalent to v8’s
textTrackSettings. You can still style browser-rendered cues with::cue; see Show captions and subtitles. - No spatial navigation. v8’s
spatialNavigationfor TV and D-pad remotes has no equivalent. - Player preferences such as volume, captions language, speed, and quality are not persisted automatically (#944). For application-managed persistence, follow Remember user preferences.
- No VHS-equivalent tuning surface. Engine settings belong to the engine you chose, and the SPF HLS component deliberately exposes few of them.
- Chapters render in the time slider from a default
<track kind="chapters">, and its segments reflectdata-active. There is no player-level active chapter value or chapter menu, so v8’s chapters menu has no equivalent (#1873). Cue points aren’t implemented (#1442). - The controls auto-hide delay isn’t configurable, so arbitrary
inactivityTimeoutvalues have no equivalent (#1728). ForinactivityTimeout: 0, setvisibility="always"on the controls component in a custom layout or installed skin source. - No full-window fullscreen fallback. Test fullscreen on your supported browsers; see Browser support.
- Multiple
<source>elements withsizemetadata don’t become a quality menu. Use HLS or DASH for adaptive quality. - The Default, Neutral, and Compat skins are available. Runtime theme switching is not supported.
- Native controls are not automatically removed when custom controls load (#1160).
- Debug mode has no equivalent yet (#1406).
See also
- Features and Presets
- Media sources
- Skins and Customize skins