播放器事件

收听播放事件

状态变化和播放错误等事件会报告给已注册的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. onPositionDiscontinuity,其中reason=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回调中才能安全地将Player.getCurrentWindowIndex()onTimelineChanged中提供的Timeline一起使用。
  • 监听器对事件是否在逻辑上一起发生感兴趣。例如,由于媒体项目转换而导致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来支持。PlayerMessage可以使用ExoPlayer.createMessage创建。可以使用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();