播放器事件

侦听播放事件

事件(例如状态更改和播放错误)会报告给已注册的 Player.Listener 实例。要注册侦听器以接收此类事件

Kotlin

// Add a listener to receive events from the player.
player.addListener(listener)

Java

// Add a listener to receive events from the player.
player.addListener(listener);

Player.Listener 具有空默认方法,因此您只需要实现您感兴趣的方法即可。有关方法的完整描述以及何时调用它们,请参阅 Javadoc。下面将更详细地描述一些最重要的方法。

侦听器可以选择实现单个事件回调或一个通用的 onEvents 回调,该回调在多个事件一起发生后调用。有关应针对不同用例首选哪一个的说明,请参阅 Individual callbacks vs onEvents

播放状态更改

可以通过在已注册的 Player.Listener 中实现 onPlaybackStateChanged(@State int state) 来接收播放器状态的更改。播放器可以处于四种播放状态之一

  • Player.STATE_IDLE:这是初始状态,播放器停止时的状态以及播放失败时的状态。在此状态下,播放器只会保留有限的资源。
  • Player.STATE_BUFFERING:播放器无法立即从其当前位置播放。这主要是因为需要加载更多数据。
  • Player.STATE_READY:播放器能够立即从其当前位置播放。
  • Player.STATE_ENDED:播放器已完成所有媒体的播放。

除了这些状态之外,播放器还有一个 playWhenReady 标志,用于指示用户播放的意图。可以通过实现 onPlayWhenReadyChanged(playWhenReady, @PlayWhenReadyChangeReason int reason) 来接收此标志的更改。

当满足以下三个条件时,播放器正在播放(即,其位置正在前进并且媒体正在呈现给用户)

  • 播放器处于 Player.STATE_READY 状态
  • playWhenReadytrue
  • 播放未因 Player.getPlaybackSuppressionReason 返回的原因而被抑制

无需单独检查这些属性,可以调用 Player.isPlaying。可以通过实现 onIsPlayingChanged(boolean isPlaying) 来接收此状态的更改

Kotlin

player.addListener(
  object : Player.Listener {
    override fun onIsPlayingChanged(isPlaying: Boolean) {
      if (isPlaying) {
        // Active playback.
      } else {
        // Not playing because playback is paused, ended, suppressed, or the player
        // is buffering, stopped or failed. Check player.playWhenReady,
        // player.playbackState, player.playbackSuppressionReason and
        // player.playerError for details.
      }
    }
  }
)

Java

player.addListener(
    new Player.Listener() {
      @Override
      public void onIsPlayingChanged(boolean isPlaying) {
        if (isPlaying) {
          // Active playback.
        } else {
          // Not playing because playback is paused, ended, suppressed, or the player
          // is buffering, stopped or failed. Check player.getPlayWhenReady,
          // player.getPlaybackState, player.getPlaybackSuppressionReason and
          // player.getPlaybackError for details.
        }
      }
    });

播放错误

可以通过在已注册的 Player.Listener 中实现 onPlayerError(PlaybackException error) 来接收导致播放失败的错误。发生故障时,此方法将在播放状态转换到 Player.STATE_IDLE 之前立即调用。可以通过调用 ExoPlayer.prepare 重试失败或停止的播放。

请注意,某些 Player 实现会传递 PlaybackException 的子类的实例,以提供有关故障的更多信息。例如,ExoPlayer 传递 ExoPlaybackException,它具有 typerendererIndex 和其他 ExoPlayer 特定的字段。

以下示例显示了如何检测播放何时因 HTTP 网络问题而失败

Kotlin

player.addListener(
  object : Player.Listener {
    override fun onPlayerError(error: PlaybackException) {
      val cause = error.cause
      if (cause is HttpDataSourceException) {
        // An HTTP error occurred.
        val httpError = cause
        // It's possible to find out more about the error both by casting and by querying
        // the cause.
        if (httpError is InvalidResponseCodeException) {
          // Cast to InvalidResponseCodeException and retrieve the response code, message
          // and headers.
        } else {
          // Try calling httpError.getCause() to retrieve the underlying cause, although
          // note that it may be null.
        }
      }
    }
  }
)

Java

player.addListener(
    new Player.Listener() {
      @Override
      public void onPlayerError(PlaybackException error) {
        @Nullable Throwable cause = error.getCause();
        if (cause instanceof HttpDataSourceException) {
          // An HTTP error occurred.
          HttpDataSourceException httpError = (HttpDataSourceException) cause;
          // It's possible to find out more about the error both by casting and by querying
          // the cause.
          if (httpError instanceof HttpDataSource.InvalidResponseCodeException) {
            // Cast to InvalidResponseCodeException and retrieve the response code, message
            // and headers.
          } else {
            // Try calling httpError.getCause() to retrieve the underlying cause, although
            // note that it may be null.
          }
        }
      }
    });

播放列表转换

