Add Advanced Cast v3 Features to your Android App

Add Custom Actions

A sender app can extend MediaIntentReceiver to handle custom actions or override its behavior. If you have implemented your own MediaIntentReceiver, you need to add it to the manifest, and also set its name in the CastMediaOptions. This example provides custom actions that override toggle remote media playback, pressing the media button and other types of actions.

// In AndroidManifest.xml
<receiver android:name="com.example.MyMediaIntentReceiver" />

// In your OptionsProvider
CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
        .setMediaIntentReceiverClassName(MyMediaIntentReceiver.class.getName())
        .build();

// Implementation of MyMediaIntentReceiver
class MyMediaIntentReceiver extends MediaIntentReceiver {
    @Override
    protected void onReceiveActionTogglePlayback(Session currentSession) {
    }

    @Override
    protected void onReceiveActionMediaButton(Session currentSession,
                                              KeyEvent keyEvent) {
    }

    @Override
    protected void onReceiveOtherAction(String action, Intent intent) {
    }
}

Customize App

Customize Theme

This example creates a custom theme style "Theme.CastVideosTheme" which can define custom colors, an introductory overlay style, a mini controller style, and an expanded controller style.

<style name="Theme.CastVideosTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Set AppCompat's color theming attrs -->
    <item name="colorPrimary">@color/primary</item>
    <item name="colorPrimaryDark">@color/primary_dark</item>
    <item name="colorAccent">@color/accent</item>
    <item name="android:textColorPrimary">@color/primary_text</item>
    <item name="android:textColorSecondary">@color/secondary_text</item>
    <item name="castIntroOverlayStyle">@style/CustomCastIntroOverlay</item>
    <item name="castMiniControllerStyle">@style/CustomCastMiniController</item>
    <item name="castExpandedControllerStyle">@style/CustomCastExpandedController</item>
</style>

The last three lines above enable you to define styles specific to introductory overlay, mini controller, and expanded controller as part of this theme. Examples are included in those sections that follow.

Customize Cast Button

Customize Theme

To add a custom mediaRouteTheme to your app's Theme:

<style name="Theme.CastVideosTheme" parent="Theme.AppCompat.Light.NoActionBar">
  <!-- ... -->
  <item name="mediaRouteTheme">@style/CustomMediaRouterTheme</item>
</style>

Declare your custom Media Router theme and declare a custom mediaRouteButtonStyle:

<style name="CustomMediaRouterTheme" parent="Theme.MediaRouter">
  <item name="mediaRouteButtonStyle">@style/CustomMediaRouteButtonStyle</item>
</style>

<style name="CustomMediaRouteButtonStyle" parent="Widget.MediaRouter.Light.MediaRouteButton">
  <item name="buttonTint">@color/yellow</item>
</style>

Customize Introductory Overlay

Customize Theme

The IntroductoryOverlay class supports various style attributes that your app can override in a custom theme. This example shows how to override the text appearance of both the button and title over the overlay widget:

<style name="CustomCastIntroOverlay" parent="CastIntroOverlay">
    <item name="castButtonTextAppearance">@style/TextAppearance.CustomCastIntroOverlay.Button</item>
    <item name="castTitleTextAppearance">@style/TextAppearance.CustomCastIntroOverlay.Title</item>
</style>
<style name="TextAppearance.CustomCastIntroOverlay.Button" parent="android:style/TextAppearance">
    <item name="android:textColor">#FFFFFF</item>
</style>
<style name="TextAppearance.CustomCastIntroOverlay.Title" parent="android:style/TextAppearance.Large">
    <item name="android:textColor">#FFFFFF</item>
</style>

Customize Mini Controller

Customize Theme

The MiniControllerFragment class supports various style attributes that your app can override in a custom theme. This example shows how to enable the display of the thumbnail image, to override the text appearance of both the subhead and closed caption, set the colors, and customize the buttons:

