一、概念
1.1 架构
| Player 播放器 | ExoPlayer 是具体实现。 |
| MediaSession 媒体会话 | 协调 MediaController 和 Player。 |
| MediaController 媒体控制器 | 用于跨组件或外部远程控制媒体播放(耳机、手表、电视、汽车、谷歌助理)。 |
| MediaSessionService | 用于支持跨组件播放的Service。(ExoPlayer支持处于后台时播放) |
1.2 协议支持
| DASH | HLS | SmoothStreaming | |
| 全称 | Dynamic Adaptive Streaming over HTTP | HTTP Live Streaming | IIS Smooth Streaming |
| 主导方 | MPEG(国际标准) | 苹果 | 微软 |
| 清单文件格式 | 基于XML的 .mpd | 基于文本的 .m3u8 | 基于XML的 .ismc |
| 现状 | 业界事实标准之一,更开放、灵活 | 业界事实标准之一,兼容性极佳 | 遗产协议,仍在某些系统使用 |
| RTSP/RTP | HLS / DASH / Smooth Streaming | |
| 协议 | 控制协议 + 数据传输协议 | 基于HTTP的应用协议 |
| 典型应用 | IP摄像头、视频会议、专网监控 | 互联网视频网站(Netflix/YouTube)、公共直播 |
| MIDI | 音频文件播放(MP3, WAV等) | |
| 文件内容 | 指令集(乐谱) | 声音波形的编码(录音) |
| 最终音质 | 取决于播放设备的音源 | 取决于文件本身的编码和比特率 |
二、简单使用
2.1 添加依赖
[versions]
media3 = "1.9.0"
[libraries]
media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
media3-datasource-okhttp = { module = "androidx.media3:media3-datasource-okhttp", version.ref = "media3" }
media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3" }
//可选,使用 MediaSession
media3-session = { module = "androidx.media3:media3-session", version.ref = "media3" }
可选协议支持
//DASH
media3-dash = { module = "androidx.media3:media3-exoplayer-dash", version.ref = "media3" }
//HLS
media3-hls = { module = "androidx.media3:media3-exoplayer-hls", version.ref = "media3" }
//SmoothStreaming
media3-smoothstreaming = { module = "androidx.media3:media3-exoplayer-smoothstreaming", version.ref = "media3" }
//RTSP
media3-rtsp = { module = "androidx.media3:media3-exoplayer-rtsp", version.ref = "media3" }
//MIDI
media3-midi = { module = "androidx.media3:media3-exoplayer-midi", version.ref = "media3" }
//For ad insertion using the Interactive Media Ads SDK with ExoPlayer
media3-ima = { module = "androidx.media3:media3-exoplayer-ima", version.ref = "media3" }
2.2 添加控件 PlayerView
可用于视频、图片和音频播放。在播放视频时,它会呈现视频和字幕;在播放图片时,它会呈现位图;并且可以显示音频文件中作为元数据包含的封面图片。
<androidx.media3.ui.PlayerView
android:id="@+id/playerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true"
/>
Activity {
override fun onStart() {
super.onResume()
playerView.onResume()
}
override fun onStop() {
super.onStop()
playerView.onPause()
}
}
2.3 创建播放器 ExoPlayer
| 每次快进快退的时间 | public Builder setSeekBackIncrementMs(@IntRange(from = 1) long seekBackIncrementMs) public Builder setSeekForwardIncrementMs(@IntRange(from = 1) long seekForwardIncrementMs) |
val player = ExoPlayer.Builder(APP.context)
.setSeekBackIncrementMs(10000) //每次快退的时间(10秒)
.setSeekForwardIncrementMs(10000) //每次快进的时间(10秒)
.build()
playerView.player = player //将播放器和控件绑定
player.prepare()
playWhenReady = true
2.4 添加媒体项 MediaItem
通过 Uri 、Bundle 指定播放文件。播放期间可以更新播放列表,无需再次准备播放器。
| public static MediaItem fromUri(String uri) public static MediaItem fromUri(Uri uri) public static MediaItem fromBundle(Bundle bundle) |
//创建播放内容
val mediaItem = MediaItem.fromUri("")
//将播放内容添加到播放器中
player.setMediaItem(mediaItem)
2.6 控制播放器
| 初始化 | void prepare(); 将使播放器脱离空闲状态,开始加载媒体内容并获取播放所需的系统资源。 |
| 播放 | void play(); |
| 暂停 | void pause(); |
| 释放播放器 | void release(); 释放播放器实例。当不再需要使用播放器时,必须调用此方法。调用此方法后,严禁继续使用该播放器实例。 |
| 自动播放 | boolean getPlayWhenReady(); void setPlayWhenReady(boolean playWhenReady); 播放器就绪状态时是否自动播放。 |
| 播放速度 | void setPlaybackSpeed(@FloatRange(from = 0, fromInclusive = false) float speed); 调整速率(不改变音调),参数 speed 必须大于 0,1.0 为正常速率,2.0 为两倍速,0.5 为半速。 |
| 状态判断 | boolean isPlaying(); 是否正在播放中。 boolean isLoading(); 是否正在加载资源。 |
| 跳转 | void seekToPreviousMediaItem(); void seekToNextMediaItem(); 跳转到上一个或下一个媒体项。 |
| boolean hasPreviousMediaItem(); boolean hasNextMediaItem(); 是否有上一个或下一个媒体项。 | |
| void seekTo(int mediaItemIndex, long positionMs); 跳转至指定媒体项中由毫秒数定义的播放位置。 | |
| 进度条 | void seekBack(); void seekForward(); 将当前媒体项的播放位置向前或向后跳到创建播放器时,通过 set |
| void seekTo(long positionMs); 跳转至当前媒体项中指定的毫秒数位置。 | |
| 音量增益 | float getVolume(); 设置音频输出增益。有效取值范围为 0(静音)至 1(单位增益,保持原信号)的闭区间。 |
三、监听事件
可以通过 player.addListener() 或挂起函数 player.listen{} 来监听。Player.Listener 没有必须实现的方法,只需要实现感兴趣的。
| 状态更改 | default void onPlaybackStateChanged(@State int playbackState) {} 分别有:空闲 Player.STATE_IDLE、缓冲中 Player.STATE_BUFFERING、就绪 Player.STATE_READY、播放完 Player.STATE_ENDED。 也可以直接通过 player.playbackState 获取。 |
| 是否在播放 | default void onIsPlayingChanged(boolean isPlaying) {} 也可以直接用通过 player.isPlaying 获取。 |
| 播放错误 | default void onPlayerError(PlaybackException error) {} |
player.addListener(object : Player.Listener {
//TODO
})
player.listen { event ->
//TODO
}
3.1 状态更改 onPlaybackStateChanged()
override fun onPlaybackStateChanged(playbackState: Int) {
when(playbackState) {
//空闲
Player.STATE_IDLE -> {}
//缓冲中
Player.STATE_BUFFERING -> {}
//就绪
Player.STATE_READY -> {}
//播放完
Player.STATE_ENDED -> {}
}
}
3.2 是否在播放 onIsPlayingChanged()
override fun onIsPlayingChanged(isPlaying: Boolean) {
if (isPlaying) {} else {}
}
3.3 播放错误 onPlayerError()
override fun onPlayerError(error: PlaybackException) {
when (error.cause) {
//http错误
is HttpDataSource.HttpDataSourceException -> {}
else -> {}
}
when (error.errorCode) {
//网络错误
PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED -> {}
//解码错误
PlaybackException.ERROR_CODE_DECODING_FAILED -> {}
else -> {}
}
}
3.4 播放列表/媒体源变化 onTimelineChanged()
override fun onTimelineChanged(timeline: Timeline, reason: Int) {
when(reason) {
//播放列表发生变化时
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED -> {}
//媒体源更新而发生变化
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE -> {}
}
}
3.5 切歌 onMediaItemTransition()
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {}
3.6 监听播放进度
如果需要设置进度条,Listener 中没有对应的回调用来持续监听播放进度,需要手动实现(按一定时间间隔去查询)。
if (player.isPlaying) {
//每秒更新一次
repeat(1000) {
val currentPosition = player.currentPosition
val duration = player.duration
seekBar.progress = if (duration > 0) {
(currentPos * 100 / duration).toInt()
} else 0
currentText = formatTime(currentPosition)
durationText = formatTime(duration)
}
}
四、媒体项 MediaItem
通过 Builder 模式构建实例。MediaItem 会由 MediaSource.Factory 转换为可播放的 MediaSource。如果未进行自定义配置,此转换将由 DefaultMediaSourceFactory 执行,它能够构建与媒体内容的属性对应的复杂媒体源。
| 设置Uri | public Builder setUri(@Nullable Uri uri) public Builder setUri(@Nullable String uri) 设置媒体资源。 |
| 设置ID | public Builder setMediaId(String mediaId) 用于识别媒体项。 |
| 设置Tag | public Builder setTag(@Nullable Object tag) 为媒体项设置一个自定义代码,可以是任意对象,一般用于元数据。 |
| 获取元数据 | public final MediaMetadata mediaMetadata; 通过属性获取媒体项的元数据,包含了:媒体文件类型、作者、专辑、时长等。 |
val mediaItem = MediaItem.Builder()
.setUri("")
.setTag("")
.setMediaId("")
.build()
4.1 串流
ExoPlayer 为 DASH、HLS 和 SmoothStreaming 提供自适应媒体源。如果此类自适应媒体内容的 URI 以标准文件扩展名结尾,系统会自动创建相应的媒体源。如果 URI 具有非标准扩展名或没有扩展名,则可以明确设置 MIME 类型以指明媒体内容的类型。对于渐进式媒体串流,不需要 MIME 类型。
| 便捷创建串流媒体项 | public static MediaItem fromUri(String uri) public static MediaItem fromUri(Uri uri) 快捷构建仅包含串流 URI 的媒体项。 |
| 设置自适应媒体的类型 | public Builder setMimeType(@Nullable String mimeType) |
val mediaItem = MediaItem.fromUri("")
.setMimeType(MimeTypes.APPLICATION_M3U8)
4.2 图像
播放图片,媒体项中必须包含时长,以指定图片在播放期间应显示的时长。
| 图片播放时常 | public Builder setImageDurationMs(long imageDurationMs) |
val mediaItem = MediaItem.Builder()
.setUri(imageUri)
.setImageDurationMs(3000)
.build()
4.3 受保护的内容
对于受保护的内容,应设置媒体内容的 DRM 属性。UUID 是必需的,所有其他属性都是可选的。
val mediaItem = MediaItem.Builder()
.setUri("")
.setDrmConfiguration(
MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
.setLicenseUri("")
.setMultiSession(true)
.setLicenseRequestHeaders(mapOf("" to ""))
.build()
).build()
五、播放列表
播放列表中的项之间可以无缝切换,无格式相同要求,甚至无媒体类型相同要求,也就是播放列表可以同时包含图片、视频、音频。
5.1 增删改查
| 清空并添加 | void setMediaItem(MediaItem mediaItem); void setMediaItems(List<MediaItem> mediaItems); 播放位置重置为起始点。 void setMediaItem(MediaItem mediaItem, boolean resetPosition); void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition); 参数resetPosition设为flase不会重置播放位置。 void setMediaItem(MediaItem mediaItem, long startPositionMs); void setMediaItems(List<MediaItem> mediaItems, int startIndex, long startPositionMs); 参数startPositionMs以毫秒为单位的播放起始点。参数startIndex起始索引。 |
| 添加 | void addMediaItem(MediaItem mediaItem); void addMediaItems(List<MediaItem> mediaItems); 添加到列表尾部。 |
| void addMediaItem(int index, MediaItem mediaItem); void addMediaItems(int index, List<MediaItem> mediaItems); 指定索引处插入。 | |
| 移动 | void moveMediaItem(int currentIndex, int newIndex); 将当前索引 currentIndex 的条目移动到目标索引 newIndex。currentIndex超过范围请求会被忽略,newIndex超过范围则为列表尾部。 将起始索引 fromIndex(包含) 到目标索引 toIndex(不包含) 之间的条目,移动到目标索引newIndex。fromIndex超出范围请求会被忽略,toIndex超出范围则一直截取到尾部,newIndex超出截取后剩余的范围则为列表尾部。 |
| 替换 | void replaceMediaItem(int index, MediaItem mediaItem); 将起始索引fromIndex(包含)到目标索引toIndex(不包含) 之间的条目,替换成目标集合mediaItems。fromIndex超出范围请求会被忽略,toIndex超出范围则一直截取到尾部。 |
| 移除 | void removeMediaItem(int index); void removeMediaItems(int fromIndex, int toIndex); 移除起始索引fromIndex(包含)到目标索引toIndex(不包含) 之间的条目。fromIndex超出范围请求会被忽略,toIndex超出范围则一直截取到尾部。 |
| 清空 | void clearMediaItems(); |
| 查找 | int getCurrentMediaItemIndex(); 当前播放的媒体项索引。 MediaItem getCurrentMediaItem(); 当前播放的媒体项。 |
5.2 重复模式 RepeatMode
| 重复模式 | int getRepeatMode(); void setRepeatMode(@RepeatMode int repeatMode); 循环关 REPEAT_MODE_OFF、单循环 REPEAT_MODE_ONE、列表循环 REPEAT_MODE_ALL。 |
5.3 随机播放模式 ShuffleMode
| 随机播放 | boolean getShuffleModeEnabled(); void setShuffleModeEnabled(boolean shuffleModeEnabled); 所有内容都会播放一次。 |
| 自定义顺序 | public DefaultShuffleOrder(int[] shuffledIndices, long randomSeed) 可以通过提供自定义重排顺序实现或 在 DefaultShuffleOrder 构造函数中设置自定义顺序。参数shuffledIndices作为播放顺序的乱序数组,参数randomSeed是随机数种子。 |
六、后台播放 MediaSession
2839

被折叠的 条评论
为什么被折叠?



