这个系列文章我们来介绍一位海外工程师如何探索 ExoPlayer 音视频播放技术,对于想要开始学习音视频技术的朋友,这些文章是份不错的入门资料,这是第 3 篇:ExoPlayer 播放列表。
—— 来自公众号关键帧Keyframe的分享
1、播放列表 API
播放列表 API 由 Player
接口定义,该接口由所有 ExoPlayer
实现。播放列表使得多个媒体项可以按顺序播放。以下示例展示了如何开始播放包含两个视频的播放列表:
// 构建媒体项。
val firstItem = MediaItem.fromUri(firstVideoUri)
val secondItem = MediaItem.fromUri(secondVideoUri)
// 添加要播放的媒体项。
player.addMediaItem(firstItem)
player.addMediaItem(secondItem)
// 准备播放器。
player.prepare()
// 开始播放。
player.play()
// 构建媒体项。
MediaItem firstItem = MediaItem.fromUri(firstVideoUri);
MediaItem secondItem = MediaItem.fromUri(secondVideoUri);
// 添加要播放的媒体项。
player.addMediaItem(firstItem);
player.addMediaItem(secondItem);
// 准备播放器。
player.prepare();
// 开始播放。
player.play();
播放列表中各项之间的过渡是无缝的。它们不要求格式相同(例如,播放列表中可以同时包含 H264 和 VP9 视频)。它们甚至可以是不同类型的(即,播放列表中可以包含视频、图像和仅音频流)。你可以在播放列表中多次使用相同的 MediaItem
。
2、修改播放列表
可以通过添加、移动、删除或替换媒体项来动态修改播放列表。这可以通过调用相应的播放列表 API 方法在播放前后完成:
// 在播放列表的第 1 个位置添加一个媒体项。
player.addMediaItem(/* index= */ 1, MediaItem.fromUri(thirdUri))
// 将第三个媒体项从第 2 个位置移动到播放列表的开头。
player.moveMediaItem(/* currentIndex= */ 2, /* newIndex= */ 0)
// 从播放列表中移除第一个项。
player.removeMediaItem(/* index= */ 0)
// 替换播放列表中的第二个项。
player.replaceMediaItem(/* index= */ 1, MediaItem.fromUri(newUri))
// 在播放列表的第 1 个位置添加一个媒体项。
player.addMediaItem(/* index= */ 1, MediaItem.fromUri(thirdUri));
// 将第三个媒体项从第 2 个位置移动到播放列表的开头。
player.moveMediaItem(/* currentIndex= */ 2, /* newIndex= */ 0);
// 从播放列表中移除第一个项。
player.removeMediaItem(/* index= */ 0);
// 替换播放列表中的第二个项。
player.replaceMediaItem(/* index= */ 1, MediaItem.fromUri(newUri));
还支持替换和清除整个播放列表:
// 用一个新的播放列表替换当前的播放列表。
val newItems: List<MediaItem> = listOf(MediaItem.fromUri(fourthUri), MediaItem.fromUri(fifthUri))
player.setMediaItems(newItems, /* resetPosition= */ true)
// 清除播放列表。如果已准备好,播放器将转换到结束状态。
player.clearMediaItems()
// 用一个新的播放列表替换当前的播放列表。
ImmutableList<MediaItem> newItems =
ImmutableList.of(MediaItem.fromUri(fourthUri), MediaItem.fromUri(fifthUri));
player.setMediaItems(newItems, /* resetPosition= */ true);
// 清除播放列表。如果已准备好,播放器将转换到结束状态。
player.clearMediaItems();
播放器会自动在播放期间正确处理修改:
- 如果当前播放的
MediaItem
被移动,播放不会中断,完成时将播放其新的后继项。 - 如果当前播放的
MediaItem
被移除,播放器将自动播放剩余的第一个后继项,或者如果没有这样的后继项,则转换到结束状态。 - 如果当前播放的
MediaItem
被替换,如果MediaItem
中与播放相关的属性未更改,则播放不会中断。例如,在大多数情况下,可以更新MediaItem.MediaMetadata
字段而不影响播放。
3、查询播放列表
可以通过 Player.getMediaItemCount
和 Player.getMediaItemAt
查询播放列表。通过调用 Player.getCurrentMediaItem
可以查询当前播放的媒体项。还有其他一些便捷方法,如 Player.hasNextMediaItem
或 Player.getNextMediaItemIndex
,以简化在播放列表中的导航。
4、重复模式
播放器支持 3 种重复模式,可以随时通过 Player.setRepeatMode
设置:
Player.REPEAT_MODE_OFF
:播放列表不会重复,播放器将在播放完播放列表中的最后一个项后转换到Player.STATE_ENDED
。Player.REPEAT_MODE_ONE
:当前项会无限循环播放。像Player.seekToNextMediaItem
这样的方法将忽略这一点并寻址到列表中的下一个项,然后该下一个项将无限循环播放。Player.REPEAT_MODE_ALL
:整个播放列表将无限循环播放。
5、洗牌模式
可以随时通过 Player.setShuffleModeEnabled
启用或禁用洗牌模式。在洗牌模式下,播放器将以预计算的随机顺序播放播放列表。所有项都将播放一次,并且可以将洗牌模式与 Player.REPEAT_MODE_ALL
结合使用,以无限循环播放相同的随机顺序。当关闭洗牌模式时,播放从当前项在其原始播放列表位置继续进行。
请注意,像 Player.getCurrentMediaItemIndex
这样的方法返回的索引始终引用原始的、未洗牌的顺序。同样,Player.seekToNextMediaItem
将不会播放 player.getCurrentMediaItemIndex() + 1
处的项,而是根据洗牌顺序的下一个项。在播放列表中插入新项或移除项将尽可能保持现有的洗牌顺序不变。
6、设置自定义洗牌顺序
默认情况下,播放器通过使用 DefaultShuffleOrder
支持洗牌。可以通过提供自定义洗牌顺序实现,或在 DefaultShuffleOrder
构造函数中设置自定义顺序来定制:
// 为播放列表中的 5 个项设置自定义洗牌顺序:
exoPlayer.setShuffleOrder(DefaultShuffleOrder(intArrayOf(3, 1, 0, 4, 2), randomSeed))
// 启用洗牌模式。
exoPlayer.shuffleModeEnabled = true
// 为播放列表中的 5 个项设置自定义洗牌顺序:
exoPlayer.setShuffleOrder(new DefaultShuffleOrder(new int[] {3, 1, 0, 4, 2}, randomSeed));
// 启用洗牌模式。
exoPlayer.setShuffleModeEnabled(/* shuffleModeEnabled= */ true);
7、识别播放列表项
为了识别播放列表项,可以在构建项时设置 MediaItem.mediaId
:
// 构建一个带有媒体 ID 的媒体项。
val mediaItem = MediaItem.Builder().setUri(uri).setMediaId(mediaId).build()
// 构建一个带有媒体 ID 的媒体项。
MediaItem mediaItem = new MediaItem.Builder().setUri(uri).setMediaId(mediaId).build();
如果应用未明确为媒体项定义媒体 ID,则使用 URI 的字符串表示形式。
8、将应用数据与播放列表项关联
除了 ID 之外,每个媒体项还可以配置自定义标签,可以是任何应用提供的对象。自定义标签的一个用途是将元数据附加到每个媒体项:
// 构建一个带有自定义标签的媒体项。
val mediaItem = MediaItem.Builder().setUri(uri).setTag(metadata).build()
// 构建一个带有自定义标签的媒体项。
MediaItem mediaItem = new MediaItem.Builder().setUri(uri).setTag(metadata).build();
9、检测播放转换到另一个媒体项时
当播放转换到另一个媒体项,或者开始重复同一个媒体项时,将调用 Listener.onMediaItemTransition(MediaItem, @MediaItemTransitionReason)
。此回调接收新的媒体项,以及一个 @MediaItemTransitionReason
,指示转换发生的原因。onMediaItemTransition
的一个常见用途是更新应用的 UI 以显示新的媒体项:
override fun onMediaItemTransition(
mediaItem: MediaItem?,
@MediaItemTransitionReason reason: Int,
) {
updateUiForPlayingMediaItem(mediaItem)
}
@Override
public void onMediaItemTransition(
@Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) {
updateUiForPlayingMediaItem(mediaItem);
}
如果用于更新 UI 的元数据是使用自定义标签附加到每个媒体项的,那么实现可能如下所示:
override fun onMediaItemTransition(
mediaItem: MediaItem?,
@MediaItemTransitionReason reason: Int,
) {
var metadata: CustomMetadata? = null
mediaItem?.localConfiguration?.let { localConfiguration ->
metadata = localConfiguration.tag as? CustomMetadata
}
updateUiForPlayingMediaItem(metadata)
}
@Override
public void onMediaItemTransition(
@Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) {
@Nullable CustomMetadata metadata = null;
if (mediaItem != null && mediaItem.localConfiguration != null) {
metadata = (CustomMetadata) mediaItem.localConfiguration.tag;
}
updateUiForPlayingMediaItem(metadata);
}
10、检测播放列表何时更改
当添加、移除或移动媒体项时,将立即调用 Listener.onTimelineChanged(Timeline, @TimelineChangeReason)
,并带有 TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED
。即使播放器尚未准备好,此回调也会被调用。
override fun onTimelineChanged(timeline: Timeline, @TimelineChangeReason reason: Int) {
if (reason == Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) {
// 根据修改后的播放列表(添加、移动或移除)更新 UI。
updateUiForPlaylist(timeline)
}
}
@Override
public void onTimelineChanged(Timeline timeline, @TimelineChangeReason int reason) {
if (reason == TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) {
// 根据修改后的播放列表(添加、移动或移除)更新 UI。
updateUiForPlaylist(timeline);
}
}
当播放列表中媒体项的持续时间等信息可用时,Timeline
将被更新,并且 onTimelineChanged
将被调用,带有 TIMELINE_CHANGE_REASON_SOURCE_UPDATE
。其他可能导致时间线更新的原因包括:
- 在准备自适应媒体项后,清单变得可用。
- 在直播流播放期间,清单定期更新。
音视频方向学习、求职,欢迎加入我们的星球
丰富的音视频知识、面试题、技术方案干货分享,还可以进行面试辅导
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。