播放列表

播放列表 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.MediaMetadata 字段而不会影响播放。

查询播放列表

可以使用 Player.getMediaItemCountPlayer.getMediaItemAt 查询播放列表。可以通过调用 Player.getCurrentMediaItem 查询当前正在播放的媒体项目。还有一些其他便捷方法,例如 Player.hasNextMediaItemPlayer.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 的一个常见用例是更新应用的新媒体项目的 UI。

Kotlin

override fun onMediaItemTransition(
  mediaItem: MediaItem?,
  @MediaItemTransitionReason reason: Int,
) {
  updateUiForPlayingMediaItem(mediaItem)
}

Java

@Override
public void onMediaItemTransition(
    @Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) {
  updateUiForPlayingMediaItem(mediaItem);
}

如果更新 UI 所需的元数据是使用自定义标签附加到每个媒体项目的,则实现可能如下所示。

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);
}

检测播放列表何时发生更改

当添加、删除或移动媒体项目时,将立即使用 TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED 调用 Listener.onTimelineChanged(Timeline, @TimelineChangeReason)。即使播放器尚未准备好,也会调用此回调。

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。其他可能导致时间轴更新的原因包括:

  • 在准备自适应媒体项目后,清单变得可用。
  • 在直播流播放期间,清单会定期更新。