This developer guide describes how to add Google Cast support to your Android
sender app using the Android Sender SDK.
**Note:** We recommend that you try the following:

- [**Cast Android Sender
  codelab tutorial**](https://developers.google.com/cast/codelabs/cast-videos-android) - Learn step-by-step how to enable an existing Android video app to use a Google Cast device to Cast videos to a TV.
- [**CastVideos-android sample app**](https://github.com/googlecast/CastVideos-android) (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).

The mobile device or laptop is the *sender* which controls the playback, and the
Google Cast device is the *Receiver* which displays the content on the TV.

The *sender framework* refers to the Cast class library binary and associated
resources present at runtime on the sender. The *sender app* or *Cast app*
refers to an app also running on the sender. The *Web Receiver app*
refers to the HTML application running on the Cast-enabled device.

The sender framework uses an asynchronous callback design to inform the sender
app of events and to transition between various states of the Cast app life
cycle.

## App flow

The following steps describe the typical high-level execution flow for a sender
Android app:

- The Cast framework automatically starts [`MediaRouter`](https://developer.android.com/reference/androidx/mediarouter/media/MediaRouter) device discovery based on the `Activity` lifecycle.
- When the user clicks on the Cast button, the framework presents the Cast dialog with the list of discovered Cast devices.
- When the user selects a Cast device, the framework attempts to launch the Web Receiver app on the Cast device.
- The framework invokes callbacks in the sender app to confirm that the Web Receiver app was launched.
- The framework creates a communication channel between the sender and Web Receiver apps.
- The framework uses the communication channel to load and control media playback on the Web Receiver.
- The framework synchronizes the media playback state between sender and Web Receiver: when the user makes sender UI actions, the framework passes those media control requests to the Web Receiver, and when the Web Receiver sends media status updates, the framework updates the state of the sender UI.
- When the user clicks on the Cast button to disconnect from the Cast device, the framework will disconnect the sender app from the Web Receiver.

For a comprehensive list of all classes, methods and events in the Google Cast
Android SDK, see the [Google Cast Sender API Reference for
Android](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/package-summary).
The following sections cover the steps for you to add Cast to your Android app.

## Configure the Android manifest

Your app's AndroidManifest.xml file requires you to configure the following
elements for the Cast SDK:

### [uses-sdk](https://developer.android.com/guide/topics/manifest/uses-sdk-element.html)

Set the minimum and target Android API levels that the Cast SDK supports.
Currently the minimum is API level 24 and the target is
API level 35.

> [!NOTE]
> **Note:** This element specifies the API Level, not the version number of the SDK, despite "SdkVersion" appearing in the attribute names.

    <uses-sdk
            android:minSdkVersion="24"
            android:targetSdkVersion="35" />

### [android:theme](https://developer.android.com/guide/topics/manifest/application-element.html#testOnly)

Set your app's theme based on the minimum Android SDK version. For example, if
you are not implementing your own theme, you should use a variant of
`Theme.AppCompat` when targeting a minimum Android SDK version that is
pre-Lollipop.

    <application
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name"
            android:theme="@style/Theme.AppCompat" >
           ...
    </application>

## Initialize the Cast Context

The framework has a global singleton object, the `CastContext`, that coordinates
all the framework's interactions.

Your app must implement the
[`OptionsProvider`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/OptionsProvider)
interface to supply options needed to initialize the
[`CastContext`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/CastContext)
singleton. `OptionsProvider` provides an instance of
[`CastOptions`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/CastOptions)
which contains options that affect the behavior of the framework. The most
important of these is the Web Receiver application ID, which is used to filter
discovery results and to launch the Web Receiver app when a Cast session is
started.
Kotlin

```kotlin
class CastOptionsProvider : OptionsProvider {
    override fun getCastOptions(context: Context): CastOptions {
        return Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .build()
    }

    override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
        return null
    }
}
```
Java

```java
public class CastOptionsProvider implements OptionsProvider {
    @Override
    public CastOptions getCastOptions(Context context) {
        CastOptions castOptions = new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .build();
        return castOptions;
    }
    @Override
    public List<SessionProvider> getAdditionalSessionProviders(Context context) {
        return null;
    }
}
```

You must declare the fully qualified name of the implemented `OptionsProvider`
as a metadata field in the AndroidManifest.xml file of the sender app:

    <application>
        ...
        <meta-data
            android:name=
                "com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
            android:value="com.foo.CastOptionsProvider" />
    </application>

`CastContext` is lazily initialized when the `CastContext.getSharedInstance()`
is called.
Kotlin

```kotlin
class MyActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val castContext = CastContext.getSharedInstance(this)
    }
}
```
Java

```java
public class MyActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        CastContext castContext = CastContext.getSharedInstance(this);
    }
}
```

## The Cast UX Widgets

The Cast framework provides the widgets that comply with the Cast Design
Checklist:

- [Introductory Overlay](https://developers.google.com/cast/docs/design_checklist/cast-button.md.txt#prompting):
  The framework provides a custom View,
  [`IntroductoryOverlay`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/IntroductoryOverlay),
  that is shown to the user to call attention to the Cast button the
  first time a receiver is available. The Sender app can
  [customize the text and the position of the title
  text](https://developers.google.com/cast/docs/android_sender/customize_ui#customize_introductory_overlay).

- [Cast Button](https://developers.google.com/cast/docs/design_checklist/cast-button.md.txt#sender-cast-icon-available):
  The Cast button is visible regardless of the availability of Cast devices.
  When the user first clicks on the Cast button, a Cast dialog is displayed
  which lists the discovered devices. When the user clicks on the Cast button
  while the device is connected, it displays the current media metadata (such as
  title, name of the recording studio and a thumbnail image) or allows the user
  to disconnect from the Cast device. The "Cast button" is sometimes referred to
  as the "Cast icon".

- [Mini Controller](https://developers.google.com/cast/docs/design_checklist/sender.md.txt#sender-mini-controller):
  When the user is casting content and has navigated away from the current
  content page or expanded controller to another screen in the sender app, the
  mini controller is displayed at the bottom of the screen to allow the user to
  see the currently casting media metadata and to control the playback.

- [Expanded Controller](https://developers.google.com/cast/docs/design_checklist/sender.md.txt#sender-expanded-controller):
  When the user is casting content, if they click on the media notification
  or mini controller, the expanded controller launches, which displays the
  currently playing media metadata and provides several buttons to control
  the media playback.

- [Notification](https://developers.google.com/cast/docs/design_checklist/sender.md.txt#sender-control-notification):
  Android only. When the user is casting content and navigates away from the
  sender app, a media notification is displayed that shows the currently casting
  media metadata and playback controls.

- [Lock Screen](https://developers.google.com/cast/docs/design_checklist/sender.md.txt#sender-control-lockscreen):
  Android only. When the user is casting content and navigates (or the device
  times out) to the lock screen, a media lock screen control is displayed that
  shows the currently casting media metadata and playback controls.

The following guide includes descriptions of how to add these widgets to
your app.

## Add a Cast Button

The Android
[`MediaRouter`](https://developer.android.com/reference/androidx/mediarouter/media/MediaRouter)
APIs are designed to enable media display and playback on secondary devices.
Android apps that use the `MediaRouter` API should include a Cast button as part
of their user interface, to allow users to select a media route to play media on
a secondary device such as a Cast device.

The framework makes adding a
[`MediaRouteButton`](https://developer.android.com/reference/androidx/mediarouter/app/MediaRouteButton)
as a
[`Cast button`](https://developers.google.com/cast/docs/design_checklist/cast-button.md.txt)
very easy. You should first add a menu item or a `MediaRouteButton` in the xml
file that defines your menu, and use
[`CastButtonFactory`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/CastButtonFactory)
to wire it up with the framework.

    // To add a Cast button, add the following snippet.
    // menu.xml
    <item
        android:id="@+id/media_route_menu_item"
        android:title="@string/media_route_menu_title"
        app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
        app:showAsAction="always" />

Kotlin

```kotlin
// Then override the onCreateOptionMenu() for each of your activities.
// MyActivity.kt
override fun onCreateOptionsMenu(menu: Menu): Boolean {
    super.onCreateOptionsMenu(menu)
    menuInflater.inflate(R.menu.main, menu)
    CastButtonFactory.setUpMediaRouteButton(
        applicationContext,
        menu,
        R.id.media_route_menu_item
    )
    return true
}
```
Java

```java
// Then override the onCreateOptionMenu() for each of your activities.
// MyActivity.java
@Override public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    getMenuInflater().inflate(R.menu.main, menu);
    CastButtonFactory.setUpMediaRouteButton(getApplicationContext(),
                                            menu,
                                            R.id.media_route_menu_item);
    return true;
}
```

Then, if your `Activity` inherits from
[`FragmentActivity`](https://developer.android.com/reference/androidx/fragment/app/FragmentActivity),
you can add a
[`MediaRouteButton`](https://developer.android.com/reference/androidx/mediarouter/app/MediaRouteButton)
to your layout.

    // activity_layout.xml
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:gravity="center_vertical"
       android:orientation="horizontal" >

       <androidx.mediarouter.app.MediaRouteButton
           android:id="@+id/media_route_button"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_weight="1"
           android:mediaRouteTypes="user"
           android:visibility="gone" />

    </LinearLayout>

Kotlin

```kotlin
// MyActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_layout)

    mMediaRouteButton = findViewById<View>(R.id.media_route_button) as MediaRouteButton
    CastButtonFactory.setUpMediaRouteButton(applicationContext, mMediaRouteButton)

    mCastContext = CastContext.getSharedInstance(this)
}
```
Java

```java
// MyActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_layout);

   mMediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button);
   CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), mMediaRouteButton);

   mCastContext = CastContext.getSharedInstance(this);
}
```

To set the appearance of the Cast button using a theme, see
[Customize Cast Button](https://developers.google.com/cast/docs/android_sender/customize_ui.md.txt#customize_cast_button).

## Configure device discovery

Device discovery is completely managed by the
[`CastContext`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/CastContext).
When initializing the CastContext, the sender app specifies the Web Receiver
application ID, and can optionally request namespace filtering by setting
`supportedNamespaces` in
[`CastOptions`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/CastOptions).
`CastContext` holds a reference to the `MediaRouter` internally, and will start
the discovery process under the following conditions:

- Based on an algorithm designed to balance device discovery latency and battery usage, discovery will occasionally be started automatically when the sender app enters the foreground.
- The Cast dialog is open.
- The Cast SDK is attempting to recover a Cast session.

The discovery process will be stopped when the Cast dialog is closed or the
sender app enters the background.
Kotlin

```kotlin
class CastOptionsProvider : OptionsProvider {
    companion object {
        const val CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace"
    }

    override fun getCastOptions(appContext: Context): CastOptions {
        val supportedNamespaces: MutableList<String> = ArrayList()
        supportedNamespaces.add(CUSTOM_NAMESPACE)

        return CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setSupportedNamespaces(supportedNamespaces)
            .build()
    }

    override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
        return null
    }
}
```
Java

```java
class CastOptionsProvider implements OptionsProvider {
    public static final String CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace";

    @Override
    public CastOptions getCastOptions(Context appContext) {
        List<String> supportedNamespaces = new ArrayList<>();
        supportedNamespaces.add(CUSTOM_NAMESPACE);

        CastOptions castOptions = new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setSupportedNamespaces(supportedNamespaces)
            .build();
        return castOptions;
    }

    @Override
    public List<SessionProvider> getAdditionalSessionProviders(Context context) {
        return null;
    }
}
```

## How session management works

The Cast SDK introduces the concept of a Cast session, the
establishment of which combines the steps of connecting to a device, launching (or joining) a Web
Receiver app, connecting to that app, and initializing a media control channel. See the Web Receiver
[Application life cycle guide](https://developers.google.com/cast/docs/web_receiver/basic.md.txt#application_life_cycle)
for more information about Cast sessions and the Web Receiver life cycle.

Sessions are managed by the class
[`SessionManager`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/SessionManager),
which your app can access via
[`CastContext.getSessionManager()`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/CastContext).
Individual sessions are represented by subclasses of the class
[`Session`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/Session).
For example,
[`CastSession`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/CastSession)
represents sessions with Cast devices. Your app can access the currently active
Cast session via
[`SessionManager.getCurrentCastSession()`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/SessionManager).

Your app can use the
[`SessionManagerListener`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/SessionManagerListener)
class to monitor session events, such as creation, suspension, resumption, and
termination. The framework automatically attempts to resume from an
abnormal/abrupt termination while a session was active.

Sessions are created and torn down automatically in response to user gestures
from the `MediaRouter` dialogs.

To better understand Cast starting errors, apps can use
[`CastContext#getCastReasonCodeForCastStatusCode(int)`](https://developers.google.com/android/reference/com/google/android/gms/cast/framework/CastContext.md.txt#getCastReasonCodeForCastStatusCode(int))
to convert the session starting error to
[`CastReasonCodes`](https://developers.google.com/android/reference/com/google/android/gms/cast/framework/CastReasonCodes.md.txt).
Please note that some session starting errors (e.g. [`CastReasonCodes#CAST_CANCELLED`](https://developers.google.com/android/reference/com/google/android/gms/cast/framework/CastReasonCodes.md.txt#CAST_CANCELLED))
are intended behavior and should not be logged as an error.

If you need to be aware of the state changes for the session, you can implement
a `SessionManagerListener`. This example listens to the availability of a
`CastSession` in an `Activity`.
Kotlin

```kotlin
class MyActivity : Activity() {
    private var mCastSession: CastSession? = null
    private lateinit var mCastContext: CastContext
    private lateinit var mSessionManager: SessionManager
    private val mSessionManagerListener: SessionManagerListener<CastSession> =
        SessionManagerListenerImpl()

    private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> {
        override fun onSessionStarting(session: CastSession?) {}

        override fun onSessionStarted(session: CastSession?, sessionId: String) {
            invalidateOptionsMenu()
        }

        override fun onSessionStartFailed(session: CastSession?, error: Int) {
            val castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error)
            // Handle error
        }

        override fun onSessionSuspended(session: CastSession?, reason Int) {}

        override fun onSessionResuming(session: CastSession?, sessionId: String) {}

        override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) {
            invalidateOptionsMenu()
        }

        override fun onSessionResumeFailed(session: CastSession?, error: Int) {}

        override fun onSessionEnding(session: CastSession?) {}

        override fun onSessionEnded(session: CastSession?, error: Int) {
            finish()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mCastContext = CastContext.getSharedInstance(this)
        mSessionManager = mCastContext.sessionManager
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }

    override fun onResume() {
        super.onResume()
        mCastSession = mSessionManager.currentCastSession
    }

    override fun onDestroy() {
        super.onDestroy()
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }
}
```
Java

```java
public class MyActivity extends Activity {
    private CastContext mCastContext;
    private CastSession mCastSession;
    private SessionManager mSessionManager;
    private SessionManagerListener<CastSession> mSessionManagerListener =
            new SessionManagerListenerImpl();

    private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> {
        @Override
        public void onSessionStarting(CastSession session) {}
        @Override
        public void onSessionStarted(CastSession session, String sessionId) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionStartFailed(CastSession session, int error) {
            int castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error);
            // Handle error
        }
        @Override
        public void onSessionSuspended(CastSession session, int reason) {}
        @Override
        public void onSessionResuming(CastSession session, String sessionId) {}
        @Override
        public void onSessionResumed(CastSession session, boolean wasSuspended) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionResumeFailed(CastSession session, int error) {}
        @Override
        public void onSessionEnding(CastSession session) {}
        @Override
        public void onSessionEnded(CastSession session, int error) {
            finish();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCastContext = CastContext.getSharedInstance(this);
        mSessionManager = mCastContext.getSessionManager();
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mCastSession = mSessionManager.getCurrentCastSession();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class);
    }
}
```

### 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.

To get the new destination device during a stream transfer or expansion,
register a
[`Cast.Listener`](https://developers.google.com/android/reference/com/google/android/gms/cast/Cast.Listener.md.txt)
using the
[`CastSession#addCastListener`](https://developers.google.com/android/reference/com/google/android/gms/cast/framework/CastSession.md.txt#public-void-addcastlistener-cast.listener-listener).
Then call
[`CastSession#getCastDevice()`](https://developers.google.com/android/reference/com/google/android/gms/cast/framework/CastSession.md.txt#getCastDevice())
during the `onDeviceNameChanged` callback.

See
[Stream transfer on Web Receiver](https://developers.google.com/cast/docs/caf_receiver/core_features#stream_transfer)
for more information.

## Automatic reconnection

The framework provides a
[`ReconnectionService`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/ReconnectionService)
which can be enabled by the sender app to handle reconnection in many subtle
corner cases, such as:

- Recover from a temporary loss of WiFi
- Recover from device sleep
- Recover from backgrounding the app
- Recover if the app crashed

This service is turned on by default, and can be turned off in
[`CastOptions.Builder`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/CastOptions.Builder).

This service can be automatically merged into your app's manifest if auto-merge
is enabled in your gradle file.

The framework will start the service when there is a media session, and stop it
when the media session ends.

## How Media Control works

The Cast framework deprecates the
[`RemoteMediaPlayer`](https://developers.google.com/android/reference/com/google/android/gms/cast/RemoteMediaPlayer.md.txt)
class from Cast 2.x in favor of a new class
[`RemoteMediaClient`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/media/RemoteMediaClient),
which provides the same functionality in a set of more convenient APIs, and
avoids having to pass in a GoogleApiClient.

When your app establishes a
[`CastSession`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/CastSession)
with a Web Receiver app that supports the media namespace, an instance of
`RemoteMediaClient` will automatically be created by the framework; your app can
access it by calling `getRemoteMediaClient()` method on the `CastSession`
instance.

All methods of `RemoteMediaClient` that issue requests to the Web Receiver will
return a PendingResult object that can be used to track that request.

It is expected that the instance of `RemoteMediaClient` may be shared by
multiple parts of your app, and indeed some internal components of the
framework, such as the persistent [mini controllers](https://developers.google.com/cast/docs/android_sender/integrate.md.txt#add_mini_controller) and
the [notification service](https://developers.google.com/cast/docs/android_sender/integrate.md.txt#add_media_controls_to_notification_and_lock_screen).
To that end, this instance supports registration of multiple instances of
`RemoteMediaClient.Listener`.

## Set media metadata

The
[`MediaMetadata`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/MediaMetadata)
class represents the information about a media item you want to Cast. The
following example creates a new MediaMetadata instance of a movie and sets the
title, subtitle and two images.
Kotlin

```kotlin
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)

movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle())
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio())
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(0))))
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(1))))
```
Java

```java
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);

movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle());
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio());
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(0))));
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(1))));
```

See
[Image Selection](https://developers.google.com/cast/docs/android_sender/advanced.md.txt#override_image_selection_for_ux_widgets)
on the use of images with media metadata.

## Load media

Your app can load a media item, as shown in the following code. First use
[`MediaInfo.Builder`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/MediaInfo.Builder)
with the media's metadata to build a
[`MediaInfo`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/MediaInfo)
instance. Get the
[`RemoteMediaClient`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/media/RemoteMediaClient)
from the current `CastSession`, then load the `MediaInfo` into that
`RemoteMediaClient`. Use `RemoteMediaClient` to play, pause, and otherwise
control a media player app running on the Web Receiver.
Kotlin

```kotlin
val mediaInfo = MediaInfo.Builder(mSelectedMedia.getUrl())
    .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
    .setContentType("videos/mp4")
    .setMetadata(movieMetadata)
    .setStreamDuration(mSelectedMedia.getDuration() * 1000)
    .build()
val remoteMediaClient = mCastSession.getRemoteMediaClient()
remoteMediaClient.load(MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build())
```
Java

```java
MediaInfo mediaInfo = new MediaInfo.Builder(mSelectedMedia.getUrl())
        .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
        .setContentType("videos/mp4")
        .setMetadata(movieMetadata)
        .setStreamDuration(mSelectedMedia.getDuration() * 1000)
        .build();
RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
remoteMediaClient.load(new MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build());
```

Also see the section on
[using media tracks](https://developers.google.com/cast/docs/android_sender/media_tracks.md.txt#use_media_tracks).

### 4K video format

To check what video format your media is, use
[`getVideoInfo()`](https://developers.google.com/android/reference/com/google/android/gms/cast/MediaStatus.md.txt#getVideoInfo())
in MediaStatus to get the current instance of
[`VideoInfo`](https://developers.google.com/android/reference/com/google/android/gms/cast/VideoInfo.md.txt).
This instance contains the type of HDR TV format and the display height
and width in pixels. Variants of 4K format are indicated by constants
[`HDR_TYPE_*`](https://developers.google.com/android/reference/com/google/android/gms/cast/VideoInfo.md.txt).

### Remote control notifications to multiple devices

When a user is casting, other Android devices on the same network will get
a notification to also let them control the playback. Anyone whose device
receives such notifications can turn them off for that device in the Settings
app at Google \> Google Cast \> **Show remote control notifications** .
(The notifications include a shortcut to the Settings app.) For more detail, see
[Cast remote control notifications](https://support.google.com/chromecast/answer/7206638).

## Add mini controller

According to the [Cast Design
Checklist](https://developers.google.com/cast/docs/design_checklist/sender#sender-mini-controller),
a sender app should provide a persistent control known as the [mini
controller](https://developers.google.com/cast/docs/design_checklist/sender#mini-controller)
that should appear when the user navigates away from the current content page to
another part of the sender app. The mini controller provides a visible reminder
to the user of the current Cast session. By tapping on the mini controller, the
user can return to the Cast full-screen expanded controller view.

The framework provides a custom View, MiniControllerFragment, which you can add
to the bottom of the layout file of each activity in which you want to show the
mini controller.

```java
<fragment
    android:id="@+id/castMiniController"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:visibility="gone"
    class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />
```

When your sender app is playing a video or audio live stream, the SDK
automatically displays a play/stop button in place of the play/pause button
in the mini controller.

To set the text appearance of the title and subtitle of this custom view,
and to choose buttons, see
[Customize Mini Controller](https://developers.google.com/cast/docs/android_sender/customize_ui.md.txt#customize_mini_controller).

## Add expanded controller

The Google Cast Design Checklist requires that a sender app provide an [expanded
controller](https://developers.google.com/cast/docs/design_checklist/sender#sender-expanded-controller)
for the media being Cast. The expanded controller is a full screen version of
the mini controller.

The Cast SDK provides a widget for the expanded controller called
[`ExpandedControllerActivity`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/media/widget/ExpandedControllerActivity).
This is an abstract class you have to subclass to add a Cast button.

First, create a new menu resource file for the expanded controller to provide
the Cast button:

    <menu xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto">

        <item
                android:id="@+id/media_route_menu_item"
                android:title="@string/media_route_menu_title"
                app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
                app:showAsAction="always"/>

    </menu>

Create a new class that extends `ExpandedControllerActivity`.
Kotlin

```kotlin
class ExpandedControlsActivity : ExpandedControllerActivity() {
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        super.onCreateOptionsMenu(menu)
        menuInflater.inflate(R.menu.expanded_controller, menu)
        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item)
        return true
    }
}
```
Java

```java
public class ExpandedControlsActivity extends ExpandedControllerActivity {
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.expanded_controller, menu);
        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item);
        return true;
    }
}
```

Now declare your new activity in the app manifest within the `application` tag:

    <application>
    ...
    <activity
            android:name=".expandedcontrols.ExpandedControlsActivity"
            android:label="@string/app_name"
            android:launchMode="singleTask"
            android:theme="@style/Theme.CastVideosDark"
            android:screenOrientation="portrait"
            android:parentActivityName="com.google.sample.cast.refplayer.VideoBrowserActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>
        </intent-filter>
    </activity>
    ...
    </application>

Edit the `CastOptionsProvider` and change `NotificationOptions` and
`CastMediaOptions` to set the target activity to your new activity:
Kotlin

```kotlin
override fun getCastOptions(context: Context): CastOptions? {
    val notificationOptions = NotificationOptions.Builder()
        .setTargetActivityClassName(ExpandedControlsActivity::class.java.name)
        .build()
    val mediaOptions = CastMediaOptions.Builder()
        .setNotificationOptions(notificationOptions)
        .setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name)
        .build()

    return CastOptions.Builder()
        .setReceiverApplicationId(context.getString(R.string.app_id))
        .setCastMediaOptions(mediaOptions)
        .build()
}
```
Java

```java
public CastOptions getCastOptions(Context context) {
    NotificationOptions notificationOptions = new NotificationOptions.Builder()
            .setTargetActivityClassName(ExpandedControlsActivity.class.getName())
            .build();
    CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
            .setNotificationOptions(notificationOptions)
            .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName())
            .build();

    return new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setCastMediaOptions(mediaOptions)
            .build();
}
```

Update the `LocalPlayerActivity` `loadRemoteMedia` method to display your
new activity when the remote media is loaded:
Kotlin

```kotlin
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
    val remoteMediaClient = mCastSession?.remoteMediaClient ?: return

    remoteMediaClient.registerCallback(object : RemoteMediaClient.Callback() {
        override fun onStatusUpdated() {
            val intent = Intent(this@LocalPlayerActivity, ExpandedControlsActivity::class.java)
            startActivity(intent)
            remoteMediaClient.unregisterCallback(this)
        }
    })

    remoteMediaClient.load(
        MediaLoadRequestData.Builder()
            .setMediaInfo(mSelectedMedia)
            .setAutoplay(autoPlay)
            .setCurrentTime(position.toLong()).build()
    )
}
```
Java

```java
private void loadRemoteMedia(int position, boolean autoPlay) {
    if (mCastSession == null) {
        return;
    }
    final RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
    if (remoteMediaClient == null) {
        return;
    }
    remoteMediaClient.registerCallback(new RemoteMediaClient.Callback() {
        @Override
        public void onStatusUpdated() {
            Intent intent = new Intent(LocalPlayerActivity.this, ExpandedControlsActivity.class);
            startActivity(intent);
            remoteMediaClient.unregisterCallback(this);
        }
    });
    remoteMediaClient.load(new MediaLoadRequestData.Builder()
            .setMediaInfo(mSelectedMedia)
            .setAutoplay(autoPlay)
            .setCurrentTime(position).build());
}
```

When your sender app is playing a video or audio live stream, the SDK
automatically displays a play/stop button in place of the play/pause button
in the expanded controller.

To set the appearance using themes, choose which buttons to display,
and add custom buttons, see
[Customize Expanded Controller](https://developers.google.com/cast/docs/android_sender/customize_ui.md.txt#customize_expanded_controller).

## Volume control

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

### Physical button volume control

On Android, the physical buttons on the sender device can be used to change the
volume of the Cast session on the Web Receiver by default for any device using
Jelly Bean or newer.

### Physical button volume control prior to Jelly Bean

To use the physical volume keys to control the Web Receiver device volume on
Android devices older than Jelly Bean, the sender app should override
[`dispatchKeyEvent`](https://developer.android.com/reference/android/app/Activity.html#dispatchKeyEvent(android.view.KeyEvent))
in their Activities, and call
[`CastContext.onDispatchVolumeKeyEventBeforeJellyBean()`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/CastContext#onDispatchVolumeKeyEventBeforeJellyBean(android.view.KeyEvent)):
Kotlin

```kotlin
class MyActivity : FragmentActivity() {
    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        return (CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
                || super.dispatchKeyEvent(event))
    }
}
```
Java

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

## Add media controls to notification and lock screen

On Android only, the Google Cast Design Checklist requires a sender app to
[implement media controls in a
notification](https://developers.google.com/cast/docs/design_checklist/sender#sender-control-notification)
and in the [lock
screen](https://developers.google.com/cast/docs/design_checklist/sender#sender-control-lockscreen),
where the sender is casting but the sender app does not have focus. The
framework provides
[`MediaNotificationService`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/media/MediaNotificationService)
and
[`MediaIntentReceiver`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/media/MediaIntentReceiver)
to help the sender app build media controls in a notification and in the lock
screen.

`MediaNotificationService` runs when the sender is casting, and will show a
notification with image thumbnail and information about the current casting
item, a play/pause button and a stop button.

`MediaIntentReceiver` is a `BroadcastReceiver` that handles user actions from
the notification.

Your app can configure notification and media control from lock screen through
[`NotificationOptions`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/media/NotificationOptions).
Your app can configure what control buttons to show in the notification, and
which `Activity` to open when the notification is tapped by the user. If actions
are not explicitly provided, the default values,
`MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK` and
`MediaIntentReceiver.ACTION_STOP_CASTING` will be used.
Kotlin

```kotlin
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting".
val buttonActions: MutableList<String> = ArrayList()
buttonActions.add(MediaIntentReceiver.ACTION_REWIND)
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK)
buttonActions.add(MediaIntentReceiver.ACTION_FORWARD)
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING)

// Showing "play/pause" and "stop casting" in the compat view of the notification.
val compatButtonActionsIndices = intArrayOf(1, 3)

// Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds.
// Tapping on the notification opens an Activity with class VideoBrowserActivity.
val notificationOptions = NotificationOptions.Builder()
    .setActions(buttonActions, compatButtonActionsIndices)
    .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS)
    .setTargetActivityClassName(VideoBrowserActivity::class.java.name)
    .build()
```
Java

```java
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting".
List<String> buttonActions = new ArrayList<>();
buttonActions.add(MediaIntentReceiver.ACTION_REWIND);
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK);
buttonActions.add(MediaIntentReceiver.ACTION_FORWARD);
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING);

// Showing "play/pause" and "stop casting" in the compat view of the notification.
int[] compatButtonActionsIndices = new int[]{1, 3};

// Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds.
// Tapping on the notification opens an Activity with class VideoBrowserActivity.
NotificationOptions notificationOptions = new NotificationOptions.Builder()
    .setActions(buttonActions, compatButtonActionsIndices)
    .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS)
    .setTargetActivityClassName(VideoBrowserActivity.class.getName())
    .build();
```

Showing media controls from notification and lock screen are turned on by
default, and can disabled by calling
[`setNotificationOptions`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/media/CastMediaOptions.Builder#setNotificationOptions(com.google.android.gms.cast.framework.media.NotificationOptions))
with null in
[`CastMediaOptions.Builder`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/media/CastMediaOptions.Builder).
Currently, the lock screen feature is turned on as long as notification is
turned on.
Kotlin

```kotlin
// ... continue with the NotificationOptions built above
val mediaOptions = CastMediaOptions.Builder()
    .setNotificationOptions(notificationOptions)
    .build()
val castOptions: CastOptions = Builder()
    .setReceiverApplicationId(context.getString(R.string.app_id))
    .setCastMediaOptions(mediaOptions)
    .build()
```
Java

```java
// ... continue with the NotificationOptions built above
CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
        .setNotificationOptions(notificationOptions)
        .build();
CastOptions castOptions = new CastOptions.Builder()
        .setReceiverApplicationId(context.getString(R.string.app_id))
        .setCastMediaOptions(mediaOptions)
        .build();
```

When your sender app is playing a video or audio live stream, the SDK
automatically displays a play/stop button in place of the play/pause button
on the notification control but not the lock screen control.

**Note** : To display lock screen controls on pre-Lollipop devices,
`RemoteMediaClient` will automatically request audio focus on your behalf.

## Handle errors

It is very important for sender apps to handle all error callbacks and decide
the best response for each stage of the Cast life cycle. The app can display
error dialogs to the user or it can decide to tear down the connection to the
Web Receiver.