# mux-video

Video element for Mux-hosted HLS streams

Video element for playing Mux-hosted HLS streams. Built on hls.js with Mux-specific optimizations.

## Import

```bash
pnpm add @videojs/mux-video
```

```ts
import '@videojs/html/media/mux-video';
```

Or load it from the [CDN](https://videojs.org/docs/framework/html/guides/cdn):

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

## Load a source

`<mux-video>` takes Mux content two ways: a stream URL through `src`, or a structured `source` object.

```html
<mux-video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM.m3u8"></mux-video>
```

The `source` object builds the URL for you from a playback ID, an optional custom domain, and `playback` params. Playback params are just camelCased [Mux playback query params](https://www.mux.com/docs/api-reference/stream/streaming/get-hls-manifest); for example, `max_resolution` becomes `maxResolution`.

```ts
const video = document.querySelector('mux-video')!;
video.source = {
  playbackId: 'BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM',
  customDomain: 'media.example.com',
  playback: { maxResolution: '1080p' },
};
```

`<mux-video>` ignores the same source object when it is assigned again. A new object fires `sourcechange` even when its values are equal, although an equivalent playback source does not reload.

## Posters and storyboards

The Mux video component derives a poster image and a storyboard from the playback ID. `source.poster` and `source.storyboard` configure the generated URLs.

When the Mux video component is inside a player, it supplies the poster for the skin to display. Set `poster` on the player when you want to use your own image instead.

The Mux video component injects the derived storyboard `<track>` automatically and removes it when the media detects a live stream. Configure the derived URLs through the source object:

```ts
const video = document.querySelector('mux-video')!;
video.source = {
  playbackId: 'BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM',
  poster: { time: 2, width: 1280 },
  storyboard: { format: 'webp' },
};
```

For declarative HTML, `poster-time="2"` reflects to `source.poster.time` after the `src` attribute is parsed.

The storyboard lives on a Mux domain, so the Mux video component has to be CORS-enabled for that cross-origin `<track>` to load at all. The [thumbnail component](https://videojs.org/docs/framework/html/reference/components/thumbnail) then fetches the sprite sheets its cues point at the same way:

```html
<mux-video
  src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM.m3u8"
  crossorigin="anonymous"
></mux-video>
```

## Signed playback

For [signed playback](https://www.mux.com/docs/guides/secure-video-playback), put the playback token on `source.playback.token`. The token replaces every other playback param, so bake modifiers like resolution and time bounds into the token when you sign it.

Signed playback needs a separate token for each image URL. Put the thumbnail token (`aud: 't'`) at `source.poster.token` and the storyboard token (`aud: 's'`) at `source.storyboard.token`. If either token is missing or has the wrong audience, the Mux video component does not generate the corresponding URL.

```ts
const video = document.querySelector('mux-video')!;
video.source = {
  playbackId: 'BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM',
  playback: { token: playbackToken },
  poster: { token: posterToken },
  storyboard: { token: storyboardToken },
};
```

## Analytics and casting

The Mux video component plays Mux streams. It doesn’t monitor them or cast them — [Mux Data](https://videojs.org/docs/framework/html/guides/mux-data) and [Google Cast](https://videojs.org/docs/framework/html/guides/casting) are separate extensions you add to the player alongside it. Mux Data needs no environment key here, since Mux attributes the views to the environment that owns the playback ID:

Install both extensions before using the example below:

```bash
pnpm add @videojs/mux-data @videojs/google-cast
```

```html
<script type="module">
  import '@videojs/html/video/player';
  import '@videojs/html/media/mux-video';
  import '@videojs/html/extensions/mux-data';
  import '@videojs/html/extensions/google-cast';
</script>

<video-player>
  <mux-video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM.m3u8" playsinline></mux-video>
  <mux-data player-software-name="mux-video"></mux-data>
  <google-cast></google-cast>
</video-player>
```

- [Mux Data](https://videojs.org/docs/framework/html/guides/mux-data): Metadata, options, and configuration
- [Cast to AirPlay and Chromecast](https://videojs.org/docs/framework/html/guides/casting): Receivers, load requests, and session state

## Examples

### Basic Usage

**index.html**

```html
<media-container class="media-container">
  <mux-video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM.m3u8" autoplay muted playsinline loop crossorigin="anonymous"></mux-video>
</media-container>
```

**index.css**

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

**index.ts**

```ts
import '@videojs/html/ui/container';
import '@videojs/html/media/mux-video';
```

## API Reference

### Attributes

Forwards these standard media attributes to the internal `<video>`. See the [MDN media element reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video): `autopictureinpicture`, `autoplay`, `controls`, `controlslist`, `crossorigin`, `disablepictureinpicture`, `disableremoteplayback`, `loading`, `loop`, `muted`, `playsinline`, `poster`, `preload`, `src`.

These Video.js-specific attributes configure media behavior:

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `stream-type` | `'on-demand' \| 'live' \| 'unknown'` | `'unknown'` | Current stream type (`'on-demand'` / `'live'` / `'unknown'`). |

### Properties

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `audioRenditions` | `AudioRenditionListLike \| undefined` | — | Read-only. Selectable audio variants, populated only while the hls.js (MSE) engine is active; otherwise `undefined`. |
| `audioTracks` | `AudioTrackListLike \| undefined` | — | Read-only. Populated only while the hls.js (MSE) engine is active; otherwise `undefined`. |
| `contentData` | `{ readonly [key: string]: MediaContentValue; title?: string; poster?: string; storyboard?: string }` | — | Read-only. What `source` says about its content. `poster` and `storyboard` are image URLs it describes rather than plays, from its `poster` and `storyboard` params; a key is absent when the URL can't be built — no playback ID, or signed playback without a matching image token. `title` and the rest come from the metadata Mux publishes for the asset, which loads with the source, so they arrive later than the URLs and are absent until then. The same object is handed back until something in it changes, and `contentdatachange` announces it when it does. Nothing here is applied for you, apart from the thumbnail track `<mux-video>` adds from `storyboard` (and drops for live streams). |
| `disableRemotePlayback` | `unknown` | `false` | Whether remote playback (AirPlay, Google Cast) is disabled for this media. |
| `engine` | `Hls \| null` | — | Read-only. Underlying playback engine — the hls.js `Hls` instance when playing via MSE, otherwise `null`. An advanced escape hatch for direct engine access; normal playback is driven through this element's own properties and methods. |
| `error` | `MediaError \| null` | — | Read-only. |
| `isFullscreen` | `boolean` | — | Read-only. |
| `isPictureInPicture` | `boolean` | — | Read-only. |
| `liveEdgeStart` | `number` | — | Read-only. Presentation time marking the start of the Live Edge Window. Derived from the delegate on every read; `NaN` when no delegate is attached or the stream is not live. |
| `preload` | `MediaPreloadType` | `'metadata'` | Preload type (`'none'` / `'metadata'` / `'auto'`). |
| `source` | `{ type?: 'application/vnd.apple.mpegurl' \| 'video/mp4'; preferPlayback?: 'mse' \| 'native'; maxAutoResolution?: '270p' \| '360p' \| '480p' \| '540p' \| '720p' \| '1080p' \| '1440p' \| '2160p'; capRenditionToPlayerSize?: boolean; minAutoResolution?: '270p' \| '360p' \| '480p' \| '540p' \| '720p' \| '1080p' \| '1440p' \| '2160p'; engine?: HlsEngineConfig; src?: string; playbackId?: string; customDomain?: string; playback?: MuxPlaybackParams; poster?: MuxPosterParams; storyboard?: MuxStoryboardParams; drm?: MuxDrmParams } \| null` | `null` | Structured Mux source. Setting it derives `src` from the playback ID, custom domain, and `playback` params (appended as `snake_case` query params). A `playback.token` replaces all other params — signed URLs bake them into the token. Engine options live under `engine`. A `drm.token` fills in `drm` itself: Mux's FairPlay, Widevine, and PlayReady license servers for this playback ID, so protected media plays whichever path the browser takes. License servers named alongside the token win, key by key, for content Mux does not license. `playback.maxResolution` and `playback.minResolution` are server-side: they decide which renditions Mux puts in the manifest at all. The inherited `maxAutoResolution` and `minAutoResolution` only look like their pair — those are client-side and bound which of the renditions that *do* arrive adaptive selection reaches for. The two halves are independent. |
| `src` | `string` | `''` | Media source URL. Setting a Mux stream URL (`https://stream.mux.com/<playback-id>.m3u8?...`) extracts the playback ID and query params into `source`; other URLs are kept as a plain `source.src`. Only playback options carry over. Mux identity comes from the URL, and the signed `poster`, `storyboard`, and `drm` tokens are scoped to a playback ID, so carrying them onto a different source would build rejected URLs. |
| `streamType` | `'on-demand' \| 'live' \| 'unknown'` | `'unknown'` | Current stream type (`'on-demand'` / `'live'` / `'unknown'`). |
| `targetLiveWindow` | `number` | — | Read-only. Seekable range size for live content. `0` for standard live, `Infinity` for DVR, `NaN` for on-demand or unknown. Fires `targetlivewindowchange` when the value changes (bridged from the delegate). |
| `videoRenditions` | `VideoRenditionListLike \| undefined` | — | Read-only. Selectable quality levels, populated only while the hls.js (MSE) engine is active; otherwise `undefined`. |
| `videoTracks` | `VideoTrackListLike \| undefined` | — | Read-only. Populated only while the hls.js (MSE) engine is active; otherwise `undefined`. |
| `webkitCurrentPlaybackTargetIsWireless` | `boolean \| undefined` | — | Read-only. |
| `webkitPresentationMode` | `WebKitPresentationMode \| undefined` | — | Read-only. |
| `webkitSetPresentationMode` | `((mode: WebKitPresentationMode) => void) \| undefined` | — | Read-only. |

Also exposes these properties from the native media API. See [HTMLVideoElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement) for details: `autoplay`, `buffered`, `controls`, `crossOrigin`, `currentSrc`, `currentTime`, `defaultMuted`, `defaultPlaybackRate`, `disablePictureInPicture`, `duration`, `ended`, `loop`, `muted`, `paused`, `playbackRate`, `played`, `playsInline`, `poster`, `readyState`, `remote`, `seekable`, `seeking`, `textTracks`, `title`, `videoHeight`, `videoWidth`, `volume`.

### Engine options

#### `source.engine.nativeHls`

Options passed under `source.engine.nativeHls`.

| Option | Type | Description |
| --- | --- | --- |
| `drmSystems` | `Partial<Record<KeySystem, DrmSystemConfig>> \| undefined` | License servers for protected content, keyed by EME key system id. An escape hatch for licensing native playback differently from every other path: naming it replaces `source.drm` here, and nowhere else. |

### Methods

Supports these media methods. See [HTMLVideoElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement) for details: `addTextTrack`, `canPlayType`, `exitFullscreen`, `exitPictureInPicture`, `load`, `pause`, `play`, `requestFullscreen`, `requestPictureInPicture`.

### Events

Re-dispatches these standard media events from the internal media element: `abort`, `addtrack`, `canplay`, `canplaythrough`, `change`, `durationchange`, `emptied`, `ended`, `enterpictureinpicture`, `error`, `leavepictureinpicture`, `loadeddata`, `loadedmetadata`, `loadstart`, `pause`, `play`, `playing`, `progress`, `ratechange`, `removetrack`, `resize`, `seeked`, `seeking`, `stalled`, `suspend`, `timeupdate`, `volumechange`, `waiting`.

Also emits these Video.js-specific events:

| Event | Description |
| --- | --- |
| `contentdatachange` | Fired when `contentData` changes: the derived URLs with `source`, and the metadata once it loads. Read `contentData` for the new value. |
| `sourcechange` | Fired when `source` changes, either directly or by resolving a new `src`. Read `source` for the new value. |
| `streamtypechange` | Fired when the detected stream type changes. Read `streamType` for the new value. |
| `targetlivewindowchange` | Fired when the target live window changes. Read `targetLiveWindow` for the new value. |

### CSS custom properties

| Variable | Description |
| --- | --- |
| `--media-video-border-radius` | Border radius of the video element. |
| `--media-object-fit` | Object fit for the video. |
| `--media-object-position` | Object position for the video. |
| `--media-caption-track-duration` | Duration of the caption track transition. |
| `--media-caption-track-delay` | Delay before the caption track transition. |
| `--media-caption-track-y` | Vertical offset of the caption track. |

---

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