MediaSession を使用して再生を制御およびアドバタイズする

メディア セッションは音声や動画をユニバーサルな方法で操作できる 表示されます。Media3 のデフォルト プレーヤーは ExoPlayer クラスです。このクラスは、 Player インターフェース。メディア セッションをプレーヤーに接続すると、アプリが メディア再生を外部にアドバタイズし、メディアの再生コマンドを 外部ソースです。

コマンドは、メディアの再生ボタンなどの物理的なボタンから ヘッドセットやテレビのリモコンで操作できます。クライアント アプリの場合もあります。 メディア コントローラ(「一時停止」の指示など)Google アシスタントへ。メディア セッションは、これらのコマンドをメディアアプリのプレーヤーに委任します。

メディア セッションを選択する場合

MediaSession を実装すると、ユーザーが再生を制御できるようになります。

  • ヘッドフォンを使用する。多くの場合 ボタンやタップ操作は ユーザーはヘッドフォンでメディアの再生や一時停止、次のメディアへの移動ができます。 確認できます。
  • Google アシスタントに話しかける。よくあるパターンは、「OK Google, 一時停止」など)。
  • Wear OS スマートウォッチから操作する。これにより 一般的な再生コントロールを使用できます。
  • メディア コントロールを使用する。このカルーセルには、各設定のコントロールが できます。
  • テレビ物理的な再生ボタン、プラットフォームの再生による操作を許可します 電源管理(例: テレビ、サウンドバー、AV レシーバーなど) がオフになったり、入力が切り替わった場合は、アプリで再生が停止します。
  • 再生に影響を与える必要のあるその他の外部プロセス。

これは多くのユースケースに適しています。特に、Google Cloud での使用について 次の場合に MediaSession を使用:

  • 映画やライブテレビなどの長尺動画コンテンツをストリーミングしている。
  • ポッドキャストや音楽などの長時間の音声コンテンツをストリーミングしている 再生リスト
  • TV アプリを作成している。

ただし、すべてのユースケースが MediaSession に適しているわけではありません。おすすめ 次の場合は Player のみを使用します。

  • ユーザー エンゲージメントやインタラクションが発生するショート動画を表示している 非常に重要です
  • アクティブな動画が 1 本ない(ユーザーがリストをスクロールしているなど) 複数の動画が同時に画面に表示されます
  • 1 回限りの紹介動画または説明動画を再生しており、 ユーザーの積極的な視聴を期待できます
  • コンテンツがプライバシーに配慮し、外部プロセスに公開されるのを望まない メディア メタデータへのアクセス(ブラウザのシークレット モードなど)

ユースケースが上記のいずれにも当てはまらない場合は、 ユーザーが積極的に操作していないときにアプリが再生を続行しても問題ない 関連付けられます。答えが「はい」であれば、おそらく MediaSession。回答が「いいえ」の場合は、Player を使用することをおすすめします。 してください。

メディア セッションを作成する

