Skip to content

GuideMigrate

Migrate from Plyr

Move an existing Plyr integration to Video.js v10, mapping Plyr options and its instance API onto components and player state

Replace Plyr’s constructor and options object with a Video.js player, media component, and skin. Read playback and UI state through player state.

Start with the Neutral skin, move media settings into markup, and replace calls to the Plyr instance with native media APIs or Video.js store actions.

AI Quickstart

Paste this prompt into your coding agent:

Migrate this project's Plyr 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-plyr, 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.

What changes

  • Player, media, and skin are separate. Put source settings on the media, poster metadata on the player, and controls in the skin.
  • HLS and DASH need a streaming media component. Keep the playback engine your app requires; see Streaming.
  • Controls are composed in a skin. To migrate Plyr’s controls, hotkeys, or gestures, edit skin source.
  • Casting needs setup. The video skins include AirPlay and Cast buttons. The Cast button needs the Google Cast extension. Follow Cast to AirPlay and Chromecast.

Basic migration

Start with a Plyr player that has captions, thumbnail previews, and a poster:

<link rel="stylesheet" href="path/to/plyr.css" />

<video id="player" src="/path/to/video.mp4" playsinline controls data-poster="/path/to/poster.jpg">
  <track kind="captions" label="English" src="/path/to/captions/en.vtt" srclang="en" default />
</video>

<script src="https://cdn.plyr.io/3.8.4/plyr.js"></script>

<script>
const player = new Plyr('#player', {
  previewThumbnails: {
    enabled: true,
    src: '/path/to/storyboard.vtt',
  },
});
</script>

The Neutral skin is the closest built-in starting point for this migration.

For an HTML page without a bundler, load the CDN preset:

<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.0.1/video-neutral.js"></script>

<video-player poster="/path/to/poster.jpg">
  <video-neutral-skin style="aspect-ratio: 16 / 9">
    <video src="/path/to/video.mp4" playsinline>
      <track kind="captions" label="English" src="/path/to/captions/en.vtt" srclang="en" default />
      <track kind="metadata" label="thumbnails" src="/path/to/storyboard.vtt" default />
    </video>
  </video-neutral-skin>
</video-player>

For a bundler installation, install the HTML package and import the player and skin:

npm install @videojs/html
import '@videojs/html/video/player';
import '@videojs/html/video/neutral-skin';

Notes

  • The skin adds its styles to its own shadow root. You do not link a stylesheet.
  • Remove controls from the media so the browser’s native controls do not appear beside the skin’s controls.
  • The poster moves from data-poster on the media to poster on <video-player>. The skin reads that value and controls how the poster appears. To supply your own image element, see Add a poster and loading placeholder.
  • To use the Default skin, change <video-neutral-skin> to <video-skin>. From the CDN, load video.js instead of video-neutral.js. With a bundler, replace the Neutral skin import with @videojs/html/video/skin.

If caption or thumbnail tracks come from a different origin than the page, set crossorigin="anonymous" on the media (crossOrigin="anonymous" in React). Also serve the media and track files with CORS headers that allow your page’s origin. See Thumbnail.

Map common features

Streaming

For HLS or DASH, replace the native video component with a media component for that format. Check that the component’s engine supports your stream before you choose it.

HLS

If your Plyr integration uses hls.js, keep it by using the hls.js video component. The installation CLI’s --media hls option selects this component.

The SPF HLS video 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 before switching.

Replace <video> with <hlsjs-video>. If you’re using the CDN, add an additional script:

<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.0.1/media/hlsjs-video.js"></script>

For a bundler installation, install the adapter package and add the media import:

npm install @videojs/html @videojs/hlsjs-video
import '@videojs/html/media/hlsjs-video';

Set src to your .m3u8 manifest URL.

DASH

Use the DASH media component for an unprotected DASH source. For DRM-protected DASH, use Shaka video and configure source.drm; the dash.js component has no DRM configuration input. See the DRM migration example.

Replace <video> with <dash-video>. If you’re using the CDN, add an additional script:

<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.0.1/media/dash-video.js"></script>

For a package-manager installation, install the DASH adapter package and add the media import:

npm install @videojs/html @videojs/dash-video
import '@videojs/html/media/dash-video';

Set src to your .mpd manifest URL.

Vimeo

Use the Vimeo media component for your existing Vimeo source.

Replace <video> with <vimeo-video>. For a CDN installation, add the media script:

