media3-cast-integration

Implements Google Cast support in Android apps using Jetpack Media3.

android/skills159 installsApache-2.0Synced Aug 26

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI
---
name: media3-cast-integration
description: Implements Google Cast support in Android apps using Jetpack Media3.
license: Apache-2.0
---

## Prerequisites

- Jetpack Media3 version must be `>= 1.9.0`. Cast isn't available in lower versions.

## Glossary

- **`CastPlayer`** : Media3 `Player` that controls playback on both local and remote Cast devices.
- **`RemoteCastPlayer`** : Media3 `Player` that communicates with a Cast receiver, only used for remote playback.
- **Google Cast SDK**: Legacy casting SDK in maintenance mode, superseded by Jetpack Media3.
- **`OptionsProvider`** : Interface providing configuration options to initialize GMS `CastContext`.

## Common guidelines

- Legacy [Google Cast SDK](https://developers.google.com/cast) is in maintenance mode.
- For new Cast setups:
  - You must use [Jetpack Media3 Cast](references/android/media/media3/cast/index.md).
  - You mustn't use legacy Cast SDK unless explicitly requested.

## Step 1: Set up dependencies

To complete this step, you **MUST** ensure the following:

- In the app-level build file, declare the `media3-cast` dependency version
  1.9.0 or higher.

      implementation("androidx.media3:media3-cast:1.11.0")

- Ensure required Media3 dependencies are present:

  - `androidx.media3:media3-exoplayer`
  - `androidx.media3:media3-session`
  - `androidx.media3:media3-ui-compose`
- If the application uses legacy Views, add `media3-ui`.

- Enforce the same versions across all Media3 dependencies.

- Use configurations in "Add build dependencies" section of [Getting started
  with CastPlayer](references/android/media/media3/cast/create-castplayer.md) as the source of
  truth.

- **For apps without an existing Cast integration:**

  - Verify legacy Cast SDK (`libs.play.services.cast.framework`) is absent.
- **If Migrating from Legacy Cast SDK:**

  - Add Media3 Cast dependencies first.
  - Keep existing legacy dependencies untouched at this stage to prevent compilation errors.

## Step 2: Update the manifest

To complete this step, you **MUST** ensure the following:

- Inside the manifest's `<application>` tag, declare the Cast options provider.
- Use `DefaultCastOptionsProvider` by default. See the "OptionsProvider" section in [Getting started with
  CastPlayer](references/android/media/media3/cast/create-castplayer.md).
- Declare a custom `OptionsProvider` only if explicitly requested. See [Customize CastOptions](references/android/media/media3/cast/customize-castoptions.md).
- Ensure `INTERNET` permission is present. Don't add any unnecessary permissions.
- **If Migrating from Legacy Cast SDK:**
  - Don't delete existing custom options provider files or manifest entries.

## Step 3: Implement the player and service

### Architecture baseline

Before integrating Media3 Cast, an existing app follows one of two setups:

- **Local-only playback:** Uses Media3 `ExoPlayer` only to support local playback.
- **Legacy Cast setup:** Uses `ExoPlayer` for local playback, alongside a `Player` wrapper over the legacy `RemoteMediaClient` for remote playback. The UI interfaces with a `MediaSession` interacting with a `ForwardingPlayer`, which finally routes controls to either local or remote playback.

To complete this step, you **MUST** ensure the following:

- Inside the application's `MediaSessionService` (or `MediaLibraryService`) `onCreate()` method, initialize `ExoPlayer` and `CastPlayer`.
- Use `CastPlayer` by default unless `RemoteCastPlayer` is explicitly requested. See the "Build a CastPlayer" section in [Getting started with
  CastPlayer](references/android/media/media3/cast/create-castplayer.md).
- For `CastPlayer`, pass the instance directly to `MediaSession.Builder`.
- Replace all legacy forwarding player wrappers.
- Don't delete legacy class files yet to prevent compilation errors during migration.

### Advanced: `RemoteCastPlayer`

- Use `RemoteCastPlayer` only if explicitly requested by user.
- Initialize `MediaSession` with `localPlayer` and set a `SessionAvailabilityListener` on `RemoteCastPlayer` to transfer playback state on Cast session availability changes:

    class PlaybackService : MediaSessionService() {
      private var mediaSession: MediaSession? = null
      private lateinit var localPlayer: ExoPlayer
      private lateinit var remotePlayer: RemoteCastPlayer

      override fun onCreate() {
        super.onCreate()

        localPlayer = ExoPlayer.Builder(this).build()
        remotePlayer = RemoteCastPlayer.Builder(this).build()
        mediaSession = MediaSession.Builder(this, localPlayer).build()

        remotePlayer.setSessionAvailabilityListener(
          object : SessionAvailabilityListener {
            override fun onCastSessionAvailable() {
              transferPlaybackState(localPlayer, remotePlayer)
            }

            override fun onCastSessionUnavailable() {
              transferPlaybackState(remotePlayer, localPlayer)
            }
          }
        )
      }

      private fun transferPlaybackState(previousPlayer: Player, newPlayer: Player) {
        if (previousPlayer.mediaItemCount > 0) {
          val transferStateBuilder = PlayerTransferState.builderFromPlayer(previousPlayer)
          if (previousPlayer.playbackState == Player.STATE_ENDED ||
              previousPlayer.currentPosition == C.TIME_END_OF_SOURCE) {
            transferStateBuilder.setCurrentMediaItemIndex(0)
            transferStateBuilder.setCurrentPosition(0)
          }
          transferStateBuilder.build().setToPlayer(newPlayer)
        }

        previousPlayer.stop()
        previousPlayer.clearMediaItems()
        newPlayer.prepare()
        mediaSession?.setPlayer(newPlayer)
      }
    }

## Step 4: Set up the UI

### Compose-based UI

To complete this step, you **MUST** ensure the following:

- See the "Add a MediaRouteButton Composable to the Player" section in [Getting started with CastPlayer](references/android/media/media3/cast/create-castplayer.md) for Compose integration guidelines.
- Use the [`MediaRouteButton` composable](https://developer.android.com/reference/kotlin/androidx/media3/cast/MediaRouteButton.composable) from `androidx.media3.cast` package.
- Don't use `AndroidView` in the Compose UI hierarchy.
- Place `MediaRouteButton` in an area next to playback controls. Don't hide it behind system UI.
- Don't use `PlayerSurface` for custom player UI. Use the Material3 [`Player`
  composable](https://developer.android.com/reference/kotlin/androidx/media3/ui/compose/material3/Player.composable).
- Force recomposition on playback location shifts to ensure UI sync. Use key
  constraints on `DeviceInfo` changes:

      @OptIn(UnstableApi::class)
      @Composable
      fun MainScreen() {
         val player = rememberMediaController()
         val deviceInfo = rememberDeviceInfo(player)
         player?.let { activePlayer -> key(deviceInfo) { PlayerScreen(player = activePlayer) } }
      }

      @Composable
      private fun rememberMediaController(): Player? {
         // Logic to connect MediaController to MediaSession and release it
      }

      @Composable
      private fun rememberDeviceInfo(player: Player?): DeviceInfo? {
         var deviceInfo by remember(player) { mutableStateOf(player?.deviceInfo) }
         DisposableEffect(player) {
             val activePlayer = player ?: return@DisposableEffect onDispose {}
             deviceInfo = activePlayer.deviceInfo
             val listener = object : Player.Listener {
                 override fun onDeviceInfoChanged(info: DeviceInfo) {
                     deviceInfo = info
                 }
             }
            activePlayer.addListener(listener)
            onDispose { activePlayer.removeListener(listener) }
         }
         return deviceInfo
      }

### View-based UI

To complete this step, you **MUST** ensure the following:

- For View-based UI setups, see the "Add UI elements" section in [Getting
  started with CastPlayer](references/android/media/media3/cast/create-castplayer.md).
- Casting Activities must extend `AppCompatActivity` or `FragmentActivity` and use a `Theme.AppCompat` descendant.
- Ensure the `AppCompat` theme has a visible `ActionBar` if adding `MediaRouteButton` to the options menu.
- Replace all instances and imports of `CastButtonFactory` with `MediaRouteButtonFactory`.
- Rebind `PlayerView.player` references upon `onDeviceInfoChanged` events to
  prevent black screens or UI freezes:

      private val playerListener: Player.Listener =
        object : Player.Listener {
          override fun onDeviceInfoChanged(deviceInfo: DeviceInfo) {
            // Resetting to null bypasses PlayerView.setPlayer()'s instance equality check
            // (this.player == player), forcing it to re-bind the video surface to the controller.
            playerView.player = null
            playerView.player = controller
          }
        }

- **Migration to Compose:**

  - Don't use `AndroidView` to wrap the legacy `PlayerView`.
  - Implement Material3 [`Player` composable](https://developer.android.com/reference/kotlin/androidx/media3/ui/compose/material3/Player.composable) and [`MediaRouteButton` composable](https://developer.android.com/reference/kotlin/androidx/media3/cast/MediaRouteButton.composable) as per [Getting started with CastPlayer](references/android/media/media3/cast/create-castplayer.md).
  - Remove legacy XML layout declarations, menu files, and View component references.

## Step 5: Clean up legacy Cast SDK code

> [!WARNING]
> **Warning:** Don't perform cleanup directly. Remove legacy files and dependencies only when explicitly requested by the user.

To complete this step, you **MUST** ensure the following:

- Remove legacy GMS Cast SDK (`libs.play.services.cast.framework`) and MediaRouter (`libs.androidx.mediarouter`) dependencies.
- Delete custom `OptionsProvider` classes and manifest entries if `DefaultCastOptionsProvider` is adopted.
- Remove legacy `MediaTransferReceiver` manifest declarations if present.
- Remove all references to legacy Cast SDK components such as legacy helper wrappers, forwarding players, and `RemoteMediaClient` interfaces.
- Delete legacy View XML layouts, menu files, and references to `PlayerView` if the migration to Compose is complete.

More Mobile skills

← All Mobile skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY