Jetpack Media3 是媒体库的新家,可让 Android 应用显示丰富的视听体验。媒体播放器是允许播放视频和音频文件的应用级组件,Media3 提供了一个 Player 接口,用于定义基本功能,如播放,暂停,跳转和显示曲目信息等功能,而 ExoPlayer 是 Media3 中此接口的默认实现。
引入依赖
kotlin
implementation("androidx.media3:media3-exoplayer:1.2.1")
implementation("androidx.media3:media3-exoplayer-dash:1.2.1")
implementation("androidx.media3:media3-ui:1.2.1")
需要注意的是,使用此库需要将 compileSdk 升到34及其以上,不然会编译报错。

创建播放器
使用 ExoPlayer 打造一个视频播放器,先创建一个 ExoPlayer 实例,然后再将播放器附加到视图上,然后填充播放列表并准备播放即可。
xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ExoPlayerActivity">
<androidx.media3.ui.PlayerView
android:id="@+id/playView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</layout>
kotlin
val player = ExoPlayer.Builder(context).build()
binding.playView.player = player
val mediaItem = MediaItem.fromUri(VIDEO_URL)
player.run {
setMediaItem(mediaItem)
prepare()
play()
}
如果有多个媒体内容需要播放,想一个接一个播放,ExoPlayer 也支持播放列表。
kotlin
val player = ExoPlayer.Builder(context).build()
binding.playView.player = player
val mediaItem1 = MediaItem.fromUri(VIDEO_URL1)
val mediaItem2 = MediaItem.fromUri(VIDEO_URL2)
player.apply {
addMediaItem(mediaItem1)
addMediaItem(mediaItem2)
prepare()
play()
}
这样,一个简易的视频播放器就做好了,这是 ExoPlayer 的默认实现,如下图所示:

