播放列表 API 由 Player 接口定义,该接口由所有 ExoPlayer 实现实现。借助播放列表,您可以按顺序播放多个媒体内容。以下示例展示了如何开始播放包含两个视频的播放列表:
Kotlin
// Build the media items. val firstItem = MediaItem.fromUri(firstVideoUri) val secondItem = MediaItem.fromUri(secondVideoUri) // Add the media items to be played. player.addMediaItem(firstItem) player.addMediaItem(secondItem) // Prepare the player. player.prepare() // Start the playback. player.play()
Java
// Build the media items. MediaItem firstItem = MediaItem.fromUri(firstVideoUri); MediaItem secondItem = MediaItem.fromUri(secondVideoUri); // Add the media items to be played. player.addMediaItem(firstItem); player.addMediaItem(secondItem); // Prepare the player. player.prepare(); // Start the playback. player.play();
播放列表中的项目之间的过渡是无缝的。它们不必采用相同的格式(例如,播放列表可以同时包含 H264 和 VP9 视频)。它们甚至可以是不同类型的(也就是说,播放列表可以只包含视频、图片和音频流)。您可以在一个播放列表中多次使用同一 MediaItem。
修改播放列表
您可以通过添加、移动、移除或替换媒体项来动态修改播放列表。您可以在播放之前和播放期间调用相应的播放列表 API 方法来完成此操作:
Kotlin
// Adds a media item at position 1 in the playlist. player.addMediaItem(/* index= */ 1, MediaItem.fromUri(thirdUri)) // Moves the third media item from position 2 to the start of the playlist. player.moveMediaItem(/* currentIndex= */ 2, /* newIndex= */ 0) // Removes the first item from the playlist. player.removeMediaItem(/* index= */ 0) // Replace the second item in the playlist. player.replaceMediaItem(/* index= */ 1, MediaItem.fromUri(newUri))
Java
// Adds a media item at position 1 in the playlist. player.addMediaItem(/* index= */ 1, MediaItem.fromUri(thirdUri)); // Moves the third media item from position 2 to the start of the playlist. player.moveMediaItem(/* currentIndex= */ 2, /* newIndex= */ 0); // Removes the first item from the playlist. player.removeMediaItem(/* index= */ 0); // Replace the second item in the playlist. player.replaceMediaItem(/* index= */ 1, MediaItem.fromUri(newUri));
此外,还支持替换和清除整个播放列表:
Kotlin
// Replaces the playlist with a new one. val newItems: List<MediaItem> = listOf(MediaItem.fromUri(fourthUri), MediaItem.fromUri(fifthUri)) player.setMediaItems(newItems, /* resetPosition= */ true) // Clears the playlist. If prepared, the player transitions to the ended state. player.clearMediaItems()
Java
// Replaces the playlist with a new one. ImmutableList<MediaItem> newItems = ImmutableList.of(MediaItem.fromUri(fourthUri), MediaItem.fromUri(fifthUri)); player.setMediaItems(newItems, /* resetPosition= */ true); // Clears the playlist. If prepared, the player transitions to the ended state. player.clearMediaItems();
播放器会在播放期间以正确的方式自动处理修改:
- 如果当前播放的
MediaItem被移动,播放不会中断,并且在播放完成后,系统会播放其新的后续内容。 - 如果移除了当前正在播放的
MediaItem,播放器将自动播放第一个剩余的后继MediaItem,或者在没有此类后继MediaItem的情况下过渡到结束状态。 - 如果替换了当前正在播放的
MediaItem,但MediaItem中与播放相关的属性均未发生变化,则播放不会中断。例如,在大多数情况下,更新MediaItem.MediaMetadata字段不会影响播放。
查询播放列表
可以使用 Player.getMediaItemCount 和 Player.getMediaItemAt 查询播放列表。可以通过调用 Player.getCurrentMediaItem 查询当前播放的媒体项。还有其他便捷方法(例如 Player.hasNextMediaItem 或 Player.getNextMediaItemIndex)可简化播放列表中的导航。
重复模式
播放器支持 3 种重复模式,可以使用 Player.setRepeatMode 随时设置:
Player.REPEAT_MODE_OFF:播放列表不会重复播放,播放器会在播放完播放列表中的最后一个项目后过渡到Player.STATE_ENDED。Player.REPEAT_MODE_ONE:当前商品以无限循环方式重复播放。Player.seekToNextMediaItem等方法会忽略此设置,并寻找列表中的下一个项,然后以无限循环的方式重复该项。Player.REPEAT_MODE_ALL:整个播放列表会无限循环播放。
随机播放模式
您可以随时使用 Player.setShuffleModeEnabled 启用或停用随机播放模式。在随机播放模式下,播放器将以预先计算的随机顺序播放播放列表。所有项目将播放一次,并且随机播放模式还可以与 Player.REPEAT_MODE_ALL 结合使用,以无限循环的方式重复相同的随机顺序。关闭随机播放模式后,系统会从当前项在播放列表中的原始位置继续播放。
请注意,由 Player.getCurrentMediaItemIndex 等方法返回的索引始终是指原始的未打乱顺序。同样,Player.seekToNextMediaItem 不会播放 player.getCurrentMediaItemIndex() + 1 处的媒体内容,而是会根据随机播放顺序播放下一项内容。在播放列表中插入新项或移除项时,系统会尽可能保持现有的随机播放顺序不变。
设置自定义随机播放顺序
默认情况下,播放器支持使用 DefaultShuffleOrder 进行随机播放。您可以通过提供自定义随机播放顺序实现或在 DefaultShuffleOrder 构造函数中设置自定义顺序来对此进行自定义:
Kotlin
// Set a custom shuffle order for the 5 items currently in the playlist: exoPlayer.setShuffleOrder(DefaultShuffleOrder(intArrayOf(3, 1, 0, 4, 2), randomSeed)) // Enable shuffle mode. exoPlayer.shuffleModeEnabled = true
Java
// Set a custom shuffle order for the 5 items currently in the playlist: exoPlayer.setShuffleOrder(new DefaultShuffleOrder(new int[] {3, 1, 0, 4, 2}, randomSeed)); // Enable shuffle mode. exoPlayer.setShuffleModeEnabled(/* shuffleModeEnabled= */ true);
识别播放列表中的内容
如需标识播放列表项,可在构建项时设置 MediaItem.mediaId:
Kotlin
// Build a media item with a media ID. val mediaItem = MediaItem.Builder().setUri(uri).setMediaId(mediaId).build()
Java
// Build a media item with a media ID. MediaItem mediaItem = new MediaItem.Builder().setUri(uri).setMediaId(mediaId).build();
如果应用未明确为媒体项定义媒体 ID,则使用 URI 的字符串表示形式。
将应用数据与播放列表项相关联
除了 ID 之外,每个媒体项还可以配置自定义标记,该标记可以是应用提供的任何对象。自定义标记的一个用途是将元数据附加到每个媒体项:
Kotlin
// Build a media item with a custom tag. val mediaItem = MediaItem.Builder().setUri(uri).setTag(metadata).build()
Java
// Build a media item with a custom tag. MediaItem mediaItem = new MediaItem.Builder().setUri(uri).setTag(metadata).build();
检测播放何时过渡到其他媒体项
当播放过渡到另一个媒体项或开始重复播放同一媒体项时,系统会调用 Listener.onMediaItemTransition(MediaItem,
@MediaItemTransitionReason)。此回调函数会接收新的媒体项,以及一个 @MediaItemTransitionReason,用于指明发生过渡的原因。onMediaItemTransition 的常见用例是更新应用界面以显示新的媒体内容:
Kotlin
override fun onMediaItemTransition( mediaItem: MediaItem?, @MediaItemTransitionReason reason: Int, ) { updateUiForPlayingMediaItem(mediaItem) }
Java
@Override public void onMediaItemTransition( @Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) { updateUiForPlayingMediaItem(mediaItem); }
如果使用自定义标记将更新界面所需的元数据附加到每个媒体项,则实现可能如下所示:
Kotlin
override fun onMediaItemTransition( mediaItem: MediaItem?, @MediaItemTransitionReason reason: Int, ) { var metadata: CustomMetadata? = null mediaItem?.localConfiguration?.let { localConfiguration -> metadata = localConfiguration.tag as? CustomMetadata } updateUiForPlayingMediaItem(metadata) }
Java
@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); }
检测播放列表何时发生变化
当媒体项被添加、移除或移动时,系统会立即调用 Listener.onTimelineChanged(Timeline, @TimelineChangeReason) 并传入 TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED。即使播放器尚未准备就绪,也会调用此回调。
Kotlin
override fun onTimelineChanged(timeline: Timeline, @TimelineChangeReason reason: Int) { if (reason == Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) { // Update the UI according to the modified playlist (add, move or remove). updateUiForPlaylist(timeline) } }
Java
@Override public void onTimelineChanged(Timeline timeline, @TimelineChangeReason int reason) { if (reason == TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) { // Update the UI according to the modified playlist (add, move or remove). updateUiForPlaylist(timeline); } }
当播放列表中的媒体项时长等信息可用时,Timeline 将会更新,并使用 TIMELINE_CHANGE_REASON_SOURCE_UPDATE 调用 onTimelineChanged。可能导致时间轴更新的其他原因包括:
- 在准备自适应媒体内容后可用的清单。
- 在直播播放期间定期更新的清单。