从 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 依赖
  • 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()
Android 多媒体:发光屏幕内有圆形进度弧线与波形条纹、两侧对称音频波形的蓝调插画
进度与波形构成播放核心。

监听 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 的通知栏控制,锁屏也能切歌暂停。