如果想要播放已经储存好的视频文件,直接把链接换成文件路径即可。
kotlin
player = ExoPlayer.Builder(context).build()
binding.playView.player = player
val mediaItem = MediaItem.fromUri(Uri.parse("${getExternalFilesDir(null)}/test.mp4"))
player.run {
setMediaItem(mediaItem)
prepare()
play()
}
记得在不需要播放器的时候将其释放,以便释放有限的资源。
kotlin
override fun onDestroy() {
super.onDestroy()
player.release()
}
控制播放器
播放器就绪后,就可以调用 ExoPlayer 的一些方法来控制播放:
- play:开始播放
- pause:暂停播放
- hasPreviousMediaItem:上一个媒体内容是否存在
- seekToPreviousMediaItem:播放上一个媒体内容
- hasNextMediaItem:下一个媒体内容是否存在
- seekToNextMediaItem:播放下一个媒体内容
- seekTo:跳转到媒体文件的指定位置进行播放,该方法接受一个时间戳参数,表示跳转的时间点,单位为毫秒,用于指定跳转的位置。
- repeatMode:设置循环模式,有三个值:Player.REPEAT_MODE_OFF(默认模式,播放器不会自动重复播放任何内容),Player.REPEAT_MODE_ONE(当媒体播放结束时,播放器会自动重复播放当前媒体),Player.REPEAT_MODE_ALL(当媒体播放结束时,播放器会自动从队列中选择下一个媒体进行播放,并循环播放整个队列)
- shuffleModeEnabled:控制播放器是否启用随机播放模式
- setPlaybackParameters:调整播放速度和音调
在随机播放模式下,播放器将按预计算的随机顺序播放播放列表,所有项都会播放一次,此时 seekToNextMediaItem 也是根据重排顺序播放下一项,关闭随机播放模式后,系统会从当前项在播放列表中的原始位置继续播放。
setPlaybackParameters 接受一个 PlaybackParameters 对象作为参数,该对象包含了两个属性:speed 和 pitch。speed 表示播放速度,1.0 表示正常播放速度,小于 1.0 表示慢放,大于 1.0 表示快放。pitch 表示音调,1.0 表示原始音调,小于 1.0 表示降低音调,大于 1.0 表示升高音调。
kotlin
val playbackParameters = PlaybackParameters(speed, pitch)
player.playbackParameters = playbackParameters
播放器事件
系统会将事件,例如状态更改或播放错误等,报告给已注册的 Player.Listener 实例,我们可以注册监听器来接收此类事件。
kotlin
player.addListener(object : Player.Listener {
//播放器状态变化
override fun onPlaybackStateChanged(playbackState: Int) {
super.onPlaybackStateChanged(playbackState)
when (playbackState) {
Player.STATE_IDLE -> {
//空闲状态:表示当前没有任何媒体数据可供播放
}
Player.STATE_BUFFERING -> {
//缓冲状态:表示当前正在获取媒体数据进行缓冲
}
Player.STATE_READY -> {
//就绪状态:表示已经准备好播放媒体的数据
}
Player.STATE_ENDED -> {
//结束状态:表示已经播放完所有媒体数据
}
}
}
//监听是否正在播放中状态,也可以通过 player.isPlaying 获取
override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying)
}
//播放错误
override fun onPlayerError(error: PlaybackException) {
super.onPlayerError(error)
val cause = error.cause
if (cause is HttpDataSource.HttpDataSourceException) {
// An HTTP error occurred
}
}
//检测播放何时转换为其他媒体项
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
super.onMediaItemTransition(mediaItem, reason)
}
})
媒体内容
修改播放列表
kotlin
//在播放列表的位置0添加媒体项目
player.addMediaItem(0, MediaItem.fromUri(VIDEO_URL))
//删除播放列表中位置为0媒体项目
player.removeMediaItem(0)
//将位置为0的媒体项目移动到位置2
player.moveMediaItem(0, 2)
//替换位置为0的媒体项目
player.replaceMediaItem(0, MediaItem.fromUri(VIDEO_URL))
//清空媒体列表
player.clearMediaItems()
//替换整个播放列表
val mediaItems = arrayListOf<MediaItem>()
mediaItems.add(MediaItem.fromUri(VIDEO_URL1))
mediaItems.add(MediaItem.fromUri(VIDEO_URL2))
mediaItems.add(MediaItem.fromUri(VIDEO_URL3))
player.replaceMediaItems(0, player.mediaItemCount, mediaItems)
查询 MediaItem
kotlin
val firstMediaItem = player.getMediaItemAt(0)
val currentMediaItem = player.currentMediaItem
val nextMediaItemIndex = player.nextMediaItemIndex
val previousMediaItemIndex = player.previousMediaItemIndex
播放列表中的内容之间可以无缝切换,不要求它们的格式相同,也可以是不同的类型,比如一个播放列表中可以同时包含视频和音频流。
标识播放列表项
设置 mediaId
kotlin
val mediaItem = MediaItem.Builder().setUri(VIDEO_URL).setMediaId("666").build()
也可以设置任何对象,将元数据附加到每项媒体内容。
ini
val media = Media("id", "name", "url")
val mediaItem = MediaItem.Builder().setUri(VIDEO_URL).setTag(media).build()
然后根据 MediaItem 的 localConfiguration 获取到该 Tag
kotlin
val data = player.currentMediaItem
val tag = data?.localConfiguration?.tag
剪辑媒体项
kotlin
val mediaItem = MediaItem.Builder().setUri(VIDEO_URL)
.setClippingConfiguration(
MediaItem.ClippingConfiguration.Builder()
.setStartPositionMs(startPositionMs)
.setEndPositionMs(endPositionMs)
.build()
)
.build()
直播
ExoPlayer 天然就支持大多数自适应直播,无需任何特殊配置。比方说,有 RTSP 的直播链接想要播放,需要引入额外的依赖。
kotlin
implementation("androidx.media3:media3-exoplayer-rtsp:$media3_version")
kotlin
player = ExoPlayer.Builder(context).build()
binding.playView.player = player
val mediaItem = MediaItem.fromUri(RTSP_URL)
player.run {
setMediaItem(mediaItem)
prepare()
play()
}
但是,如果想要实现更加丰富的直播效果,个人还是不建议使用 ExoPlayer 的,坑比较多,就比如,我播放 RTMP 的时候就一直是失败的,明明有引入 media3 的 RTMP 库了,还是报错不支持的格式,我也不晓得是为啥?有知道的大佬可以告知一下,不胜感谢。