<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.0.1/media/vimeo-video.js"></script>

For a bundler installation, install the adapter package and add the module import:

npm install @videojs/html @videojs/vimeo-video
import '@videojs/html/media/vimeo-video';

Set the src to the URL for the Vimeo video, for example https://vimeo.com/76979871.

YouTube

Use the YouTube media component for your existing YouTube source.

Replace <video> with <youtube-video>. For a CDN installation, add the media script:

<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.0.1/media/youtube-video.js"></script>

For a bundler installation, install the adapter package and add the module import:

npm install @videojs/html @videojs/youtube-video
import '@videojs/html/media/youtube-video';
<video-player>
  <video-neutral-skin>
    <youtube-video src="https://youtu.be/aqz-KE-bpKQ" playsinline></youtube-video>
  </video-neutral-skin>
</video-player>

The component accepts YouTube watch, youtu.be short-link, embed, Shorts, live, playlist, and privacy-enhanced (youtube-nocookie.com) URLs, as well as raw 11-character video IDs.

Internationalization

Video.js defaults to English. To migrate Plyr’s i18n strings, register translation overrides or pass them to a provider. For shipped locales and language selection, follow Internationalize the player.

To select a locale, wrap the player in <media-i18n> and set lang to your locale tag, such as lang="ja". The CDN preset registers this element. With a bundler, import @videojs/html/i18n to register it. For English overrides from the CDN:

<script type="module">
  import { registerI18n } from 'https://cdn.jsdelivr.net/npm/@videojs/cdn@10.0.1/i18n.js';

  registerI18n('en', {
    buttons: {
      play: 'Start video',
      pause: 'Pause video',
    },
    menu: {
      settings: 'Options',
    },
  });
</script>

<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.0.1/video-neutral.js"></script>

<video-player>
  <video-neutral-skin>
    <video src="/video.mp4" playsinline></video>
  </video-neutral-skin>
</video-player>

With a bundler, import registerI18n from @videojs/html/i18n.

Configuration

Move media options to attributes or props. For controls and interactions, use a skin or edit its installed source. Where the table says to edit local skin source, it means those installed files:

Plyr option Video.js v10
controls Choose a skin with the controls you need, or edit local skin source. Video skins do not include rewind or fast-forward buttons.
rewind, fast-forward, seekTime The audio skins include 10-second skip buttons; the video skins do not. In local skin source, add a seek button, which skips by its seconds value (default 30; negative values seek backward).
settings The skins show a settings menu automatically when quality, speed, audio tracks, or captions are available.
autoplay, muted, loop, playsinline, preload Set these on the media. For video-backed React media, use autoPlay and playsInline; the other names stay the same. YouTube and Vimeo use autoplay.
poster / data-poster Set poster on the player. See Basic migration.
ratio Set aspect-ratio in CSS on the skin component.
hideControls Preset skins hide controls after inactivity. For hideControls: false, set visibility="always" on the controls component in a custom layout or installed skin source. The hide delay is not configurable yet.
clickToPlay Preset video skins include click and tap gestures. Edit local skin source to remove or change them.
keyboard Preset video skins include common hotkeys. Edit local skin source to remove or change them. For keyboard.global: true, set target="document" on the hotkey components.
tooltips Preset skins include tooltips for common controls. Edit local skin source to customize them.
captions Add <track kind="captions"> or <track kind="subtitles">; preset skins show captions controls when tracks are available.
previewThumbnails Add <track kind="metadata" label="thumbnails">; preset video skins show slider thumbnails when thumbnail cues are available.
quality The quality menu appears when the media exposes renditions, such as HLS or DASH. Plain MP4 source arrays do not currently become a quality menu automatically.
speed The preset settings menu uses a fixed rate list; speed.options does not carry over. See Known gaps.
fullscreen Native fullscreen is supported. Video.js has no equivalent of Plyr’s full-window fallback.
provider: 'vimeo' Use the Vimeo media component inside the player skin as shown in Vimeo above.
provider: 'youtube' Use the YouTube media component inside the player skin as shown in YouTube above.
storage Persist preferences in your app; see Remember user preferences.
i18n English is the default. Select a locale with an i18n provider or provide custom translations. See Internationalization.
ads Unsupported at this time.

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.

Customize the controls

