Customize CAF Receiver UI

Styling the player

The CAF Receiver SDK provides a built-in player UI. In order to use this UI, you need to add cast-media-player element to your HTML. CSS-like styling allows setting various things including background image, splash image, font family and other things.

External

index.html

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="css/receiver.css" media="screen" />
  <script type="text/javascript" src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js">
  </script>
</head>
<body>
  <cast-media-player></cast-media-player>
</body>
<footer>
  <script src="js/receiver.js"></script>
</footer>
</html>

js/receiver.js

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

...

// Update style using javascript
let playerElement = document.getElementsByTagName("cast-media-player")[0];
playerElement.style.setProperty('--splash-image', 'url("http://some/other/image.png")');

...

context.start();

css/receiver.css

body {
  --playback-logo-image: url('http://some/image.png');
}
cast-media-player {
  --theme-hue: 100;
  --progress-color: rgb(0, 255, 0);
  --splash-image: url('http://some/image.png');
  --splash-size: cover;
}
Inline
<!DOCTYPE html>
<html>
<head>
  <script type="text/javascript" src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js">
  </script>
</head>
<body>
  <cast-media-player></cast-media-player>
  <style>
    body {
      --playback-logo-image: url('http://some/image.png');
    }
    cast-media-player {
      --theme-hue: 100;
      --progress-color: rgb(0, 255, 0);
      --splash-image: url('http://some/image.png');
    }
  </style>
  <script>
    const context = cast.framework.CastReceiverContext.getInstance();

    ...

    // Update style using javascript
    let playerElement = document.getElementsByTagName("cast-media-player")[0];
    playerElement.style.setProperty('--splash-image', 'url("http://some/other/image.png")');

    ...

    context.start();
  </script>
</body>
</html>

The following tables list all currently available customization parameters.

Name Default Value Description
--background-image Background image.
--ad-title Ad Title of the ad.
--skip-ad-title Skip ad Text of the Skip Ad text box.
--font-family Open Sans Font family for metadata and progress bar.
--spinner-image Default image The image to display while launching.
--buffering-image Default image The image to display while buffering.
--pause-image Default image The image to display while paused.
--play-image The image to show in metadata while playing.
--theme-hue 42 The hue to use for the player.
--progress-color hsl(hue, 95%, 60%) Color for progress bar.
--break-color hsl(hue, 100%, 50%) Color for the ad break mark.
--splash-background CSS background property
--splash-image App name CSS background-image property
--splash-size auto CSS background-size property
--splash-color transparent CSS background-color property
--logo-repeat repeat CSS background-repeat property
--logo-background CSS background property
--logo-image CSS background-image property
--logo-size auto CSS background-size property
--logo-color transparent CSS background-color property
--logo-repeat repeat CSS background-repeat property
--playback-logo-image CSS background-image property
--watermark-background CSS background property
--watermark-image CSS background-image property
--watermark-position 0 CSS background-position property
--watermark-size auto CSS background-size property
--watermark-color transparent CSS background-color property
--watermark-repeat repeat CSS background-repeat property

A blank value in the "Default value" column indicates no default exists.

Slideshow

To have up to 10 images cycle through during idle state (in place of the splash image), use the following slideshow parameters.

Name Default Value Description
--slideshow-interval 10s Time between images.
--slideshow-animation 2s Duration of transition.
--slideshow-image-1 First image in slideshow.
--slideshow-image-2 Second image in slideshow.
--slideshow-image-3 Third image in slideshow.
--slideshow-image-4 Fourth image in slideshow.
--slideshow-image-5 Fifth image in slideshow.
--slideshow-image-6 Sixth image in slideshow.
--slideshow-image-7 Seventh image in slideshow.
--slideshow-image-8 Eighth image in slideshow.
--slideshow-image-9 Ninth image in slideshow.
--slideshow-image-10 Tenth image in slideshow.

A blank value in the "Default value" column indicates no default exists.

Overscan

Layouts for TV have some unique requirements due to the evolution of TV standards and the desire to always present a full screen picture to viewers. TV devices can clip the outside edge of an app layout in order to ensure that the entire display is filled. This behavior is generally referred to as overscan. Avoid screen elements getting clipped due to overscan by incorporating a 10% margin on all sides of your layout.

Default audio UI

MetadataType.MUSIC_TRACK

A. --logo-image

B. MusicTrackMediaMetadata.albumName

C. MusicTrackMediaMetadata.title

D. MusicTrackMediaMetadata.albumArtist, MusicTrackMediaMetadata.artist, or MusicTrackMediaMetadata.composer

