Mengontrol dan mengiklankan pemutaran menggunakan MediaSession

Sesi media memberikan cara universal untuk berinteraksi dengan audio atau video web. Di Media3, pemutar default adalah class ExoPlayer, yang mengimplementasikan antarmuka Player. Menghubungkan sesi media ke pemutar memungkinkan aplikasi untuk mengiklankan pemutaran media secara eksternal dan untuk menerima perintah pemutaran dari sumber eksternal.

Perintah dapat berasal dari tombol fisik seperti tombol putar pada headset atau remote control TV. Sumber data tersebut mungkin juga berasal dari aplikasi klien yang memiliki pengontrol media, seperti memerintahkan "jeda" ke Asisten Google. Media mendelegasikan perintah ini ke pemutar aplikasi media.

Kapan harus memilih sesi media

Saat mengimplementasikan MediaSession, Anda mengizinkan pengguna mengontrol pemutaran:

  • Melalui headphone mereka. Sering kali ada tombol atau interaksi sentuh yang yang dapat dijalankan di headphone untuk memutar, menjeda media, atau membuka atau lagu sebelumnya.
  • Dengan berbicara ke Asisten Google. Pola yang umum adalah mengucapkan "OK Google, jeda" untuk menjeda media apa pun yang sedang diputar di perangkat.
  • Melalui smartwatch Wear OS. Hal ini memungkinkan akses yang lebih mudah ke yang paling kontrol pemutaran yang umum saat memutar media di ponsel mereka.
  • Melalui Kontrol media. Carousel ini menampilkan kontrol untuk setiap sesi media yang berjalan.
  • Di TV. Memungkinkan tindakan dengan tombol pemutaran fisik, pemutaran platform kontrol, dan pengelolaan daya (misalnya, jika TV, soundbar, atau penerima A/V akan nonaktif atau input dialihkan, pemutaran akan berhenti di aplikasi).
  • Serta proses eksternal lainnya yang perlu memengaruhi pemutaran.

Ini bagus untuk banyak kasus penggunaan. Secara khusus, Anda harus mempertimbangkan menggunakan MediaSession saat:

  • Anda melakukan streaming konten video panjang, seperti film atau TV live.
  • Anda melakukan streaming konten audio berdurasi panjang, seperti podcast atau musik playlist.
  • Anda sedang membuat aplikasi TV.

Namun, tidak semua kasus penggunaan cocok dengan MediaSession. Anda mungkin ingin hanya gunakan Player dalam kasus berikut:

  • Anda menampilkan konten video pendek, tempat interaksi dan engagement pengguna sangatlah penting.
  • Tidak ada satu video pun yang aktif, seperti pengguna sedang men-scroll daftar dan beberapa video ditampilkan di layar secara bersamaan.
  • Anda memutar video pengantar atau penjelasan satu kali, yang Anda harapkan akan terus ditonton pengguna Anda.
  • Konten Anda sensitif privasi dan Anda tidak ingin proses eksternal mengakses metadata media (misalnya mode samaran di browser)

Jika kasus penggunaan Anda tidak sesuai dengan salah satu yang tercantum di atas, pertimbangkan apakah Anda tidak masalah jika aplikasi melanjutkan pemutaran saat pengguna tidak berinteraksi secara aktif dengan kontennya. Jika jawabannya ya, Anda mungkin ingin memilih MediaSession. Jika jawabannya tidak, Anda mungkin ingin menggunakan Player sebagai gantinya.

Membuat sesi media

Sesi media berdampingan dengan pemutar yang dikelolanya. Anda dapat membangun sesi media dengan objek Context dan Player. Anda harus membuat dan melakukan inisialisasi sesi media saat diperlukan, seperti onStart() atau Metode siklus proses onResume() dari Activity atau Fragment, atau onCreate() Service yang memiliki sesi media dan pemutar terkaitnya.

Untuk membuat sesi media, lakukan inisialisasi Player dan berikan ke MediaSession.Builder seperti ini:

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();

