This page contains code snippets and descriptions of the features available for
a Custom Web Receiver app.
**Note:** We recommend that you try the following:

- [**Cast Web Receiver codelab
  tutorial**](https://developers.google.com/cast/codelabs/cast-receiver) - Learn step-by-step how to build a Cast-enabled Web Receiver app using the Google Cast SDK that can receive video casts.
- [**CastReceiver sample app**](https://github.com/googlecast/CastReceiver) (GitHub) - Run, navigate, and view this reference sample app which complies with the [UX Guidelines](https://developers.google.com/cast/docs/ux_guidelines.md.txt) and [Design Checklist](https://developers.google.com/cast/docs/design_checklist.md.txt).

1. **A `cast-media-player` element that represents the built-in player UI
   provided with Web Receiver.**
2. Custom CSS-like styling for the `cast-media-player` element to style various UI elements such as the `background-image`, `splash-image`, and `font-family`.
3. **A script element to load the Web Receiver framework.**
4. JavaScript code to intercepting messages and handling events.
5. Queue for autoplay.
6. Options to configure playback.
7. Options to set the Web Receiver context.
8. Options to set commands which are supported by the Web Receiver app.
9. **A JavaScript call to start the Web Receiver application.**

> [!NOTE]
> **Note:** It is **strongly** recommended to use the `cast-media-player` HTML element, since it handles all of the UI state of the player along with its styling.

## Application configuration and options

### Configure the application

The
[`CastReceiverContext`](https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.CastReceiverContext.md.txt)
is the outermost class exposed to the developer, and it manages loading of
underlying libraries and handles initialization of the Web Receiver SDK. The SDK
provides APIs that allow application developers to configure the SDK through
[`CastReceiverOptions`](https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.CastReceiverOptions.md.txt).
These configurations are evaluated once per application launch and are passed to
the SDK when setting the optional parameter in the call to
[`start`](https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.CastReceiverContext.md.txt#start).

The example below shows how to override the default behavior for detecting if a
sender connection is still actively connected. When the Web Receiver has not
been able to communicate with a sender for
[`maxInactivity`](https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.CastReceiverContext.md.txt#setInactivityTimeout)
seconds, a `SENDER_DISCONNECTED` event is dispatched. The configuration below
overrides this timeout. This can be useful when debugging issues as it prevents
the Web Receiver app from closing the Chrome Remote Debugger session when there
are zero connected senders in an `IDLE` state.

> [!NOTE]
> **Note:** *production* Web Receiver applications should avoid setting `maxInactivity` and instead rely on the default value.

```javascript
const context = cast.framework.CastReceiverContext.getInstance();
const options = new cast.framework.CastReceiverOptions();
options.maxInactivity = 3600; // Development only
context.start(options);
```

### Configure the player

When loading content, the Web Receiver SDK provides a way to configure playback
variables such as [DRM
information](https://developers.google.com/cast/docs/media/streaming_protocols#drm),
retry configurations, and request handlers using
[`cast.framework.PlaybackConfig`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.PlaybackConfig).
This information is handled by
[`PlayerManager`](https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.PlayerManager.md.txt)
and is evaluated at the time that the players are created. Players are created
each time a new load is passed to the Web Receiver SDK. Modifications to the
`PlaybackConfig` after the player has been created are evaluated in the next
content load. The SDK provides the following methods for modifying the
`PlaybackConfig`.

- [`CastReceiverOptions.playbackConfig`](https://developers.google.com/cast.framework.CastReceiverOptions#playbackConfig) to override the default configuration options when initializing the `CastReceiverContext`.
- [`PlayerManager.getPlaybackConfig()`](https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.PlayerManager.md.txt#getPlaybackConfig) to obtain the current configuration.
- [`PlayerManager.setPlaybackConfig()`](https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.PlayerManager.md.txt#setPlaybackConfig) to override the current configuration. This setting is applied to all subsequent loads or until it is overridden again.
- [`PlayerManager.setMediaPlaybackInfoHandler()`](https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.PlayerManager.md.txt#setMediaPlaybackInfoHandler) to apply additional configurations only for the media item being loaded on top of the current configurations. The handler is called just before player creation. Changes made here are not permanent and aren't included in queries to `getPlaybackConfig()`. When the next media item is loaded, this handler is called again.

The example below shows how to set the `PlaybackConfig` when initializing the
`CastReceiverContext`. The configuration overrides outgoing requests for
obtaining manifests. The handler specifies that CORS Access-Control requests
should be made using credentials such as cookies or authorization headers.

```javascript
const playbackConfig = new cast.framework.PlaybackConfig();
playbackConfig.manifestRequestHandler = requestInfo => {
  requestInfo.withCredentials = true;
};
context.start({playbackConfig: playbackConfig});
```

The example below shows how to override the `PlaybackConfig` using the getter
and setter provided in `PlayerManager`. The setting configures the player to
resume content playback after 1 segment has been loaded.

```javascript
const playerManager =
    cast.framework.CastReceiverContext.getInstance().getPlayerManager();
const playbackConfig = (Object.assign(
            new cast.framework.PlaybackConfig(), playerManager.getPlaybackConfig()));
playbackConfig.autoResumeNumberOfSegments = 1;
playerManager.setPlaybackConfig(playbackConfig);
```

The example below shows how to override the `PlaybackConfig` for a specific load
request using the media playback info handler. The handler calls an application
implemented method `getLicenseUrlForMedia` to obtain the `licenseUrl` from the
current item's `contentId`.

```javascript
playerManager.setMediaPlaybackInfoHandler((loadRequestData, playbackConfig) => {
  const mediaInformation = loadRequestData.media;
  playbackConfig.licenseUrl = getLicenseUrlForMedia(mediaInformation.contentId);

  return playbackConfig;
});
```

## Event listener

The Web Receiver SDK allows your Web Receiver app to handle player events. The
event listener takes a
[`cast.framework.events.EventType`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.events#.EventType)
parameter (or an array of these parameters) that specifies the event(s) that
should trigger the listener. Preconfigured arrays of
`cast.framework.events.EventType` that are useful for debugging can be found in
[`cast.framework.events.category`](https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.events.category.md.txt).
The event parameter provides additional information about the event.

For example, if you want to know when a
[`mediaStatus`](https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.messages.MediaStatus.md.txt)
change is being broadcasted, you can use the following logic to handle the
event:

```javascript
const playerManager =
    cast.framework.CastReceiverContext.getInstance().getPlayerManager();
playerManager.addEventListener(
    cast.framework.events.EventType.MEDIA_STATUS, (event) => {
      // Write your own event handling code, for example
      // using the event.mediaStatus value
});
```

> [!NOTE]
> **Note:** The Web Receiver framework automatically tracks when a sender connects or disconnects from it and doesn't require an explicit `SENDER_DISCONNECTED` event listener in your own Web Receiver logic.

## Message interception

The Web Receiver SDK allows your Web Receiver app to intercept messages and
execute custom code on those messages. The message interceptor takes a
[`cast.framework.messages.MessageType`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.messages#.MessageType)
parameter that specifies what type of message should be intercepted.

The interceptor should return the modified request or a Promise that resolves
with the modified request value. Returning `null` will prevent calling the
default message handler. See [Loading media](https://developers.google.com/cast/docs/web_receiver/core_features.md.txt#loading_media) for more details.

For example, if you want to change the load request data, you can use the
following logic to intercept and modify it:

> [!CAUTION]
> **Caution:** Do not add or modify the [`queueData`](https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.messages.LoadRequestData.md.txt#queueData) property of the [`LoadRequestData`](https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.messages.LoadRequestData.md.txt) in the `LOAD` interceptor. It will have no effect on playback since the `queueData` has already been read and applied to the queue. For more information, see the [queueing guide](https://developers.google.com/cast/docs/web_receiver/queueing.md.txt#default_queue).

```javascript
const context = cast.framework.CastReceiverContext.getInstance();
const playerManager = context.getPlayerManager();

playerManager.setMessageInterceptor(
    cast.framework.messages.MessageType.LOAD, loadRequestData => {
      const error = new cast.framework.messages.ErrorData(
                      cast.framework.messages.ErrorType.LOAD_FAILED);
      if (!loadRequestData.media) {
        error.reason = cast.framework.messages.ErrorReason.INVALID_PARAM;
        return error;
      }

      if (!loadRequestData.media.entity) {
        return loadRequestData;
      }

      return thirdparty.fetchAssetAndAuth(loadRequestData.media.entity,
                                          loadRequestData.credentials)
        .then(asset => {
          if (!asset) {
            throw cast.framework.messages.ErrorReason.INVALID_REQUEST;
          }

          loadRequestData.media.contentUrl = asset.url;
          loadRequestData.media.metadata = asset.metadata;
          loadRequestData.media.tracks = asset.tracks;
          return loadRequestData;
        }).catch(reason => {
          error.reason = reason; // cast.framework.messages.ErrorReason
          return error;
        });
    });

context.start();
```

### Error handling

When errors happen in message interceptor, your Web Receiver app should return
an appropriate
[`cast.framework.messages.ErrorType`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.messages#.ErrorType)
and
[`cast.framework.messages.ErrorReason`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.messages#.ErrorReason).

```javascript
playerManager.setMessageInterceptor(
    cast.framework.messages.MessageType.LOAD, loadRequestData => {
      const error = new cast.framework.messages.ErrorData(
                      cast.framework.messages.ErrorType.LOAD_CANCELLED);
      if (!loadRequestData.media) {
        error.reason = cast.framework.messages.ErrorReason.INVALID_PARAM;
        return error;
      }

      ...

      return fetchAssetAndAuth(loadRequestData.media.entity,
                               loadRequestData.credentials)
        .then(asset => {
          ...
          return loadRequestData;
        }).catch(reason => {
          error.reason = reason; // cast.framework.messages.ErrorReason
          return error;
        });
    });
```

## Message interception vs event listener

Some key differences between message interception and event listener are as
follows:

- An event listener does **not** allow you to modify the request data.
- An event listener is best used to trigger analytics or a custom function.

> [!NOTE]
> **Note:** An event listener can also be used to listen to an umbrella of events using the `CORE`, `DEBUG`, or `FINE` enum.

```javascript
playerManager.addEventListener(cast.framework.events.category.CORE,
    event => {
        console.log(event);
    });
```

- Message interception allows you to listen to a message, intercept it, and modify the request data itself.
- Message interception is best used to handle custom logic with regards to request data.

## Loading media

[`MediaInformation`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.messages.MediaInformation)
provides numerous properties to load media in the
`cast.framework.messages.MessageType.LOAD` message including the `entity`,
`contentUrl`, and `contentId`.

- The [`entity`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.messages.MediaInformation#entity) is the suggested property to use in your implementation for both your sender and receiver apps. The property is a deep link URL that can be either a playlist or media content. You application should parse this URL and populate at least one of the other two fields.
- The [`contentUrl`](https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.messages.MediaInformation.md.txt#contentUrl) corresponds to the playable URL that the player will use to load the content. For example, this URL could point to a DASH manifest.
- The [`contentId`](https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.messages.MediaInformation.md.txt#contentId) can be either a playable content URL (similar to that of the `contentUrl` property) or a unique identifier for the content or playlist being loaded. If using this property as an identifier, your application should populate a playable URL in the `contentUrl`.

The suggestion is to use `entity` to store the real ID or key parameters, and
use `contentUrl` for the URL of the media. An example of this is shown in the
following snippet where the `entity` is present in the `LOAD` request and the
playable `contentUrl` is retrieved:

```javascript
playerManager.setMessageInterceptor(
    cast.framework.messages.MessageType.LOAD, loadRequestData => {
      ...

      if (!loadRequestData.media.entity) {
        // Copy the value from contentId for legacy reasons if needed
        loadRequestData.media.entity = loadRequestData.media.contentId;
      }

      return thirdparty.fetchAssetAndAuth(loadRequestData.media.entity,
                                          loadRequestData.credentials)
        .then(asset => {
          loadRequestData.media.contentUrl = asset.url;
          ...
          return loadRequestData;
        });
    });

```

## Device capabilities

The
[`getDeviceCapabilities`](https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.CastReceiverContext.md.txt#getDeviceCapabilities)
method provides device information on the connected Cast device and the video or
audio device attached to it. The `getDeviceCapabilities` method provides support
information for Google Assistant, Bluetooth, and the connected display and audio
devices.

This method returns an object which you can query by passing in one of the
specified enums to get the device capability for that enum. The enums are
defined in
[`cast.framework.system.DeviceCapabilities`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.system#.DeviceCapabilities).

This example checks if the Web Receiver device is capable of playing HDR and
DolbyVision (DV) with the `IS_HDR_SUPPORTED` and `IS_DV_SUPPORTED` keys,
respectively.

```javascript
const context = cast.framework.CastReceiverContext.getInstance();
context.addEventListener(cast.framework.system.EventType.READY, () => {
  const deviceCapabilities = context.getDeviceCapabilities();
  if (deviceCapabilities &&
      deviceCapabilities[cast.framework.system.DeviceCapabilities.IS_HDR_SUPPORTED]) {
    // Write your own event handling code, for example
    // using the deviceCapabilities[cast.framework.system.DeviceCapabilities.IS_HDR_SUPPORTED] value
  }
  if (deviceCapabilities &&
      deviceCapabilities[cast.framework.system.DeviceCapabilities.IS_DV_SUPPORTED]) {
    // Write your own event handling code, for example
    // using the deviceCapabilities[cast.framework.system.DeviceCapabilities.IS_DV_SUPPORTED] value
  }
});
context.start();
```

## Handling user interaction

A user can interact with your Web Receiver application through sender
applications (Web, Android, and iOS), voice commands on Assistant-enabled
devices, touch controls on smart displays, and remote controls on Android TV
devices. The Cast SDK provides various APIs to allow the Web Receiver app to
handle these interactions, update the application UI through
[user action states](https://developers.google.com/cast/docs/web_receiver/core_features.md.txt#managing_user_action_states),
and optionally send the changes to update any backend services.

### Supported media commands

The UI controls states are driven by the
[`MediaStatus.supportedMediaCommands`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.messages.MediaStatus#supportedMediaCommands)
for iOS and Android sender expanded controllers, receiver and remote control
apps running on touch devices, and receiver apps on Android TV devices. When a
particular bitwise `Command` is enabled in the property, the buttons which are
related to that action are enabled. If the value is not set, then the button is
disabled. These values can be changed on the Web Receiver by:

1. Using [`PlayerManager.setSupportedMediaCommands`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.PlayerManager#setSupportedMediaCommands) to set the specific [`Commands`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.messages#.Command)
2. Adding a new command using [`addSupportedMediaCommands`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.PlayerManager#addSupportedMediaCommands)
3. Removing an existing command using [`removeSupportedMediaCommands`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.PlayerManager#removeSupportedMediaCommands).

```javascript
playerManager.setSupportedMediaCommands(cast.framework.messages.Command.SEEK |
  cast.framework.messages.Command.PAUSE);
```

When the receiver prepares the updated `MediaStatus`, it will include the
changes in the `supportedMediaCommands` property. When the status is
broadcasted, the connected sender apps will update the buttons in their UI
accordingly.

> [!NOTE]
> **Note:** Making changes to the `supportedMediaCommands` will not change the type of button that is displayed. These are set directly in the sender apps ([iOS](https://developers.google.com/cast/docs/ios_sender/customize_ui.md.txt#choose_controller_buttons), [Android](https://developers.google.com/cast/docs/android_sender/customize_ui.md.txt#choose_buttons_2)) and [`Controls`](https://developers.google.com/cast/docs/web_receiver/optimize-smart-displays.md.txt#customize_ui_control_button_layout) API of the receiver. The only exception that uses `supportedMediaCommands` to drive these changes are the media playback cards on the home screen of smart display devices.

For more information about supported media commands and touch devices see
[`Accessing UI controls`](https://developers.google.com/cast/docs/web_receiver/optimize-smart-displays.md.txt#accessing_ui_controls)
guide.

### Managing user action states

When users interact with the UI or send voice commands, they can control the
playback of the content and properties related to the item playing. Requests
that control the playback are handled automatically by the SDK. Requests that
modify properties for the current item playing, such as a `LIKE` command,
require that the receiver application handle them. The SDK provides a series of
APIs to handle these types of requests. To support these requests, the following
must be done:

- Set the `MediaInformation` [`userActionStates`](https://developers.google.com/cast.framework.messages.MediaInformation#userActionStates) with a user's preferences when loading a media item.
- Intercept `USER_ACTION` messages and determine the action requested.
- Update the `MediaInformation` `UserActionState` to update the UI.

> [!NOTE]
> **Note:** If needed, you may `POST` the action to any relevant backends to persist the user's preference.

The following snippet intercepts the `LOAD` request and populates the
`LoadRequestData`'s `MediaInformation`. In this case, the user likes the
content that is being loaded.

```javascript
playerManager.setMessageInterceptor(
    cast.framework.messages.MessageType.LOAD, (loadRequestData) => {
      const userActionLike = new cast.framework.messages.UserActionState(
          cast.framework.messages.UserAction.LIKE);
      loadRequestData.media.userActionStates = [userActionLike];

      return loadRequestData;
    });
```

The following snippet intercepts the `USER_ACTION` message and handles calling
the backend with the requested change. It then makes a call to update the
`UserActionState` on the receiver.

```javascript
playerManager.setMessageInterceptor(cast.framework.messages.MessageType.USER_ACTION,
  (userActionRequestData) => {
    // Obtain the media information of the current content to associate the action to.
    let mediaInfo = playerManager.getMediaInformation();

    // If there is no media info return an error and ignore the request.
    if (!mediaInfo) {
        console.error('Not playing media, user action is not supported');
        return new cast.framework.messages.ErrorData(messages.ErrorType.BAD_REQUEST);
    }

    // Reach out to backend services to store user action modifications. See sample below.
    return sendUserAction(userActionRequestData, mediaInfo)

    // Upon response from the backend, update the client's UserActionState.
    .then(backendResponse => updateUserActionStates(backendResponse))

    // If any errors occurred in the backend return them to the cast receiver.
    .catch((error) => {
      console.error(error);
      return error;
    });
});
```

The following snippet simulates a call to a backend service. The function checks
the `UserActionRequestData` to see the type of change that the user requested
and only makes a network call if the action is supported by the backend.

```javascript
function sendUserAction(userActionRequestData, mediaInfo) {
  return new Promise((resolve, reject) => {
    switch (userActionRequestData.userAction) {
      // Handle user action changes supported by the backend.
      case cast.framework.messages.UserAction.LIKE:
      case cast.framework.messages.UserAction.DISLIKE:
      case cast.framework.messages.UserAction.FOLLOW:
      case cast.framework.messages.UserAction.UNFOLLOW:
      case cast.framework.messages.UserAction.FLAG:
      case cast.framework.messages.UserAction.SKIP_AD:
        let backendResponse = {userActionRequestData: userActionRequestData, mediaInfo: mediaInfo};
        setTimeout(() => {resolve(backendResponse)}, 1000);
        break;
      // Reject all other user action changes.
      default:
        reject(
          new cast.framework.messages.ErrorData(cast.framework.messages.ErrorType.INVALID_REQUEST));
    }
  });
}
```

The following snippet takes the `UserActionRequestData` and either adds or
removes the `UserActionState` from the `MediaInformation`. Updating the
`UserActionState` of the `MediaInformation` changes the state of the button that
is associated with the requested action. This change is reflected in the smart
display controls UI, remote control app, and Android TV UI. It is also
broadcasted through outgoing `MediaStatus` messages to update the UI of the
expanded controller for iOS and Android senders.

```javascript
function updateUserActionStates(backendResponse) {
  // Unwrap the backend response.
  let mediaInfo = backendResponse.mediaInfo;
  let userActionRequestData = backendResponse.userActionRequestData;

  // If the current item playing has changed, don't update the UserActionState for the current item.
  if (playerManager.getMediaInformation().entity !== mediaInfo.entity) {
    return;
  }

  // Check for existing userActionStates in the MediaInformation.
  // If none, initialize a new array to populate states with.
  let userActionStates = mediaInfo.userActionStates || [];

  // Locate the index of the UserActionState that will be updated in the userActionStates array.
  let index = userActionStates.findIndex((currUserActionState) => {
    return currUserActionState.userAction == userActionRequestData.userAction;
  });

  if (userActionRequestData.clear) {
    // Remove the user action state from the array if cleared.
    if (index >= 0) {
      userActionStates.splice(index, 1);
    }
    else {
      console.warn("Could not find UserActionState to remove in MediaInformation");
    }
  } else {
    // Add the UserActionState to the array if enabled.
    userActionStates.push(
      new cast.framework.messages.UserActionState(userActionRequestData.userAction));
  }

  // Update the UserActionState array and set the new MediaInformation
  mediaInfo.userActionStates = userActionStates;
  playerManager.setMediaInformation(mediaInfo, true);
  return;
}
```

### Voice commands

The following media commands are currently supported in the Web Receiver SDK for
Assistant-enabled devices. The default implementations of these commands are
found in
[`cast.framework.PlayerManager`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.PlayerManager).

| Command | Description |
|---|---|
| **Play** | Play or resume playback from paused state. |
| **Pause** | Pause currently playing content. |
| **Previous** | Skip to the previous media item in your media queue. |
| **Next** | Skip to the next media item in your media queue. |
| **Stop** | Stop the currently playing media. |
| **Repeat None** | Disable repeating of media items in the queue once the last item in the queue is done playing. |
| **Repeat Single** | Repeat the currently playing media indefinitely. |
| **Repeat All** | Repeat all items in the queue once the last item in the queue is played. |
| **Repeat All and Shuffle** | Once the last item in the queue is done playing, shuffle the queue and repeat all items in the queue. |
| **Shuffle** | Shuffle media items in your media queue. > [!NOTE] > **Note:** You can also shuffle the media prior to playback using the [`shuffle`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.messages.QueueUpdateRequestData#shuffle) flag, if it is set in the [`loadRequestData`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.messages.LoadRequestData) of the `LOAD` message. |
| **Closed Captions ON / OFF** | Enable / Disable Closed Captioning for your media. Enable / Disable is also available by language. |
| **Seek** to **absolute** time | Jumps to the specified absolute time. |
| **Seek** to **time relative** to current time | Jumps forward or backward by the specified time period relative to the current playback time. |
| **Play Again** | Restart the currently playing media or play the last played media item if nothing is currently playing. |
| **Set playback rate** | Vary media playback rate. This should be handled by default. You can use the [`SET_PLAYBACK_RATE`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.messages#.MessageType) message interceptor to override incoming rate requests. |

#### Supported media commands with voice

To prevent a voice command from triggering a media command on an Assistant-
enabled device, you must first set the
[supported media commands](https://developers.google.com/cast/docs/caf_receiver/core_features#supported_media_commands)
that you plan on supporting. Then you must enforce those commands by enabling
the
[`CastReceiverOptions.enforceSupportedCommands`](https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.CastReceiverOptions.md.txt#enforceSupportedCommands)
property. The UI on Cast SDK senders and touch-enabled devices will change to
reflect these configurations. If the flag is not enabled the incoming voice
commands will execute.

For example, if you allow `PAUSE` from your sender applications and
touch-enabled devices, you must also configure your receiver to reflect those
settings. When configured, any incoming voice commands will be dropped if not
included in the list of supported commands.

In the example below we are supplying the `CastReceiverOptions` when starting
the `CastReceiverContext`. We've added support for the `PAUSE` command and
enforced the player to support only that command. Now if a voice command
requests another operation such as `SEEK` it will be denied. The user will be
notified that the command is not supported yet.

```javascript
const context = cast.framework.CastReceiverContext.getInstance();

context.start({
  enforceSupportedCommands: true,
  supportedCommands: cast.framework.messages.Command.PAUSE
});
```

You can apply separate logic for each command that you want to restrict. Remove
the `enforceSupportedCommands` flag and for each command that you want to
restrict you can intercept the incoming message. Here we intercept the request
provided by the SDK so that `SEEK` commands issued to Assistant-enabled devices
do not trigger a seek in your Web Receiver application.

For media commands your application does not support, return an appropriate
error reason, such as
[`NOT_SUPPORTED`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.messages#.ErrorReason).

```javascript
playerManager.setMessageInterceptor(cast.framework.messages.MessageType.SEEK,
  seekData => {
    // Block seeking if the SEEK supported media command is disabled
    if (!(playerManager.getSupportedMediaCommands() & cast.framework.messages.Command.SEEK)) {
      let e = new cast.framework.messages.ErrorData(cast.framework.messages.ErrorType
      .INVALID_REQUEST);
      e.reason = cast.framework.messages.ErrorReason.NOT_SUPPORTED;
      return e;
    }

    return seekData;
  });
```

#### Backgrounding from voice activity

If the Cast platform backgrounds your application's sound due to Assistant
activity such as listening to user speech or talking back, a
[`FocusState`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.messages#.FocusState)
message of `NOT_IN_FOCUS` is sent to the Web Receiver application when the
activity starts. Another message with `IN_FOCUS` is sent when the activity ends.
Depending on your application and the media being played, you might want to
pause media when the `FocusState` is `NOT_IN_FOCUS` by intercepting the message
type `FOCUS_STATE`.

For example, it's a good user experience to pause audiobook playback if the
Assistant is responding to a user query.

```javascript
playerManager.setMessageInterceptor(cast.framework.messages.MessageType.FOCUS_STATE,
  focusStateRequestData => {
    // Pause content when the app is out of focus. Resume when focus is restored.
    if (focusStateRequestData.state == cast.framework.messages.FocusState.NOT_IN_FOCUS) {
      playerManager.pause();
    } else {
      playerManager.play();
    }

    return focusStateRequestData;
  });
```

#### Voice-specified caption language

When a user does not explicitly state the language for the captions, the
language used for captions is the same language in which the command was spoken.
In these scenarios, the
[`isSuggestedLanguage`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.messages.EditTracksInfoRequestData#isSuggestedLanguage)
parameter of the incoming message indicates whether the associated language was
suggested or explicitly requested by user.

For example, `isSuggestedLanguage` is set to `true` for the command "OK Google,
turn captions on," because the language was inferred by the language the
command was spoken in. If the language is explicitly requested, such as in "OK
Google, turn on English captions," `isSuggestedLanguage` is set to `false`.

#### Metadata and voice casting

While voice commands are handled by the Web Receiver by default, you should
ensure the metadata for your content is complete and accurate. This ensures that
voice commands are handled properly by the Assistant and that the metadata
surfaces properly across new types of interfaces such as the Google Home app and
smart displays like the Google Home Hub.

## Stream transfer

> [!NOTE]
> **Note:** Audio Web Receivers support stream expansion by default, where additional Cast-enabled devices are added to the session. Video Web Receivers need to perform the steps in this section to support stream transfer.

Preserving session state is the basis of stream transfer, where
users can move existing audio and video streams across devices using voice commands, Google Home
App, or smart displays. Media stops playing on one device (the source) and continues on another (the
destination). Any Cast device with the latest firmware can serve as sources or destinations in a
stream transfer.

The event flow for stream transfer is:

1. On the source device:
   1. Media stops playing.
   2. The Web Receiver application receives a command to save the current media state.
   3. The Web Receiver application is shut down.
2. On the destination device:
   1. The Web Receiver application is loaded.
   2. The Web Receiver application receives a command to restore the saved media state.
   3. Media resumes playing.

Elements of media state include:

- Specific position or timestamp of the song, video, or media item.
- Its place in a broader queue (such as a playlist or artist radio).
- The authenticated user.
- Playback state (for example, playing or paused).

#### Enabling stream transfer

To implement stream transfer for your Web Receiver:

1. Update [`supportedMediaCommands`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.messages#.Command) with the `STREAM_TRANSFER` command:

   ```
   playerManager.addSupportedMediaCommands(
   cast.framework.messages.Command.STREAM_TRANSFER, true);
   ```
2. Optionally override the `SESSION_STATE` and `RESUME_SESSION` message interceptors as described in [Preserving session
   state](https://developers.google.com/cast/docs/web_receiver/core_features#preserving_session_state). Only override these if custom data needs to be stored as part of the session snapshot. Otherwise, the default implementation for preserving session states will support stream transfer.

### Preserving session state

The Web Receiver SDK provides a default implementation for Web Receiver apps to
preserve session states by taking a snapshot of current media status, converting
the status into a load request, and resuming the session with the load request.

The load request generated by the Web Receiver can be overridden in the
`SESSION_STATE` message interceptor if necessary. If you want to add custom data
into the load request, we suggest putting them in
[`loadRequestData.customData`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.messages.LoadRequestData#customData).

```javascript
playerManager.setMessageInterceptor(
    cast.framework.messages.MessageType.SESSION_STATE,
    function (sessionState) {
        // Override sessionState.loadRequestData if needed.
        const newCredentials = updateCredentials_(sessionState.loadRequestData.credentials);
        sessionState.loadRequestData.credentials = newCredentials;

        // Add custom data if needed.
        sessionState.loadRequestData.customData = {
            'membership': 'PREMIUM'
        };

        return sessionState;
    });
```

The custom data can be retrieved from
[`loadRequestData.customData`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.messages.LoadRequestData#customData)
in the `RESUME_SESSION` message interceptor.

```javascript
let cred_ = null;
let membership_ = null;

playerManager.setMessageInterceptor(
    cast.framework.messages.MessageType.RESUME_SESSION,
    function (resumeSessionRequest) {
        let sessionState = resumeSessionRequest.sessionState;

        // Modify sessionState.loadRequestData if needed.
        cred_ = sessionState.loadRequestData.credentials;

        // Retrieve custom data.
        membership_ = sessionState.loadRequestData.customData.membership;

        return resumeSessionRequest;
    });
```

## Content preload

The Web Receiver supports preloading of media items after the current playback
item in the queue.

The preload operation pre-downloads several segments of the
upcoming items. The specification is done on the
[preloadTime](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.QueueItem#preloadTime)
value in the
[QueueItem](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.QueueItem) object
(default to 20 seconds if not provided). The time is expressed in seconds,
relative to the end of the currently playing item . Only positive values are
valid. For example, if the value is 10 seconds, this item will be preloaded 10
seconds before the previous item has finished. If the time to preload is higher
than the time left on the currentItem, the preload will just happen as soon as
possible. So if a very large value of preload is specified on the queueItem, one
could achieve the effect of whenever we are playing the current item we are
already preloading the next item. However, we leave the setting and choice of
this to developer as this value can affect bandwidth and streaming performance
of the current playing item.

Preloading will work for HLS, DASH, and Smooth streaming content by default.

Regular MP4 video and audio files such as MP3 will not be preloaded as Cast
devices support one media element only and cannot be used to preload while an
existing content item is still playing.

## Custom messages

Message exchange is the key interaction method for Web Receiver applications.

A sender issues messages to a Web Receiver using the sender APIs for the
platform the sender is running (Android, iOS, Web). The event object (which
is the manifestation of a message) that is passed to the event listeners has a
data element (`event.data`) where the data takes on the properties of the
specific event type.

A Web Receiver application may choose to listen for messages on a specified
namespace. By virtue of doing so, the Web Receiver application is said to
support that namespace protocol. It is then up to any connected senders wishing
to communicate on that namespace to use the appropriate protocol.

All namespaces are defined by a string and must begin with "`urn:x-cast:`"
followed by any string. For example,
"`urn:x-cast:com.example.cast.mynamespace`".

> [!NOTE]
> **Note:** The media message namespace "`urn:x-cast:com.google.cast.media`" is reserved. Media messages sent using the Cast APIs on both the sender and Web Receiver use the media namespace protocol by convention. See [Media and players](https://developers.google.com/cast/docs/web_receiver/basic.md.txt#media_and_players) for more about media messages.

Here is a code snippet for the Web Receiver to listen to custom messages from
connected senders:

```javascript
const context = cast.framework.CastReceiverContext.getInstance();

const CUSTOM_CHANNEL = 'urn:x-cast:com.example.cast.mynamespace';
context.addCustomMessageListener(CUSTOM_CHANNEL, function(customEvent) {
  // handle customEvent.
});

context.start();
```

Similarly, Web Receiver applications can keep senders informed about the state
of the Web Receiver by sending messages to connected senders. A Web Receiver
application can send messages using
[`sendCustomMessage(namespace, senderId, message)`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.CastReceiverContext#sendCustomMessage)
on
[`CastReceiverContext`](https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.CastReceiverContext).
A Web Receiver can send messages to an individual sender, either in response to
a received message or due to an application state change. Beyond point-to-point
messaging (with a limit of 64kb), a Web Receiver may also broadcast messages to
all connected senders.

## Cast for audio devices

See [Google Cast for audio devices guide](https://developers.google.com/cast/docs/audio.md.txt) for support on audio
only playback.

## Android TV

This section discusses how the Google Web Receiver uses your inputs as playback,
and Android TV compatibility.

### Integrating your application with the remote control

The Google Web Receiver running on the Android TV device translates input from
the device's control inputs (i.e. hand-held remote control) as media playback
messages defined for the `urn:x-cast:com.google.cast.media` namespace, as
described in [Media Playback Messages](https://developers.google.com/cast/docs/reference/messages). Your
application must support these messages to control the application media
playback in order to allow basic playback control from Android TV's control
inputs.

### Guidelines for Android TV compatibility

Here are some recommendations and common pitfalls to avoid in order to ensure
your application is compatible with Android TV:

- Be aware that the user-agent string contains both "Android" and "CrKey"; some sites may redirect to a mobile-only site because they detect the "Android" label. Don't assume that "Android" in the user-agent string always indicates a mobile user.
- Android's media stack may use transparent GZIP for fetching data. Make sure your media data can respond to `Accept-Encoding: gzip`.
- Android TV HTML5 media events may be triggered in different timings than Chromecast, this may reveal issues that were hidden on Chromecast.
- When updating the media, use media related events fired by `<audio>/<video>` elements, like `timeupdate`, `pause` and `waiting`. Avoid using networking related events like `progress`, `suspend` and `stalled`, as these tend to be platform dependent. See [Media events](https://developers.google.com/cast/docs/custom_receiver#events) for more information about handling media events in your receiver.
- When configuring your receiver site's HTTPS certificates, be sure to include intermediate CA certificates. See the [Qualsys SSL test page](https://www.ssllabs.com/ssltest/analyze.html) to verify: if the trusted certification path for your site includes a CA certificate labelled "extra download", then it may not load on Android-based platforms.
- While Chromecast displays the receiver page on a 720p graphics plane, other Cast platforms including Android TV may display the page up to 1080p. Ensure your receiver page scales gracefully at different resolutions.