Media3 Cast Integration
Organizationandroid
Implements Google Cast support in Android apps using Jetpack Media3. Handles adding build dependencies, updating manifest, configuring OptionsProvider, and managing CastPlayer or RemoteCastPlayer for playback in both Compose and View-based UIs. Use when adding Cast functionality or migrating from legacy Cast SDK to Media3 Cast.
Overview
- Repository
- android/skills
- Publisher
- android
- Stars
- 10.5K
- Forks
- 695
- Bundled files
- 3
- License
- Apache-2.0
- Updated
- Nov 11, 2026
Installation
tmp=$(mktemp -d)
git clone --depth 1 https://github.com/android/skills.git "$tmp"
mkdir -p ~/.claude/skills
cp -R "$tmp/media/media3-cast-integration" ~/.claude/skills/media3-cast-integration
rm -rf "$tmp"tmp=$(mktemp -d)
git clone --depth 1 https://github.com/android/skills.git "$tmp"
mkdir -p ~/.codex/skills
cp -R "$tmp/media/media3-cast-integration" ~/.codex/skills/media3-cast-integration
rm -rf "$tmp"Install the Media3 Cast Integration agent skill from https://github.com/android/skills/tree/main/media/media3-cast-integration into this project's skills folder.Instructions
Prerequisites
- Jetpack Media3 version must be
>= 1.9.0. Cast isn't available in lower versions.
Glossary
CastPlayer: Media3Playerthat controls playback on both local and remote Cast devices.RemoteCastPlayer: Media3Playerthat 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 GMSCastContext.
Common guidelines
- Legacy Google Cast SDK is in maintenance mode.
- For new Cast setups:
- You must use Jetpack Media3 Cast.
- 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-castdependency version 1.9.0 or higher.implementation("androidx.media3:media3-cast:1.11.1")Ensure required Media3 dependencies are present:
androidx.media3:media3-exoplayerandroidx.media3:media3-sessionandroidx.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 as the source of truth.
For apps without an existing Cast integration:
- Verify legacy Cast SDK (
libs.play.services.cast.framework) is absent.
- Verify legacy Cast SDK (
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
DefaultCastOptionsProviderby default. See the "OptionsProvider" section in Getting started with CastPlayer. - Declare a custom
OptionsProvideronly if explicitly requested. See Customize CastOptions. - Ensure
INTERNETpermission 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
ExoPlayeronly to support local playback. - Legacy Cast setup: Uses
ExoPlayerfor local playback, alongside aPlayerwrapper over the legacyRemoteMediaClientfor remote playback. The UI interfaces with aMediaSessioninteracting with aForwardingPlayer, which finally routes controls to either local or remote playback.
To complete this step, you MUST ensure the following:
- Inside the application's
MediaSessionService(orMediaLibraryService)onCreate()method, initializeExoPlayerandCastPlayer. - Use
CastPlayerby default unlessRemoteCastPlayeris explicitly requested. See the "Build a CastPlayer" section in Getting started with CastPlayer. - For
CastPlayer, pass the instance directly toMediaSession.Builder. - Replace all legacy forwarding player wrappers.
- Don't delete legacy class files yet to prevent compilation errors during migration.
Advanced: RemoteCastPlayer
Use
RemoteCastPlayeronly if explicitly requested by user.Initialize
MediaSessionwithlocalPlayerand set aSessionAvailabilityListeneronRemoteCastPlayerto 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 for Compose integration guidelines.
Use the
MediaRouteButtoncomposable fromandroidx.media3.castpackage.Don't use
AndroidViewin the Compose UI hierarchy.Place
MediaRouteButtonin an area next to playback controls. Don't hide it behind system UI.Don't use
PlayerSurfacefor custom player UI. Use the Material3Playercomposable.Force recomposition on playback location shifts to ensure UI sync. Use key constraints on
DeviceInfochanges:@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.
Casting Activities must extend
AppCompatActivityorFragmentActivityand use aTheme.AppCompatdescendant.Ensure the
AppCompattheme has a visibleActionBarif addingMediaRouteButtonto the options menu.Replace all instances and imports of
CastButtonFactorywithMediaRouteButtonFactory.Rebind
PlayerView.playerreferences upononDeviceInfoChangedevents 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
AndroidViewto wrap the legacyPlayerView. - Implement Material3
Playercomposable andMediaRouteButtoncomposable as per Getting started with CastPlayer. - Remove legacy XML layout declarations, menu files, and View component references.
- Don't use
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
OptionsProviderclasses and manifest entries ifDefaultCastOptionsProvideris adopted. - Remove legacy
MediaTransferReceivermanifest declarations if present. - Remove all references to legacy Cast SDK components such as legacy helper wrappers, forwarding players, and
RemoteMediaClientinterfaces. - Delete legacy View XML layouts, menu files, and references to
PlayerViewif the migration to Compose is complete.
Bundled files
FAQ
What does Media3 Cast Integration do?
Implements Google Cast support in Android apps using Jetpack Media3. Handles adding build dependencies, updating manifest, configuring OptionsProvider, and managing CastPlayer or RemoteCastPlayer for playback in both Compose and View-based UIs. Use when adding Cast functionality or migrating from legacy Cast SDK to Media3 Cast.
Who made Media3 Cast Integration?
android (an organization) publishes it in the android/skills repository on GitHub.
Is Media3 Cast Integration free?
Yes. Media3 Cast Integration is open source, published by android under the Apache-2.0 license.
What comes with Media3 Cast Integration?
Its instructions and 3 files the agent reads only when a task needs them, such as references, examples or scripts.
Which agents can use Media3 Cast Integration?
Any coding agent that reads SKILL.md files, including Claude Code, Codex, Cursor, GitHub Copilot and Gemini CLI. The agent loads the skill when a task matches its description.
What are agent skills?
An agent skill is a folder with a SKILL.md file: instructions that teach a coding agent how to do one kind of task, and optionally files it can read while it works. The agent keeps only the skill's name and description in mind and reads the rest when a task calls for it.
How are agent skills different from MCP servers?
An MCP server gives an agent new tools to call, code that runs somewhere and returns results. A skill gives it knowledge: how to approach a task with the tools it already has. Many agents use both.
How can my agent test my Android app?
Robomori lets coding agents create, start and control Android emulators and devices from one command, so they can install your app, take screenshots and check dark mode, font sizes and languages.