上一篇聊了 CameraX,这篇继续媒体方向。视频播放是很多 App 的核心功能——短视频、直播、视频教程、在线课程。Google 在 2023 年推出了 Media3,统一了以前散乱的 ExoPlayer、MediaSession 和播放器 API。到底 Media3 是什么?跟 ExoPlayer 什么关系?怎么迁移?
Media3 是什么
Media3 是 Jetpack 的媒体库系列(androidx.media3),替代了以前的几个独立库:
ExoPlayer 2.x → Media3 ExoPlayer
MediaSessionCompat → Media3 Session
Cast framework → Media3 Cast(实验性)
Transformer → Media3 Transformer(视频编辑/转码)
核心变化是把播放器、会话、UI 统一到一个 artifact 体系下,API 设计一致,版本同步发布。
为什么不直接用 MediaPlayer
Android 原生的 MediaPlayer 有不少硬伤:
只支持系统解码器,自定义解码器不行
状态机不透明,错误信息几乎没用
不支持 DASH、HLS 自适应流
缓冲策略不可控
多实例管理和释放容易泄漏
ExoPlayer/Media3 解决了这些,还提供了:ABR(自适应码率)、DRM 支持、广告插播、预加载等高级能力。
基础用法
// 依赖
// implementation("androidx.media3:media3-exoplayer:1.4.1")
// implementation("androidx.media3:media3-ui:1.4.1")
// implementation("androidx.media3:media3-common:1.4.1")
// 布局
// <androidx.media3.ui.PlayerView android:id="@+id/playerView" />
class VideoFragment : Fragment() {
private var player: ExoPlayer? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val playerView = view.findViewById<PlayerView>(R.id.playerView)
player = ExoPlayer.Builder(requireContext()).build().also {
playerView.player = it
it.setMediaItem(MediaItem.fromUri("https://example.com/video.mp4"))
it.prepare()
it.playWhenReady = true
}
}
override fun onDestroyView() {
super.onDestroyView()
player?.release()
player = null
}
}
注意:release() 必须在生命周期结束时调用,否则解码器资源泄漏。
与 ExoPlayer 2.x 的区别
迁移工作量不大但改动面广——主要是包名替换。
迁移工具
Google 提供了官方迁移脚本:
# 在项目根目录执行
./gradlew :app:migrateToMedia3
脚本会自动把 com.google.android.exoplayer2 的 import 替换为 androidx.media3,并处理 API 差异。但有些手动改动:
// ExoPlayer 2.x
val dataSourceFactory = DefaultDataSourceFactory(context, userAgent)
val mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(uri)
player.prepare(mediaSource)
// Media3
player.setMediaItem(MediaItem.fromUri(uri))
player.prepare()
Media3 把 MediaSource 的概念简化了,大部分场景直接用 MediaItem 就行。
自适应流(HLS/DASH)
val mediaItem = MediaItem.Builder()
.setUri("https://example.com/stream.m3u8")
.setMimeType(MimeTypes.APPLICATION_M3U8)
.build()
player.setMediaItem(mediaItem)
player.prepare()
Media3 自动处理 HLS/DASH 的码率切换。默认策略是根据网络带宽选择最高可用码率。
自定义 ABR 策略:
val trackSelector = DefaultTrackSelector(context).apply {
setParameters(
buildUponParameters()
.setMaxVideoSize(1280, 720) // 限制最高分辨率
.setForceHighestSupportedBitrate(false)
)
}
player = ExoPlayer.Builder(context)
.setTrackSelector(trackSelector)
.build()
MediaSession:后台播放
如果你的 App 需要在后台播放音频(如音乐播放器),需要 MediaSession:
// 依赖
// implementation("androidx.media3:media3-session:1.4.1")
val mediaSession = MediaSession.Builder(context, player)
.build()
// 在 Service 中使用
class PlaybackService : MediaSessionService() {
private var mediaSession: MediaSession? = null
override fun onCreate() {
super.onCreate()
val player = ExoPlayer.Builder(this).build()
mediaSession = MediaSession.Builder(this, player).build()
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? {
return mediaSession
}
override fun onDestroy() {
mediaSession?.run {
player.release()
release()
}
super.onDestroy()
}
}
注册 Service 后,系统通知栏会自动显示播放控制,蓝牙耳机的媒体键也能控制播放。
预加载
短视频场景需要预加载下一段视频,减少切换延迟:
val mediaSource = ProgressiveMediaSource.Factory(cacheDataSourceFactory)
.createMediaSource(MediaItem.fromUri(nextVideoUrl))
// 用 CacheWriter 预加载
val cache = SimpleCache(
File(context.cacheDir, "media"),
LeastRecentlyUsedCacheEvictor(100 * 1024 * 1024) // 100MB
)
预加载是短视频流畅度的关键。但要注意缓存大小管理,定期清理。
常见坑
音频焦点。播放音频时要请求音频焦点,不然跟其他播放器同时出声:
player.setHandleAudioBecomingNoisy(true) // 拔耳机时暂停
val audioAttributes = AudioAttributes.Builder()
.setUsage(C.USAGE_MEDIA)
.setContentType(C.AUDIO_CONTENT_TYPE_MOVIE)
.build()
player.setAudioAttributes(audioAttributes, true) // 自动管理焦点
画面比例。PlayerView 默认 resize 模式是
RESIZE_MODE_FIT(保持比例留黑边)。全屏播放用RESIZE_MODE_FILL或RESIZE_MODE_ZOOM。Codec 不支持。某些老设备不支持 H.265/AV1。用
MediaCodecUtil.getCodecInfoForMime()检查,必要时降级到 H.264。
小结
Media3 是当前 Android 媒体播放的标准方案,新项目直接上 Media3,存量 ExoPlayer 2.x 项目用迁移工具。核心是理解 MediaItem 简化了媒体源管理,MediaSession 统一了后台播放。短视频场景别忘了预加载和缓存。
下一篇聊聊 Compose 自定义布局与 Modifier 原理。