メディア セッションは、それが管理するプレーヤーとともに存在します。新しい Pod を Context オブジェクトと Player オブジェクトを持つメディア セッション。データ アナリストとして 必要に応じてメディア セッションを初期化する(onStart()ActivityFragment、または onCreate()onResume() ライフサイクル メソッド メディア セッションとそれに関連付けられたプレーヤーを所有する Service のメソッド。

メディア セッションを作成するには、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 ライブラリは、 渡します。そのため、API プロバイダからのマッピングを手動で処理 必要があります。

これは、従来の手法とは別個に作成、維持する必要があった プレーヤー自体から独立した PlaybackStateCompat。たとえば、 エラーを示します。

一意のセッション ID

デフォルトでは、MediaSession.Builder は空の文字列のセッションを作成します。 渡されます。アプリが 1 つだけ作成する場合は、これで十分です。 これは最も一般的なケースです

アプリで複数のセッション インスタンスを同時に管理する場合、 各セッションのセッション ID が一意になるようにしますセッション ID は、 MediaSession.Builder.setId(String id) でセッションを作成するときに設定します。

エラーによりアプリをクラッシュさせる IllegalStateException が表示される場合 メッセージ IllegalStateException: Session ID must be unique. ID= の場合は、 以前作成されたセッションが予期せず作成された可能性がある インスタンスが解放されます。外部 IP アドレスによってセッションがリークしないようにするため、 そのようなケースが検出され、 発生します。

他のクライアントに制御権限を付与する

メディア セッションは、再生を制御する鍵となります。これにより、 再生するプレーヤーに外部ソースからのコマンドを できます。こうしたソースには、メディアの再生ボタンなどの ヘッドセットやテレビのリモコン、「一時停止」などの間接的なコマンド Google アシスタントへ。同様に、Android または iOS モバイル デバイスに 通知やロック画面のコントロールを容易にするシステム、または Wear OS への ウォッチフェイスから再生を操作できるようになります。外部クライアントは メディア コントローラを使用して、メディアアプリに再生コマンドを発行する。これらは デリゲートされ、最終的にコマンドがキューにデリゲートされます。 できます。

<ph type="x-smartling-placeholder">
</ph> MediaSession と MediaController の間のインタラクションを示す図。
図 1: メディア コントローラは、 コマンドを外部ソースからメディア セッションに送信できます。
で確認できます。

コントローラがメディア セッションに接続しようとすると、 onConnect() メソッドが呼び出されます。提供されている ControllerInfo を使用できます。 承諾するかどうかを決定する または拒否 表示されます。接続リクエストの受け入れ例については、宣言 使用可能なコマンドのセクションがあります。

接続後、コントローラからセッションに再生コマンドを送信できます。「 セッションはそれらのコマンドをプレーヤーに委任します。再生とプレイリスト Player インターフェースで定義されたコマンドは、 あります。

他のコールバック メソッドを使用すると、 カスタム再生コマンド 再生リストの変更など)。 これらのコールバックにも同様に ControllerInfo オブジェクトが含まれているため、 コントローラ単位で各リクエストにどのように応答するかを指定できます。

再生リストを変更する

メディア セッションは、 プレイリストに関する ExoPlayer ガイド。 コントローラは、次のいずれかの場合にも再生リストを変更できます。 COMMAND_SET_MEDIA_ITEM または COMMAND_CHANGE_MEDIA_ITEMS コントローラで利用可能になります。

再生リストに新しいアイテムを追加する場合、通常は MediaItem が必要です。 インスタンスが 定義済み URI プレイ可能にしますデフォルトでは、新しく追加したアイテムは自動的に転送されます。 URI が定義されている場合は、player.addMediaItem などのプレーヤー メソッドに追加します。

プレーヤーに追加された MediaItem インスタンスをカスタマイズする場合は、次の操作を行います。 オーバーライド onAddMediaItems()。 この手順は、メディアをリクエストするコントローラをサポートする場合に必要です。 渡されます。代わりに、通常、MediaItem には、 リクエストされたメディアを説明するために設定された、次のフィールドの 1 つ以上:

  • MediaItem.id: メディアを識別する汎用 ID。
  • MediaItem.RequestMetadata.mediaUri: カスタム URL を使用できるリクエスト URI 直接プレイできるとは限りません。
  • MediaItem.RequestMetadata.searchQuery: テキスト検索クエリ。例: 操作できます。
  • MediaItem.MediaMetadata: 構造化メタデータ(「title」など)入力します

まったく新しい再生リストをさらにカスタマイズするには、 さらにオーバーライドして onSetMediaItems() を使用して、再生リストの開始アイテムと位置を定義できます。たとえば リクエストした 1 つのアイテムを再生リスト全体に展開して、 プレーヤーは最初にリクエストされたアイテムのインデックスから再生を開始します。 onSetMediaItems() の実装例 セッションのデモアプリで確認できます。

カスタム レイアウトとカスタム コマンドを管理する

以降のセクションでは、カスタム レイアウトの広告を カスタム モードを送信するようにコントローラを認可し、 使用できます。

セッションのカスタム レイアウトを定義する

どの再生コントロールを表示するかをクライアント アプリに示すため セッションのカスタム レイアウトを設定します。 アプリケーションの onCreate() メソッドで MediaSession をビルドする場合、 あります。

Kotlin

