MediaSession は、音声や動画のプレーヤーを操作するための汎用的な手段を提供します。Media3 では、デフォルトのプレーヤーは ExoPlayer クラスです。これは
Player インターフェースを実装します。メディア セッションをプレーヤーに接続すると、アプリはメディア再生を外部に公開し、外部ソースから再生コマンドを受け取ることができます。
コマンドは、ヘッドセットやテレビのリモコンの再生ボタンなどの物理ボタンから送信される場合があります。また、Google アシスタントに「一時停止」と指示するなど、メディア コントローラを備えたクライアント アプリから送信される場合もあります。メディア セッションは、これらのコマンドをメディア アプリのプレーヤーに委任します。
メディア セッションを選択する場合
MediaSession を実装すると、ユーザーは次の方法で再生を操作できます。
- ヘッドフォン 。ヘッドフォンには、メディアの再生や一時停止、次のトラックや前のトラックへの移動を行うためのボタンやタッチ操作が用意されていることがよくあります。
- Google アシスタント に話しかける。一般的なパターンは、「OK Google、一時停止」と言って、デバイスで現在再生中のメディアを一時停止することです。
- Wear OS スマートウォッチ 。これにより、スマートフォンで再生中に最も一般的な再生コントロールに簡単にアクセスできます。
- メディア コントロール 。このカルーセルには、実行中のメディア セッションごとにコントロールが表示されます。
- テレビ 。物理的な再生ボタン、プラットフォームの再生コントロール、電源管理(テレビ、サウンドバー、A/V レシーバーの電源が切れた場合や入力が切り替わった場合、アプリでの再生を停止するなど)を使用できます。
- Android Auto のメディア コントロール。これにより、運転中に安全に再生を操作できます。
- 再生に影響を与える必要のあるその他の外部プロセス。
これは多くのユースケースで役立ちます。特に、次のような場合は MediaSession の使用を強く検討してください。
- 映画やライブテレビなどの長尺の動画コンテンツをストリーミングしている場合。
- ポッドキャストや音楽 プレイリストなどの長尺の音声コンテンツをストリーミングしている場合。
- TV アプリ を作成している場合。
ただし、すべてのユースケースが MediaSession に適しているわけではありません。次のような場合は、Player のみを使用することをおすすめします。
- 外部コントロールやバックグラウンド 再生を必要としないショートフォーム コンテンツを表示している場合。
- ユーザーがリストをスクロールしているときに複数の動画が同時に画面に表示される など、アクティブな動画が 1 つではない場合。
- ユーザーが外部再生コントロールを必要とせずに積極的に視聴することを想定している一度限りの紹介動画や解説動画を再生している場合。
- コンテンツがプライバシーに配慮した もので、外部プロセスがメディア メタデータにアクセスすることを望まない場合(ブラウザのシークレット モードなど)。
上記のいずれにも当てはまらないユースケースの場合は、ユーザーがコンテンツを積極的に操作していないときにアプリが再生を続行しても問題ないかどうかを検討してください。問題ない場合は、MediaSession を選択することをおすすめします。問題がある場合は、代わりに Player を使用することをおすすめします。
メディア セッションを作成する
メディア セッションは、それが管理するプレーヤーとともに存在します。メディア セッションは、Context オブジェクトと Player オブジェクトを使用して作成できます。メディア セッションの作成と初期化は、必要なときに行う必要があります。たとえば、Activity または Fragment の onStart() または onResume() ライフサイクル メソッド、またはメディア セッションとその関連プレーヤーを所有する Service の onCreate() メソッドなどです。
メディア セッションを作成するには、Player を初期化して、次のように MediaSession.Builder に渡します。
Kotlin
val player = ExoPlayer.Builder(context).build() val mediaSession = MediaSession.Builder(context, player).build()
Java
ExoPlayer player = new ExoPlayer.Builder(context).build(); MediaSession mediaSession = new MediaSession.Builder(context, player).build();
状態の自動処理
Media3 ライブラリは、プレーヤーの状態を使用してメディア セッションを自動的に更新します。そのため、プレーヤーからセッションへのマッピングを手動で処理する必要はありません。
これは、プラットフォームのメディア セッションとは異なります。プラットフォームのメディア セッションでは、プレーヤー自体とは別に PlaybackState を作成して
維持する必要がありました。たとえば、
エラーを示す場合などです。
一意のセッション ID
デフォルトでは、MediaSession.Builder はセッション ID として空の文字列を使用してセッションを作成します。アプリが 1 つのセッション インスタンスのみを作成する場合(最も一般的なケース)は、これで十分です。
アプリが複数のセッション インスタンスを同時に管理する場合は、各セッションのセッション ID が一意であることを確認する必要があります。セッション ID は、MediaSession.Builder.setId(String id) を使用してセッションをビルドするときに設定できます。
IllegalStateException というエラー
メッセージ IllegalStateException: Session ID must be unique. ID= でアプリがクラッシュした場合は、同じ ID を持つ以前に作成された
インスタンスが解放される前に、セッションが予期せず作成された可能性があります。プログラミング エラーによってセッションがリークしないように、このようなケースは例外をスローして検出され、通知されます。
他のクライアントに制御を許可する
メディア セッションは、再生を制御するための鍵となります。これにより、外部ソースからのコマンドを、メディアの再生を行うプレーヤーにルーティングできます。これらのソースは、ヘッドセットやテレビのリモコンの再生ボタンなどの物理ボタンや、Google アシスタントに「一時停止」と指示するなどの間接的なコマンドです。同様に、通知やロック画面のコントロールを容易にするために Android システムへのアクセスを許可したり、ウォッチフェイスから再生を操作できるように Wear OS スマートウォッチへのアクセスを許可したりすることもできます。外部クライアントは、メディア コントローラを使用してメディアアプリに再生コマンドを発行できます。これらのコマンドはメディア セッションで受信され、最終的にメディア プレーヤーに委任されます。
コントローラがメディア セッションに接続しようとすると、
onConnect()
メソッドが呼び出されます。提供された ControllerInfo
を使用して、リクエストを 受け入れる
か 拒否する
かを決定できます。接続リクエストを受け入れる例については、カスタム コマンドを宣言するをご覧ください。
接続後、コントローラはセッションに再生コマンドを送信できます。セッションは、これらのコマンドをプレーヤーに委任します。Player インターフェースで定義された再生コマンドとプレイリスト コマンドは、セッションによって自動的に処理されます。
その他のコールバック メソッドを使用すると、たとえば、カスタム
コマンドのリクエストやプレイリストの変更などを処理できます。これらのコールバックにも同様に ControllerInfo オブジェクトが含まれているため、コントローラごとに各リクエストへの応答方法を変更できます。
プレイリストを変更する
メディア セッションは、プレイリストに関する
the
ExoPlayer ガイドで説明されているように、プレーヤーのプレイリストを直接変更できます。
コントローラで COMMAND_SET_MEDIA_ITEM または COMMAND_CHANGE_MEDIA_ITEMS を使用できる場合、コントローラはプレイリストを変更することもできます。
プレイリストに新しいアイテムを追加する場合、プレーヤーは通常、MediaItem
再生可能な URI が
定義された
インスタンスを必要とします。デフォルトでは、URI が定義されている場合、新しく追加されたアイテムは player.addMediaItem などのプレーヤー メソッドに自動的に転送されます。
プレーヤーに追加された MediaItem インスタンスをカスタマイズする場合は、
オーバーライド
onAddMediaItems()できます。
この手順は、定義された URI なしでメディアをリクエストするコントローラをサポートする場合に必要です。通常、MediaItem には、リクエストされたメディアを記述するために、次のフィールドの 1 つ以上が設定されています。
MediaItem.id: メディアを識別する汎用 ID。MediaItem.RequestMetadata.mediaUri: カスタム スキーマを使用する可能性があり、プレーヤーで直接再生できるとは限らないリクエスト URI。MediaItem.RequestMetadata.searchQuery: テキスト検索クエリ(Google アシスタントなど)。MediaItem.MediaMetadata: 「タイトル」や「アーティスト」などの構造化メタデータ。
完全に新しいプレイリストのカスタマイズ オプションについては、プレイリストの開始アイテムと位置を定義できる
additionally override
onSetMediaItems()
をオーバーライドすることもできます。たとえば、リクエストされた 1 つのアイテムをプレイリスト全体に展開し、プレーヤーに最初にリクエストされたアイテムのインデックスから開始するように指示できます。この機能を備えた
サンプル実装は、セッション デモアプリにあります。onSetMediaItems()
メディアボタンの設定を管理する
すべてのコントローラ(システム UI、Android Auto、Wear OS など)は、ユーザーに表示するボタンを独自に決定できます。ユーザーに公開する再生コントロールを示すには、MediaSession でメディアボタンの設定を指定します。 これらの設定は、CommandButton インスタンスの順序付きリストで構成され、それぞれがユーザー インターフェースのボタンの設定を定義します。
コマンドボタンを定義する
CommandButton インスタンスは、メディアボタンの設定を定義するために使用されます。すべてのボタンは、目的の UI 要素の 3 つの側面を定義します。
- アイコン。視覚的な外観を定義します。アイコンは、
CommandButton.Builderを作成するときに、事前定義された定数のいずれかに設定する必要があります。これは実際のビットマップや画像リソースではありません。汎用的な定数を使用すると、コントローラは独自の UI 内で一貫したルック&フィールを実現する適切なリソースを選択できます。事前定義されたアイコン定数のいずれもユースケースに合わない場合は、代わりにsetCustomIconResIdを使用できます。 - コマンド。ユーザーがボタンを操作したときにトリガーされるアクションを定義します。
Player.CommandにはsetPlayerCommandを使用し、SessionCommandにはsetSessionCommandを使用します。 - スロット。コントローラ UI でボタンを配置する場所を定義します。このフィールドは省略可能で、アイコンと コマンドに基づいて自動的に設定されます。たとえば、ボタンをデフォルトの「オーバーフロー」領域ではなく、UI の「前方」ナビゲーション領域に表示するように指定できます。
Kotlin
val button = CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15) .setPlayerCommand(Player.COMMAND_SEEK_FORWARD) .setSlots(CommandButton.SLOT_FORWARD) .build()
Java
CommandButton button = new CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15) .setPlayerCommand(Player.COMMAND_SEEK_FORWARD) .setSlots(CommandButton.SLOT_FORWARD) .build();
メディアボタンの設定が解決されると、次のアルゴリズムが適用されます。
- メディアボタンの設定の各
CommandButtonについて、使用可能な最初の スロットにボタン を配置します。 - 中央、前方、後方のスロットのいずれかに ボタンが配置されていない場合は、このスロットのデフォルトのボタンを追加します。
CommandButton.DisplayConstraints を使用すると、UI の表示制約に応じてメディアボタンの設定がどのように解決されるかのプレビューを生成できます。
メディアボタンの設定を行う
メディアボタンの設定を行う最も簡単な方法は、MediaSession をビルドするときにリストを定義することです。または、MediaSession.Callback.onConnect をオーバーライドして、接続されているコントローラごとにメディアボタンの設定をカスタマイズすることもできます。
Kotlin
val mediaSession = MediaSession.Builder(context, player) .setMediaButtonPreferences(ImmutableList.of(likeButton, favoriteButton)) .build()
Java
MediaSession mediaSession = new MediaSession.Builder(context, player) .setMediaButtonPreferences(ImmutableList.of(likeButton, favoriteButton)) .build();
ユーザー インタラクション後にメディアボタンの設定を更新する
プレーヤーとの操作を処理した後、コントローラ UI に表示されるボタンを更新することが必要になる場合があります。一般的な例としては、このボタンに関連付けられたアクションをトリガーした後にアイコンとアクションを変更する切り替えボタンがあります。メディアボタンの設定を更新するには、MediaSession.setMediaButtonPreferences を使用して、すべてのコントローラまたは特定のコントローラの設定を更新します。
Kotlin
// Handle "favoritesButton" action, replace by opposite button mediaSession.setMediaButtonPreferences(ImmutableList.of(likeButton, removeFromFavoritesButton))
Java
// Handle "favoritesButton" action, replace by opposite button mediaSession.setMediaButtonPreferences(ImmutableList.of(likeButton, removeFromFavoritesButton));
カスタム コマンドを追加してデフォルトの動作をカスタマイズする
使用可能なプレーヤー コマンドはカスタム コマンドで拡張できます。また、受信したプレーヤー コマンドとメディアボタンをインターセプトしてデフォルトの動作を変更することもできます。
カスタム コマンドを宣言して処理する
メディア アプリケーションでは、
メディアボタンの設定などで使用できるカスタム コマンドを定義できます。たとえば、ユーザーがメディア アイテムをお気に入りアイテムのリストに保存できるようにするボタンを実装できます。MediaController はカスタム コマンドを送信し、MediaSession.Callback はそれを受信します。
カスタム コマンドを定義するには、MediaSession.Callback.onConnect() をオーバーライドして、接続されているコントローラごとに使用可能なカスタム コマンドを設定する必要があります。
Kotlin
private class CustomMediaSessionCallback : MediaSession.Callback { // Configure commands available to the controller in onConnect() override fun onConnectAsync( session: MediaSession, controller: ControllerInfo, ): ListenableFuture<ConnectionResult> { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY)) .build() return Futures.immediateFuture( AcceptedResultBuilder(session).setAvailableSessionCommands(sessionCommands).build() ) } }
Java
private static class CustomMediaSessionCallback implements MediaSession.Callback { // Configure commands available to the controller in onConnect() @Override public ListenableFuture<ConnectionResult> onConnectAsync( MediaSession session, ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS .buildUpon() .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle())) .build(); return Futures.immediateFuture( new AcceptedResultBuilder(session).setAvailableSessionCommands(sessionCommands).build()); } }
MediaController からカスタム コマンド リクエストを受信するには、Callback の onCustomCommand() メソッドをオーバーライドします。
Kotlin
private class CustomCallback : MediaSession.Callback { // ... override fun onCustomCommand( session: MediaSession, controller: ControllerInfo, customCommand: SessionCommand, args: Bundle, ): ListenableFuture<SessionResult> { if (customCommand.customAction == SAVE_TO_FAVORITES) { // Do custom logic here saveToFavorites(session.player.currentMediaItem) return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) } // ... return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) } }
Java
private static class CustomCallback implements MediaSession.Callback { // ... @Override public ListenableFuture<SessionResult> onCustomCommand( MediaSession session, ControllerInfo controller, SessionCommand customCommand, Bundle args) { if (customCommand.customAction.equals(SAVE_TO_FAVORITES)) { // Do custom logic here saveToFavorites(session.getPlayer().getCurrentMediaItem()); return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS)); } // ... return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS)); } }
Callback メソッドに渡される MediaSession.ControllerInfo オブジェクトの packageName プロパティを使用すると、リクエストを行っているメディア コントローラを追跡できます。これにより、システム、独自のアプリ、または他のクライアント アプリから送信されたコマンドに応じて、アプリの動作を調整できます。
デフォルトのプレーヤー コマンドをカスタマイズする
すべてのデフォルト コマンドと状態処理は、MediaSession 上の Player に委任されます。
Player インターフェースで定義されたコマンド(play() や seekToNext() など)の動作をカスタマイズするには、Player を
ForwardingSimpleBasePlayer でラップしてから MediaSession に渡します。
Kotlin
val forwardingPlayer = object : ForwardingSimpleBasePlayer(player) { // Customizations } val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()
Java
ForwardingSimpleBasePlayer forwardingPlayer = new ForwardingSimpleBasePlayer(player) { // Customizations }; MediaSession mediaSession = new MediaSession.Builder(context, forwardingPlayer).build();
ForwardingSimpleBasePlayer の詳細については、カスタマイズに関する ExoPlayer ガイドをご覧ください。
プレーヤー コマンドのリクエスト元のコントローラを特定する
Player メソッドの呼び出しが MediaController によって開始された場合は、MediaSession.controllerForCurrentRequest で送信元を特定し、現在のリクエストの ControllerInfo を取得できます。
Kotlin
private class CallerAwarePlayer(player: Player) : ForwardingSimpleBasePlayer(player) { private lateinit var session: MediaSession override fun handleSeek( mediaItemIndex: Int, positionMs: Long, seekCommand: Int, ): ListenableFuture<*> { Log.d( "caller", "seek operation from package ${session.controllerForCurrentRequest?.packageName}", ) return super.handleSeek(mediaItemIndex, positionMs, seekCommand) } }
Java
private static final class CallerAwarePlayer extends ForwardingSimpleBasePlayer { private MediaSession session; public CallerAwarePlayer(Player player) { super(player); } @Override protected ListenableFuture<?> handleSeek(int mediaItemIndex, long positionMs, int seekCommand) { Log.d( "caller", "seek operation from package: " + session.getControllerForCurrentRequest().getPackageName()); return super.handleSeek(mediaItemIndex, positionMs, seekCommand); } }
メディアボタンの処理をカスタマイズする
メディアボタンとは、Android デバイスや周辺機器にあるハードウェア ボタンのことです。たとえば、Bluetooth ヘッドセットの再生/一時停止ボタンなどです。Media3 は、セッションに到着した
メディアボタン イベントを処理し、セッション プレーヤーで
適切な Player メソッドを呼び出します。
受信したすべてのメディアボタン イベントは、対応する Player メソッドで処理することをおすすめします。より高度なユースケースでは、MediaSession.Callback.onMediaButtonEvent(Intent) でメディアボタン イベントをインターセプトできます。
エラー処理とレポート
セッションが発行してコントローラに報告するエラーには、次の 2 種類があります。 致命的なエラーは、再生を中断するセッション プレーヤーの技術的な再生エラーを報告します。致命的なエラーが発生すると、コントローラに自動的に報告されます。致命的でないエラーは、技術的なエラーやポリシー エラーではなく、再生を中断せず、アプリによってコントローラに手動で送信されます。
致命的な再生エラー
致命的な再生エラーは、プレーヤーによってセッションに報告され、Player.Listener.onPlayerError(PlaybackException) と Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException) を呼び出してコントローラに報告されます。
この場合、再生状態は STATE_IDLE に移行し、MediaController.getPlaybackError() は移行の原因となった PlaybackException を返します。コントローラは PlayerException.errorCode を調べて、エラーの原因に関する情報を取得できます。
カスタム プレーヤー エラーを設定する
プレーヤーによって報告される致命的なエラーに加えて、アプリは
MediaSession レベルでカスタム PlaybackException を
MediaSession.setPlaybackException(PlaybackException)を使用して設定できます。これにより、アプリは接続されているコントローラにエラー状態を通知できます。例外は、接続されているすべてのコントローラまたは特定の ControllerInfo に対して設定できます。
アプリがこの API を使用して PlaybackException を設定すると、次のようになります。
接続されている
MediaControllerインスタンスに通知されます。コントローラのListener.onPlayerError(PlaybackException)コールバックとListener.onPlayerErrorChanged(@Nullable PlaybackException)コールバックが、指定された例外で呼び出されます。MediaController.getPlayerError()メソッドは、アプリによって設定されたPlaybackExceptionを返します。影響を受けるコントローラの再生状態が
Player.STATE_IDLEに変わります。使用可能なコマンドが削除され、
COMMAND_GET_TIMELINEなどの読み取りコマンドのみが、すでに許可されている場合に残ります。たとえば、Timelineの状態は、例外がコントローラに適用されたときの状態に固定されます。特定のコントローラの再生例外がアプリによって削除されるまで、COMMAND_PLAYなどのプレーヤーの状態を変更しようとするコマンドは削除されます。
以前に設定したカスタム PlaybackException をクリアして、通常の
プレーヤーの状態レポートを復元するには、アプリで
MediaSession.setPlaybackException(/* playbackException= */ null) または
MediaSession.setPlaybackException(ControllerInfo,
/* playbackException= */ null) を呼び出します。
致命的なエラーのカスタマイズ
ローカライズされた有意義な情報をユーザーに提供するには、実際のプレーヤーから送信される致命的な再生エラーのエラーコード、エラー メッセージ、エラー エクストラをカスタマイズします。これは、セッションをビルドするときに ForwardingPlayer を使用することで実現できます。
Kotlin
val session = MediaSession.Builder(context, ErrorForwardingPlayer(context, player)).build()
Java
MediaSession session = new MediaSession.Builder(context, new ErrorForwardingPlayer(context, player)).build();
転送プレーヤーは ForwardingSimpleBasePlayer を使用してエラーをインターセプトし、エラーコード、メッセージ、エクストラをカスタマイズできます。同様に、元のプレーヤーに存在しない新しいエラーを生成することもできます。
Kotlin
private class ErrorForwardingPlayer(private val context: Context, player: Player) : ForwardingSimpleBasePlayer(player) { override fun getState(): State { var state = super.getState() if (state.playerError != null) { state = state.buildUpon().setPlayerError(customizePlaybackException(state.playerError!!)).build() } return state } private fun customizePlaybackException(error: PlaybackException): PlaybackException { val buttonLabel: String val errorMessage: String when (error.errorCode) { PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW -> { buttonLabel = context.getString(R.string.err_button_label_restart_stream) errorMessage = context.getString(R.string.err_msg_behind_live_window) } else -> { buttonLabel = context.getString(R.string.err_button_label_ok) errorMessage = context.getString(R.string.err_message_default) } } val extras = Bundle() extras.putString("button_label", buttonLabel) return PlaybackException(errorMessage, error.cause, error.errorCode, extras) } }
Java
private static class ErrorForwardingPlayer extends ForwardingSimpleBasePlayer { private final Context context; public ErrorForwardingPlayer(Context context, Player player) { super(player); this.context = context; } @Override protected State getState() { State state = super.getState(); if (state.playerError != null) { state = state.buildUpon().setPlayerError(customizePlaybackException(state.playerError)).build(); } return state; } private PlaybackException customizePlaybackException(PlaybackException error) { String buttonLabel; String errorMessage; switch (error.errorCode) { case PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW: buttonLabel = context.getString(R.string.err_button_label_restart_stream); errorMessage = context.getString(R.string.err_msg_behind_live_window); break; default: buttonLabel = context.getString(R.string.err_button_label_ok); errorMessage = context.getString(R.string.err_message_default); break; } Bundle extras = new Bundle(); extras.putString("button_label", buttonLabel); return new PlaybackException(errorMessage, error.getCause(), error.errorCode, extras); } }
致命的でないエラー
技術的な例外から発生していない致命的でないエラーは、アプリからすべてのコントローラまたは特定のコントローラに送信できます。
Kotlin
val sessionError = SessionError( SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED, context.getString(R.string.error_message_authentication_expired), ) // Option 1: Sending a nonfatal error to all controllers. mediaSession.sendError(sessionError) // Option 2: Sending a nonfatal error to the media notification controller only // to set the error code and error message in the playback state of the platform // media session. mediaSession.mediaNotificationControllerInfo?.let { mediaSession.sendError(it, sessionError) }
Java
SessionError sessionError = new SessionError( SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED, context.getString(R.string.error_message_authentication_expired)); // Option 1: Sending a nonfatal error to all controllers. mediaSession.sendError(sessionError); // Option 2: Sending a nonfatal error to the media notification controller only // to set the error code and error message in the playback state of the platform // media session. ControllerInfo mediaNotificationControllerInfo = mediaSession.getMediaNotificationControllerInfo(); if (mediaNotificationControllerInfo != null) { mediaSession.sendError(mediaNotificationControllerInfo, sessionError); }
致命的でないエラーがメディア通知コントローラに送信されると、エラーコードとエラー メッセージがプラットフォーム メディア セッションに複製されますが、PlaybackState.state は STATE_ERROR に変更されません。
致命的でないエラーを受信する
MediaController は、
MediaController.Listener.onError を実装することで、致命的でないエラーを受信します。
Kotlin
val future = MediaController.Builder(context, sessionToken) .setListener( object : MediaController.Listener { override fun onError(controller: MediaController, sessionError: SessionError) { // Handle nonfatal error. } } ) .buildAsync()
Java
MediaController.Builder future = new MediaController.Builder(context, sessionToken) .setListener( new MediaController.Listener() { @Override public void onError(MediaController controller, SessionError sessionError) { // Handle nonfatal error. } });