播放器事件

監聽播放事件

狀態變更和播放錯誤等事件會回報至已註冊的 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 狀態
  • playWhenReady」將在 true後開始
  • 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.
          }
        }
      }
    });

播放清單轉場效果

每當玩家變更播放清單 onMediaItemTransition(MediaItem mediaItem, @MediaItemTransitionReason int reason) 中的新媒體項目時,系統就會對已註冊的 Player.Listener 物件呼叫。原因會指出這是不是自動轉換、搜尋 (例如呼叫 player.next() 之後)、重複項目,或播放清單變更 (例如目前播放的項目已移除) 所致。

Metadata

許多原因都可能造成 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 的使用者介面。
  • 事件監聽器需要存取 Player 物件以觸發其他事件,例如在媒體項目轉換後搜尋。
  • 事件監聽器打算使用透過個別回呼或 Player getter 方法回報的多個狀態值。舉例來說,將 Player.getCurrentWindowIndex()onTimelineChanged 中提供的 Timeline 搭配使用,只能在 onEvents 回呼中安全。
  • 事件監聽器會想知道事件是否在邏輯上發生。舉例來說,因為媒體項目轉換而將 onPlaybackStateChanged 變更為 STATE_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();