override fun onCreate() {
  super.onCreate()

  val likeButton = CommandButton.Builder()
    .setDisplayName("Like")
    .setIconResId(R.drawable.like_icon)
    .setSessionCommand(SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING))
    .build()
  val favoriteButton = CommandButton.Builder()
    .setDisplayName("Save to favorites")
    .setIconResId(R.drawable.favorite_icon)
    .setSessionCommand(SessionCommand(SAVE_TO_FAVORITES, Bundle()))
    .build()

  session =
    MediaSession.Builder(this, player)
      .setCallback(CustomMediaSessionCallback())
      .setCustomLayout(ImmutableList.of(likeButton, favoriteButton))
      .build()
}

Java

@Override
public void onCreate() {
  super.onCreate();

  CommandButton likeButton = new CommandButton.Builder()
    .setDisplayName("Like")
    .setIconResId(R.drawable.like_icon)
    .setSessionCommand(new SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING))
    .build();
  CommandButton favoriteButton = new CommandButton.Builder()
    .setDisplayName("Save to favorites")
    .setIconResId(R.drawable.favorite_icon)
    .setSessionCommand(new SessionCommand(SAVE_TO_FAVORITES, new Bundle()))
    .build();

  Player player = new ExoPlayer.Builder(this).build();
  mediaSession =
      new MediaSession.Builder(this, player)
          .setCallback(new CustomMediaSessionCallback())
          .setCustomLayout(ImmutableList.of(likeButton, favoriteButton))
          .build();
}

利用可能なプレーヤーとカスタム コマンドを宣言する

メディア アプリケーションでは、カスタム コマンドを定義して、 作成できますたとえば、特定の Google サービス内で ユーザーは、お気に入りアイテムのリストにメディア アイテムを保存できます。MediaController カスタム コマンドを送信し、MediaSession.Callback がそれを受信します。

ユーザーで使用可能なカスタム セッション コマンドを定義できます。 MediaController: メディア セッションに接続したとき。これを実現するには MediaSession.Callback.onConnect() をオーバーライドする。設定と戻り 使用可能なコマンドのセットは、 onConnect コールバック メソッド内の MediaController:

Kotlin

private inner class CustomMediaSessionCallback: MediaSession.Callback {
  // Configure commands available to the controller in onConnect()
  override fun onConnect(
    session: MediaSession,
    controller: MediaSession.ControllerInfo
  ): MediaSession.ConnectionResult {
    val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
        .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY))
        .build()
    return AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build()
  }
}

Java

class CustomMediaSessionCallback implements MediaSession.Callback {
  // Configure commands available to the controller in onConnect()
  @Override
  public ConnectionResult onConnect(
    MediaSession session,
    ControllerInfo controller) {
    SessionCommands sessionCommands =
        ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
            .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle()))
            .build();
    return new AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
}

MediaController からカスタム コマンド リクエストを受信するには、 CallbackonCustomCommand() メソッド。

Kotlin

private inner class CustomMediaSessionCallback: MediaSession.Callback {
  ...
  override fun onCustomCommand(
    session: MediaSession,
    controller: MediaSession.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)
      )
    }
    ...
  }
}

Java

class CustomMediaSessionCallback 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)
      );
    }
    ...
  }
}

リクエストを行っているメディア コントローラをトラックするには、 次の MediaSession.ControllerInfo オブジェクトの packageName プロパティ。 Callback メソッドに渡されます。これにより、アプリのニーズや 特定のコマンドに対する動作(そのコマンドの実行元がシステム、 他のクライアント アプリにも適用できます。

ユーザー操作後にカスタム レイアウトを更新する

カスタム コマンドなど、プレーヤーの操作を処理した後で、 コントローラ UI に表示されるレイアウトを更新したい場合があります。典型的な例 は、関連付けられたアクションをトリガーした後にアイコンを変更する切り替えボタンです。 このボタンで操作できます。レイアウトを更新するには、次のコマンドを使用します。 MediaSession.setCustomLayout:

Kotlin

val removeFromFavoritesButton = CommandButton.Builder()
  .setDisplayName("Remove from favorites")
  .setIconResId(R.drawable.favorite_remove_icon)
  .setSessionCommand(SessionCommand(REMOVE_FROM_FAVORITES, Bundle()))
  .build()
mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton))

Java

CommandButton removeFromFavoritesButton = new CommandButton.Builder()
  .setDisplayName("Remove from favorites")
  .setIconResId(R.drawable.favorite_remove_icon)
  .setSessionCommand(new SessionCommand(REMOVE_FROM_FAVORITES, new Bundle()))
  .build();
mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton));

再生コマンドの動作をカスタマイズする

Player インターフェースで定義されたコマンドの動作をカスタマイズする場合( play() または seekToNext() として、PlayerForwardingPlayer でラップします。

Kotlin

val player = ExoPlayer.Builder(context).build()

val forwardingPlayer = object : ForwardingPlayer(player) {
  override fun play() {
    // Add custom logic
    super.play()
  }

  override fun setPlayWhenReady(playWhenReady: Boolean) {
    // Add custom logic
    super.setPlayWhenReady(playWhenReady)
  }
}

val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();

ForwardingPlayer forwardingPlayer = new ForwardingPlayer(player) {
  @Override
  public void play() {
    // Add custom logic
    super.play();
  }

  @Override
  public void setPlayWhenReady(boolean playWhenReady) {
    // Add custom logic
    super.setPlayWhenReady(playWhenReady);
  }
};

MediaSession mediaSession =
  new MediaSession.Builder(context, forwardingPlayer).build();

ForwardingPlayer の詳細については、ExoPlayer ガイドをご覧ください。 カスタマイズ

プレーヤー コマンドのリクエスト元コントローラを特定する

Player メソッドの呼び出しが MediaController から行われた場合は、次のことが可能です。 MediaSession.controllerForCurrentRequest で送信元を特定する 現在のリクエストの ControllerInfo を取得します。

Kotlin

class CallerAwareForwardingPlayer(player: Player) :
  ForwardingPlayer(player) {

  override fun seekToNext() {
    Log.d(
      "caller",
      "seekToNext called from package ${session.controllerForCurrentRequest?.packageName}"
    )
    super.seekToNext()
  }
}

Java

public class CallerAwareForwardingPlayer extends ForwardingPlayer {
  public CallerAwareForwardingPlayer(Player player) {
    super(player);
  }

  @Override
  public void seekToNext() {
    Log.d(
        "caller",
        "seekToNext called from package: "
            + session.getControllerForCurrentRequest().getPackageName());
    super.seekToNext();
  }
}

メディアボタンに反応する

メディアボタンは、Android デバイスやその他の周辺機器に搭載されているハードウェア ボタンです。 (Bluetooth ヘッドセットの再生/一時停止ボタンなど)。Media3 ハンドル メディアボタン イベントがセッションに到着し、 セッション プレーヤーの適切な Player メソッド。

デフォルトの動作は、デフォルト動作をオーバーライドするために MediaSession.Callback.onMediaButtonEvent(Intent)。その場合、アプリは すべての API 仕様を独自に処理する必要があります。

エラー処理と報告

セッションによって出力され、コントローラに報告されるエラーには 2 種類があります。 致命的なエラーは、セッションの技術的な再生エラーを報告する 再生を中断する場合もあります。致命的なエラーがコントローラに報告される 自動的に適用されます。非致命的なエラーは技術面以外のエラーまたはポリシー上のエラー エラーは再生を中断せず、メディア リクエストによって 手動で行う必要はありません。

致命的な再生エラー

致命的な再生エラーがプレーヤーによってセッションに報告され、その後 コントローラに報告され、 Player.Listener.onPlayerError(PlaybackException)Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException)

その場合、再生状態は STATE_IDLE に移行し、 MediaController.getPlaybackError() は、発生した PlaybackException を返します。 おすすめします。コントローラは PlayerException.errorCode を検査して、 エラーの理由に関する情報が表示されます。

相互運用性を確保するため、致命的なエラーが PlaybackStateCompat に複製されます。 プラットフォーム セッションの状態が STATE_ERROR に移行し、 PlaybackException に応じたエラーコードとメッセージ。

致命的なエラーのカスタマイズ

ローカライズされた意味のある情報、エラーコード、 致命的な再生エラーのエラー メッセージとエラー エクストラは、 セッションの作成時に ForwardingPlayer を使用します。

Kotlin

val forwardingPlayer = ErrorForwardingPlayer(player)
val session = MediaSession.Builder(context, forwardingPlayer).build()

Java

Player forwardingPlayer = new ErrorForwardingPlayer(player);
MediaSession session =
    new MediaSession.Builder(context, forwardingPlayer).build();

