播放器事件

監聽播放事件

系統會向已註冊的 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.
          }
        }
      }
    });

播放清單轉場效果

每當播放器在播放清單中切換至新的媒體項目時,系統就會在已註冊的 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 的原因。
  • 監聽器只會對透過回呼參數提供的新值採取行動,或觸發其他不依附於回呼參數的項目。
  • 事件監聽器實作會偏好在方法名稱中,清楚指出觸發事件的原因。
  • 監聽器會向需要瞭解所有個別事件和狀態變更的 Analytics 系統回報。

在下列情況中,建議使用一般 onEvents(Player player, Events events)

  • 聽眾想為多個事件觸發相同邏輯。舉例來說,同時更新 onPlaybackStateChangedonPlayWhenReadyChanged 的 UI。
  • 監聽器需要存取 Player 物件,才能觸發後續事件,例如在媒體項目轉換後搜尋。
  • 監聽器打算一起使用透過個別回呼回報的多個狀態值,或與 Player getter 方法搭配使用。舉例來說,搭配 onTimelineChanged 中提供的 Timeline 使用 Player.getCurrentWindowIndex() 時,只有在 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();