# Media capability guards

Type guards that narrow a Media object to the capabilities it supports

The media a player attaches is typed as `Media`, which guarantees only a `play()` method and the `addEventListener`, `removeEventListener`, and `dispatchEvent` event methods. A native `<video>` element, an engine-backed media such as `HlsVideo`, and an embed such as YouTube each support a different set of everything else. Capability guards test whether a media supports one capability and narrow its type to that capability’s members.

## Import

```tsx
import { isMediaSeekCapable, isMediaVolumeCapable } from "@videojs/react";
```

Every guard on this page is a named export of the same package.

## Usage

Get the media with [`useMedia`](https://videojs.org/docs/framework/react/reference/api/use-media), then narrow it before touching anything other than `play()` and the event methods:

**HalfVolumeButton.tsx**

```tsx
import { isMediaVolumeCapable, useMedia } from "@videojs/react";

export function HalfVolumeButton() {
  const media = useMedia();
  if (!isMediaVolumeCapable(media)) return null;

  return <button onClick={() => { media.volume = 0.5; }}>50% volume</button>;
}
```

The examples below assume `media` comes from `useMedia()` in a component rendered inside `Player`.

Each `isMedia*Capable` guard accepts any value and returns `false` for `null`, `undefined`, and other non-objects, so you can pass the media straight in without a separate null check.

## Behavior

A guard checks that a few members of a capability are present, then narrows to every member of that capability’s contract. The table lists both.

Some media, such as embeds, can’t provide buffered ranges, text tracks, or remote playback, and report a shared empty placeholder in their place. `isMediaBufferCapable`, `isMediaTextTrackCapable`, and `isMediaRemotePlaybackCapable` return `false` for that placeholder.

A guard describes what the media object supports, not its current state. `isMediaErrorCapable` returns `true` while `error` is `null`, and `isMediaVolumeCapable` returns `true` on platforms that ignore volume changes. For current state and availability, such as `volumeAvailability` from the [volume feature](https://videojs.org/docs/framework/react/reference/api/feature-volume), read the player’s [features](https://videojs.org/docs/framework/react/guides/features) instead.

| Guard | Checks | Narrows to |
| --- | --- | --- |
| `isMediaPauseCapable` | `paused`, `ended`, `pause()` | `pause()`, `paused`, `ended` |
| `isMediaSeekCapable` | `currentTime`, `duration`, `seeking` | `currentTime`, `loop`, `duration`, `seeking` |
| `isMediaSourceCapable` | `src`, `currentSrc`, `readyState`, `load()` | `src`, `currentSrc`, `readyState`, `preload`, `crossOrigin`, `load()`, `canPlayType()` |
| `isMediaVolumeCapable` | `volume`, `muted` | `volume`, `muted`, `defaultMuted` |
| `isMediaPlaybackRateCapable` | `playbackRate` | `playbackRate`, `defaultPlaybackRate` |
| `isMediaBufferCapable` | `buffered`, `seekable`, neither an empty placeholder | `buffered`, `seekable` |
| `isMediaErrorCapable` | `error` (`null` counts) | `error` |
| `isMediaTextTrackCapable` | `textTracks`, not an empty placeholder | `textTracks`, `addTextTrack()` |
| `isMediaVideoRenditionCapable` | `videoRenditions` | `videoRenditions` |
| `isMediaAudioTrackCapable` | `audioTracks` | `audioTracks`, `addAudioTrack()`, `removeAudioTrack()` |
| `isMediaVideoDimensionsCapable` | `videoWidth`, `videoHeight` | `videoWidth`, `videoHeight` |
| `isMediaRemotePlaybackCapable` | `remote` is an object, not an empty placeholder | `remote`, `disableRemotePlayback` |
| `isMediaStreamTypeCapable` | `streamType` | `streamType` |
| `isMediaLiveCapable` | `liveEdgeStart`, `targetLiveWindow` | `liveEdgeStart`, `targetLiveWindow` |

`hasMetadata` isn’t a type guard: it takes a media already narrowed by `isMediaSourceCapable` and returns a boolean.

## Guards

### `isMediaPauseCapable`

Narrows to `pause()` and the read-only `paused` and `ended` booleans.

```tsx
function togglePaused() {
  if (!isMediaPauseCapable(media)) return;

  if (media.paused) media.play();
  else media.pause();
}
```

### `isMediaSeekCapable`

Narrows to the writable `currentTime` and `loop`, and the read-only `duration` and `seeking`. All times are in seconds.

```tsx
function skipForward() {
  if (isMediaSeekCapable(media)) media.currentTime = Math.min(media.currentTime + 10, media.duration);
}
```

### `isMediaSourceCapable`

Narrows to the writable `src`, `preload`, and `crossOrigin`; the read-only `currentSrc` and `readyState`; and the `load()` and `canPlayType(type)` methods. `canPlayType` returns `''`, `'maybe'`, or `'probably'`.

```tsx
function playNext(url: string) {
  if (!isMediaSourceCapable(media)) return;

  media.src = url;
  media.play();
}
```

### `hasMetadata`

Returns `true` when the media’s `readyState` is at least `HAVE_METADATA` (`1`), meaning duration and dimensions are known. It reads only `readyState`, so narrow with `isMediaSourceCapable` first.

```tsx
function resumeAt(seconds: number) {
  if (!isMediaSourceCapable(media) || !hasMetadata(media)) return;
  if (isMediaSeekCapable(media)) media.currentTime = seconds;
}
```

### `isMediaVolumeCapable`

Narrows to the writable `volume` (`0` to `1`), `muted`, and `defaultMuted`.

```tsx
function unmuteAtHalfVolume() {
  if (!isMediaVolumeCapable(media)) return;

  media.muted = false;
  media.volume = 0.5;
}
```

### `isMediaPlaybackRateCapable`

Narrows to the writable `playbackRate` and `defaultPlaybackRate`.

```tsx
function playFaster() {
  if (isMediaPlaybackRateCapable(media)) media.playbackRate = 1.5;
}
```

### `isMediaBufferCapable`

Narrows to the read-only `buffered` and `seekable` time ranges. Each range list has a `length` and `start(index)` and `end(index)` methods that return seconds.

```tsx
function getBufferedEnd() {
  if (!isMediaBufferCapable(media) || media.buffered.length === 0) return 0;

  return media.buffered.end(media.buffered.length - 1);
}
```

### `isMediaErrorCapable`

Narrows to the read-only `error`, which is `null` or an object with a numeric `code` and a `message`.

```tsx
function reportError() {
  if (isMediaErrorCapable(media) && media.error) console.error(media.error.code, media.error.message);
}
```

### `isMediaTextTrackCapable`

Narrows to the read-only `textTracks` list and `addTextTrack(kind, label?, language?)`. The list is iterable and indexable, and each track has `kind`, `label`, `language`, `id`, a writable `mode` (`'showing'`, `'hidden'`, or `'disabled'`), and `cues`.

```tsx
function showCaptions(language: string) {
  if (!isMediaTextTrackCapable(media)) return;

  for (const track of media.textTracks) {
    if (track.kind === "captions" || track.kind === "subtitles") {
      track.mode = track.language === language ? "showing" : "disabled";
    }
  }
}
```

### `isMediaVideoRenditionCapable`

Narrows to the read-only `videoRenditions` list. The list is iterable and indexable, has `getRenditionById(id)`, and has a writable `selectedIndex`; `-1` means automatic selection. Each rendition has `id`, `width`, `height`, `bitrate`, `frameRate`, `codec`, and `selected`.

```tsx
function switchToAutomaticQuality() {
  if (isMediaVideoRenditionCapable(media)) media.videoRenditions.selectedIndex = -1;
}
```

### `isMediaAudioTrackCapable`

Narrows to the read-only `audioTracks` list, `addAudioTrack(kind, label?, language?)`, and `removeAudioTrack(track)`. The list is iterable and indexable, and each track has `id`, `kind`, `label`, `language`, and a writable `enabled`.

```tsx
function selectAudioLanguage(language: string) {
  if (!isMediaAudioTrackCapable(media)) return;

  for (const track of media.audioTracks) track.enabled = track.language === language;
}
```

### `isMediaVideoDimensionsCapable`

Narrows to the read-only `videoWidth` and `videoHeight`, in pixels. Both are `0` until metadata loads.

```tsx
function getAspectRatio() {
  if (!isMediaVideoDimensionsCapable(media) || !media.videoHeight) return null;

  return media.videoWidth / media.videoHeight;
}
```

### `isMediaRemotePlaybackCapable`

Narrows to the read-only `remote` object and the writable `disableRemotePlayback`. `remote` has a `state` (`'connecting'`, `'connected'`, or `'disconnected'`), `prompt()`, `watchAvailability(callback)`, and `cancelWatchAvailability(id?)`, matching the browser’s [Remote Playback API](https://developer.mozilla.org/en-US/docs/Web/API/RemotePlayback).

```tsx
function castToDevice() {
  if (isMediaRemotePlaybackCapable(media)) media.remote.prompt();
}
```

Call `prompt()` from a user gesture, such as a click handler; browsers reject it otherwise.

### `isMediaStreamTypeCapable`

Narrows to `streamType`: `'on-demand'`, `'live'`, or `'unknown'` before the type is determined.

```tsx
function isLiveStream() {
  return isMediaStreamTypeCapable(media) && media.streamType === "live";
}
```

### `isMediaLiveCapable`

Narrows to the read-only `liveEdgeStart` and `targetLiveWindow`. Playback is at the live edge when `currentTime` is at or past `liveEdgeStart`, which is `NaN` when the stream isn’t live or the value is unknown. `targetLiveWindow` is `0` for a sliding live window, `Infinity` for a live event with playback history, and `NaN` for on-demand or unknown; it isn’t a duration.

```tsx
function isAtLiveEdge() {
  if (!isMediaLiveCapable(media) || !isMediaSeekCapable(media)) return false;

  return media.currentTime >= media.liveEdgeStart;
}
```

---

React documentation: https://videojs.org/docs/framework/react/llms.txt
All documentation: https://videojs.org/llms.txt