<style name="CustomCastMiniController" parent="CastMiniController">
    <item name="castShowImageThumbnail">true</item>
    <item name="castTitleTextAppearance">@style/TextAppearance.AppCompat.Subhead</item>
    <item name="castSubtitleTextAppearance">@style/TextAppearance.AppCompat.Caption</item>
    <item name="castBackground">#FFFFFF</item>
    <item name="castProgressBarColor">#FFFFFF</item>
    <item name="castPlayButtonDrawable">@drawable/cast_ic_mini_controller_play</item>
    <item name="castPauseButtonDrawable">@drawable/cast_ic_mini_controller_pause</item>
    <item name="castStopButtonDrawable">@drawable/cast_ic_mini_controller_stop</item>
    <item name="castLargePlayButtonDrawable">@drawable/cast_ic_mini_controller_play_large</item>
    <item name="castLargePauseButtonDrawable">@drawable/cast_ic_mini_controller_pause_large</item>
    <item name="castLargeStopButtonDrawable">@drawable/cast_ic_mini_controller_stop_large</item>
    <item name="castSkipPreviousButtonDrawable">@drawable/cast_ic_mini_controller_skip_prev</item>
    <item name="castSkipNextButtonDrawable">@drawable/cast_ic_mini_controller_skip_next</item>
    <item name="castRewind30ButtonDrawable">@drawable/cast_ic_mini_controller_rewind30</item>
    <item name="castForward30ButtonDrawable">@drawable/cast_ic_mini_controller_forward30</item>
    <item name="castMuteToggleButtonDrawable">@drawable/cast_ic_mini_controller_mute</item>
    <item name="castClosedCaptionsButtonDrawable">@drawable/cast_ic_mini_controller_closed_caption</item>
</style>

Hide Album Art

The album art for a mini controller can be hidden by setting attribute castShowImageThumbnail to false in the fragment layout file:

<fragment
    ...
    app:castShowImageThumbnail="false"
    ... >

If the album art is shown, then the first control button will not be displayed. The fragment will be visible when a media session starts, and will be invisible when a media session ends.

Choose Buttons

A MiniControllerFragment has three slots in which it can display up to three control buttons:

SLOT  SLOT  SLOT
  1     2     3

By default the fragment shows a play/pause toggle button. Developers can use the attribute castControlButtons to override which buttons to show. The supported control buttons are defined as ID resources:

Button Type Description
@id/cast_button_type_empty Do not place a button in this slot
@id/cast_button_type_custom Custom button
@id/cast_button_type_play_pause_toggle Toggles between playback and pause
@id/cast_button_type_skip_previous Skips to the previous item in the queue
@id/cast_button_type_skip_next Skips to the next item in the queue
@id/cast_button_type_rewind_30_seconds Rewinds the playback by 30 seconds
@id/cast_button_type_forward_30_seconds Skips forward the playback by 30 seconds
@id/cast_button_type_mute_toggle Mutes and unmutes the remote receiver
@id/cast_button_type_closed_caption Opens a dialog to select text and audio tracks

Here is an example that places a rewind button, a play/pause toggle button and a skip forward button in that order from left to right:

<array name="cast_mini_controller_control_buttons">
    <item>@id/cast_button_type_rewind_30_seconds</item>
    <item>@id/cast_button_type_play_pause_toggle</item>
    <item>@id/cast_button_type_forward_30_seconds</item>
</array>
...
<fragment
    android:id="@+id/cast_mini_controller"
    ...
    app:castControlButtons="@array/cast_mini_controller_control_buttons"
    class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment">

Warning: This array must contain exactly three items, otherwise a runtime exception will be thrown. If you don't want to show a button in a slot, use @id/cast_button_type_empty.

Add Custom Buttons

A MiniControllerFragment supports adding custom control buttons which are not provided by the SDK, such as a "thumb's up" button. The steps are:

  1. Specify a slot to contain a custom button using @id/cast_button_type_custom in the castControlButtons attribute of the MiniControllerFragment.

  2. Implement a subclass of UIController. The UIController contains methods that are called by the SDK when the state of the cast session or media session changes. Your subclass of UIController should take an ImageView as one of the parameters and update its state as needed.

  3. Subclass MiniControllerFragment, then override onCreateView and call getButtonImageViewAt(int) to get the ImageView for that custom button. Then call bindViewToUIController(View, UIController) to associate the view with your custom UIController.

  4. See MediaIntentReceiver at Add Custom Actions for how to handle the action from your custom button.

Here is an example of associating a button at slot 2 to a UIController called MyCustomUIController:

// arrays.xml
<array name="cast_expanded_controller_control_buttons">
    <item>@id/cast_button_type_empty</item>
    <item>@id/cast_button_type_rewind_30_seconds</item>
    <item>@id/cast_button_type_custom</item>
    <item>@id/cast_button_type_empty</item>
