プレーヤーの状態の変化(再生の開始、バッファリング、エラーなど)
は、登録済みの Player.Listener インスタンスに送信されるイベントをトリガーします。これらの
イベントは整数定数で表され、Player.Event
と Player.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.Listener で onPlaybackStateChanged(@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.Listener で onPlayerError(PlaybackException error) を実装することで受信できます。エラーが発生すると、再生状態が Player.STATE_IDLE に移行する直前にこのメソッドが呼び出されます。ExoPlayer.prepare を呼び出すことで、失敗した再生や停止した再生を再試行できます。
Player 実装によっては、
PlaybackException のサブクラスのインスタンスを渡して、エラーに関する追加情報を提供します。たとえば、ExoPlayer は ExoPlaybackException を渡します。これには、type、
rendererIndex、その他の 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 インスタンスに対して一連のコールバックが行われます。
onPositionDiscontinuity(reason=DISCONTINUITY_REASON_SEEK)。これは、Player.seekToを呼び出した直接の結果です。コールバックには、シーク前後の位置を示すPositionInfoフィールドがあります。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) を優先する必要があります。
- リスナーが複数のイベントに対して同じロジックをトリガーする場合。たとえば、
onPlaybackStateChangedとonPlayWhenReadyChangedの両方で UI を更新する場合などです。 - リスナーが、メディア アイテムの切り替え後のシークなど、さらにイベントをトリガーするために
Playerオブジェクトにアクセスする必要がある場合。 - リスナーが、個別のコールバックで報告される複数の状態値を一緒に使用する場合、または
Playerゲッター メソッドと組み合わせて使用する場合。 たとえば、Player.getCurrentWindowIndex()をTimelineで提供されるonTimelineChangedで使用できるのは、onEventsコールバック内からのみです。 - リスナーが、イベントが論理的に同時に発生したかどうかに関心がある場合。
たとえば、メディア アイテムの切り替えが原因で
onPlaybackStateChangedがSTATE_BUFFERINGになる場合などです。
場合によっては、リスナーが個々のコールバックと汎用 onEvents コールバックを組み合わせる必要があります。たとえば、onMediaItemTransition でメディア アイテムの変更理由を記録し、すべての状態変更を onEvents でまとめて使用できるようにする場合などです。
コルーチンを使用して再生イベントをリッスンする
または、Player.listenTo を使用して Kotlin コルーチンを開始し、関連する Player.Event を指定することもできます。
Player.listen と Player.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.listen と Player.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 です。EventLogger を ExoPlayer に追加すると、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();