E. MusicTrackMediaMetadata.images[0]

F. MediaStatus.currentTime

G. MediaInformation.duration

H. Play / Pause

Optimizing for smart displays

Smart displays are devices with touch functionality to allow receiver applications to support touch-enabled controls.

This guide explains how to optimize your CAF receiver application when launched on smart displays and how to customize the player controls.

Accessing UI controls

The UI Controls object can be accessed with the following code:

const controls = cast.framework.ui.Controls.getInstance();

If you are not using cast-media-player element, you need to set touchScreenOptimizedApp in CastReceiverOptions.

context.start({ touchScreenOptimizedApp: true });

Default video UI

Default control buttons are assigned to each slot based on MetadataType and MediaStatus.supportedMediaCommands.

MetadataType.Movie, MetadataType.Generic

A. --playback-logo-image

B. MovieMediaMetadata.subtitle or GenericMediaMetadata.subtitle.

C. MovieMediaMetadata.title or GenericMediaMetadata.title.

D. MediaStatus.currentTime

E. MediaInformation.duration

F. ControlsSlot.SLOT_1

G. ControlsSlot.SLOT_2

H. Play / Pause

I. ControlsSlot.SLOT_3

J. ControlsSlot.SLOT_4

When the value of supportedMediaCommands equals to ALL_BASIC_MEDIA, default control layout will display as below:

When the value of supportedMediaCommands equals to ALL_BASIC_MEDIA | QUEUE_PREV | QUEUE_NEXT, default control layout will display as below:

When the value of supportedMediaCommands equals to PAUSE | QUEUE_PREV | QUEUE_NEXT, default control layout will display as below:

When text tracks are available, closed caption button would be always shown at SLOT_1.

To dynamically change the value of supportedMediaCommands after starting a receiver context, you can call PlayerManager.setSupportedMediaCommands to override the value. Also, you can add new command by using addSupportedMediaCommands or remove existing command by using removeSupportedMediaCommands.

Default audio UI

MetadataType.MUSIC_TRACK

A. --playback-logo-image

B. MusicTrackMediaMetadata.albumName

C. MusicTrackMediaMetadata.title

D. MusicTrackMediaMetadata.albumArtist, MusicTrackMediaMetadata.artist, or MusicTrackMediaMetadata.composer

E. MusicTrackMediaMetadata.images[0]

F. MediaStatus.currentTime

G. MediaInformation.duration

H. ControlsSlot.SLOT_1

I. ControlsSlot.SLOT_2

J. Play / Pause

K. ControlsSlot.SLOT_3

L. ControlsSlot.SLOT_4

When the value of supportedMediaCommands equals to ALL_BASIC_MEDIA, default control layout will display as below:

When the value of supportedMediaCommands equals to ALL_BASIC_MEDIA | QUEUE_PREV | QUEUE_NEXT, default control layout will display as below:

To dynamically change the value of supportedMediaCommands after starting a receiver context, you can call PlayerManager.setSupportedMediaCommands to override the value. Also, you can add new commands by using addSupportedMediaCommands or remove existing commands by using removeSupportedMediaCommands.

Customize UI control button layout

Using a custom layout if you want to change buttons in UI controls.

It's highly recommended to remove all default buttons to prevent conflicts of custom layout and default layout.

const controls = cast.framework.ui.Controls.getInstance();
controls.clearDefaultSlotAssignments();

Then, you can assign control buttons to 4 slots by calling assignButton.

controls.assignButton(
  cast.framework.ui.ControlsSlot.SLOT_1,
  cast.framework.ui.ControlsButton.LIKE
)

controls.assignButton(
  cast.framework.ui.ControlsSlot.SLOT_4,
  cast.framework.ui.ControlsButton.DISLIKE
)

And, the custom layout will display as below:

When the assigned button is not supported in MediaStatus.supportedMediaCommands, the button is greyed out. For example, if the supportedMediaCommands equals ALL_BASIC_MEDIA | QUEUE_NEXT | LIKE | DISLIKE, then the QUEUE_PREV button is disabled.

To dynamically change the value of supportedMediaCommands after starting a receiver context, you can call PlayerManager.setSupportedMediaCommands to override the value. Also, you can add new commands by using addSupportedMediaCommands or remove existing commands by using removeSupportedMediaCommands.

Media Browse

Cast Media Browse (CMB) is a new CAF feature that lets smart display users discover and engage with your audio or video content catalog. CMB does so by enhancing the CAF Receiver with a streamlined browsing experience that is specially tuned for smart displays.

