播放器事件

监听播放事件

状态变化和播放错误等事件会报告给注册的 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 回调之间进行选择。如需了解不同用例的首选,请参阅单独回调与 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. 带有 reason=DISCONTINUITY_REASON_SEEKonPositionDiscontinuity。这是调用 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 回调内部使用 Player.getCurrentWindowIndex()onTimelineChanged 中提供的 Timeline 才是安全的。
  • 监听器对事件是否逻辑上同时发生感兴趣。例如,由于媒体项过渡导致 onPlaybackStateChangedSTATE_BUFFERING

在某些情况下,监听器可能需要将单独回调与通用 onEvents 回调结合使用,例如使用 onMediaItemTransition 记录媒体项更改原因,但只有当所有状态更改都可以在 onEvents 中一起使用时才执行操作。

使用 AnalyticsListener

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

使用 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();