</array>

// MyCustomUIController.java
class MyCustomUIController extends UIController {
    private final View mView;

    public MyCustomUIController(View view) {
        mView = view;
    }

    @Override
    public onMediaStatusUpdated() {
        // Update the state of mView based on the latest the media status.
        ...
        mView.setVisible(false);
        ...
    }
}

// MyMiniControllerFragment.java
class MyMiniControllerFragment extends MiniControllerFragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        ImageView customButtonView = getButtonImageViewAt(2);
        MyCustomUIController myCustomUiController = new MyCustomUIController(customButtonView);
        getUIMediaController().bindViewToUIController(customButtonView, myCustomUiController);
        ...
    }
}

Customize Expanded Controller

Customize Theme

If an expanded controller's Activity uses a dark theme toolbar, you can set a theme on the toolbar to use light text and a light icon color:

<style name="Theme.CastVideosTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="castExpandedControllerToolbarStyle">
        @style/ThemeOverlay.AppCompat.Dark.ActionBar
    </item>
</style>

You can specify your own images that are used to draw the buttons on the expanded controller:

<style name="CustomCastExpandedController" parent="CastExpandedController">
    <item name="castButtonColor">@null</item>
    <item name="castPlayButtonDrawable">@drawable/cast_ic_expanded_controller_play</item>
    <item name="castPauseButtonDrawable">@drawable/cast_ic_expanded_controller_pause</item>
    <item name="castStopButtonDrawable">@drawable/cast_ic_expanded_controller_stop</item>
    <item name="castSkipPreviousButtonDrawable">@drawable/cast_ic_expanded_controller_skip_previous</item>
    <item name="castSkipNextButtonDrawable">@drawable/cast_ic_expanded_controller_skip_next</item>
    <item name="castRewind30ButtonDrawable">@drawable/cast_ic_expanded_controller_rewind30</item>
    <item name="castForward30ButtonDrawable">@drawable/cast_ic_expanded_controller_forward30</item>
</style>

Choose Buttons

The expanded controller's Activity has five slots to show control buttons. The middle slot always shows a play/pause toggle button and is non-configurable. The other four slots are configurable, from left to right, by the sender app.

SLOT  SLOT  PLAY/PAUSE  SLOT  SLOT
  1     2     BUTTON      3     4

By default the Activity shows a closed caption button, a skip to the previous item button, a skip to the next item button, and a mute toggle button in these four slots, from left to right. Developers can use the attribute castControlButtons to override which buttons to show in which slots. The list of supported control buttons are defined as ID resources identical to the button types for mini controller buttons.

Here is an example that places a rewind button in the second slot, a skip forward button in the third slot, and leaving the first and last slots empty:

// arrays.xml
<array name="cast_expanded_controller_control_buttons">
    <item>@id/cast_button_type_empty</item>
    <item>@id/cast_button_type_rewind_30_seconds</item>
    <item>@id/cast_button_type_forward_30_seconds</item>
    <item>@id/cast_button_type_empty</item>
</array>
...
// styles.xml
<style name="Theme.MyTheme">
    <item name="castExpandedControllerStyle">
        @style/CustomCastExpandedController
    </item>
</style>
...
<style name="CustomCastExpandedController" parent="CastExpandedController">
    <item name="castControlButtons">
        @array/cast_expanded_controller_control_buttons
    </item>
</style>

The array must contain exactly four items, otherwise a runtime exception will be thrown. If you don't want to show a button in a slot, use @id/cast_button_type_empty. CastContext can manage the lifecycle and presentation of this activity.

Add Custom Buttons

An ExpandedControllerActivity supports adding custom control buttons which are not provided by the SDK, such as a "thumb's up" button. The steps are:

  1. Specify a slot to contain a custom button using @id/cast_button_type_custom in the castControlButtons attribute of the ExpandedControllerActivity. You can then use getButtonImageViewAt(int) to obtain the ImageView for that custom button.

  2. Implement a subclass of UIController. UIController contains methods that are called by the SDK when the state of the cast session or media session changes. Your subclass of UIController should take an ImageView as one of the parameters, and update its state as needed.

  3. Subclass ExpandedControllerActivity, then override onCreate and call getButtonImageViewAt(int) to get the view object of the button. Then call bindViewToUIController(View, UIController) to associate the view with your custom UIController.

  4. See MediaIntentReceiver at Add Custom Actions for how to handle the action from your custom button.