Penanganan status otomatis

Library Media3 secara otomatis memperbarui sesi media menggunakan status pemutar. Dengan demikian, Anda tidak perlu menangani pemetaan secara manual dari pemain ke sesi panggilan.

Hal ini merupakan jeda dari pendekatan lama, yang mengharuskan Anda membuat dan mengelola project PlaybackStateCompat secara terpisah dari pemutar itu sendiri, misalnya untuk menunjukkan {i>error<i}.

ID sesi unik

Secara default, MediaSession.Builder membuat sesi dengan string kosong sebagai ID sesi. Tindakan ini sudah cukup jika aplikasi hanya bermaksud membuat satu instance sesi, yang merupakan kasus yang paling umum.

Jika sebuah aplikasi ingin mengelola beberapa instance sesi secara bersamaan, harus memastikan bahwa ID sesi dari setiap sesi adalah unik. ID sesi dapat ditetapkan saat membuat sesi dengan MediaSession.Builder.setId(String id).

Jika Anda melihat IllegalStateException membuat aplikasi Anda error dan mengalami error kirim pesan IllegalStateException: Session ID must be unique. ID= maka kemungkinan bahwa sesi telah dibuat secara tidak terduga sebelum sesi dibuat instance dengan ID yang sama telah dirilis. Untuk menghindari sesi dibocorkan oleh {i>error <i}pemrograman, kasus tersebut terdeteksi dan diberi tahu dengan melemparkan pengecualian.

Memberikan kontrol ke klien lain

Sesi media adalah kunci untuk mengontrol pemutaran. Ini memungkinkan Anda untuk merutekan perintah dari sumber eksternal ke pemutar yang bertugas memutar lainnya. Sumber ini dapat berupa tombol fisik seperti tombol putar di headset atau remote control TV, atau perintah tidak langsung seperti memerintahkan "jeda" ke Asisten Google. Demikian juga, Anda mungkin ingin memberikan akses ke antarmuka untuk memfasilitasi kontrol notifikasi dan layar kunci, atau ke Wear OS menonton sehingga Anda dapat mengontrol pemutaran dari tampilan jam. Klien eksternal dapat menggunakan pengontrol media untuk mengeluarkan perintah pemutaran ke aplikasi media Anda. Berikut adalah yang diterima oleh sesi media Anda, yang pada akhirnya mendelegasikan perintah ke pemutar media.

Diagram menunjukkan interaksi antara MediaSession dan MediaController.
Gambar 1: Pengontrol media memfasilitasi penerusan perintah dari sumber eksternal ke sesi media.

Saat pengontrol akan terhubung ke sesi media Anda, onConnect() dipanggil. Anda dapat menggunakan ControllerInfo yang disediakan untuk memutuskan apakah akan menerima atau tolak terhadap permintaan. Lihat contoh penerimaan permintaan koneksi dalam dokumentasi Deklarasi perintah yang tersedia.

Setelah terhubung, pengontrol dapat mengirimkan perintah pemutaran ke sesi. Tujuan lalu mendelegasikan perintah tersebut ke pemain. Pemutaran dan playlist perintah yang ditentukan dalam antarmuka Player secara otomatis ditangani oleh sesi.

Metode callback lainnya memungkinkan Anda menangani, misalnya, permintaan untuk perintah pemutaran khusus dan mengubah playlist). Callback ini juga menyertakan objek ControllerInfo sehingga Anda dapat memodifikasi cara Anda merespons setiap permintaan per pengontrol.

Mengubah playlist

Sesi media dapat langsung memodifikasi playlist pemutarnya seperti yang dijelaskan dalam tindakan Panduan ExoPlayer untuk playlist. Pengontrol juga dapat memodifikasi playlist jika salah satu COMMAND_SET_MEDIA_ITEM atau COMMAND_CHANGE_MEDIA_ITEMS tersedia untuk pengontrol.

