## Ad Breaks

The Android Sender SDK provides support for Ad Breaks and companion ads within a
given media stream.

<br />


See the
[Web Receiver Ad Breaks Overview](https://developers.google.com/cast/docs/web_receiver/ad_breaks.md.txt) for more
information on how Ad Breaks work.


While breaks can be specified on both the sender and receiver, it is recommended for them to be
specified on the [Web Receiver](https://developers.google.com/cast/docs/web_receiver/ad_breaks.md.txt) and
[Android TV Receiver](https://developers.google.com/cast/docs/android_tv_receiver/ad_breaks.md.txt) to maintain consistent
behavior across platforms.

<br />

On Android, specify ad breaks in a load command using
[`AdBreakClipInfo`](https://developers.google.com/android/reference/com/google/android/gms/cast/AdBreakClipInfo.md.txt)
and [`AdBreakInfo`](https://developers.google.com/android/reference/com/google/android/gms/cast/AdBreakInfo.md.txt):
Kotlin

```kotlin
val breakClip1: AdBreakClipInfo =
    AdBreakClipInfo.Builder("bc0")
        .setTitle("Clip title")
        .setPosterUrl("https://www.some.url")
        .setDuration(60000)
        .setWhenSkippableInMs(5000)  // Set this field so that the ad is skippable
        .build()

val breakClip2: AdBreakClipInfo = ...
val breakClip3: AdBreakClipInfo = ...

val break1: AdBreakClipInfo =
    AdBreakInfo.Builder(/* playbackPositionInMs= */ 10000)
        .setId("b0")
        .setBreakClipIds({"bc0","bc1","bc2"})
        ...
        .build()

val mediaInfo: MediaInfo = MediaInfo.Builder()
    ...
    .setAdBreaks({break1})
    .setAdBreakClips({breakClip1, breakClip2, breakClip3})
    .build()

val mediaLoadRequestData: MediaLoadRequestData = MediaInfo.Builder()
    ...
    .setMediaInfo(mediaInfo)
    .build()

remoteMediaClient.load(mediaLoadRequestData)
```
Java

```java
AdBreakClipInfo breakClip1 =
    new AdBreakClipInfo.Builder("bc0")
        .setTitle("Clip title")
        .setPosterUrl("https://www.some.url")
        .setDuration(60000)
        .setWhenSkippableInMs(5000)  // Set this field so that the ad is skippable
        .build();

AdBreakClipInfo breakClip2 = ...
AdBreakClipInfo breakClip3 = ...

AdBreakInfo break1 =
    new AdBreakInfo.Builder(/* playbackPositionInMs= */ 10000)
        .setId("b0")
        .setBreakClipIds({"bc0","bc1","bc2"})
        ...
        .build();

MediaInfo mediaInfo = new MediaInfo.Builder()
    ...
    .setAdBreaks({break1})
    .setAdBreakClips({breakClip1, breakClip2, breakClip3})
    .build();

MediaLoadRequestData mediaLoadRequestData = new MediaInfo.Builder()
    ...
    .setMediaInfo(mediaInfo)
    .build();

remoteMediaClient.load(mediaLoadRequestData);
```

## Add custom actions

A sender app can extend
[`MediaIntentReceiver`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/media/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" />

Kotlin

```kotlin
// In your OptionsProvider
var mediaOptions = CastMediaOptions.Builder()
    .setMediaIntentReceiverClassName(MyMediaIntentReceiver::class.java.name)
    .build()

// Implementation of MyMediaIntentReceiver
internal class MyMediaIntentReceiver : MediaIntentReceiver() {
    override fun onReceiveActionTogglePlayback(currentSession: Session) {
    }

    override fun onReceiveActionMediaButton(currentSession: Session, intent: Intent) {
    }

    override fun onReceiveOtherAction(context: Context?, action: String, intent: Intent) {
    }
}
```
Java

```java
// 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, Intent intent) {
    }

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

## 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](https://developers.google.com/cast/docs/custom_receiver#messages)
using the same namespace.

The custom channel is implemented with the
[`Cast.MessageReceivedCallback`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/Cast.MessageReceivedCallback)
interface:
Kotlin

```kotlin
class HelloWorldChannel : MessageReceivedCallback {
    val namespace: String
        get() = "urn:x-cast:com.example.custom"

    override fun onMessageReceived(castDevice: CastDevice, namespace: String, message: String) {
        Log.d(TAG, "onMessageReceived: $message")
    }
}
```
Java

```java
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);
    }
}
```

> [!NOTE]
> **Note:** This procedure does *not* use the [`setSupportedNamespaces()`](https://developers.google.com/android/reference/com/google/android/gms/cast/framework/CastOptions.Builder.md.txt#setSupportedNamespaces(java.util.List%3Cjava.lang.String%3E)) method.

Once the sender app is connected to the receiver app, the custom channel can
be created using the
[`setMessageReceivedCallbacks`](https://developers.google.com/android/reference/com/google/android/gms/cast/framework/CastSession.md.txt#setMessageReceivedCallbacks(java.lang.String,%20com.google.android.gms.cast.Cast.MessageReceivedCallback))
method:
Kotlin

```kotlin
try {
    mCastSession.setMessageReceivedCallbacks(
        mHelloWorldChannel.namespace,
        mHelloWorldChannel)
} catch (e: IOException) {
    Log.e(TAG, "Exception while creating channel", e)
}
```
Java

```java
try {
    mCastSession.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`](https://developers.google.com/cast/docs/reference/chrome/cast.framework.CastSession#sendMessage)
method to send string messages to the receiver over that channel:
Kotlin

```kotlin
private fun sendMessage(message: String) {
    if (mHelloWorldChannel != null) {
        try {
            mCastSession.sendMessage(mHelloWorldChannel.namespace, message)
                .setResultCallback { status ->
                    if (!status.isSuccess) {
                        Log.e(TAG, "Sending message failed")
                    }
                }
        } catch (e: Exception) {
            Log.e(TAG, "Exception while sending message", e)
        }
    }
}
```
Java

```java
private void sendMessage(String message) {
    if (mHelloWorldChannel != null) {
        try {
            mCastSession.sendMessage(mHelloWorldChannel.getNamespace(), message)
                .setResultCallback( status -> {
                    if (!status.isSuccess()) {
                        Log.e(TAG, "Sending message failed");
                    }
                });
        } catch (Exception e) {
            Log.e(TAG, "Exception while sending message", e);
        }
    }
}
```

## Supporting autoplay

See the section [Autoplay \& Queueing APIs](https://developers.google.com/cast/docs/android_sender/queueing.md.txt).

## 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`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/media/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)`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/media/ImagePicker#onPickImage(com.google.android.gms.cast.MediaMetadata,%20com.google.android.gms.cast.framework.media.ImageHints))
method to provide an alternate implementation, and then select that subclass
with the
[`setImagePicker`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/media/CastMediaOptions.Builder#setImagePicker(com.google.android.gms.cast.framework.media.ImagePicker))
method of `CastMediaOptions.Builder`.
[`ImageHints`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/media/ImageHints)
provides hints to an `ImagePicker` about the type and size of an image to be
selected for display in the UI.

## Customizing Cast dialogs

### Managing Session Lifecycle

[`SessionManager`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/SessionManager)
is the central place for managing session lifecycle. `SessionManager` listens
to Android
[`MediaRouter`](https://developer.android.com/reference/androidx/mediarouter/media/MediaRouter)
route selection state changes to start, resume and end sessions. When a route is
selected, `SessionManager` will create a
[`Session`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/Session)
object and tries to start or resume it. When a route is unselected,
`SessionManager` will end the current session.

Therefore, to ensure `SessionManager` manages session lifecycles properly, you
must make sure that:

- In the [route selector dialog](https://developers.google.com/cast/docs/design_checklist/cast-dialog.md.txt#sender-cast-menu-available), call [`MediaRouter.selectRoute(MediaRouter.RouteInfo)`](https://developer.android.comreference/androidx/mediarouter/media/MediaRouter#selectRoute(androidx.mediarouter.media.MediaRouter.RouteInfo)) when a user selects a device.
- In the route controller dialog (either in [connected
  state](https://developers.google.com/cast/docs/design_checklist/cast-dialog#sender-cast-menu-connected) or [casting
  state](https://developers.google.com/cast/docs/design_checklist/cast-dialog#sender-cast-menu-ccasting)), call [`MediaRouter.unselect(int)`](https://developer.android.com/reference/androidx/mediarouter/media/MediaRouter#unselect(int)) when the user stops casting.

Depending on how you create the Cast dialogs, additional actions may need to be
done:

- If you create Cast dialogs using [`MediaRouteChooserDialog`](https://developer.android.com/reference/androidx/mediarouter/app/MediaRouteChooserDialog) and [`MediaRouteControllerDialog`](https://developer.android.com/reference/androidx/mediarouter/app/MediaRouteControllerDialog), then these dialogs will update route selection in `MediaRouter` automatically, so nothing needs to be done.
- If you set up your Cast button using [`CastButtonFactory.setUpMediaRouteButton(Context, Menu, int)`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/CastButtonFactory#setUpMediaRouteButton(android.content.Context,%20android.view.Menu,int)) or [`CastButtonFactory.setUpMediaRouteButton(Context, MediaRouteButton)`](https://developers.google.com/cast/docs/reference/android/com/google/android/gms/cast/framework/CastButtonFactory#setUpMediaRouteButton(android.content.Context,%20android.support.v7.app.MediaRouteButton)), then the dialogs are actually created using `MediaRouteChooserDialog` and `MediaRouteControllerDialog`, so nothing needs to be done, too.
- For other cases, you will be creating custom Cast dialogs, so you need to follow the above instructions to update the route selection state in `MediaRouter`.

### Zero Devices State

If you create custom Cast dialogs, your custom
`MediaRouteChooserDialog` should properly handle the case of zero devices being
found. The dialog should have indicators making it clear to your users when your
app is still attempting to find devices and when the discovery attempt is no
longer active.

If you are using the default `MediaRouteChooserDialog`, the zero devices state
is already handled.

## Next steps

This concludes the features that you can add to your Android Sender app.
You can now build a sender app for another platform
([iOS](https://developers.google.com/cast/docs/ios_sender.md.txt) or [Web](https://developers.google.com/cast/docs/web_sender.md.txt)), or
build a [Web Receiver app](https://developers.google.com/cast/docs/caf_receiver).