Loading...
Loading...
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.
npx skill4agent add android/skills media3-cast-integration>= 1.9.0CastPlayerPlayerRemoteCastPlayerPlayerOptionsProviderCastContextmedia3-castimplementation("androidx.media3:media3-cast:1.10.1")androidx.media3:media3-exoplayerandroidx.media3:media3-sessionandroidx.media3:media3-ui-composemedia3-uilibs.play.services.cast.framework<application>DefaultCastOptionsProviderOptionsProviderINTERNETExoPlayerExoPlayerPlayerRemoteMediaClientMediaSessionForwardingPlayerMediaSessionServiceMediaLibraryServiceonCreate()ExoPlayerCastPlayerCastPlayerRemoteCastPlayerCastPlayerMediaSession.BuilderRemoteCastPlayerRemoteCastPlayerMediaSessionlocalPlayerSessionAvailabilityListenerRemoteCastPlayeroverride 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)
}MediaRouteButtonandroidx.media3.castAndroidViewMediaRouteButtonPlayerSurfacePlayerDeviceInfo@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
}AppCompatActivityFragmentActivityTheme.AppCompatAppCompatActionBarMediaRouteButtonCastButtonFactoryMediaRouteButtonFactoryPlayerView.playeronDeviceInfoChangedprivate 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
}
}AndroidViewPlayerViewPlayerMediaRouteButton[!WARNING] Warning: Don't perform cleanup directly. Remove legacy files and dependencies only when explicitly requested by the user.
libs.play.services.cast.frameworklibs.androidx.mediarouterOptionsProviderDefaultCastOptionsProviderMediaTransferReceiverRemoteMediaClientPlayerView