media3-cast-integration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePrerequisites
前提条件
- Jetpack Media3 version must be . Cast isn't available in lower versions.
>= 1.9.0
- Jetpack Media3版本必须。低于此版本不支持Cast功能。
>= 1.9.0
Glossary
术语表
- : Media3
CastPlayerthat controls playback on both local and remote Cast devices.Player - : Media3
RemoteCastPlayerthat communicates with a Cast receiver, only used for remote playback.Player - Google Cast SDK: Legacy casting SDK in maintenance mode, superseded by Jetpack Media3.
- : Interface providing configuration options to initialize GMS
OptionsProvider.CastContext
- :Media3的
CastPlayer组件,可控制本地和远程Cast设备上的播放。Player - :Media3的
RemoteCastPlayer组件,仅用于远程播放,与Cast接收器通信。Player - Google Cast SDK:已进入维护模式的旧版投屏SDK,已被Jetpack Media3取代。
- :为初始化GMS
OptionsProvider提供配置选项的接口。CastContext
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.
- 旧版Google Cast SDK已进入维护模式。
- 对于新的Cast集成:
- 必须使用Jetpack Media3 Cast。
- 除非有明确要求,否则不得使用旧版Cast SDK。
Step 1: Set up dependencies
步骤1:配置依赖项
To complete this step, you MUST ensure the following:
-
In the app-level build file, declare thedependency version 1.9.0 or higher.
media3-castimplementation("androidx.media3:media3-cast:1.10.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 () is absent.
libs.play.services.cast.framework
- 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.
完成此步骤必须确保以下内容:
-
在应用级构建文件中,声明版本1.9.0或更高的依赖。
media3-castimplementation("androidx.media3:media3-cast:1.10.1") -
确保存在必要的Media3依赖:
androidx.media3:media3-exoplayerandroidx.media3:media3-sessionandroidx.media3:media3-ui-compose
-
如果应用使用旧版Views,需添加。
media3-ui -
确保所有Media3依赖使用相同版本。
-
以CastPlayer入门指南中“添加构建依赖项”部分的配置为标准。
-
对于无现有Cast集成的应用:
- 确认不存在旧版Cast SDK()。
libs.play.services.cast.framework
- 确认不存在旧版Cast SDK(
-
从旧版Cast SDK迁移:
- 先添加Media3 Cast依赖。
- 此阶段保留现有旧版依赖,避免编译错误。
Step 2: Update the manifest
步骤2:更新清单文件
To complete this step, you MUST ensure the following:
- Inside the manifest's tag, declare the Cast options provider.
<application> - Use by default. See the "OptionsProvider" section in Getting started with CastPlayer.
DefaultCastOptionsProvider - Declare a custom only if explicitly requested. See Customize CastOptions.
OptionsProvider - Ensure permission is present. Don't add any unnecessary permissions.
INTERNET - If Migrating from Legacy Cast SDK:
- Don't delete existing custom options provider files or manifest entries.
完成此步骤必须确保以下内容:
- 在清单的标签内,声明Cast选项提供器。
<application> - 默认使用。详情见CastPlayer入门指南中的“OptionsProvider”部分。
DefaultCastOptionsProvider - 仅在有明确要求时才声明自定义。详情见自定义CastOptions。
OptionsProvider - 确保存在权限,不要添加不必要的权限。
INTERNET - 从旧版Cast SDK迁移:
- 不要删除现有自定义选项提供器文件或清单条目。
Step 3: Implement the player and service
步骤3:实现播放器与服务
Architecture baseline
架构基础
Before integrating Media3 Cast, an existing app follows one of two setups:
- Local-only playback: Uses Media3 only to support local playback.
ExoPlayer - Legacy Cast setup: Uses for local playback, alongside a
ExoPlayerwrapper over the legacyPlayerfor remote playback. The UI interfaces with aRemoteMediaClientinteracting with aMediaSession, which finally routes controls to either local or remote playback.ForwardingPlayer
To complete this step, you MUST ensure the following:
- Inside the application's (or
MediaSessionService)MediaLibraryServicemethod, initializeonCreate()andExoPlayer.CastPlayer - Use by default unless
CastPlayeris explicitly requested. See the "Build a CastPlayer" section in Getting started with CastPlayer.RemoteCastPlayer - For , pass the instance directly to
CastPlayer.MediaSession.Builder - Replace all legacy forwarding player wrappers.
- Don't delete legacy class files yet to prevent compilation errors during migration.
集成Media3 Cast之前,现有应用通常采用以下两种架构之一:
- 仅本地播放:仅使用Media3 支持本地播放。
ExoPlayer - 旧版Cast架构:使用进行本地播放,同时使用基于旧版
ExoPlayer的RemoteMediaClient包装器处理远程播放。UI通过Player与MediaSession交互,最终将控制指令路由到本地或远程播放。ForwardingPlayer
完成此步骤必须确保以下内容:
- 在应用的(或
MediaSessionService)的MediaLibraryService方法中,初始化onCreate()和ExoPlayer。CastPlayer - 默认使用,除非明确要求使用
CastPlayer。详情见CastPlayer入门指南中的“构建CastPlayer”部分。RemoteCastPlayer - 对于,直接将实例传递给
CastPlayer。MediaSession.Builder - 替换所有旧版转发播放器包装器。
- 暂时不要删除旧版类文件,避免迁移期间出现编译错误。
Advanced: RemoteCastPlayer
RemoteCastPlayer进阶:RemoteCastPlayer
RemoteCastPlayer-
Useonly if explicitly requested by user.
RemoteCastPlayer -
Initializewith
MediaSessionand set alocalPlayeronSessionAvailabilityListenerto transfer playback state on Cast session availability changes:RemoteCastPlayerclass PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null private lateinit var localPlayer: ExoPlayer private lateinit var remotePlayer: RemoteCastPlayeroverride 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) }}
-
仅在用户明确要求时使用。
RemoteCastPlayer -
使用初始化
localPlayer,并在MediaSession上设置RemoteCastPlayer,以便在Cast会话可用性变化时转移播放状态:SessionAvailabilityListenerclass PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null private lateinit var localPlayer: ExoPlayer private lateinit var remotePlayer: RemoteCastPlayeroverride 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
步骤4:配置UI
Compose-based UI
基于Compose的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 thecomposable from
MediaRouteButtonpackage.androidx.media3.cast -
Don't usein the Compose UI hierarchy.
AndroidView -
Placein an area next to playback controls. Don't hide it behind system UI.
MediaRouteButton -
Don't usefor custom player UI. Use the Material3
PlayerSurfacecomposable.Player -
Force recomposition on playback location shifts to ensure UI sync. Use key constraints onchanges:
DeviceInfo@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 }
完成此步骤必须确保以下内容:
-
关于Compose集成指南,参见CastPlayer入门指南中的“向播放器添加MediaRouteButton组合项”部分。
-
使用包中的
androidx.media3.cast组合项。MediaRouteButton -
不要在Compose UI层次结构中使用。
AndroidView -
将放置在播放控件附近,不要隐藏在系统UI后方。
MediaRouteButton -
不要使用自定义播放器UI,使用Material3的
PlayerSurface组合项。Player -
播放位置切换时强制重组,确保UI同步。在变化时使用键约束:
DeviceInfo@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? { // 将MediaController连接到MediaSession并释放的逻辑 } @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
基于View的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 extendor
AppCompatActivityand use aFragmentActivitydescendant.Theme.AppCompat -
Ensure thetheme has a visible
AppCompatif addingActionBarto the options menu.MediaRouteButton -
Replace all instances and imports ofwith
CastButtonFactory.MediaRouteButtonFactory -
Rebindreferences upon
PlayerView.playerevents to prevent black screens or UI freezes:onDeviceInfoChangedprivate 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 to wrap the legacy
AndroidView.PlayerView - Implement Material3 composable and
Playercomposable as per Getting started with CastPlayer.MediaRouteButton - Remove legacy XML layout declarations, menu files, and View component references.
- Don't use
完成此步骤必须确保以下内容:
-
基于View的UI配置,参见CastPlayer入门指南中的“添加UI元素”部分。
-
投屏相关Activity必须继承或
AppCompatActivity,并使用FragmentActivity的派生主题。Theme.AppCompat -
如果要将添加到选项菜单,确保
MediaRouteButton主题有可见的AppCompat。ActionBar -
将所有的实例和导入替换为
CastButtonFactory。MediaRouteButtonFactory -
在事件时重新绑定
onDeviceInfoChanged引用,防止黑屏或UI冻结:PlayerView.playerprivate val playerListener: Player.Listener = object : Player.Listener { override fun onDeviceInfoChanged(deviceInfo: DeviceInfo) { // 重置为null可绕过PlayerView.setPlayer()的实例相等性检查 // (this.player == player),强制将视频表面重新绑定到控制器。 playerView.player = null playerView.player = controller } } -
迁移到Compose:
- 不要使用包装旧版
AndroidView。PlayerView - 按照CastPlayer入门指南实现Material3的组合项和
Player组合项。MediaRouteButton - 删除旧版XML布局声明、菜单文件和View组件引用。
- 不要使用
Step 5: Clean up legacy Cast SDK code
步骤5:清理旧版Cast SDK代码
[!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 () and MediaRouter (
libs.play.services.cast.framework) dependencies.libs.androidx.mediarouter - Delete custom classes and manifest entries if
OptionsProvideris adopted.DefaultCastOptionsProvider - Remove legacy manifest declarations if present.
MediaTransferReceiver - Remove all references to legacy Cast SDK components such as legacy helper wrappers, forwarding players, and interfaces.
RemoteMediaClient - Delete legacy View XML layouts, menu files, and references to if the migration to Compose is complete.
PlayerView
[!WARNING] **警告:**不要直接执行清理操作。仅在用户明确要求时,再删除旧版文件和依赖。
完成此步骤必须确保以下内容:
- 删除旧版GMS Cast SDK()和MediaRouter(
libs.play.services.cast.framework)依赖。libs.androidx.mediarouter - 如果采用,删除自定义
DefaultCastOptionsProvider类和清单条目。OptionsProvider - 如果存在旧版清单声明,将其删除。
MediaTransferReceiver - 删除所有对旧版Cast SDK组件的引用,比如旧版助手包装器、转发播放器和接口。
RemoteMediaClient - 如果已完成向Compose的迁移,删除旧版View XML布局、菜单文件和引用。
PlayerView