添加依赖项
Media3 库包含一个基于 Jetpack Compose 的界面模块。如需使用它,请添加以下依赖项:
Kotlin
implementation("androidx.media3:media3-ui-compose:1.6.0")
Groovy
implementation "androidx.media3:media3-ui-compose:1.6.0"
我们强烈建议您以 Compose 为先的方式开发应用,或从使用 View 迁移。
完全使用 Compose 的演示版应用
虽然 media3-ui-compose
库不包含开箱即用的可组合项(例如按钮、指示器、图片或对话框),但您可以找到一个完全使用 Compose 编写的演示版应用,该应用避免了任何互操作性解决方案,例如将 PlayerView
封装在 AndroidView
中。该演示版应用使用 media3-ui-compose
模块中的界面状态持有器类,并使用 Compose Material3 库。
界面状态容器
如需更好地了解如何灵活使用界面状态容器(而不是可组合项),请参阅 Compose 如何管理状态。
按钮状态容器
对于某些界面状态,我们假定它们很可能会被按钮类可组合项使用。
状态 | remember*State | 类型 |
---|---|---|
PlayPauseButtonState |
rememberPlayPauseButtonState |
2 个切换开关 |
PreviousButtonState |
rememberPreviousButtonState |
常量 |
NextButtonState |
rememberNextButtonState |
常量 |
RepeatButtonState |
rememberRepeatButtonState |
3-Toggle |
ShuffleButtonState |
rememberShuffleButtonState |
2 个切换开关 |
PlaybackSpeedState |
rememberPlaybackSpeedState |
菜单或 N 个切换开关 |
PlayPauseButtonState
的用法示例:
@Composable
fun PlayPauseButton(player: Player, modifier: Modifier = Modifier) {
val state = rememberPlayPauseButtonState(player)
IconButton(onClick = state::onClick, modifier = modifier, enabled = state.isEnabled) {
Icon(
imageVector = if (state.showPlay) Icons.Default.PlayArrow else Icons.Default.Pause,
contentDescription =
if (state.showPlay) stringResource(R.string.playpause_button_play)
else stringResource(R.string.playpause_button_pause),
)
}
}
请注意,state
没有任何主题信息,例如用于播放或暂停的图标。它的唯一职责是将 Player
转换为界面状态。
然后,您可以在偏好设置的布局中混合搭配按钮:
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
) {
PreviousButton(player)
PlayPauseButton(player)
NextButton(player)
}
可视化输出状态容器
PresentationState
用于存储有关何时可以显示 PlayerSurface
中的视频输出或何时应由占位符界面元素遮盖的信息。
val presentationState = rememberPresentationState(player)
val scaledModifier = Modifier.resize(ContentScale.Fit, presentationState.videoSizeDp)
Box(modifier) {
// Always leave PlayerSurface to be part of the Compose tree because it will be initialised in
// the process. If this composable is guarded by some condition, it might never become visible
// because the Player won't emit the relevant event, e.g. the first frame being ready.
PlayerSurface(
player = player,
surfaceType = SURFACE_TYPE_SURFACE_VIEW,
modifier = scaledModifier,
)
if (presentationState.coverSurface) {
// Cover the surface that is being prepared with a shutter
Box(Modifier.background(Color.Black))
}
在这里,我们既可以使用 presentationState.videoSizeDp
将 Surface 缩放到所需的宽高比(如需了解更多类型,请参阅 ContentScale 文档),也可以使用 presentationState.coverSurface
了解何时不适合显示 Surface。在这种情况下,您可以在 Surface 上方放置一个不透明的快门,该快门会在 Surface 准备就绪时消失。
流程在哪里?
许多 Android 开发者都熟悉使用 Kotlin Flow
对象收集不断变化的界面数据。例如,您可能希望找到可以以生命周期感知方式 collect
的 Player.isPlaying
流程。或者使用 Player.eventsFlow
之类的函数为您提供 Flow<Player.Events>
,以便您按照自己的方式进行 filter
。
不过,使用流程来处理 Player
界面状态存在一些缺点。其中一个主要问题是数据传输的异步性质。我们希望确保 Player.Event
与界面端的使用之间尽可能减少延迟时间,避免显示与 Player
不同步的界面元素。
其他要点包括:
- 包含所有
Player.Events
的流程不会遵循单一责任原则,每个使用方都必须滤除相关事件。 - 若要为每个
Player.Event
创建一个流程,您需要为每个界面元素将它们组合(使用combine
)。Player.Event 与界面元素更改之间存在多对多映射。必须使用combine
可能会导致界面进入可能非法的状态。
创建自定义界面状态
如果现有界面状态无法满足您的需求,您可以添加自定义界面状态。查看现有状态的源代码以复制模式。典型的界面状态容器类会执行以下操作:
- 接受
Player
。 - 使用协程订阅
Player
。如需了解详情,请参阅Player.listen
。 - 通过更新其内部状态来响应特定
Player.Events
。 - 接受将转换为适当
Player
更新的业务逻辑命令。 - 可在界面树的多个位置创建,并且始终会保持玩家状态的一致视图。
- 公开可供可组合项使用以动态响应更改的 Compose
State
字段。 - 附带
remember*State
函数,用于在组合之间记住实例。
幕后会发生些什么:
class SomeButtonState(private val player: Player) {
var isEnabled by mutableStateOf(player.isCommandAvailable(Player.COMMAND_ACTION_A))
private set
var someField by mutableStateOf(someFieldDefault)
private set
fun onClick() {
player.actionA()
}
suspend fun observe() =
player.listen { events ->
if (
events.containsAny(
Player.EVENT_B_CHANGED,
Player.EVENT_C_CHANGED,
Player.EVENT_AVAILABLE_COMMANDS_CHANGED,
)
) {
someField = this.someField
isEnabled = this.isCommandAvailable(Player.COMMAND_ACTION_A)
}
}
}
如需对您自己的 Player.Events
做出响应,您可以使用 Player.listen
捕获它们,这是一个 suspend fun
,可让您进入协程世界并无限期监听 Player.Events
。各种界面状态的 Media3 实现有助于最终开发者不必担心了解 Player.Events
。