# Cast to AirPlay and Chromecast

Send playback to AirPlay and Google Cast devices, with availability detection and connection state.

Send playback to an Apple TV over AirPlay or a Chromecast over Google Cast while the browser stays in control.

> **Note**
>
> Using a pre-built [skin](https://videojs.org/docs/framework/html/guides/skins)? It already includes the controls shown here. You may still need the media or player setup in this guide. The component examples are for building your own player UI from individual [components](https://videojs.org/docs/framework/html/guides/ui-components).

## Recommended approach

Use the [`<google-cast>` extension](https://videojs.org/docs/framework/html/reference/components/google-cast) with a [`<media-cast-button>`](https://videojs.org/docs/framework/html/reference/components/cast-button), and add an [`<media-airplay-button>`](https://videojs.org/docs/framework/html/reference/components/airplay-button) for Safari. The prebuilt skins already include both buttons; add `<google-cast>` next to a streaming media component to enable the configurable Cast route.

**index.html**

```html
<video-player class="video-player">
  <media-container>
    <hlsjs-video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM.m3u8" autoplay muted playsinline loop></hlsjs-video>
    <google-cast></google-cast>
    <div class="media-remote-buttons">
      <media-airplay-button class="media-airplay-button">
        <span class="connected">Stop AirPlay</span>
        <span class="not-connected">Start AirPlay</span>
      </media-airplay-button>
      <media-cast-button class="media-cast-button">
        <span class="connected">Stop casting</span>
        <span class="disconnected">Start casting</span>
      </media-cast-button>
    </div>
  </media-container>
</video-player>
```

**index.css**

```css
.video-player {
  position: relative;
  display: block;
}

.video-player video {
  width: 100%;
}

.media-remote-buttons {
  position: absolute;
  right: 10px;
  bottom: 10px;
  display: flex;
  gap: 8px;
}

.media-airplay-button,
.media-cast-button {
  padding-block: 8px;
  padding-inline: 20px;
  color: black;
  cursor: pointer;
  background: rgba(255, 255, 255, 0.7);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 9999px;
  backdrop-filter: blur(10px);
}

.media-airplay-button .connected,
.media-airplay-button .not-connected,
.media-cast-button .connected,
.media-cast-button .disconnected {
  display: none;
}

/* AirPlay: swap label on connection state */
.media-airplay-button[data-airplay-state="connected"] .connected {
  display: inline;
}
.media-airplay-button:not([data-airplay-state="connected"]) .not-connected {
  display: inline;
}

/* Cast: swap label on connection state */
.media-cast-button[data-cast-state="connected"] .connected {
  display: inline;
}
.media-cast-button:not([data-cast-state="connected"]) .disconnected {
  display: inline;
}

/* Explicitly disabled while the route is unavailable. */
.media-airplay-button[data-disabled],
.media-cast-button[data-disabled] {
  cursor: not-allowed;
  opacity: 0.5;
  filter: grayscale(1);
}
```

**index.ts**

```ts
import '@videojs/html/video/player';
import '@videojs/html/ui/container';
import '@videojs/html/extensions/google-cast';
import '@videojs/html/media/hlsjs-video';
import '@videojs/html/ui/airplay-button';
import '@videojs/html/ui/cast-button';
```

The demo pairs the streaming media element with the `<google-cast>` extension. Without that extension, `<media-cast-button>` drives the browser’s native Remote Playback API instead, which does not provide Video.js receiver or load-request configuration.

## How it works

The [remote playback feature](https://videojs.org/docs/framework/html/reference/api/feature-remote-playback) gives AirPlay and Google Cast one state shape:

- `remotePlaybackAvailability` is `'available'` when a remote device can be reached, `'unavailable'` when the API exists but no device is reachable, and `'unsupported'` when the browser has no remote playback route.
- `remotePlaybackState` is `'disconnected'`, `'connecting'`, or `'connected'`.
- `toggleRemotePlayback()` opens the browser’s device picker, or disconnects an active session.

AirPlay uses WebKit’s presentation APIs. Google Cast uses a sender and receiver: the browser sends the receiver a media URL and then controls playback. Your UI reads the same Video.js state for either route.

While connected, playback actions — play, pause, seek, volume — proxy to the remote device, and player state keeps reflecting the remote session. Local playback is suspended.

The Google Cast SDK loads only when the `<google-cast>` extension is present in a Chromium browser. Players without that extension and browsers that cannot cast do not load it.

## Availability and constraints

- Remote playback is asynchronous and can fail mid-session when a device or network disappears. React to `remotePlaybackState` rather than assuming a request succeeds.
- Google Cast works only in Chromium-based browsers; elsewhere its route reports `'unsupported'`.
- AirPlay works only in Safari; availability requires an AirPlay device reachable on the network.
- The remote device fetches the source and tracks itself. Localhost, VPN-only, expired, or cross-origin URLs without the necessary CORS headers will fail on the receiver even if they play in the browser.
- Set the `disableremoteplayback` attribute on the media element to opt out entirely.

## Common variations

### Custom Cast receiver

By default, Video.js sends the current source to Google’s Default Media Receiver. Set `receiver` when you have your own receiver application:

```html
<google-cast receiver="YOUR_APP_ID"></google-cast>
```

Use `src` and `contentType` when the receiver should load a different source than the browser. `streamType` and `customData` let you describe that source and attach application-specific data. See the [`<google-cast>` reference](https://videojs.org/docs/framework/html/reference/components/google-cast) for the exact option types and attribute-to-property mapping.

### Read connection state

Use `remotePlaybackAvailability` to decide whether to offer a route and `remotePlaybackState` to show whether it is disconnected, connecting, or connected. `<media-cast-button>` and `<media-airplay-button>` already apply these states to their presentation; read them directly only when another part of your app needs them.

## Troubleshooting

### The cast button doesn’t render, or renders disabled

The Cast button hides when the route is unsupported. In Chromium with no Cast device reachable, it stays visible but disabled. The AirPlay button hides unless Safari reports an AirPlay device.

### Casting starts but the device shows an error

The receiver cannot fetch the media. Confirm the source URL is reachable from the device and served with CORS headers, including caption tracks.

### The UI freezes on “connecting”

The user dismissed the picker or the device disconnected. `remotePlaybackState` returns to `'disconnected'`; drive your UI from that state.

## Related pages

### Components

- [media-cast-button](https://videojs.org/docs/framework/html/reference/components/cast-button): Accessible Cast toggle button with state reflection and keyboard support
- [media-airplay-button](https://videojs.org/docs/framework/html/reference/components/airplay-button): Accessible AirPlay toggle button that opens the WebKit playback target picker and reflects session state
- [google-cast](https://videojs.org/docs/framework/html/reference/components/google-cast): Options for the Google Cast extension that configures the receiver and load request

### API

- [Remote Playback](https://videojs.org/docs/framework/html/reference/api/feature-remote-playback): Remote playback state and actions for the player store
- [hlsjs-video](https://videojs.org/docs/framework/html/reference/components/hlsjs-video): HLS video element powered by hls.js for adaptive bitrate streaming

### Guides

- [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

---

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