プレーヤー イベント

再生イベントをリッスンする

状態の変化や再生エラーなどのイベントは、登録済みの 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 をご覧ください。最も重要なメソッドについて、以下で詳しく説明します。

リスナーは、個別のイベント コールバックを実装するか、1 つ以上のイベントが同時に発生した後に呼び出される汎用の onEvents コールバックを実装するかを選択できます。さまざまなユースケースに適したものについては、Individual callbacks vs onEvents をご覧ください。

再生状態の変化

プレーヤーの状態の変更を受信するには、登録済みの Player.ListeneronPlaybackStateChanged(@State int state) を実装します。プレーヤーは、次の 4 つの再生状態のいずれかになります。

  • Player.STATE_IDLE: 初期状態、プレーヤーが停止した状態、再生に失敗したときの状態です。この状態では、プレーヤーは限られたリソースのみを保持します。
  • Player.STATE_BUFFERING: プレーヤーは現在の位置からすぐに再生できません。これは主に、より多くのデータを読み込む必要があることが原因で発生します。
  • Player.STATE_READY: プレーヤーは現在の位置からすぐに再生できる。
  • Player.STATE_ENDED: プレーヤーがすべてのメディアの再生を終了しました。

これらの状態に加えて、プレーヤーにはユーザーがプレイする意思を示す playWhenReady フラグが表示されます。このフラグの変更を受信するには、onPlayWhenReadyChanged(playWhenReady, @PlayWhenReadyChangeReason int reason) を実装します。

次の 3 つの条件がすべて満たされる場合、プレーヤーは再生中(位置が進んでおり、メディアがユーザーに表示されている状態)です。

  • プレーヤーが 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.ListeneronPlayerError(PlaybackException error) を実装することで受け取ることができます。エラーが発生すると、再生状態が Player.STATE_IDLE に遷移する直前にこのメソッドが呼び出されます。失敗または停止した再生を再試行するには、ExoPlayer.prepare を呼び出します。

なお、一部の Player 実装では、PlaybackException のサブクラスのインスタンスを渡すことで、失敗に関する追加情報が提供されます。たとえば、ExoPlayerExoPlaybackException を渡します。これには、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 ゲッター メソッドと組み合わせてレポートします。たとえば、onTimelineChanged で提供される TimelinePlayer.getCurrentWindowIndex() を併用することは、onEvents コールバック内でのみ安全です。
  • リスナーは、イベントが論理的に同時に発生したかどうかに関心があります。たとえば、メディア アイテムの遷移により onPlaybackStateChanged から STATE_BUFFERING が選択されます。

場合によっては、リスナーが個々のコールバックを汎用の onEvents コールバックと組み合わせることが必要になる場合があります(たとえば、onMediaItemTransition でメディア アイテムの変更理由を記録する場合など)。ただし、すべての状態変更を onEvents で併用できる場合にのみ動作します。

AnalyticsListener の使用

ExoPlayer を使用する場合は、addAnalyticsListener を呼び出して AnalyticsListener をプレーヤーに登録できます。AnalyticsListener の実装では、分析やロギングに役立つ詳細なイベントをリッスンできます。詳細については、分析ページをご覧ください。

EventLogger の使用

EventLogger は、ロギングのためにライブラリから直接提供される AnalyticsListener です。EventLoggerExoPlayer に追加すると、1 行で便利な追加のロギングが有効になります。

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