転送プレーヤーが Player.Listener を実際のプレーヤーに登録する エラーを報告するコールバックをインターセプトします。カスタマイズされた その後、PlaybackException は、 転送プレーヤーに登録されます。これを機能させるには、転送プレーヤーが Player.addListenerPlayer.removeListener をオーバーライドして、 カスタマイズしたエラーコード、メッセージ、エクストラを送信するリスナー:

Kotlin

class ErrorForwardingPlayer(private val context: Context, player: Player) :
  ForwardingPlayer(player) {

  private val listeners: MutableList<Player.Listener> = mutableListOf()

  private var customizedPlaybackException: PlaybackException? = null

  init {
    player.addListener(ErrorCustomizationListener())
  }

  override fun addListener(listener: Player.Listener) {
    listeners.add(listener)
  }

  override fun removeListener(listener: Player.Listener) {
    listeners.remove(listener)
  }

  override fun getPlayerError(): PlaybackException? {
    return customizedPlaybackException
  }

  private inner class ErrorCustomizationListener : Player.Listener {

    override fun onPlayerErrorChanged(error: PlaybackException?) {
      customizedPlaybackException = error?.let { customizePlaybackException(it) }
      listeners.forEach { it.onPlayerErrorChanged(customizedPlaybackException) }
    }

    override fun onPlayerError(error: PlaybackException) {
      listeners.forEach { it.onPlayerError(customizedPlaybackException!!) }
    }

    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)
        }
        // Apps can customize further error messages by adding more branches.
        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)
    }

    override fun onEvents(player: Player, events: Player.Events) {
      listeners.forEach {
        it.onEvents(player, events)
      }
    }
    // Delegate all other callbacks to all listeners without changing arguments like onEvents.
  }
}

Java

private static class ErrorForwardingPlayer extends ForwardingPlayer {

  private final Context context;
  private List<Player.Listener> listeners;
  @Nullable private PlaybackException customizedPlaybackException;

  public ErrorForwardingPlayer(Context context, Player player) {
    super(player);
    this.context = context;
    listeners = new ArrayList<>();
    player.addListener(new ErrorCustomizationListener());
  }

  @Override
  public void addListener(Player.Listener listener) {
    listeners.add(listener);
  }

  @Override
  public void removeListener(Player.Listener listener) {
    listeners.remove(listener);
  }

  @Nullable
  @Override
  public PlaybackException getPlayerError() {
    return customizedPlaybackException;
  }

  private class ErrorCustomizationListener implements Listener {

    @Override
    public void onPlayerErrorChanged(@Nullable PlaybackException error) {
      customizedPlaybackException =
          error != null ? customizePlaybackException(error, context) : null;
      for (int i = 0; i < listeners.size(); i++) {
        listeners.get(i).onPlayerErrorChanged(customizedPlaybackException);
      }
    }

    @Override
    public void onPlayerError(PlaybackException error) {
      for (int i = 0; i < listeners.size(); i++) {
        listeners.get(i).onPlayerError(checkNotNull(customizedPlaybackException));
      }
    }

    private PlaybackException customizePlaybackException(
        PlaybackException error, Context context) {
      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;
        // Apps can customize further error messages by adding more case statements.
        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);
    }

    @Override
    public void onEvents(Player player, Events events) {
      for (int i = 0; i < listeners.size(); i++) {
        listeners.get(i).onEvents(player, events);
      }
    }
    // Delegate all other callbacks to all listeners without changing arguments like onEvents.
  }
}

致命的でないエラー

技術的な例外に起因しない致命的でないエラーを送信できます。 または特定のコントローラに送信する場合:

Kotlin

val sessionError = SessionError(
  SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED,
  context.getString(R.string.error_message_authentication_expired),
)

// Sending a nonfatal error to all controllers.
mediaSession.sendError(sessionError)

// Interoperability: Sending a nonfatal error to the media notification controller 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));

// Sending a nonfatal error to all controllers.
mediaSession.sendError(sessionError);

// Interoperability: Sending a nonfatal error to the media notification controller 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);
}

メディア通知コントローラに送信された非致命的なエラーは、 プラットフォーム セッションの PlaybackStateCompat。これにより、エラーコードと それに応じてエラー メッセージが PlaybackStateCompat に設定され、 PlaybackStateCompat.stateSTATE_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.
              }
            });