Compose - 使用 Media3(ExoPlayer)

View版及更多功能使用:详见

一、概念

1.1 实现方式选择

media3-ui-composemedia3-ui-compose-material3
界面组件基础组件。开箱即用,含预设样式的按钮或控件。
状态管理提供 remember***State 状态持有者来管理逻辑。在内部管理状态,但仍可根据需要访问状态容器。
使用场景使用自定义风格构建播放器界面。使用 Material3 预设快速构建。

1.2 显示组件

ContentFrame

@Composable
fun ContentFrame(
  player: Player?,
  modifier: Modifier = Modifier,
  surfaceType: @SurfaceType Int = SURFACE_TYPE_SURFACE_VIEW,
  contentScale: ContentScale = ContentScale.Fit,
  keepContentOnReset: Boolean = false,
  shutter: @Composable () -> Unit = { Box(Modifier.fillMaxSize().background(Color.Black)) },
)

参数 contentScale:视频缩放。

参数 keepContentOnReset:为 true 播放器重置时将保持最后一帧画面显示,为 false 则会清空渲染表面。

参数 shutter:用于在需要覆盖视频渲染表面时显示。默认情况下,它显示为黑色背景。

1.3 控制组件

组件中可以直接拿到对应状态里的属性/方法(推荐通过this调用方便阅读)。

通用组件PlayPauseButton播放和暂停。
SeekBackButton根据预设值,向前调整播放进度。
SeekForwardButton根据预设值,向后调整播放进度。
NextButton跳转到下一个媒体项。
PreviousButton跳转到上一个媒体项。
RepeatButton切换重复模式。
ShuffleButton切换随机模式。
MuteButton切换静音模式。
TimeText显示时长相关文本。
Material3 PositionAndDurationText当前位置和总时长的文本。
PositionText当前位置的文本。
DurationText总时长的文本。
RemainingDurationText剩余时长的文本。

自定义方式:

PlayPauseButton(player) {
    Icon(
        modifier = Modifier
            .size(20.dp)
            .clickable(
                enabled = this.isEnabled,
                onClick = { this.onClick() }
            ),
        imageVector = if (this.showPlay) Icons.Default.PlayArrow else Icons.Default.PauseCircle,
        contentDescription = if (this.showPlay) "Play" else "Pause"
    )
}

Material3 方式:

Row {
  SeekBackButton(player)
  PlayPauseButton(player)
  SeekForwardButton(player)
}

1.4 状态(自定义控制组件)

如果上面没有需要的组件,可以自行通过状态来构建自定义组件。

状态获取方式
播放暂停rememberPlayPauseButtonState
上一项rememberPreviousButtonState
下一项rememberNextButtonState
重复模式rememberRepeatButtonState
随机模式rememberShuffleButtonState
播放速度rememberPlaybackSpeedState
val state = rememberPlayPauseButtonState(player)
Icon(
    modifier = Modifier
        .size(20.dp)
        .clickable(
            enabled = state.isEnabled,
            onClick = { state.onClick() }
        ),
    imageVector = if (state.showPlay) Icons.Default.PlayArrow else Icons.Default.PauseCircle,
    contentDescription = if (state.showPlay) "Play" else "Pause"
)

二、添加依赖

最新版本

[versions]
media3 = "1.9.0"

[libraries]
media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
media3-datasource-okhttp = { module = "androidx.media3:media3-datasource-okhttp", version.ref = "media3" }
#二选一
media3-ui-compose = { module = "androidx.media3:media3-ui-compose", version.ref = "media3" }
media3-ui-compose-material3 = { moudle = "androidx.media3:media3-ui-compose-material3", version.ref = "media3" }

三、实现方式

3.1 ViewModel

在 ViewModel 中提供 ExoPlayer,将业务和UI分离。

class PlayerVM : ViewModel() {
    private val playlist = mutableListOf<MediaItem>()
    private val playerListener by lazy {
        object : Player.Listener {

        }
    }
    val player by lazy {
        ExoPlayer.Builder(APP.context)
            .setSeekBackIncrementMs(10000)
            .setSeekForwardIncrementMs(10000)
            .build()
            .apply {
                addListener(playerListener)
                setMediaItem(MediaItem.fromUri("https://www.w3schools.com/html/movie.mp4"))
                prepare()
                playWhenReady = true
            }

    }
    override fun onCleared() {
        super.onCleared()
        player.release()
    }
}

3.2 UI

@Composable
private fun Demo(
    viewModel: PlayerVM = viewModel()
) {
    ContentFrame(viewModel.player) 
}
[versions] androidGradlePlugin = "8.5.0" kotlin = "1.9.22" media3 = "1.2.1" # 检查最新版:https://developer.android.com/jetpack/androidx/releases/media3 [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } [libraries] # AndroidX androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version = "1.13.1" } androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version = "2.8.0" } androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version = "2.8.0" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version = "1.9.0" } # Compose androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version = "2024.05.00" } androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" } androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" } androidx-compose-animation = { group = "androidx.compose.animation", name = "animation" } androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version = "2.7.7" } # Media androidx-media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" } androidx-media3-ui = { group = "androidx.media3", name = "media3-ui", version.ref = "media3" } androidx-media3-ui-compose = { group = "androidx.media3", name = "media3-ui-compose", version.ref = "media3" } androidx-media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3" } # Coroutines kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" } kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" } # Desugaring android-desugar-jdk-libs = { group = "com.android.tools", name = "desugar_jdk_libs", version = "2.0.4" } # Testing junit = { group = "junit", name = "junit", version = "4.13.2" } androidx-junit = { group = "androidx.test.ext", name = "junit", version = "1.1.5" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version = "3.5.1" } androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
11-26
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值