Saat menambahkan item baru ke playlist, pemutar biasanya memerlukan MediaItem dengan instance URI yang ditentukan untuk membuat video tersebut dapat diputar. Secara default, item yang baru ditambahkan akan otomatis diteruskan ke metode pemutar seperti player.addMediaItem jika mereka telah menentukan URI.

Jika ingin menyesuaikan instance MediaItem yang ditambahkan ke pemutar, Anda dapat abaikan onAddMediaItems(). Langkah ini diperlukan jika Anda ingin mendukung pengontrol yang meminta media tanpa URI yang ditentukan. Sebagai gantinya, MediaItem biasanya memiliki satu atau beberapa kolom berikut ditetapkan untuk mendeskripsikan media yang diminta:

  • MediaItem.id: ID umum yang mengidentifikasi media.
  • MediaItem.RequestMetadata.mediaUri: URI permintaan yang mungkin menggunakan URL kustom skema dan belum tentu dapat dimainkan secara langsung oleh pemain.
  • MediaItem.RequestMetadata.searchQuery: Kueri penelusuran tekstual, misalnya dari Asisten Google.
  • MediaItem.MediaMetadata: Metadata terstruktur seperti 'title' atau 'artis'.

Untuk opsi penyesuaian lainnya untuk daftar putar yang benar-benar baru, Anda dapat juga mengganti onSetMediaItems() yang memungkinkan Anda menentukan item awal dan posisi dalam playlist. Misalnya, Anda dapat memperluas satu item yang diminta ke seluruh playlist dan memerintahkan mulai dari indeks item yang diminta sebelumnya. J contoh implementasi onSetMediaItems() dengan fitur ini dapat ditemukan di aplikasi demo sesi.

Mengelola tata letak khusus dan perintah khusus

Bagian berikut menjelaskan cara mengiklankan tata letak kustom ke aplikasi klien dan memberi otorisasi kepada pengontrol untuk mengirim perintah.

Menentukan tata letak kustom sesi

Untuk menunjukkan kepada aplikasi klien kontrol pemutaran mana yang ingin Anda tampilkan pengguna, setel tata letak kustom sesi saat membuat MediaSession dalam metode onCreate() dari layanan.

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();
}

Mendeklarasikan pemutar yang tersedia dan perintah kustom

Aplikasi media dapat menentukan perintah khusus yang, misalnya, dapat digunakan dalam tata letak khusus. Misalnya, Anda mungkin ingin mengimplementasikan tombol yang memungkinkan pengguna untuk menyimpan item media ke daftar item favorit. MediaController mengirimkan perintah kustom dan MediaSession.Callback menerimanya.

Anda dapat menentukan perintah sesi khusus mana yang tersedia untuk MediaController saat terhubung ke sesi media Anda. Anda mencapainya dengan menggantikan MediaSession.Callback.onConnect(). Konfigurasi dan tampilkan seperangkat perintah yang tersedia saat menerima permintaan koneksi dari MediaController dalam metode callback onConnect:

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();
  }
}

Untuk menerima permintaan perintah kustom dari MediaController, ganti metode Metode onCustomCommand() di Callback.

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

Anda dapat melacak pengontrol media mana yang membuat permintaan dengan menggunakan Properti packageName dari objek MediaSession.ControllerInfo yang diteruskan ke metode Callback. Hal ini memungkinkan Anda menyesuaikan perilaku sebagai respons terhadap perintah tertentu jika berasal dari sistem, aplikasi Anda sendiri, atau aplikasi klien lainnya.

Memperbarui tata letak kustom setelah interaksi pengguna

Setelah menangani perintah khusus atau interaksi lain dengan pemutar, Anda mungkin ingin memperbarui tata letak yang ditampilkan di UI pengontrol. Contoh umum adalah tombol yang mengubah ikonnya setelah memicu tindakan yang terkait dengan tombol ini. Untuk memperbarui tata letak, Anda dapat menggunakan 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));

Menyesuaikan perilaku perintah pemutaran

