从 MediaPlayer 到 Media3
MediaPlayer 能播单条音视频,但流媒体、自适应、无缝切换这些现代需求它都不擅长。Media3 是 Jetpack 的媒体库,核心是 ExoPlayer,把播放器的复杂度收拾得明明白白。
播放器不是放个文件那么简单,是一整套调度系统。
Media3 分 Player、MediaSource、Renderer 三层:Player 管状态,MediaSource 管数据源,Renderer 管解码渲染。换格式、换协议、加缓存,都是换 MediaSource 的事。
接入 ExoPlayer
先加依赖,再创建 Player 实例绑定到播放视图。ExoPlayer 推荐用 Builder 配置,播放视图 PlayerView 直接接管画面和手势。
val player = ExoPlayer.Builder(context).build()
playerView.player = player
val mediaItem = MediaItem.fromUri("https://example.com/audio.mp3")
player.setMediaItem(mediaItem)
player.prepare()
player.playWhenReady = true
- media3-exoplayer播放核心。
- media3-uiPlayerView 等 UI。
- media3-session媒体会话(通知栏)。
Player 生命周期要跟 Activity 走:onStart 恢复、onStop 暂停、onDestroy 释放。忘了释放,媒体资源会一直占着,耗电又占内存。
如果应用需要同时播放多个音频,给每个播放器一个清晰的职责边界,别让两个实例抢同一个音频输出通道;用 Player.Listener 统一管理状态,排查问题时也能按实例分别看日志。
媒体源与播放控制
MediaItem 是单条内容,MediaItem 列表可以组成播放队列,自动切歌。控制用 Player 的接口:play、pause、seekTo、next、previous。
val items = listOf(
MediaItem.fromUri(url1),
MediaItem.fromUri(url2)
)
player.setMediaItems(items, 0, 0L)
player.prepare()
player.play()
监听 Player.Listener 拿状态变化:STATE_READY、STATE_BUFFERING、isPlaying。缓冲转圈、播放结束、出错重试都从这里接,UI 跟着状态走就不会乱。
播放器的错误处理要分级:临时性错误(缓冲超时、网络抖动)自动重试一两次,重试仍失败再提示;永久性错误(地址 404、格式不支持)直接告诉用户内容不可用,别让播放器一直转圈。重试要带退避策略,别在弱网下高频请求把服务器打崩。ExoPlayer 支持自定义 LoadControl,可以按业务调整缓冲水位:直播想要低延迟就把缓冲调小,点播想抗抖动就把缓冲调大,默认配置适合大多数场景,但直播点播混合的应用建议分开配置,把体验档位做细。
另外,播放器要跟随屏幕与系统状态:页面退到后台暂停播放,回到前台自动恢复;耳机拔出、蓝牙断开这类系统事件也要监听,及时暂停避免误播;旋转屏幕时保持进度不丢,回来接着播。
音频焦点与后台播放
音乐 App 要响应电话、其他应用的声音,必须申请音频焦点。拿到焦点才播,失去焦点就暂停或降音量。后台播放要起前台服务加通知栏媒体控制。
val result = audioManager.requestAudioFocus(
focusListener,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN
)
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
player.play()
}
焦点丢失分暂时和永久:暂时丢焦点暂停,回来自动恢复;永久丢焦点彻底停。配合 MediaSession 的通知栏控制,锁屏也能切歌暂停。
自适应流与 HLS
ExoPlayer 原生支持 HLS、DASH、平滑流。给一个 HLS 地址,它自动按网速切换清晰度,弱网降码率、强网升码率,用户几乎无感。
val mediaItem = MediaItem.Builder()
.setUri("https://example.com/live/stream.m3u8")
.setMimeType(MimeTypes.APPLICATION_M3U8)
.build()
player.setMediaItem(mediaItem)
player.prepare()
直播场景开启低延迟模式,播放器缓冲区调小,跟手更快。录播再配合缓存策略,Wi-Fi 预下载、移动网按需拉,流量和体验两头顾。
实战:音乐播放器核心
把前面的能力串成一个最小播放器:队列播放、进度拖动、音频焦点、通知栏控制。核心就三块:Player 管理状态,MediaSession 暴露控制,Service 保活后台。
player.addListener(object : Player.Listener {
override fun onIsPlayingChanged(isPlaying: Boolean) {
if (isPlaying) {
binding.playPause.setIconResource(R.drawable.ic_pause)
} else {
binding.playPause.setIconResource(R.drawable.ic_play)
}
}
})
进度条用 Handler 或协程轮询 player.currentPosition,别在 onProgress 里频繁 setText。出错时区分网络错误和资源不存在,给不同的提示和重试策略。
小结
Media3 的 ExoPlayer 是现代 Android 播放器的默认答案:MediaItem 组织内容、Player 控制状态、MediaSource 对接 HLS 等协议。音频焦点和前台服务是播放器的礼仪,缺了会被系统打断。从最小播放器开始,把队列、焦点、通知栏依次接上,一个能上线的播放器就成型了。