Here is an example of associating a button at slot 2 to a UIController called MyCustomUIController:

// arrays.xml
<array name="cast_expanded_controller_control_buttons">
    <item>@id/cast_button_type_empty</item>
    <item>@id/cast_button_type_rewind_30_seconds</item>
    <item>@id/cast_button_type_custom</item>
    <item>@id/cast_button_type_empty</item>
</array>

// MyCustomUIController.java
class MyCustomUIController extends UIController {
    private final View mView;

    public MyCustomUIController(View view) {
        mView = view;
    }

    @Override
    public onMediaStatusUpdated() {
        // Update the state of mView based on the latest the media status.
        ...
        mView.setVisible(false);
        ...
    }
}

// MyExpandedControllerActivity.java
class MyExpandedControllerActivity extends ExpandedControllerActivity {
    @Override
    public void onCreate() {
        super.onCreate();
        ImageView customButtonView = getButtonImageViewAt(2);
        MyCustomUIController myCustomUiController = new MyCustomUIController(customButtonView);
        getUIMediaController().bindViewToUIController(customButtonView, myCustomUiController);
        ...
    }
}

How Volume Control works

The framework automatically manages the volume for the sender app. The framework automatically synchronizes the sender and receiver apps so that the sender UI always reports the volume specified by the receiver.

Volume control prior to Jelly Bean

To use the physical volume keys to control the receiver device volume on Android devices older than Jelly Bean, the sender app should override dispatchKeyEvent in their Activities, and call CastContext.onDispatchVolumeKeyEventBeforeJellyBean():

class MyActivity extends FragmentActivity {
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        return CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
            || super.dispatchKeyEvent(event);
    }
}

Use media tracks

MediaTrack represents a media track, which can be an audio stream, a video stream, or text (such as subtitles or closed caption). Your app can group, style and activate media tracks.

Configure a track

You can configure a track and assign a unique ID to it. The following code creates an English text track, a French text track and a French audio track, each with their own ID:

MediaTrack englishSubtitle = new MediaTrack.Builder(1 /* ID */,
MediaTrack.TYPE_TEXT)
  .setName("English Subtitle")
  .setSubtype(MediaTrack.SUBTYPE_SUBTITLES)
  .setContentId("https://some-url/caption_en.vtt")
  /* language is required for subtitle type but optional otherwise */
  .setLanguage("en-US")
  .build();

MediaTrack frenchSubtitle = new MediaTrack.Builder(2, MediaTrack.TYPE_TEXT)
  .setName("French Subtitle")
  .setSubtype(MediaTrack.SUBTYPE_SUBTITLES)
  .setContentId("https://some-url/caption_fr.vtt")
  .setLanguage("fr")
  .build();

MediaTrack frenchAudio = new MediaTrack.Builder(3, MediaTrack.TYPE_AUDIO)
  .setName("French Audio")
  .setContentId("trk0001")
  .setLanguage("fr")
  .build();

Group tracks

You can group multiple tracks into a media item, which is represented by MediaInfo. An instance of MediaInfo takes an array of tracks and aggregates other information about the media item. Building on the example, your app can add those three media tracks to a media item by passing a list of those three tracks into MediaInfo.Builder.setMediaTracks(List). Your app needs to associate tracks in a MediaInfo in this way before it loads the media to the receiver.

List tracks = new ArrayList();
tracks.add(englishSubtitle);
tracks.add(frenchSubtitle);
tracks.add(frenchAudio);
MediaInfo mediaInfo = MediaInfo.Builder(url)
  .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
  .setContentType(getContentType())
  .setMetadata(getMetadata())
  .setMediaTracks(tracks)
  .build();

Remove tracks

To remove all the tracks from the current media (such as turning off the three subtitles in the example), call MediaInfo.Builder.setMediaTracks and pass an empty list of IDs. Your app can activate one or more tracks that were associated with the media item (after the media is loaded), by calling RemoteMediaClient.setActiveMediaTracks(long[]) and passing the IDs of the tracks to be activated. This example activates the French subtitle and French audio:

