Media3 - ExoPlayer 打造音视频播放器(一)

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 库了,还是报错不支持的格式,我也不晓得是为啥?有知道的大佬可以告知一下,不胜感谢。

相关推荐
SHERlocked932 天前
摄像头 RTSP 流视频多路实时监控解决方案实践
c++·后端·音视频开发
mortimer16 天前
Python + FFmpeg 视频自动化处理指南:从硬件加速到精确剪辑
python·ffmpeg·音视频开发
否子戈17 天前
做中国人自己的视频编辑UI框架,WebCut正式开源
前端框架·音视频开发·视频编码
音视频牛哥18 天前
从低延迟到高可用:RTMP与 HTTP/HTTPS-FLV在App播放体系中的角色重构
人工智能·音视频·音视频开发·http-flv播放器·https-flv播放器·ws-flv播放器·wss-flv播放器
音视频牛哥23 天前
轻量级RTSP服务的工程化设计与应用:从移动端到边缘设备的实时媒体架构
人工智能·计算机视觉·音视频·音视频开发·rtsp播放器·安卓rtsp服务器·安卓实现ipc功能
快乐10124 天前
Media3 ExoPlayer无法播放不带.m3u8后缀hls媒资
音视频开发
_AaronWong25 天前
基于 Vue 3 的屏幕音频捕获实现:从原理到实践
前端·vue.js·音视频开发
快手技术1 个月前
超越 VTM-RA!快手双向智能视频编码器 BRHVC 亮相 NeurIPS2025
音视频开发
快乐1011 个月前
Media3 ExoPlayer扩展切换声道能力
音视频开发
yangguang1 个月前
音视频开发全景图:播放器是怎样炼成的
音视频开发