systematic-debugging
OBobra/superpowers299k近 7 天新增 2,675 个 star
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.
>= 1.9.0. Cast isn't available in lower versions.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.OptionsProvider : Interface providing configuration options to initialize GMS CastContext.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.1")
Ensure required Media3 dependencies are present:
androidx.media3:media3-exoplayerandroidx.media3:media3-sessionandroidx.media3:media3-ui-composeIf 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:
libs.play.services.cast.framework) is absent.If Migrating from Legacy Cast SDK:
To complete this step, you MUST ensure the following:
<application> tag, declare the Cast options provider.DefaultCastOptionsProvider by default. See the "OptionsProvider" section in Getting started with
CastPlayer.OptionsProvider only if explicitly requested. See Customize CastOptions.INTERNET permission is present. Don't add any unnecessary permissions.Before integrating Media3 Cast, an existing app follows one of two setups:
ExoPlayer only to support local playback.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:
MediaSessionService (or MediaLibraryService) onCreate() method, initialize ExoPlayer and CastPlayer.CastPlayer by default unless RemoteCastPlayer is explicitly requested. See the "Build a CastPlayer" section in Getting started with
CastPlayer.CastPlayer, pass the instance directly to MediaSession.Builder.RemoteCastPlayerUse 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)
}
}
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 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.
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
}
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 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:
AndroidView to wrap the legacy PlayerView.Player composable and MediaRouteButton composable as per Getting started with CastPlayer.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:
libs.play.services.cast.framework) and MediaRouter (libs.androidx.mediarouter) dependencies.OptionsProvider classes and manifest entries if DefaultCastOptionsProvider is adopted.MediaTransferReceiver manifest declarations if present.RemoteMediaClient interfaces.PlayerView if the migration to Compose is complete.OBobra/superpowers299k近 7 天新增 2,675 个 star
OBobra/superpowers299k近 7 天新增 2,675 个 star
OBobra/superpowers299k近 7 天新增 2,675 个 star
MAmattpocock/skills290k近 7 天新增 10,235 个 star
MAmattpocock/skills290k近 7 天新增 10,235 个 star
MAmattpocock/skills290k近 7 天新增 10,235 个 star