第2章 ExoPlayer依赖集成与基本初始化

2.1 为什么选择ExoPlayer

ExoPlayer是Google官方维护的Android媒体播放器库,相比MediaPlayer,它提供了更细粒度的控制、更好的自定义能力和对DASH/HLS/SS等自适应流协议的原生支持。在Jetpack Compose项目中集成ExoPlayer是构建高质量播放界面的第一步。

2.2 Gradle依赖配置

在模块级 build.gradle.kts 或 build.gradle 中添加ExoPlayer核心依赖。推荐使用 2.19.1 或以上版本,保持与Compose的兼容性。

基础依赖(Kotlin DSL)

// app/build.gradle.kts
dependencies {
    // ExoPlayer 核心库
    implementation("com.google.android.exoplayer:exoplayer-core:2.19.1")
    // UI 组件库(用于默认控制视图,非必需但常用)
    implementation("com.google.android.exoplayer:exoplayer-ui:2.19.1")
    // 可选:支持 DASH 自适应流
    implementation("com.google.android.exoplayer:exoplayer-dash:2.19.1")
    // 可选:支持 HLS 流
    implementation("com.google.android.exoplayer:exoplayer-hls:2.19.1")
    // 可选:支持 SmoothStreaming
    implementation("com.google.android.exoplayer:exoplayer-smoothstreaming:2.19.1")
}

版本统一管理(推荐)

// 在项目根 build.gradle.kts 或 libs.versions.toml 中定义版本变量
// gradle/libs.versions.toml
[versions]
exoplayer = "2.19.1"

[libraries]
exoplayer-core = { group = "com.google.android.exoplayer", name = "exoplayer-core", version.ref = "exoplayer" }
exoplayer-ui = { group = "com.google.android.exoplayer", name = "exoplayer-ui", version.ref = "exoplayer" }

关键检查点:

  • 同步Gradle后确认依赖已下载:查看 External Libraries 中是否出现 exoplayer-core。
  • 若目标设备API低于21(Android 5.0),需额外使用 exoplayer-extension-okhttp 或 exoplayer-extension-mediasession。
  • ExoPlayer没有 androidx.media3 之前的版本,如果是Media3项目请使用 androidx.media3:media3-exoplayer。

2.3 Player对象创建

ExoPlayer实例通过 ExoPlayer.Builder 创建。Builder允许配置渲染器、加载控制、带宽估计器等。在Compose环境下,建议将Player创建与 remember 和 DisposableEffect 结合,确保资源释放。

最基本的Player创建

val context = LocalContext.current
val player = remember {
    ExoPlayer.Builder(context)
        .setRenderersFactory(DefaultRenderersFactory(context))
        .setLoadControl(DefaultLoadControl())
        .build()
}

带自定义配置的Builder

val player = remember {
    ExoPlayer.Builder(context)
        .setRenderersFactory(
            DefaultRenderersFactory(context)
                .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON)
        )
        .setLoadControl(
            DefaultLoadControl.Builder()
                .setBufferDurationsMs(
                    DefaultLoadControl.DEFAULT_MIN_BUFFER_MS,
                    DefaultLoadControl.DEFAULT_MAX_BUFFER_MS,
                    DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
                    DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
                )
                .createDefaultLoadControl()
        )
        .setBandwidthMeter(DefaultBandwidthMeter.Builder(context).build())
        .build()
}
Builder方法 默认值 说明
setRenderersFactory DefaultRenderersFactory 控制音频、视频、文本渲染器
setLoadControl DefaultLoadControl 定义缓冲策略和加载阈值
setBandwidthMeter DefaultBandwidthMeter 用于自适应流的带宽估计
setTrackSelector DefaultTrackSelector 选择音视频轨道
setAnalyticsCollector AnalyticsCollector 收集播放统计信息

2.4 生命周期绑定

ExoPlayer是一个重量级对象,必须跟随Composable生命周期创建和释放。典型模式:在 remember 中创建,在 DisposableEffect 的 onDispose 中释放。

基础生命周期绑定(Compose)

