# Migrate from Mux Player

Move a Mux Player embed to Video.js v10, splitting one element into a player, a media, and a skin

Mux Player combines HLS playback, UI, analytics, captions, remote playback, and keyboard shortcuts in one element. Video.js v10 composes those responsibilities from a player, media component, skin, and optional [extensions](https://videojs.org/docs/framework/html/guides/architecture).

This guide moves a working Mux Player embed to those pieces, then maps the settings and APIs you are most likely to need next.

> **Tip**
>
> Did you use Mux Player without controls as a hero, ambient loop, or decorative video? Go to [Add a background video](https://videojs.org/docs/framework/html/guides/background-video). It starts with a browser-playable video file, then covers the HLS and Mux background components.

## Before you migrate

A few minutes of auditing tells you which sections of this guide apply to you:

- **List the Mux Player attributes or React props you set.** Compare them with the mapping tables below to make your migration checklist.
- **Grep your codebase for `mux-player` selectors.** CSS rules and `querySelector` calls that reach into the element — `mux-player::part(…)`, `mux-player [role="slider"]`, `player.shadowRoot` — will silently stop matching after the swap. Plan to restyle with the skin’s custom properties or rebuild against Video.js components.
- **Find your event listeners and imperative calls** (`play()`, `currentTime`, `addChapters`). Media APIs move to the media element; the rest is mapped under [Drive playback](#drive-playback).
- **Note your theme and CSS variables.** Two skins replace Mux Player’s five themes, and `--accent-color` and friends have new names.
- **Check your catalog for DRM or TS-packaged assets.** They decide which of the two Mux media flavors you can use; see [Which Mux media should you use?](#which-mux-media-should-you-use).

## Three pieces instead of one

Mux Player packs three jobs into a single element. Video.js splits them up, so it helps to learn the names before you write any code.

**The player** is the outer custom element. It holds state, hands that state to everything inside it, and draws no UI itself. Which state it holds depends on the [features](https://videojs.org/docs/framework/html/guides/features) it’s built from.

**The media** is the thing that plays the video. The Mux media is the one you want: it knows what a playback ID is and how to talk to Mux. Swap it for another [media component](https://videojs.org/docs/framework/html/guides/media-sources) and the rest of your player keeps working.

**The skin** is the UI: the controls, the poster, the captions, the settings menu, the keyboard shortcuts. [Skins](https://videojs.org/docs/framework/html/guides/skins) are pre-built arrangements of smaller components, and you can use one as-is, restyle it, or take it apart.

So a Mux Player embed becomes a player wrapped around a skin wrapped around a media:

```html
<video-player>
  <video-skin>
    <mux-video></mux-video>
  </video-skin>
</video-player>
```

Everything else in this guide is about which of those three a Mux Player setting now belongs to.

## Your first player

Here’s a typical Mux Player embed. A playback ID, a title and viewer ID for analytics, and a poster pulled from two seconds in.

```html
<script src="https://cdn.jsdelivr.net/npm/@mux/mux-player" defer></script>

<mux-player
  playback-id="EcHgOK9coz5K…"
  metadata-video-title="Test VOD"
  metadata-viewer-user-id="user-id-007"
  thumbnail-time="2"
></mux-player>
```

From the CDN, load the video [preset](https://videojs.org/docs/framework/html/guides/presets), the Mux media, and analytics:

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

<video-player content-title="Test VOD">
  <video-skin style="aspect-ratio: 16 / 9">
    <mux-video
      src="https://stream.mux.com/EcHgOK9coz5K….m3u8"
      poster-time="2"
      playsinline
      crossorigin="anonymous"
    ></mux-video>
    <mux-data player-software-name="my-app"></mux-data>
  </video-skin>
</video-player>

<script type="module">
  document.querySelector('mux-data').metadata = {
    video_title: 'Test VOD',
    viewer_user_id: 'user-id-007',
  };
</script>
```

If you use a bundler instead of the CDN, the imports are the same three pieces plus the Mux Data extension:

```bash
npm install @videojs/html @videojs/mux-video @videojs/mux-data
```

```js
import '@videojs/html/video/player';
import '@videojs/html/video/skin';
import '@videojs/html/media/mux-video';
import '@videojs/html/extensions/mux-data';
```

> **Caution: Analytics changes from opt-out to opt-in**
>
> Mux Player monitors playback by default. Video.js sends no Mux Data beacons unless you add the `<mux-data>` extension shown above. Keep it when you want a behaviorally equivalent migration; omit it only when you intend to stop monitoring.

A few things worth calling out:

- **The skin has no size of its own**, so the examples give it one with an inline `aspect-ratio`. Any styling that sizes the skin works the same way, whether that’s a class of yours or a Tailwind utility like `aspect-video`. See [Move your layout styles](#move-your-layout-styles).
- **The Mux media supplies the poster.** It builds the image URL from the playback ID, and the skin displays it automatically. The example keeps the frame from two seconds in by configuring the Mux media. Set `poster` on the player only when you want to use your own URL.
- **You didn’t add a storyboard track, and you get hover previews.** The Mux media adds and maintains the thumbnail track itself, and removes it for live streams, where storyboards don’t exist.
- **Analytics needs no environment key.** Mux resolves the environment from the playback ID. See [Mux Data](https://videojs.org/docs/framework/html/guides/mux-data).
- **Chromecast is opt-in.** Follow [Cast to AirPlay and Chromecast](https://videojs.org/docs/framework/html/guides/casting) when you want it; leave the extension out when you don’t.

### Move your layout styles

Mux Player was one visible, measurable element. Video.js separates player state from the visible surface.

If you use `<video-skin>`, put width, height, aspect ratio, positioning, and DOM measurements on `<video-skin>`. Importing the skin registers the styles that make the media, controls, poster, and overlays fill and layer within it. The skin also makes `<video-player>` boxless.

If you leave out `<video-skin>` to build your own UI, add a [`<media-container>`](https://videojs.org/docs/framework/html/reference/components/player-container). Style and measure that element, and put overlays inside it:

```html
<video-player>
  <media-container class="player-surface">
    <mux-video src="https://stream.mux.com/EcHgOK9coz5K….m3u8"></mux-video>
    <my-overlay></my-overlay>
  </media-container>
</video-player>
```

```css
.player-surface {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
}
```

`<mux-video>` and the other custom video elements do not create their own layout box. They fill their parent, so size and measure `<media-container>`, not the video element.

## Mux settings live in a source object

Mux Player exposes Mux stream parameters as attributes such as `max-resolution`, `asset-start-time`, and `custom-domain`.

Video.js collects them into a single **source** object that describes what to play and how.

```js
{
  playbackId: 'EcHgOK9coz5K…',
  playback: { maxResolution: '1080p', assetStartTime: 10, assetEndTime: 30 },
  poster: { time: 2, width: 1280 },
}
```

The groups map to the three URLs Mux serves. `playback` modifies the video stream, `poster` modifies the still image, and `storyboard` modifies the hover-preview track. Video.js builds all three URLs and converts your camel-case keys to the `snake_case` query parameters Mux expects, so `assetStartTime` goes out as `asset_start_time`.

Assign it as a property, since an object has no attribute form:

```ts
document.querySelector('mux-video')!.source = { playbackId: 'EcHgOK9coz5K…' };
```

You can also skip the object entirely and set `src` to a full Mux URL. The element parses it back into a source, query parameters included, which is what you want in markup you can’t run JavaScript against:

```html
<mux-video src="https://stream.mux.com/EcHgOK9coz5K….m3u8?max_resolution=1080p"></mux-video>
```

There’s no `playback-id` attribute, so declarative markup uses the URL form.

Here’s where the Mux Player attributes land:

| Mux Player | Video.js v10 |
| --- | --- |
| `playback-id` | `source.playbackId` |
| `custom-domain` | `source.customDomain` |
| `max-resolution`, `min-resolution` | `source.playback.maxResolution`, `.minResolution` |
| `rendition-order` | `source.playback.renditionOrder` |
| `asset-start-time`, `asset-end-time` | `source.playback.assetStartTime`, `.assetEndTime` |
| `program-start-time`, `program-end-time` | `source.playback.programStartTime`, `.programEndTime` |
| `default-subtitles-lang` | `source.playback.defaultSubtitlesLang` |
| `playback-token` | `source.playback.token` |
| `thumbnail-token`, `storyboard-token` | `source.poster.token`, `source.storyboard.token` |
| `drm-token` | `source.drm.token` |
| `thumbnail-time` | `source.poster.time` |
| poster size, crop, rotation, format | `source.poster.width`, `.height`, `.fitMode`, `.rotate`, `.ext` |

Signed playback behaves the way it does in Mux Player: a token replaces every other parameter in its group, so caps and clipping have to be baked into the token itself.

## Analytics moves to its own extension

Mux Player’s analytics settings become attributes or properties on the [Mux Data extension](https://videojs.org/docs/framework/html/reference/components/mux-data), placed inside the player.

| Mux Player | Video.js v10 |
| --- | --- |
| `metadata-*`, the `metadata` property | the `metadata` property |
| `env-key` | `env-key`, rarely needed for Mux-hosted content |
| `disable-cookies` | `disable-cookies` |
| `beacon-collection-domain` | `beacon-collection-domain` |
| `player-software-name`, `player-software-version` | `player-software-name`, `player-software-version` |
| `debug` | the `debug` property |
| `disable-tracking` | omit the component |

`metadata` is a property rather than an attribute because an object has no sensible string form.

Your metadata keys don’t change. They’re the same `snake_case` names Mux Data has always taken, so the values port across untouched.

## Titles and posters

Mux Player used the same title for analytics and on-screen display. Video.js separates them.

For analytics, use `metadata.video_title` on the Mux Data extension.

For display, use `content-title` on the player.

An asset titled in the Mux dashboard supplies its own: the Mux media loads the asset’s metadata and the player falls back to that title when none is set on it, so the display title is optional for Mux-hosted video.

The packaged video and live-video skins show the resolved title at the top of the player and fade it with the controls, so the embeds above already display it. The [`<media-title>`](https://videojs.org/docs/framework/html/reference/components/title#styling) element sets `data-visible` while the controls are visible, so your skin overrides can move it differently. The audio skins have no title display.

A skin layout you added to your project, or a custom layout, shows the title only where you place it. Keep it inside the sized container from [Move your layout styles](#move-your-layout-styles) so it overlays the media:

```js
import '@videojs/html/ui/container';
import '@videojs/html/ui/title';
```

```html
<video-player content-title="Test VOD">
  <media-container class="player-surface">
    <mux-video src="https://stream.mux.com/EcHgOK9coz5K….m3u8"></mux-video>
    <media-title></media-title>
  </media-container>
</video-player>
```

`<mux-video>` supplies a poster from the playback ID, and `source.poster` controls the generated image. The skin displays it automatically. Pass `poster` to the player when you want to use your own URL:

```html
<video-player poster="https://image.mux.com/EcHgOK9coz5K…/thumbnail.webp?time=2">
  <video-skin>
    <mux-video src="https://stream.mux.com/EcHgOK9coz5K….m3u8"></mux-video>
    <img
      slot="poster"
      alt=""
      style="background: url('data:image/webp;base64,…') center / contain no-repeat"
    />
  </video-skin>
</video-player>
```

The player supplies the real poster. The slotted image supplies Mux Player’s `placeholder` as its background while that poster loads.

Mux serves the poster from its `thumbnail` endpoint, and `source.poster` configures that URL: `thumbnail-time` is `source.poster.time`, and the size, crop, rotation, and format options are `width`, `height`, `fitMode`, `rotate`, and `ext` beside it. Video.js converts those camel-case keys to the `snake_case` query parameters Mux expects. See [Add a poster and loading placeholder](https://videojs.org/docs/framework/html/guides/poster) for more examples or the [`<media-poster>`](https://videojs.org/docs/framework/html/reference/components/poster) reference for the element itself.

## Customize your player

Mux Player gives you attributes and documented CSS variables. Past that, you’re stuck. Video.js gives you three levels, and you should try them in order.

### Level 1: pick a skin

Two are packaged. The default skin is a modern, frosted look. The minimal skin is closer to a classic 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](https://videojs.org/docs/framework/html/guides/skins).

### Level 2: restyle it

Set custom properties on the skin. The common case is a brand color:

```css
/* Mux Player */
mux-player {
  --accent-color: rebeccapurple;
}

/* Video.js v10 */
video-skin {
  --media-accent-color: rebeccapurple;
}
```

`--media-accent-color` reaches the sliders, the active buttons, and the accent surfaces. Video.js derives a readable text color to sit on top of it; override that with `--media-accent-text-color` if you’d rather choose. `--media-border-radius` rounds the player, and `--media-scale-unit` scales the whole control bar at once. [Customize skins](https://videojs.org/docs/framework/html/guides/customize-skins#style-a-packaged-skin) has the full list.

The skins use a different visual design, so you may not need to replace Mux Player’s `primaryColor` and `secondaryColor` directly. Start with `--media-accent-color`. Restyle individual surfaces, or add the skin source to your project, only when you need a closer palette match.

### Level 3: edit skin source

For changes to controls, layout, or interactions, 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.

This is the level that per-control tweaks need, and it’s a genuine step up in effort from a Mux Player attribute. `forward-seek-offset="30"` was one character. In Video.js, the skins bake their seek step into their keyboard shortcuts and gestures, so changing it means editing those lines:

```html
<media-hotkey keys="ArrowRight" action="seekStep" value="30"></media-hotkey>
<media-hotkey keys="ArrowLeft" action="seekStep" value="-30"></media-hotkey>
<media-gesture type="doubletap" action="seekStep" value="30" region="right"></media-gesture>
```

While you’re in there: neither packaged video skin includes skip *buttons*. Mux Player’s themes show them, so if your users expect them, add a [seek button](https://videojs.org/docs/framework/html/reference/components/seek-button), which takes `seconds` and defaults to 30.

Removing a control is removing a line. That’s the trade you get for the extra setup.

## Read player state

`<mux-video>` is a real media element, so every media event you already listen for still fires on it: `play`, `timeupdate`, `ended`, `error`, and the rest. Mux Player listeners port directly. There are a few extras: `streamtypechange`, `targetlivewindowchange`, `sourcechange`, and `contentdatachange`.

For UI state rather than media state, use [`PlayerController`](https://videojs.org/docs/framework/html/reference/api/player-controller) inside a custom element. Give it a selector for the feature you care about and it keeps your element in sync:

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

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

### Drive playback

| Mux Player | Video.js v10 |
| --- | --- |
| `player.play()`, `player.pause()` | the player’s `play`, `pause`, `togglePaused`, or `media.play()`, `media.pause()` |
| `player.currentTime = 10` | the player’s `seek(10)`, or `media.currentTime = 10` |
| `player.volume`, `player.muted` | the player’s `setVolume`, `toggleMuted`, or `media.volume`, `media.muted` |
| `player.playbackRate` | the player’s `setPlaybackRate`, or `media.playbackRate` |
| `player.requestFullscreen()` | the player’s `requestFullscreen`, `exitFullscreen`, `toggleFullscreen` |
| `player.addChapters([…])` | a `<track kind="chapters">`, covered below |

Control everything through the player: “the player’s” entries are store actions, reached the same way you read state above. Setting a standard media property directly works too — player state derives from the native media events, so the UI stays in sync either way.

## Which Mux media should you use?

You can skip this section until you hit one of the two problems in it.

`<mux-video>` comes in two flavors, backed by two different playback engines, and the import path chooses between them. Start with the default. The tradeoff: the `/spf` flavor is smaller and the `/hls-js` flavor is more compatible:

| Import | Engine | When |
| --- | --- | --- |
| `.../media/mux-video` | hls.js | **The default. Start here.** |
| `.../media/mux-video/spf` | SPF | You want a much smaller bundle and neither problem below applies. |
| `.../media/mux-video/hls-js` | hls.js | You want to pin hls.js, and reach its instance and settings. |

SPF is Video.js’s own playback engine, and it’s the reason v10 can be as small as it is. It doesn’t do two things hls.js does:

- **TS-packaged media.** Some Mux assets are packaged as TS rather than CMAF. If any of your catalog is, stay on the default.
- **DRM.** SPF doesn’t license protected content.

All three imports register the same `<mux-video>` tag and accept the same source, so changing the engine is an import change. Load only one registration for that tag on a page.

The default doesn’t promise which engine it uses, which is what leaves it free to change. Import `/hls-js` if your app depends on hls.js being the one. Otherwise take the default: it plays everything.

The Mux audio component follows the same three paths, and pairs with the audio player and audio skins.

## Live streams

Unlike Mux Player, you’ll have to pick a live preset yourself; it won’t switch automatically for you.

Register the live preset and Mux media. From the CDN:

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

<live-video-player>
  <live-video-skin>
    <mux-video src="https://stream.mux.com/EcHgOK9coz5K….m3u8"></mux-video>
  </live-video-skin>
</live-video-player>
```

With a bundler, import the same pieces:

```js
import '@videojs/html/live-video/player';
import '@videojs/html/live-video/skin';
import '@videojs/html/media/mux-video';
```

You get `targetLiveWindow` and `liveEdgeStart` in player state, plus a live button that jumps to the live edge. The live preset doesn’t include `streamType` state and doesn’t force a source to be live. The Mux media detects the manifest: `targetLiveWindow` is `NaN` for an on-demand or unknown source, `0` for a sliding live window, and `Infinity` for a live event with playback history. See [the live feature](https://videojs.org/docs/framework/html/reference/api/feature-live).

The live composition is narrower than the video one on purpose: it leaves out quality, audio-track, and playback-rate state. A live skin’s settings menu therefore holds captions and nothing else. If you need one of the omitted features on a live player, build your own player from a feature list rather than taking the preset’s.

Use [`createPlayer`](https://videojs.org/docs/framework/html/reference/api/html-create-player) to build a player element from that list.

### Add your own jump-to-live button

The live skin already includes a live button. If you build your own controls, use that same component and style it to match your app. It keeps track of whether playback is live and returns viewers to the newest available point in the stream.

```html
<script type="module">
  import '@videojs/html/ui/live-button';
</script>

<media-live-button class="station-live-button">On air</media-live-button>
```

Do not rebuild this behavior from `duration` or set `currentTime` to `Infinity`.

### Troubleshoot live playback

#### An on-demand video shows live controls

Video.js does not replace the preset after reading the HLS playlist. Render the video preset for on-demand content and the live preset for live content. If the same part of your app handles both, choose the preset from application or content metadata before rendering. If only the manifest can tell you, use a custom player with the stream type feature and choose the controls from that state.

#### `selectStreamType` returns `undefined`

The live preset does not include the [stream type feature](https://videojs.org/docs/framework/html/reference/api/feature-stream-type). Add `streamTypeFeature` to a custom player when you need `streamType` in player state.

The HLS media object also reads `'live'` or `'on-demand'` from the playlist. Read `streamType` from `<mux-video>`.

#### A live stream has a finite duration

The browser may report `Infinity` for live playback, but the player’s [time feature](https://videojs.org/docs/framework/html/reference/api/feature-time) reports the end of the available video. That number stays finite and moves forward with the stream.

Do not use `duration` to decide whether a source is live. Read `streamType`, or check that `targetLiveWindow` is not `NaN`.

#### `targetLiveWindow` does not match the rewind time

Despite its name, `targetLiveWindow` does not report a number of seconds. It describes the kind of live stream:

| Value | Meaning |
| --- | --- |
| `0` | A sliding live window |
| `Infinity` | A live event with playback history |
| `NaN` | On-demand or not known yet |

To find the times a viewer can seek to, read `buffer.seekable`. It contains `[start, end]` pairs. The first start is the oldest available time, and the last end is the newest. Both move forward on a sliding live stream.

`liveEdgeStart` is the playback time where the player starts treating the viewer as live. The live button seeks to the last end in `buffer.seekable`, which may be later than `liveEdgeStart`.

## The settings menu

On a video player, the settings menu appears on its own when there’s something to put in it:

| What | Component | Notes |
| --- | --- | --- |
| Quality | [Quality radio group](https://videojs.org/docs/framework/html/reference/components/quality-radio-group) | Lets a viewer pick the video quality. To set the highest available quality instead, use `source.playback.maxResolution`. |
| Audio tracks | [Audio track radio group](https://videojs.org/docs/framework/html/reference/components/audio-track-radio-group) | For multi-language audio. |
| Captions | [Captions radio group](https://videojs.org/docs/framework/html/reference/components/captions-radio-group) | Rendered by the browser. |
| Speed | [Playback rate radio group](https://videojs.org/docs/framework/html/reference/components/playback-rate-radio-group) | The rates are a fixed set — `0.2`, `0.5`, `0.7`, `1`, `1.2`, `1.5`, `1.7`, `2` — and you can’t choose your own yet ([#1404](https://github.com/videojs/v10/issues/1404)). |

Caption *styling* is limited to the positioning custom properties the skins expose. There’s no equivalent of a text-track settings dialog.

## Chapters and cue points

Chapters are content, not an API call. Add a chapters track inside your media and the skins segment the [time slider](https://videojs.org/docs/framework/html/reference/components/time-slider) and show the chapter title on hover:

```html
<mux-video src="https://stream.mux.com/EcHgOK9coz5K….m3u8">
  <track kind="chapters" src="/chapters.vtt" default />
</mux-video>
```

There’s no `addChapters()`. If you were building that VTT on the fly, you’ll still need to, but you point the player at it instead of passing an array ([#1268](https://github.com/videojs/v10/issues/1268)).

The time slider marks the segment containing the current playback time with `data-active`, so you can style the active segment. The player still has no active-chapter state, `chapterchange` event, or menu for jumping between chapters ([#1873](https://github.com/videojs/v10/issues/1873)). If UI outside the time slider needs the current chapter, derive it from `currentTime` and the read-only `chaptersCues` array. Cue points aren’t implemented at all ([#1442](https://github.com/videojs/v10/issues/1442)).

## Signed playback and DRM

**Signed playback works today.** Follow Mux’s [secure video playback guide](https://www.mux.com/docs/guides/secure-video-playback) to create a signing key and generate these tokens on your server. Each token is a parameter in its own group, and Video.js checks each one’s audience before building a URL, so a token in the wrong slot produces no URL rather than a request Mux would reject:

```js
{
  playbackId: 'EcHgOK9coz5K…',
  playback: { token: '…' },   // audience: v
  poster: { token: '…' },     // audience: t
  storyboard: { token: '…' }, // audience: s
}
```

Signed playback needs its own poster token even when the Mux media would otherwise derive the poster from the playback ID. Put Mux’s thumbnail token at `source.poster.token`. If it is missing or has the wrong audience, the media cannot build a poster URL.

Neither player refreshes tokens. Mux Player at least notices an expired one and shows a friendly message; Video.js doesn’t surface that yet ([#1432](https://github.com/videojs/v10/issues/1432)).

**DRM works on the default Mux media** and on native HLS. Hand it a license token and Video.js derives the FairPlay, Widevine, and PlayReady license servers from it, along with the FairPlay application certificate:

```js
{
  playbackId: 'EcHgOK9coz5K…',
  playback: { token: '…' },
  drm: { token: '…' }, // audience: d
}
```

DRM playback is always signed, so `drm.token` needs a `playback.token` beside it. For content Mux doesn’t license, name license servers yourself, keyed by key system; yours win over the derived ones key by key.

The SPF flavor doesn’t license DRM ([#1776](https://github.com/videojs/v10/issues/1776)), which is one of the two reasons to stay on the default import.

## Access the media and playback engine

| Mux Player | Video.js v10 |
| --- | --- |
| `media.nativeEl` | the `<mux-video>` element itself for standard media properties and methods |
| the hls.js instance | `.engine` on the `<mux-video>` element |
| `prefer-playback` | `source.preferPlayback`, or pick [native HLS](https://videojs.org/docs/framework/html/reference/components/native-hls-video) outright |

`source.preferPlayback` is the closest mapping: set it to `'native'` and the Mux media hands playback to the browser’s own HLS support instead of building an MSE pipeline, which is what `prefer-playback="native"` did.

> **Caution: Treat engine access as an escape hatch**
>
> Reading `.engine` couples your app to hls.js and the `/hls-js` flavor. Prefer Video.js state, events, and media APIs when they cover your use case. Reach into the engine only for functionality Video.js doesn’t expose.

Program Date Time has no convenience surface: no `getStartDate()`, no `currentPdt`. The player exposes `liveEdgeStart` and `targetLiveWindow`; for PDT itself, reach into the engine.

## Known gaps

Ordered roughly by how many migrations they’ll touch.

- Two skins, against Mux Player’s five themes, and no runtime theme switch. That’s a deliberate trade, fewer skins whose files you can add to your project, but it’s a real difference if you shipped `theme="classic"`.
- Lazy loading (`loading="viewport|page"`) has no equivalent.
- The controls auto-hide delay isn’t configurable ([#1728](https://github.com/videojs/v10/issues/1728)). Always-visible controls are available through `visibility="always"` on the controls component in a custom layout or a skin layout in your project.
- Nothing persists between sessions: volume, captions language, speed, quality. That’s out of scope for GA ([#944](https://github.com/videojs/v10/issues/944)). Default subtitle language is tracked at [#1423](https://github.com/videojs/v10/issues/1423), though Mux users can set `source.playback.defaultSubtitlesLang` and let the HLS playlist decide.
- Smaller conveniences without homes yet: debug mode ([#1406](https://github.com/videojs/v10/issues/1406)) and autoplay with a muted fallback ([#1039](https://github.com/videojs/v10/issues/1039)). Unmuting from zero volume already restores a sensible level, so Mux Player’s smart-unmute behavior carries over.

## See also

- [Mux Video](https://videojs.org/docs/framework/html/reference/components/mux-video) and [Mux Audio](https://videojs.org/docs/framework/html/reference/components/mux-audio)
- [Mux Data](https://videojs.org/docs/framework/html/guides/mux-data)
- [Skins](https://videojs.org/docs/framework/html/guides/skins) and [Customize skins](https://videojs.org/docs/framework/html/guides/customize-skins)

---

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