// the ID for the French subtitle is '2' and for the French audio '3'
mRemoteMediaClient.setActiveMediaTracks(new long[]{2, 3})
  .setResultCallback(new ResultCallback() {
    @Override
    public void onResult(MediaChannelResult mediaChannelResult) {
      if (!mediaChannelResult.getStatus().isSuccess()) {
        Log.e(TAG, "Failed with status code:" +
          mediaChannelResult.getStatus().getStatusCode());
      }
    }
  });

Style text tracks

TextTrackStyle encapsulates the styling information of a text track. After creating or updating an existing TextTrackStyle, you can apply that style to the currently playing media item by calling RemoteMediaClient.setTextTrackStyle, like this:

mRemoteMediaient.setTextTrackStyle(style)
  .setResultCallback(new ResultCallback() {
    @Override
    public void onResult(MediaChannelResult result) {
      if (!result.getStatus().isSuccess()) {
        Log.e(TAG, "Failed to set the style, status code: " +
              result.getStatus().getStatusCode());
      }
    }
  });

Your app should allow users to update the style for text tracks, either using the settings provided by the system or by the app itself. In Android KitKat and later, you can use the system-wide closed caption settings provided by the framework:

TextTrackStyle textTrackStyle = TextTrackStyle.fromSystemSettings(context);

For versions prior to KitKat, the above call will return an object whose fields are undefined, so you need you need to populate those fields in your app, based on user selections and some default values. You can style the following text track style elements:

  • Foreground (text) color and opacity
  • Background color and opacity
  • Edge type
  • Edge Color
  • Font Scale
  • Font Family
  • Font Style

For example, set the text color to red (FF) with 50% opacity (80) as follows:

textTrackStyle.setForegroundColor(Color.parseColor("#80FF0000"));

In KitKat and later versions, you should register your app to be notified when system-wide closed caption settings are updated. To this end, you need to implement CaptioningManager.CaptioningChangeListener in your app and register this listener by calling:

CaptioningManager.addCaptioningChangeListener(yourChangeListener);

When your app receives a call back that the caption settings have changed, you would then need to extract the new settings and update the style of the text caption for the media that is currently playing by calling RemoteMediaClient.setTextTrackStyle and passing in the new style.

Receive Status updates

When multiple senders are connected to the same receiver, it is important for each sender to be aware of the changes in the receiver even if those changes were initiated from other senders.

To this end, your app should register a RemoteMediaClient.Listener and a RemoteMediaClient.ProgressListener.

If the TextTrackStyle of the current media changes, then all of the connected senders will be notified through both of the above registered listeners. In this case, the receiver framework classes do not verify whether the new style is different from the previous one and notifies all of the connected senders regardless. If, however, the status of active tracks changes, only the RemoteMediaClient.ProgressListener in connected senders will be notified.

Satisfy CORS requirements

For adaptive media streaming, Google Cast requires the presence of CORS headers, but even simple mp4 media streams require CORS if they include Tracks. If you want to enable Tracks for any media, you must enable CORS for both your track streams and your media streams. So, if you do not have CORS headers available for your simple mp4 media on your server, and you then add a simple subtitle track, you will not be able to stream your media unless you update your server to include the appropriate CORS header. In addition, you need to allow at least the following headers: Content-Type, Accept-Encoding, and Range. Note that the last two headers are additional headers that you may not have been needed previously.

Add a Custom Channel

For the sender app to communicate with the receiver app, your app needs to create a custom channel. The sender can use the custom channel to send string messages to the receiver. Each custom channel is defined by a unique namespace and must start with the prefix urn:x-cast:, for example, urn:x-cast:com.example.custom. It is possible to have multiple custom channels, each with a unique namespace. The receiver app can also send and receive messages using the same namespace.

The custom channel is implemented with the Cast.MessageReceivedCallback interface:

class HelloWorldChannel implements Cast.MessageReceivedCallback {
  public String getNamespace() {
    return "urn:x-cast:com.example.custom";
  }
  @Override
  public void onMessageReceived(CastDevice castDevice, String namespace,
        String message) {
    Log.d(TAG, "onMessageReceived: " + message);
  }
}

Once the sender app is connected to the receiver app, the custom channel can be created using the setMessageReceivedCallbacks method:

try {
  myCastSession.setMessageReceivedCallbacks(
      mHelloWorldChannel.getNamespace(),
      mHelloWorldChannel);
} catch (IOException e) {
  Log.e(TAG, "Exception while creating channel", e);
}