@Composable
fun VideoPlayerScreen(videoUri: Uri) {
    val context = LocalContext.current
    val player = remember {
        ExoPlayer.Builder(context).build().also {
            // 设置媒体源
            val mediaItem = MediaItem.fromUri(videoUri)
            it.setMediaItem(mediaItem)
            it.prepare()
            it.playWhenReady = true
        }
    }

    // 绑定生命周期
    DisposableEffect(AndroidView(factory = {}) ) {
        onDispose {
            player.apply {
                stop()
                release()
            }
        }
    }

    // 使用 AndroidView 嵌入 PlayerView
    AndroidView(
        factory = { ctx ->
            PlayerView(ctx).apply {
                this.player = player
            }
        },
        modifier = Modifier.fillMaxSize()
    )
}

常见陷阱:

  • 重复创建:不要在Composable的每次重组中创建新Player,必须使用remember保持实例稳定。
  • 忘记释放:未释放Player会导致内存泄漏、媒体服务连接泄漏,严重时使系统媒体服务卡死。
  • 多个屏幕/旋转:若Activity重建(如旋转),需在 savedInstanceState 中恢复播放位置,否则用户会看到从头播放。

Activity/Fragment生命周期绑定(传统视图)

// 如果项目中仍有非Compose页面,可以使用 LifecycleObserver
class PlayerLifecycleObserver(private val player: ExoPlayer) : DefaultLifecycleObserver {

    override fun onStart(owner: LifecycleOwner) {
        // 准备播放
        player.prepare()
    }

    override fun onStop(owner: LifecycleOwner) {
        player.stop()
    }

    override fun onDestroy(owner: LifecycleOwner) {
        player.release()
    }
}

2.5 基本属性设置

初始化完成后可调整播放参数:

player.apply {
    // 音量 0.0f ~ 1.0f
    volume = 0.8f
    // 播放速度
    setPlaybackSpeed(1.0f)
    // 是否循环播放
    repeatMode = Player.REPEAT_MODE_ALL
    // 音频焦点管理
    setHandleAudioFocus(true)
    // 保持屏幕常亮
    setWakeMode(C.WAKE_MODE_NETWORK)
}

2.6 初始化流程总览

ExoPlayer 初始化流程 添加 Gradle 依赖 ExoPlayer.Builder(context) setRenderersFactory / setLoadControl setMediaItem → prepare DisposableEffect 绑定/释放 ▶ playWhenReady = true 初始化注意事项 • 依赖必须与compileSdk匹配 • 用remember保持Player引用 • onDispose中release防止泄漏 • 横竖屏切换需恢复播放位置

2.7 常见错误与调试

错误现象 可能原因 解决方式
播放时黑屏但音频正常 SurfaceView未正确关联到Player 检查PlayerView是否正确设置了player实例
IllegalStateException: player is not initialized 在release后调用Player方法 使用 if (player.isPlaying) 等条件判断后操作
播放完成时崩溃 缺乏Player.Listener处理 添加 Player.Listener 监听播放结束事件
Gradle同步失败:Failed to resolve: com.google.android.exoplayer... 未添加google()仓库或版本号错误 确保 settings.gradle.kts 包含 google(),版本来自maven central

2.8 性能与稳定性优化

  • 预加载与缓冲策略:根据网络类型调整 minBufferMs 和 maxBufferMs,移动网络下建议降低缓冲阈值。
  • 释放时机:使用 DisposableEffect 确保Player在Composable离开组合后被正确释放;若使用 AndroidView 的 onRelease 回调也可。
  • 避免重复创建:当MediaItem变化时,调用 player.setMediaItem(newMediaItem) 并 player.prepare(),不要recreate整个Player。
  • 音频焦点:开启 setHandleAudioFocus(true),避免与其他App冲突;自定义 AudioFocusManager 可优化暂停/继续行为。
  • Surface生命周期:如果直接使用 SurfaceView,需在Surface可用时再调用 player.setVideoSurface(surface),推荐 PlayerView 自动处理。

2.9 小结

本章完成了ExoPlayer的Gradle依赖配置、Player对象创建、生命周期绑定以及基本属性设置。这些是后续所有播放界面功能的基础,确保每个环节正确后,才能顺利进行自定义UI、手势控制、后台播放等高级特性开发。