# Migrate from Plyr

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

Video.js v10 is not a drop-in replacement for Plyr. Plyr wraps one media element with a constructor and options object. Video.js composes a player, media component, and skin in markup, then exposes behavior through [player state](https://videojs.org/docs/framework/html/guides/features).

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

## What changes

- **The player becomes three pieces.** Player state, the media component, and the skin are separate, so you can replace one without wrapping or forking the others.
- **Streaming behavior becomes player state.** HLS, DASH, and Mux integrations expose renditions, tracks, and live state to the UI instead of leaving that wiring to application code.
- **The UI is composable.** Buttons, sliders, menus, gestures, and hotkeys are individual accessible components. Start with the Minimal or Default skin, then [add its source to your project](#edit-skin-source) when you need to change the structure.
- **Remote playback is built in.** The video skins include AirPlay and Cast controls. Follow [Cast to AirPlay and Chromecast](https://videojs.org/docs/framework/html/guides/casting) to add the Google Cast extension.

> **Note**
>
> Video.js v10 is still moving toward GA. Features like ads, playlists, preference persistence, and cue-point APIs are active areas, so check [Known gaps](#known-gaps) when those features matter to your migration.

## Basic migration

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

```html
<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 Minimal skin is the closest built-in starting point for this migration.

The easiest path is the CDN:

```html
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.0.0-rc.4/video-minimal.js"></script>

<video-player poster="/path/to/poster.jpg">
  <video-minimal-skin>
    <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-minimal-skin>
</video-player>
```

If you prefer not to use the CDN, you can use the individual modules:

```js
import '@videojs/html/video/player';
import '@videojs/html/video/minimal-skin';
```

### Notes

- The skin attaches its styles automatically to its shadow root.
- 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. For more advanced control (like supplying your own image element), see [Add a poster and loading placeholder](https://videojs.org/docs/framework/html/guides/poster).
- There’s also a default skin with a modern, frosted appearance that some may prefer. Try it by removing the `-minimal` suffix from the script `src` if you’re using the CDN, or the `minimal-` prefix from the imports.

## Map common features

### Streaming

If you’re using HLS or DASH to stream your media, you’re in luck; we have prebuilt [media components](https://videojs.org/docs/framework/html/guides/media-sources) you can slot in with much improved integration over Plyr implementations.

#### HLS

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

```html
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.0.0-rc.4/media/hls-video.js"></script>
```

For a package-manager installation, install SPF and add its façade import:

```bash
npm install @videojs/html @videojs/spf
```

```js
import '@videojs/html/media/hls-video';
```

Set the `src` to the URL for the m3u8 manifest.

#### DASH

Very similar to HLS in that we have a drop-in component.

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

```html
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.0.0-rc.4/media/dash-video.js"></script>
```

For a package-manager installation, install dash.js and add its façade import:

```bash
npm install @videojs/html @videojs/dash-video
```

```js
import '@videojs/html/media/dash-video';
```

Set the `src` to the URL for the mpd manifest.

### Vimeo

Vimeo is supported through a prebuilt component.

Replace `<video>` with `<vimeo-video>` and add the module import:

```bash
npm install @videojs/html @videojs/vimeo-video
```

```js
import '@videojs/html/media/vimeo-video';
```

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

### YouTube

YouTube is supported through a first-class media component.

Replace `<video>` with `<youtube-video>` and add the module import:

```js
import '@videojs/html/media/youtube-video';
```

```html
<video-player>
  <video-minimal-skin>
    <youtube-video src="https://youtu.be/aqz-KE-bpKQ" playsinline></youtube-video>
  </video-minimal-skin>
</video-player>
```

The component accepts YouTube watch, short, embed, Shorts, live, playlist, and privacy-enhanced URLs, as well as raw 11-character video IDs.

### Internationalization

Video.js v10 ships with English labels by default and includes locale packs for:

`ar`, `az`, `bg`, `bn`, `bs`, `ca`, `cs`, `cy`, `da`, `de`, `el`, `es`, `et`, `eu`, `fa`, `fi`, `fr`, `gd`, `gl`, `he`, `hi`, `hr`, `hu`, `id`, `it`, `ja`, `ko`, `lt`, `lv`, `mr`, `nb`, `ne`, `nl`, `nn`, `oc`, `pl`, `pt-BR`, `pt-PT`, `ro`, `ru`, `sk`, `sl`, `sr`, `sv`, `te`, `th`, `tr`, `uk`, `vi`, `zh-CN`, and `zh-TW`.

The shorthand tags `pt` and `zh` are also available as aliases. See [Internationalize the player](https://videojs.org/docs/framework/html/guides/internationalization) for the full picture.

For CDN users:

```html
<script type="module">
  import { registerI18n } from 'https://cdn.jsdelivr.net/npm/@videojs/cdn@10.0.0-rc.4/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.0-rc.4/video-minimal.js"></script>

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

For package users, same idea but import from `@videojs/html/i18n`.

## Configuration

Plyr uses an object to set configuration options whereas Video.js v10 uses a component structure and attributes instead. We’re using a composition model rather than a configuration model. This reduces bundle size and only includes functionality you actually require.

Here’s a matrix for configuration options in Plyr and how each maps to Video.js v10:

| Plyr option | Video.js v10 |
| --- | --- |
| `controls` | The [skins](https://videojs.org/docs/framework/html/guides/skins) include the common controls, laid out in a familiar way that users would expect. Not every Plyr control ships in every skin—the video skins do not include `rewind` and `fast-forward` buttons, for example. To change which controls appear, or to customize the skin beyond basic colors, [add the skin source to your project](#edit-skin-source) and change controls, layout, styles, or icons. |
| `rewind`, `fast-forward`, `seekTime` | The audio skins include 10-second skip buttons; the video skins do not. [Add the skin source to your project](#edit-skin-source), then add a [seek button](https://videojs.org/docs/framework/html/reference/components/seek-button), which skips by its `seconds` value (default `30`; negative values seek backward). |
| `settings` | Included automatically in the skins when quality, speed, audio tracks, or captions are available. |
| `autoplay`, `muted`, `loop`, `playsinline`, `preload` | These are attributes on your media (e.g. `<video>`) component. |
| `poster` / `data-poster` | Set `poster` on the player. See [Basic migration](#basic-migration). |
| `ratio` | Set `aspect-ratio` in CSS on the skin component. |
| `hideControls` | Preset skins auto-hide controls based on activity. `hideControls: false` maps to `visibility="always"` on the controls component in a custom or installed skin layout; the delay is currently not configurable. |
| `clickToPlay` | Preset video skins include click and tap gestures. [Add the skin source to your project](#edit-skin-source) to remove or change them. |
| `keyboard` | Preset video skins include common hotkeys. [Add the skin source to your project](#edit-skin-source) to remove or change them. |
| `tooltips` | Preset skins include tooltips for common controls. [Add the skin source to your project](#edit-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` | Works when the media provider exposes renditions. Plain MP4 source arrays do not currently become a quality menu automatically. |
| `speed` | Included in the preset settings menu when playback rates are available. |
| `fullscreen` | Native fullscreen is supported; Plyr’s full-window fallback is not a matching feature. |
| `provider: 'vimeo'` | Use the Vimeo media component inside the player skin as shown in [Vimeo](#vimeo) above. |
| `provider: 'youtube'` | Use the YouTube media component inside the player skin as shown in [YouTube](#youtube) above. |
| `storage` | Unsupported at this time. |
| `i18n` | The most common languages are available by default but you can also provide custom translations, if required. See [Internationalization](#internationalization) above for more info. |
| `ads` | Unsupported at this time. |

## 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](#edit-skin-source) and edit it. Adding a child to a skin does not place it inside the control bar.

If your app already has a custom control bar, build it from individual [UI components](https://videojs.org/docs/framework/html/guides/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

Control everything through the player’s store. Every Plyr call has a matching store action, so one mental model covers playback, volume, fullscreen, and captions alike:

| Plyr | Video.js v10 store action |
| --- | --- |
| `player.play()`, `player.pause()` | `play()`, `pause()`, or `togglePaused()` |
| `player.currentTime = 10` | `seek(10)` |
| `player.volume = 0.5` | `setVolume(0.5)` |
| `player.muted = true` | `toggleMuted()` |
| `player.speed = 1.5` | `setPlaybackRate(1.5)` |
| `player.fullscreen.enter()` | `requestFullscreen()` |
| `player.toggleCaptions()` | `toggleSubtitles()` |

You can still script the media element directly when you want to. Plyr routed calls through its wrapper because the wrapper had to know about every change; Video.js derives player state from the native media events, so `video.play()` or `video.currentTime = 10` keeps every control in sync. The media element is also the way to change content: set `src` on the media component to swap sources, and replace the component when the media type changes, such as moving from `<video>` to `<hls-video>`. The player UI follows the attached media.

The store’s actions live on the player element’s `store.state`: query `<video-player>` and call `player.store.state.togglePaused()`. Your media element is also a plain child in your markup, so you can query and drive it directly. To keep a custom element in sync with player state, use [`PlayerController`](https://videojs.org/docs/framework/html/reference/api/player-controller):

```js
import { PlayerController, playerContext, ReactiveElement, selectTime } from '@videojs/html';

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

## Rewrite styles

Video.js v10 skins offer similar color customization via CSS custom properties. [Add the skin source to your project](#edit-skin-source) when you need deeper control over layout, control structure, icons, or interaction styling.

```css
/* Plyr */
.plyr {
  --plyr-color-main: rebeccapurple;
}
```

```css
/* 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 to sit on top of it; set `--media-accent-text-color` to choose that yourself. `--media-border-radius` and `--media-scale-unit` cover rounding and control sizing. See [Customize skins](https://videojs.org/docs/framework/html/guides/customize-skins#style-a-packaged-skin) for the full list.

## Edit skin source

For changes to controls, layout, or icons, add the skin source to your project. Its components and styles become local files. [Customize skins](https://videojs.org/docs/framework/html/guides/customize-skins#style-skin-source) 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](https://github.com/videojs/v10/issues/944); subtitle language preference is tracked in [#1423](https://github.com/videojs/v10/issues/1423).
- Plyr’s full-window fullscreen fallback has no matching Video.js feature. This was designed as a fallback when the Fullscreen API wasn’t supported, but given [browser support for fullscreen is around 96%](https://caniuse.com/fullscreen), it’s unlikely to be required.
- Plain MP4 source arrays with `size` metadata do not automatically create a quality menu. Use Mux, HLS, or DASH for adaptive quality when possible. A simpler source-driven quality menu may be considered later.
- Preset skins segment the time slider and show chapter titles when the media includes a default `<track kind="chapters">`. Dedicated cue-point APIs are not complete yet; see [#1442](https://github.com/videojs/v10/issues/1442).
- The controls auto-hide delay is not configurable yet ([#1728](https://github.com/videojs/v10/issues/1728)). Disabling auto-hide is possible with `visibility="always"` on the controls component, but not from a preset skin’s attributes.
- Native controls are not automatically removed when custom controls load; see [#1160](https://github.com/videojs/v10/issues/1160).

## Related pages

### Guides

- [Features](https://videojs.org/docs/framework/html/guides/features): The state and actions each feature adds to the player
- [Media sources](https://videojs.org/docs/framework/html/guides/media-sources): Set what a media element plays and how its engine plays it with the structured source property
- [Customize skins](https://videojs.org/docs/framework/html/guides/customize-skins): Style a packaged Video.js skin or add its source to change controls, layout, styles, and interactions

---

HTML documentation: https://videojs.org/docs/framework/html/llms.txt
All documentation: https://videojs.org/llms.txt