每当播放器切换到播放列表中的新媒体项目时,都会在已注册的 Player.Listener 对象上调用 onMediaItemTransition(MediaItem mediaItem, @MediaItemTransitionReason int reason)。原因指示这是自动转换、搜索(例如在调用 player.next() 后)、同一项目的重复还是由播放列表更改引起的(例如,如果当前播放的项目被删除)。

元数据

player.getCurrentMediaMetadata() 返回的元数据可能会因多种原因而更改:播放列表转换、流内元数据更新或在播放过程中更新当前 MediaItem

如果您对元数据更改感兴趣,例如更新显示当前标题的 UI,则可以侦听 onMediaMetadataChanged

搜索

调用 Player.seekTo 方法会导致一系列回调到已注册的 Player.Listener 实例

  1. onPositionDiscontinuityreason=DISCONTINUITY_REASON_SEEK。这是调用 Player.seekTo 的直接结果。回调具有搜索前后的位置的 PositionInfo 字段。
  2. onPlaybackStateChanged 以及与搜索相关的任何直接状态更改。请注意,可能不会发生此类更改。

单个回调与 onEvents

侦听器可以选择实现 onIsPlayingChanged(boolean isPlaying) 等单个回调,以及通用的 onEvents(Player player, Events events) 回调。通用回调提供对 Player 对象的访问,并指定一起发生的 events 集。此回调始终在与单个事件相对应的回调之后调用。

Kotlin

override fun onEvents(player: Player, events: Player.Events) {
  if (
    events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED) ||
      events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)
  ) {
    uiModule.updateUi(player)
  }
}

Java

@Override
public void onEvents(Player player, Events events) {
  if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)
      || events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)) {
    uiModule.updateUi(player);
  }
}

在以下情况下应首选单个事件

  • 侦听器对更改的原因感兴趣。例如,为 onPlayWhenReadyChangedonMediaItemTransition 提供的原因。
  • 侦听器仅对通过回调参数提供的新值采取行动,或触发不依赖于回调参数的其他内容。
  • 侦听器实现更喜欢在方法名称中清楚地指示触发事件的内容。
  • 侦听器向需要了解所有单个事件和状态更改的分析系统报告。

在以下情况下应首选通用 onEvents(Player player, Events events)

  • 侦听器希望为多个事件触发相同的逻辑。例如,为 onPlaybackStateChangedonPlayWhenReadyChanged 更新 UI。
  • 侦听器需要访问 Player 对象以触发进一步的事件,例如在媒体项目转换后进行搜索。
  • 侦听器打算使用通过单独回调一起报告的多个状态值,或与 Player getter 方法结合使用。例如,仅在 onEvents 回调中使用 onTimelineChanged 中提供的 TimelinePlayer.getCurrentWindowIndex() 是安全的。
  • 侦听器对事件是否在逻辑上一起发生感兴趣。例如,由于媒体项目转换而导致 onPlaybackStateChanged 转换为 STATE_BUFFERING

在某些情况下,侦听器可能需要将单个回调与通用 onEvents 回调结合使用,例如使用 onMediaItemTransition 记录媒体项目更改原因,但仅在所有状态更改可以在 onEvents 中一起使用时才采取行动。

使用 AnalyticsListener

使用 ExoPlayer 时,可以通过调用 addAnalyticsListenerAnalyticsListener 注册到播放器。 AnalyticsListener 实现能够侦听可能对分析和日志记录有用的详细事件。有关更多详细信息,请参阅 分析页面

使用 EventLogger

EventLogger 是库直接提供的用于日志记录目的的 AnalyticsListener。将 EventLogger 添加到 ExoPlayer 以使用一行启用有用的其他日志记录

Kotlin

player.addAnalyticsListener(EventLogger())

Java

player.addAnalyticsListener(new EventLogger());

有关更多详细信息,请参阅 调试日志记录页面

在指定的播放位置触发事件

某些用例需要在指定的播放位置触发事件。这使用 PlayerMessage 支持。可以使用 ExoPlayer.createMessage 创建 PlayerMessage。可以使用 PlayerMessage.setPosition 设置应执行它的播放位置。消息默认在播放线程上执行,但可以使用 PlayerMessage.setLooper 自定义。 PlayerMessage.setDeleteAfterDelivery 可用于控制是否每次遇到指定的播放位置时都执行该消息(由于搜索和重复模式,这可能会发生多次),或者仅第一次执行。配置 PlayerMessage 后,可以使用 PlayerMessage.send 调度它。

Kotlin

player
  .createMessage { messageType: Int, payload: Any? -> }
  .setLooper(Looper.getMainLooper())
  .setPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 120000)
  .setPayload(customPayloadData)
  .setDeleteAfterDelivery(false)
  .send()

Java

player
    .createMessage(
        (messageType, payload) -> {
          // Do something at the specified playback position.
        })
    .setLooper(Looper.getMainLooper())
    .setPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 120_000)
    .setPayload(customPayloadData)
    .setDeleteAfterDelivery(false)
    .send();