Plyr’s controls option chooses which controls appear. Video.js skins come with their own control set and layout. Keep the skin when that UI fits your player. To remove, reorder, or restyle its controls, add the skin source to your project and edit it. A component you add as a child of a packaged skin does not appear in its control bar.

If your app already has a custom control bar, build it from individual UI components. Video.js handles the media action, accessible name, and state. You add the visible contents, layout, and CSS.

Individual HTML buttons include no text, icon, or finished styling. Add your own content, or import @videojs/html/icons/element and place a <media-icon> inside the button. Attach click handlers directly to controls you add, and keep playback event handlers on the <video> or <audio>. To change or listen to a button inside a ready-made skin, add the skin source to your project first.

Use the imperative API

Use store actions for playback, seeking, volume, fullscreen, and captions:

Plyr Video.js v10
player.play(), player.pause() play(), pause()
player.currentTime = 10 seek(10)
player.volume = 0.5 setVolume(0.5), which also unmutes
player.muted = true setMuted(true)
player.speed = 1.5 setPlaybackRate(1.5)
player.fullscreen.enter(), player.fullscreen.exit() requestFullscreen(), exitFullscreen()
player.fullscreen.toggle() isFullscreen ? exitFullscreen() : requestFullscreen()
player.toggleCaptions() toggleSubtitles()

Plyr’s volume setter unmutes for values above zero, as setVolume does. Set media.volume directly only when you intend to preserve 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.

Native media methods and properties also update player state through media events. To change content, set src on the media component. If the new source needs a different playback engine, replace the element, such as moving from <video> to <hlsjs-video>. The player UI follows the attached media.

Call store actions after the player is defined and media is attached:

<button id="toggle-playback" type="button">Toggle playback</button>

<script type="module">
  await customElements.whenDefined('video-player');
  const player = document.querySelector('video-player');

  document.querySelector('#toggle-playback').addEventListener('click', () => {
    if (!player.store.target) return;

    if (player.store.paused) {
      player.store.play();
    } else {
      player.store.pause();
    }
  });
</script>

To keep a custom element in sync with player state, use PlayerController:

import { PlayerController, playerContext, selectTime, UIElement } from '@videojs/html';

class MyElapsed extends UIElement {
  #time = new PlayerController(this, playerContext, selectTime);

  update(changed) {
    super.update(changed);
    this.textContent = `Elapsed: ${(this.#time.value?.currentTime ?? 0).toFixed(1)} s`;
  }
}

customElements.define('my-elapsed', MyElapsed);

Render it inside your existing <video-player> so it can read that player’s state:

<my-elapsed></my-elapsed>

Rewrite styles

Like Plyr, Video.js skins let you customize colors with CSS custom properties. Add the skin source to your project when you need deeper control over layout, control structure, icons, or interaction styling.

/* Plyr */
.plyr {
  --plyr-color-main: rebeccapurple;
}
/* Video.js HTML */
video-player {
  --media-accent-color: rebeccapurple;
}

--media-accent-color reaches the sliders, active buttons, and accent surfaces, so it’s the closest match for Plyr’s --plyr-color-main. 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 and --media-scale-unit cover rounding and control sizing. See Customize skins for the full list.

Edit skin source

For changes to controls, layout, or icons, add the skin source to your project. Its TypeScript components and styles become local files that need a build step. A CDN-only page must add a build step before it can use this source. Customize skins covers the setup and available skins.

Known gaps

  • Plyr’s ads option has no built-in equivalent.
  • Plyr’s storage option has no built-in equivalent for persisted volume, captions language, muted state, speed, or quality. Player setting persistence is tracked in #944. For application-managed persistence, follow Remember user preferences.
  • Playback rates are fixed at 0.2, 0.5, 0.7, 1, 1.2, 1.5, 1.7, and 2. Plyr’s speed.options has no equivalent yet (#1404).
  • Plyr’s full-window fullscreen fallback has no matching Video.js feature. Test fullscreen on your supported browsers; see Browser support.
  • Plain MP4 source arrays with size metadata do not automatically create a quality menu. Use Mux, HLS, or DASH for adaptive quality when possible.
  • Preset skins segment the time slider and show chapter titles when the media includes a default <track kind="chapters">. Cue points are not implemented yet; see #1442.
  • The controls auto-hide delay is not configurable yet (#1728). To disable auto-hide, set visibility="always" on the controls component in a custom layout or installed skin source. Packaged skins do not expose this setting.
  • Native controls are not automatically removed when custom controls load; see #1160.

Guides