CMB defines standardized templates that deliver a consistent browsing experience that follows smart display UI conventions. Developers supply data to populate these standardized templates. Templates support both audio and video content or a mix of both.

Entry points

There are two entry points for CMB, from which a user can browse and select content using touch or voice control.

In-player browse

Swipe up during playback to choose from a list of application-supplied content:

Video

Media Browse Entry - In Player Video 1 Media Browse Entry - In Player Video 2

Audio

Media Browse Entry - In Player Audio 1 Media Browse Entry - In Player Audio 2

Landing page browse

When a CAF receiver with the cast-media-player element is running on Smart Displays, it shows CMB while in the IDLE state.

Video and Audio

Media Browse Entry - Landing Page Video Media Browse Entry - Landing Page Audio

Populating content

Developers are responsible for populating the template for each entry point with data for each content item. The content used to populate In-Player Browse can be different than the content used to populate Landing Page Browse.

Use In-Player Browse to display items that relate to the content a user is currently playing, or items of a playlist. Live TV providers could also use this entry point to populate a list of channels for easy access.

Use Landing Page Browse to raise awareness of new original content, content that is currently live, or content that might be of further interest to your user.

Enable Media Browse

Provide a list of media contents for browsing by calling setBrowseContent:

const controls = cast.framework.ui.Controls.getInstance();
controls.setBrowseContent(BrowseContent);

The Media Browse UI is updated immediately after calling this method.

Safe area height

When CMB is enabled, the height of the Cast SDK UI safe area changes, and you might need to update your existing CAF Receiver UI. Use getSafeAreaHeight to determine the height of the safe area.

// Media Browse UI enabled
controls.setBrowseContent(BrowseContent);
console.log(controls.getSafeAreaHeight()); // 338px on Google Nest Hub

// Media Browse UI disabled
controls.setBrowseContent(null);
console.log(controls.getSafeAreaHeight()); // 408px on Google Nest Hub

Remove Media Browse

To remove the Media Browse UI, use null with setBrowseContent:

controls.setBrowseContent(null);

Customize Media Browse

Browsing content

Use BrowseContent to customize the title of the Media Browse UI and update items:

Media Browse - Browse Content

A. BrowseContent.title

B. BrowseContent.items

Use BrowseItem to display title, subtitle, duration, and image for each item in the Media Browse UI:

Media Browse - Browse Item

A. BrowseItem.image

B. BrowseItem.duration

C. BrowseItem.title

D. BrowseItem.subtitle

Aspect ratio

Use targetAspectRatio to select the best aspect ratio for your image assets. Three aspect ratios are supported by the CAF Receiver SDK:

Aspect Ratio Example
SQUARE_1_TO_1 Media Browse - Square Aspect Ratio
PORTRAIT_2_TO_3 Media Browse - Portrait Aspect Ratio
LANDSCAPE_16_TO_9 Media Browse - Landscape Aspect Ratio

Messages

When a user selects one of the items from the Media Browse UI, the CAF Receiver SDK sends a LOAD message to the application according to the values of the selected BrowseItem.

Sample code

const controls = cast.framework.ui.Controls.getInstance();

const item1 = new cast.framework.ui.BrowseItem();
item1.title = 'Title 1';
item1.subtitle = 'Subtitle 1';
item1.duration = 300;
item1.imageType = cast.framework.ui.BrowseImageType.MUSIC_TRACK;
item1.image = new cast.framework.messages.Image('1.jpg');
item1.entity = 'example://gizmos/1';

const item2 = new cast.framework.ui.BrowseItem();
item2.title = 'Title 2';
item2.subtitle = 'Subtitle 2';
item2.duration = 100;
item2.imageType = cast.framework.ui.BrowseImageType.MUSIC_TRACK;
item2.image = new cast.framework.messages.Image('2.jpg');
item2.entity = 'example://gizmos/2';

const items = [item1, item2];

const browseContent = new cast.framework.ui.BrowseContent();
browseContent.title = 'Up Next';
browseContent.items = items;

playerDataBinder.addEventListener(
  cast.framework.ui.PlayerDataEventType.MEDIA_CHANGED,
  (e) => {
    // Media browse
    touchControls.setBrowseContent(browseContent);
  });

playerManager.setMessageInterceptor(
  cast.framework.messages.MessageType.LOAD, loadRequestData => {
    if (loadRequestData.media && loadRequestData.media.entity) {
      // Load by entity
      loadRequestData.media.contentId = entityToId(loadRequestData.media.entity);
      loadRequestData.media.contentUrl = entityToUrl(loadRequestData.media.entity);
    }
    return loadRequestData;
  });