Once the custom channel is created, the sender can use the sendMessage method to send string messages to the receiver over that channel:

private void sendMessage(String message) {
 if (mHelloWorldChannel != null) {
  try {
    myCastSession.sendMessage(mHelloWorldChannel.getNamespace(), message)
    .setResultCallback(
      new ResultCallback<Status>() {
        @Override
        public void onResult(Status result) {
          if (!result.isSuccess()) {
            Log.e(TAG, "Sending message failed");
          }
        }
      });
  } catch (Exception e) {
    Log.e(TAG, "Exception while sending message", e);
  }
 }
}

Use Queueing

The Cast framework provides queueing classes that support the creation of lists of MediaQueueItem instances, which can be built from MediaInfo instances such as video or audio streams, to play sequentially on the Cast receiver. This queue of content items can be edited, reordered, updated, and so forth.

The Cast receiver framework classes maintain the queue and respond to operations on the queue as long as the queue has at least one item currently active (playing or paused). Senders can join the session and add items to the queue. The receiver maintains a session for queue items until the last item completes playback or the sender stops the playback and terminates the session, or until a sender loads a new queue on the receiver. By default, the receiver does not maintain any information about terminated queues. Once the last item in the queue finishes, the media session ends and the queue vanishes.

Create and load media queue items

A media queue item is represented in the Cast framework as a MediaQueueItem instance. When you create a media queue item, if you are using the Media Player Library with adaptive content, you can set the preload time so that the player can begin buffering the media queue item before the item ahead of it in the queue finishes playing. Setting the item's autoplay attribute to true allows the receiver to play it automatically. For example, you can use a builder pattern to create your media queue item as follows:

MediaQueueItem queueItem = new MediaQueueItem.Builder(mediaInfo)
  .setAutoplay(true)
  .setPreloadTime(20)
  .build();

Load an array of media queue items in the queue by using the appropriate queueLoad method of RemoteMediaClient.

Receive Media Queue status updates

When the receiver loads a media queue item, it assigns a unique ID to the item which persists for the duration of the session (and the life of the queue). Your app can learn the status of the queue in terms of which item is currently loaded (it might not be playing), loading, or preloaded. You app can also get an ordered list of all the items in the queue. The MediaStatus class provides this status information:

  • getPreloadedItemId() method - If the next item has been preloaded, returns the preloaded item ID.
  • getLoadingItemId() method - Returns the item ID of the item that is currently loading (but isn't active in the queue) on the receiver.
  • getCurrentItemId() method - Returns the item ID of the item that that was active in the queue (it might not be playing) at the time the media status change happened.
  • getQueueItems() method - Returns the list of MediaQueueItem instances as an unmodifiable list.

Use these methods together with the other media status methods to inform your app about the status of the queue and the items in the queue. In addition to media status updates from the receiver, your app can listen for changes to the queue by implementing RemoteMediaClient.Listener.

Edit the queue

To operate on the items in the queue, use the queue methods of the RemoteMediaClient class. These let you load an array of items into a new queue, insert items into an existing queue, update the properties of items in the queue, make an item jump forward or backward in the queue, set the properties of the queue itself (for example, change the repeatMode algorithm that selects the next item), remove items from the queue, and reorder the items in the queue.

Supporting Autoplay

See the section Autoplay & Queueing APIs.

Override Image Selection for UX widgets

Various components of the framework (namely the Cast dialog, the mini controller, and the UIMediaController, if so configured) will display artwork for the currently casting media. The URLs to the image artwork are typically included in the MediaMetadata for the media, but the sender app may have an alternate source for the URLs.

The ImagePicker class defines a means for selecting an appropriate image from the list of images in a MediaMetadata, based on the use of the image, for example, notification thumbnail or full screen background. The default ImagePicker implementation always chooses the first image, or returns null if no image is available in the MediaMetadata. Your app can subclass ImagePicker and override the onPickImage(MediaMetadata, ImageHints) method to provide an alternate implementation, and then select that subclass with the setImagePicker method of CastMediaOptions.Builder. ImageHints provides hints to an ImagePicker about the type and size of an image to be selected for display in the UI.

Next Step

This concludes the features that you can add to your Android Sender app. You can now build a sender app for another platform (iOS or Chrome), or build a receiver app.