Untuk menyesuaikan perilaku perintah yang ditentukan di antarmuka Player, seperti sebagai play() atau seekToNext(), gabungkan Player Anda dalam ForwardingPlayer.

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();

Untuk informasi selengkapnya tentang ForwardingPlayer, lihat panduan ExoPlayer di Penyesuaian.

Mengidentifikasi pengontrol permintaan dari perintah pemutar

Saat panggilan ke metode Player berasal dari MediaController, Anda dapat identifikasi sumber asal dengan MediaSession.controllerForCurrentRequest dan mendapatkan ControllerInfo untuk permintaan saat ini:

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();
  }
}

Merespons tombol media

Tombol media adalah tombol hardware yang ditemukan di perangkat Android dan perangkat periferal lainnya seperti tombol putar/jeda pada headset Bluetooth. Tuas Media3 peristiwa tombol media untuk Anda ketika mereka tiba di sesi dan memanggil metode Player yang sesuai pada pemain sesi.

Aplikasi dapat mengganti perilaku default dengan mengganti MediaSession.Callback.onMediaButtonEvent(Intent). Dalam kasus semacam ini, aplikasi dapat/perlu menangani sendiri semua spesifikasi API.

Penanganan dan pelaporan error

Ada dua jenis error yang dimunculkan sesi dan dilaporkan ke pengontrol. Error fatal melaporkan kegagalan pemutaran teknis sesi pemutar yang mengganggu pemutaran. Error fatal dilaporkan ke pengontrol secara otomatis saat terjadi. Error non-fatal adalah error non-teknis atau kebijakan yang tidak mengganggu pemutaran dan dikirim ke pengontrol oleh aplikasi secara manual.

Error saat pemutaran fatal

Error pemutaran yang fatal dilaporkan ke sesi oleh pemutar, lalu dilaporkan ke {i>controller<i} untuk memanggil Player.Listener.onPlayerError(PlaybackException) dan Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException).

Dalam kasus semacam itu, status pemutaran dialihkan ke STATE_IDLE dan MediaController.getPlaybackError() menampilkan PlaybackException yang menyebabkan dalam proses transisi. Pengontrol dapat memeriksa PlayerException.errorCode untuk mendapatkan informasi tentang alasan kesalahan.

Untuk interoperabilitas, error fatal direplikasi ke PlaybackStateCompat sesi platform dengan mentransisikan statusnya ke STATE_ERROR dan menyetelnya kode error dan pesan sesuai dengan PlaybackException.

Penyesuaian error fatal

Untuk memberikan informasi yang dilokalkan dan bermakna bagi pengguna, kode error, pesan {i>error<i} dan tambahan {i>error<i} dari sebuah kesalahan pemutaran fatal dapat disesuaikan dengan menggunakan ForwardingPlayer saat membangun sesi:

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();

Pemutar penerusan mendaftarkan Player.Listener ke pemutar yang sebenarnya dan mencegat callback yang melaporkan error. Solusi yang disesuaikan PlaybackException kemudian didelegasikan ke pemroses yang terdaftar di pemutar penerusan. Agar berfungsi, pemutar yang diteruskan menggantikan Player.addListener dan Player.removeListener untuk memiliki akses ke pemroses yang akan digunakan untuk mengirim kode error, pesan, atau tambahan yang disesuaikan:

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.
  }
}

Error non-fatal

Error nonfatal yang tidak berasal dari pengecualian teknis dapat dikirimkan oleh aplikasi ke semua atau ke pengontrol tertentu:

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);
}

{i>Error<i} nonfatal yang dikirim ke {i>controller<i} notifikasi media direplikasi ke PlaybackStateCompat sesi platform. Dengan demikian, hanya kode {i>error<i} dan pesan error disetel ke PlaybackStateCompat sebagaimana mestinya, sementara PlaybackStateCompat.state tidak diubah menjadi STATE_ERROR.

Menerima error nonfatal

MediaController menerima error nonfatal dengan menerapkan 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.
              }
            });