Media3 Cast Integration
Android (Google)OfficialCreated by Google LLCDistributed by Android (Google)
- Skill
- Tool integration
- Software development
- Apache-2.0
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a comprehensive guide for implementing Google Cast support in Android apps using the Jetpack Media3 library. It covers the necessary build configuration, manifest updates, and player initialization required to enable seamless casting. The guide also details how to manage playback state between local and remote devices, supports both Compose and View-based UI architectures, and provides a structured approach for migrating from the legacy Google Cast SDK to the modern Media3 implementation.
What it does
- Configures build dependencies for Jetpack Media3 Cast integration.
- Updates Android manifest files with required Cast options providers.
- Initializes CastPlayer and RemoteCastPlayer for unified playback control.
- Provides patterns for managing playback state transitions between local and remote devices.
- Offers guidance for implementing Cast UI components in both Jetpack Compose and legacy View systems.
- Outlines a safe migration path from the legacy Google Cast SDK to Media3.
When to use it
- When adding new Google Cast functionality to an Android application.
- When migrating an existing application from the legacy Google Cast SDK to Jetpack Media3.
- When building media playback services that require switching between local and remote Cast-enabled devices.
Advantages
- Uses modern Jetpack Media3 APIs for consistent media playback management.
- Simplifies the transition between local and remote playback states.
- Provides clear architectural patterns for both Compose and View-based UIs.
- Reduces complexity by replacing legacy forwarding player wrappers.
Usage examples
- “How do I add Google Cast support to my Android app using Media3?”
- The agent provides the necessary Gradle dependencies, manifest configuration steps, and instructions for initializing the CastPlayer within a MediaSessionService.
- “How can I migrate my existing Cast implementation to Media3?”
- The agent outlines a step-by-step migration process, including adding Media3 dependencies, updating the manifest, and eventually removing legacy Cast SDK components.
- “How do I implement a MediaRouteButton in Jetpack Compose?”
- The agent provides the implementation details for the MediaRouteButton composable and explains how to sync UI state with the active player's device information.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/android/media3-cast-integration/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip media3-cast-integration.zip -d .agents/skills
Documentation by Google LLC
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.
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.
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.
Files
- ATTRIBUTION.mdAdded by AutoGineer978 B
- LICENSELicence file added by AutoGineer11.1 KiB
- SKILL.md10.5 KiB
- references/android/media/media3/cast/create-castplayer.md14.0 KiB
- references/android/media/media3/cast/customize-castoptions.md2.9 KiB
- references/android/media/media3/cast/index.md1.3 KiB
references/android/media/media3/cast/create-castplayer.md
The CastPlayer is a Jetpack Media3 Player implementation that supports
both local playback and casting to a remote Cast-enabled device. CastPlayer
simplifies adding cast functionality to your app and provides rich features to
seamlessly switch between local and remote playback. This guide shows you how to
integrate CastPlayer into your media app.
To integrate Cast with other platforms, see the Cast SDK.
Get a Cast-enabled device
To test CastPlayer, you need a Cast-enabled device. Choices include Android
TV, Chromecast, smart speakers, and smart displays. Verify that your device is
set up and connected to the same Wi-Fi network as your development mobile for
discovery.
Add build dependencies
To start using CastPlayer, add the AndroidX Media3 and CastPlayer
dependencies to the build.gradle file of your app module.
Kotlin
implementation("androidx.media3:media3-exoplayer:1.11.1")
implementation("androidx.media3:media3-ui:1.11.1")
implementation("androidx.media3:media3-session:1.11.1")
implementation("androidx.media3:media3-cast:1.11.1")Groovy
implementation "androidx.media3:media3-exoplayer:1.11.1"
implementation "androidx.media3:media3-ui:1.11.1"
implementation "androidx.media3:media3-session:1.11.1"
implementation "androidx.media3:media3-cast:1.11.1"Configure your CastPlayer
To configure the CastPlayer, update your AndroidManifest.xml file with an
options provider.
Options provider
The CastPlayer requires an options provider to configure its behavior. For a
basic setup, you can use the DefaultCastOptionsProvider by adding it to your
AndroidManifest.xml file. This uses default settings, including the default
receiver application.
<application>
...
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="androidx.media3.cast.DefaultCastOptionsProvider" />
...
</application>To customize the configuration, implement your own custom OptionsProvider. See
the CastOptions guide to learn how.
Add a receiver for media transfers
Adding a MediaTransferReceiver to your manifest enables the System UI to
discover Cast-enabled devices on the network and reroute media without opening
the app activity. For example, a user can change the device playing your app's
media from the media notification.
<application>
...
<receiver android:name="androidx.mediarouter.media.MediaTransferReceiver" />
...
</application>Build a CastPlayer
For remote playback with Cast, your app should be able to manage playback even
when the user isn't interacting with an Activity from your app, such as through
the system media notification. For this reason, you should create your
ExoPlayer (for local playback) and CastPlayer (for remote playback)
instances in a service, such as MediaSessionService or MediaLibraryService.
First, create your ExoPlayer instance and then when building your
CastPlayer instance, set ExoPlayer as the local player instance. You can
then switch media playback between your mobile and the Cast-enabled device from
the media notification or the lock screen notification. Media3 uses the Output
Switcher feature to handle player transfers when the output route changes from
local to remote or from remote to local.
Image: Screenshot showing the Output Switcher UI in notifications. Figure 1: (a) Device chip on Media notification (b) Cast-enabled devices shown on tapping the device chip (c) Device chip on Lock screen notification
Kotlin
override fun onCreate() {
super.onCreate()
val exoPlayer = ExoPlayer.Builder(context).build()
val castPlayer = CastPlayer.Builder(context).setLocalPlayer(exoPlayer).build()
mediaSession = MediaSession.Builder(context, castPlayer).build()
}Java
@Override
public void onCreate() {
super.onCreate();
ExoPlayer exoPlayer = new ExoPlayer.Builder(context).build();
CastPlayer castPlayer = new CastPlayer.Builder(context).setLocalPlayer(exoPlayer).build();
mediaSession =
new MediaSession.Builder(/* context= */ context, /* player= */ castPlayer).build();
}<br />
[!IMPORTANT] Important: The preceding code snippet shows the
onCreatemethod of aMediaSessionService. In anActivity, players should be created inonStartoronResume.
Add UI elements
Add a MediaRouteButton to your app's UI. Tapping the MediaRouteButton
opens a dialog displaying a list of available Cast-enabled devices on the
network. When the user selects a device, the media playback is transferred from
the mobile to the selected receiver device. This section shows you how to add
the button and listen for events to update your UI when playback switches
between local and remote devices.
Set the MediaRouteButton
There are four ways to add the MediaRouteButton to your activity's UI. The
best choice depends on your app's design and requirements.
Compose UI: Add a button composable.
Views UI :
Add the button to the app bar menu.
Add the button inside
PlayerView.Add the button as a standard
View.
Image: Screenshot showing the MediaRouteButton in the UI. Figure 2: (a) MediaRouteButton in menu bar, (b) as a View, (c) in PlayerView, and (d) Dialog of Cast-enabled devices.
[!IMPORTANT] Important: To use the
MediaRouteButtonin Views UI, the containing activity must be a subclass ofFragmentActivity.
[!TIP] Tip: These screenshots are from the demo-session apps in Media3. You can checkout the app to see an example of
CastPlayerimplementation.
Add a Composable MediaRouteButton to the Player
You can add the MediaRouteButton Composable to your player's UI. For more
information, see the Compose guide.
@Composable
fun PlayerComposeView(player: Player, modifier: Modifier = Modifier) {
var controlsVisible by remember { mutableStateOf(false) }
Box(
modifier = modifier.clickable { controlsVisible = true },
contentAlignment = Alignment.Center,
) {
PlayerSurface(player = player, modifier = modifier)
AnimatedVisibility(visible = controlsVisible, enter = fadeIn(), exit = fadeOut()) {
Box(modifier = Modifier.fillMaxSize()) {
MediaRouteButton(modifier = Modifier.align(Alignment.TopEnd))
PrimaryControls(player = player, modifier = Modifier.align(Alignment.Center))
}
}
}
}
@Composable
fun PrimaryControls(player: Player, modifier: Modifier = Modifier) {
// ...
}<br />
Add the MediaRouteButton to the PlayerView
You can add the MediaRouteButton directly within the PlayerView's UI
controls. After setting the MediaController as the player for your
PlayerView, provide a MediaRouteButtonViewProvider to display the Cast
button on the Player.
Kotlin
override fun onStart() {
super.onStart()
playerView.player = mediaController
playerView.setMediaRouteButtonViewProvider(MediaRouteButtonViewProvider())
}Java
@Override
public void onStart() {
super.onStart();
playerView.setPlayer(mediaController);
playerView.setMediaRouteButtonViewProvider(new MediaRouteButtonViewProvider());
}<br />
Add the MediaRouteButton to the app bar menu
To set up a MediaRouteButton in the app bar menu, create an XML menu and
override onCreateOptionsMenu in your Activity.
<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:showAsAction="always"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"/>
</menu>Kotlin
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// ...
menuInflater.inflate(R.menu.sample_media_route_button_menu, menu)
val menuItemFuture: ListenableFuture<MenuItem> =
MediaRouteButtonFactory.setUpMediaRouteButton(context, menu, R.id.media_route_menu_item)
Futures.addCallback(
menuItemFuture,
object : FutureCallback<MenuItem> {
override fun onSuccess(menuItem: MenuItem) {
// Do something with the menu item.
}
override fun onFailure(t: Throwable) {
// Handle the failure.
}
},
executor,
)
// ...
return true
}Java
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// ...
getMenuInflater().inflate(R.menu.sample_media_route_button_menu, menu);
ListenableFuture<MenuItem> menuItemFuture =
MediaRouteButtonFactory.setUpMediaRouteButton(context, menu, R.id.media_route_menu_item);
Futures.addCallback(
menuItemFuture,
new FutureCallback<MenuItem>() {
@Override
public void onSuccess(MenuItem menuItem) {
// Do something with the menu item.
}
@Override
public void onFailure(Throwable t) {
// Handle the failure.
}
},
executor);
// ...
return true;
}<br />
Add the MediaRouteButton as a View
You can set up a MediaRouteButton in your activity layout.xml.
<androidx.mediarouter.app.MediaRouteButton
android:id="@+id/media_route_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:mediaRouteButtonTint="@android:color/white" />To complete the setup for the MediaRouteButton, use the Media3 Cast
MediaRouteButtonFactory in your Activity code.
Kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
findViewById<MediaRouteButton>(R.id.media_route_button)?.also {
val unused = MediaRouteButtonFactory.setUpMediaRouteButton(context, it)
}
}Java
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
MediaRouteButton button = findViewById(R.id.media_route_button);
ListenableFuture<Void> setUpFuture =
MediaRouteButtonFactory.setUpMediaRouteButton(context, button);
}<br />
Activity Listener
Create a Player.Listener in your Activity to listen for changes to media
playback location. When the playbackType changes between PLAYBACK_TYPE_LOCAL
and PLAYBACK_TYPE_REMOTE, you can adjust your UI as needed. To prevent memory
leaks and to confine listener activity to only when your app is visible,
register the listener in onStart and unregister it in onStop:
Kotlin
private val playerListener: Player.Listener =
object : Player.Listener {
override fun onDeviceInfoChanged(deviceInfo: DeviceInfo) {
if (deviceInfo.playbackType == DeviceInfo.PLAYBACK_TYPE_LOCAL) {
// Add UI changes for local playback.
} else if (deviceInfo.playbackType == DeviceInfo.PLAYBACK_TYPE_REMOTE) {
// Add UI changes for remote playback.
}
}
}
override fun onStart() {
super.onStart()
mediaController.addListener(playerListener)
}
override fun onStop() {
super.onStop()
mediaController.removeListener(playerListener)
}Java
private final Player.Listener playerListener =
new Player.Listener() {
@Override
public void onDeviceInfoChanged(DeviceInfo deviceInfo) {
if (deviceInfo.playbackType == DeviceInfo.PLAYBACK_TYPE_LOCAL) {
// Add UI changes for local playback.
} else if (deviceInfo.playbackType == DeviceInfo.PLAYBACK_TYPE_REMOTE) {
// Add UI changes for remote playback.
}
}
};
@Override
protected void onStart() {
super.onStart();
mediaController.addListener(playerListener);
}
@Override
protected void onStop() {
super.onStop();
mediaController.removeListener(playerListener);
}<br />
For more information about listening and responding to playback events, see the player events guide.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Google LLC
- Publisher
- Android (Google) (Official)
- Distributed by
- Android (Google)
- Source
- android/skills · media/media3-cast-integration · commit bf68e2d
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE.txt)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Google LLC or Android (Google).
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download media3-cast-integration.zip