プレーヤーの状態の変化(再生の開始、バッファリング、エラーなど)により、登録済みの 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 モジュールが提供する suspend 拡張関数を使用して、コルーチンでイベントをリッスンすることもできます。その場合、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はtruePlayer.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ゲッター メソッドと組み合わせて使用することを目的としています。たとえば、onTimelineChangedで提供されるTimelineでPlayer.getCurrentWindowIndex()を使用しても、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 コールバックの実装を提供します。どのイベントで Lambda の呼び出しをトリガーするかに応じて、Player.listen と Player.listenTo のいずれかを選択できます。ただし、それ以外の点では、この 2 つの関数は同等です。
聴く
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 インターフェースを使用する必要があります。
AnalyticsListener を使用する
ExoPlayer を使用する場合、addAnalyticsListener を呼び出すことで AnalyticsListener をプレーヤーに登録できます。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 を使用すると、指定された再生位置に到達するたびにメッセージを実行するか(シークモードやリピートモードにより複数回発生する可能性があります)、最初の 1 回だけ実行するかを制御できます。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();