Customize Android Sender UI

You can customize Cast widgets by setting the colors, styling the buttons, text, and thumbnail appearance, and by choosing the types of buttons to display.

Customize app 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

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="mediaRouteButtonTint">#EEFF41</item>
</style>

setTint should be used if the support library version is newer than 26.0.0. For older support library versions, please use buttonTint instead.

Customize Introductory Overlay 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>

Choose buttons

A MiniControllerFragment has three slots that can display the album art and two buttons, or three control buttons if the album art is not populated.

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 receiver
@id/cast_button_type_closed_caption Opens a dialog to select text and audio tracks

Here is an example that would use the album art, 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_empty</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>
Kotlin
// MyCustomUIController.kt
class MyCustomUIController(private val mView: View) : UIController() {
    override fun onMediaStatusUpdated() {
        // Update the state of mView based on the latest the media status.
        ...
        mView.visibility = View.INVISIBLE
        ...
    }
}

// MyMiniControllerFragment.kt
class MyMiniControllerFragment : MiniControllerFragment() {
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        super.onCreateView(inflater, container, savedInstanceState)
        val customButtonView = getButtonImageViewAt(2)
        val myCustomUiController = MyCustomUIController(customButtonView)
        uiMediaController.bindViewToUIController(customButtonView, myCustomUiController)
        ...
    }
}
Java
// 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.setVisibility(View.INVISIBLE);
        ...
    }
}

// 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>
Kotlin
// MyCustomUIController.kt
class MyCustomUIController(private val mView: View) : UIController() {
    override fun onMediaStatusUpdated() {
        // Update the state of mView based on the latest the media status.
        ...
        mView.visibility = View.INVISIBLE
        ...
    }
}

// MyExpandedControllerActivity.kt
internal class MyExpandedControllerActivity : ExpandedControllerActivity() {
    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val customButtonView = getButtonImageViewAt(2)
        val myCustomUiController = MyCustomUIController(customButtonView)
        uiMediaController.bindViewToUIController(customButtonView, myCustomUiController)
        ...
    }
}
Java
// 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.setVisibility(View.INVISIBLE);
        ...
    }
}

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