プレーヤー イベント

プレーヤーの状態の変化(再生の開始、バッファリング、エラーなど) は、登録済みの Player.Listener インスタンスに送信されるイベントをトリガーします。これらの イベントは整数定数で表され、Player.EventPlayer.Eventsで定義されます。

Player.Listener を登録する

プレーヤー イベントは、登録済みの 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);

Kotlin を使用している場合は、media3-common-ktx モジュールで提供される一時停止拡張関数を使用して、コルーチンを使用してイベントをリッスンすることもできます。 その場合、Player.Listener を明示的に登録または登録解除する必要はありません。

Player.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 状態である
  • 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.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);
  }
}

次のような場合は、個々のイベントを優先する必要があります。

  • リスナーが変更の理由に関心がある場合。たとえば、onPlayWhenReadyChanged または onMediaItemTransition に指定された理由などです。
  • リスナーがコールバック パラメータで提供される新しい値に対してのみ動作する場合、またはコールバック パラメータに依存しない他の処理をトリガーする場合。
  • リスナーの実装で、メソッド名にイベントをトリガーした内容を明確に読み取れるようにすることが望ましい場合。
  • リスナーが、個々のイベントと状態の変化をすべて把握する必要がある分析システムに報告する場合。

次のような場合は、汎用 onEvents(Player player, Events events) を優先する必要があります。

  • リスナーが複数のイベントに対して同じロジックをトリガーする場合。たとえば、onPlaybackStateChangedonPlayWhenReadyChanged の両方で UI を更新する場合などです。
  • リスナーが、メディア アイテムの切り替え後のシークなど、さらにイベントをトリガーするために Player オブジェクトにアクセスする必要がある場合。
  • リスナーが、個別のコールバックで報告される複数の状態値を一緒に使用する場合、または Player ゲッター メソッドと組み合わせて使用する場合。 たとえば、Player.getCurrentWindowIndex()Timeline で提供される onTimelineChanged で使用できるのは、onEvents コールバック内からのみです。
  • リスナーが、イベントが論理的に同時に発生したかどうかに関心がある場合。 たとえば、メディア アイテムの切り替えが原因で onPlaybackStateChangedSTATE_BUFFERING になる場合などです。

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

コルーチンを使用して再生イベントをリッスンする

または、Player.listenTo を使用して Kotlin コルーチンを開始し、関連する Player.Event を指定することもできます。

Player.listenPlayer.listenTo はどの スレッドからでも呼び出すことができますが、コールバック ラムダは常に Player.getApplicationLooper に関連付けられたスレッドで呼び出されます。そのため、コルーチンが別のスレッドで開始された場合でも、コールバック ラムダ内で Player メソッドと状態プロパティに安全にアクセスできます。

再生状態の変化

coroutineScope.launch {
  player.listenTo(Player.EVENT_IS_PLAYING_CHANGED) {
    // `Player` is a receiver scope for this trailing lambda
    if (isPlaying) {
      // Active playback.
    } else {
      // Not playing.
    }
  }
}

再生エラー

coroutineScope.launch {
  player.listenTo(Player.EVENT_PLAYER_ERROR) {
    val error = playerError ?: return@listenTo
    val cause = error.cause
    if (cause is HttpDataSourceException) {
      // An HTTP error occurred.
      if (cause is InvalidResponseCodeException) {
        // Retrieve the response code, message and headers
      } else {
        // Try calling cause.cause to retrieve the underlying cause
      }
    }
  }
}

個々のコールバックと onEvents

コルーチン内でプレーヤー イベントをリッスンする場合、個々のコールバックではなく、常に onEventsコールバックの実装を提供します。ラムダの呼び出しをトリガーするイベントに応じて、Player.listenPlayer.listenTo を選択できます。ただし、関数は同等です。

聴く

coroutineScope.launch {
  player.listen { events ->
    // `Player` is a receiver scope for this trailing lambda
    if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)) {
      // Access the player state directly from the receiver
      updateUi(playbackState)
    }

    if (events.contains(Player.EVENT_PLAYER_ERROR)) {
      // Access the error directly from the player
      handleError(playerError)
    }
  }
}

listenTo

coroutineScope.launch {
  player.listenTo(Player.EVENT_PLAYBACK_STATE_CHANGED, Player.EVENT_PLAYER_ERROR) { events ->
    // `Player` is a receiver scope for this trailing lambda
    if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)) {
      // Access the player state directly from the receiver
      updateUi(playbackState)
    }

    if (events.contains(Player.EVENT_PLAYER_ERROR)) {
      // Access the error directly from the player
      handleError(playerError)
    }
  }
}

複数のイベントタイプに関心がある場合は、イベントのリストを Player.listenToに渡すことができます。これらのイベントのいずれかが発生すると、ラムダが呼び出されます。Events パラメータを調べて、実際に発生したイベントを確認できます。

coroutineScope.launch {
  player.listenTo(Player.EVENT_PLAYBACK_STATE_CHANGED, Player.EVENT_PLAYER_ERROR) { events ->
    // Unclear which event got triggered without querying `events` parameter
    // The following function will fire whenever either one is caught
    updateUiAndHandleError(playbackState, playerError)
  }
}

これらの関数は onEvents で動作するため、onMediaItemTransition(..., int reason) の理由や onPositionDiscontinuity(...)oldPosition など、個々のコールバックに渡される一時的な引数にはアクセスできません。ロジックがこれらの特定の引数に依存している場合 (Player状態プロパティとして使用できない場合)、代わりに標準の Player.Listener インターフェ 1 スを使用する必要があります。

AnalyticsListener を使用する

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

EventLogger を使用する

EventLogger は、ロギングを目的としてライブラリから直接提供される AnalyticsListener です。EventLoggerExoPlayer に追加すると、1 行で便利な追加ロギングを有効にできます。

Kotlin

player.addAnalyticsListener(EventLogger())

Java

player.addAnalyticsListener(new EventLogger());

詳細については、デバッグ ロギングのページをご覧ください。

指定した再生位置でイベントを発生させる

ユースケースによっては、指定した再生位置でイベントを発生させる必要があります。これは PlayerMessage を使用してサポートされています。PlayerMessageExoPlayer.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();