Acara pemain

Perubahan pada status Pemutar (seperti pemutaran dimulai, buffering, atau error) memicu peristiwa yang dikirim ke instance Player.Listener yang terdaftar. Peristiwa ini direpresentasikan oleh konstanta bilangan bulat dan ditentukan oleh Player.Event dan Player.Events.

Mendaftarkan Player.Listener

Peristiwa pemutar dilaporkan ke instance Player.Listener yang terdaftar. Untuk mendaftarkan pemroses untuk menerima peristiwa tersebut:

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

Jika menggunakan Kotlin, Anda juga dapat menggunakan fungsi ekstensi penangguhan yang disediakan oleh modul media3-common-ktx untuk memantau peristiwa menggunakan coroutine. Dalam hal ini, Anda tidak perlu mendaftarkan atau membatalkan pendaftaran Player.Listener secara eksplisit.

Memproses peristiwa pemutaran menggunakan Player.Listener

Player.Listener memiliki metode default kosong, jadi Anda hanya perlu menerapkan metode yang Anda minati. Lihat Javadoc untuk mengetahui deskripsi lengkap tentang metode dan kapan metode tersebut dipanggil. Beberapa metode yang paling penting dijelaskan lebih detail di bawah.

Pendengar dapat memilih antara menerapkan callback peristiwa individual atau callback onEvents generik yang dipanggil setelah satu atau beberapa peristiwa terjadi bersamaan. Lihat Individual callbacks vs onEvents untuk mengetahui penjelasan tentang mana yang sebaiknya dipilih untuk berbagai kasus penggunaan.

Perubahan status pemutaran

Perubahan status pemutar dapat diterima dengan menerapkan onPlaybackStateChanged(@State int state) dalam Player.Listener yang terdaftar. Pemutar dapat berada di salah satu dari empat status pemutaran:

  • Player.STATE_IDLE: Ini adalah status awal, status saat pemutar dihentikan, dan saat pemutaran gagal. Pemutar hanya akan menyimpan resource terbatas dalam status ini.
  • Player.STATE_BUFFERING: Pemutar tidak dapat langsung melakukan pemutaran dari posisi saat ini. Hal ini sebagian besar terjadi karena lebih banyak data perlu dimuat.
  • Player.STATE_READY: Pemutar dapat langsung melakukan pemutaran dari posisi saat ini.
  • Player.STATE_ENDED: Pemutar selesai memutar semua media.

Selain status ini, pemutar memiliki tanda playWhenReady untuk menunjukkan niat pengguna untuk memutar. Perubahan pada tanda ini dapat diterima dengan menerapkan onPlayWhenReadyChanged(playWhenReady, @PlayWhenReadyChangeReason int reason).

Pemutar sedang memutar (yaitu, posisinya maju dan media ditampilkan kepada pengguna) jika ketiga kondisi berikut terpenuhi:

  • Pemain berada dalam status Player.STATE_READY
  • playWhenReady adalah true
  • Pemutaran tidak di-suppress karena alasan yang ditampilkan oleh Player.getPlaybackSuppressionReason

Daripada harus memeriksa properti ini satu per satu, Player.isPlaying dapat dipanggil. Perubahan pada status ini dapat diterima dengan menerapkan 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.
        }
      }
    });

Error saat pemutaran

Error yang menyebabkan pemutaran gagal dapat diterima dengan menerapkan onPlayerError(PlaybackException error) dalam Player.Listener yang terdaftar. Jika terjadi kegagalan, metode ini akan dipanggil tepat sebelum status pemutaran bertransisi ke Player.STATE_IDLE. Pemutaran yang gagal atau berhenti dapat dicoba lagi dengan memanggil ExoPlayer.prepare.

Perhatikan bahwa beberapa penerapan Player meneruskan instance subclass PlaybackException untuk memberikan informasi tambahan tentang kegagalan. Misalnya, ExoPlayer meneruskan ExoPlaybackException, yang memiliki type, rendererIndex, dan kolom khusus ExoPlayer lainnya.

Contoh berikut menunjukkan cara mendeteksi saat pemutaran gagal karena masalah jaringan 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.
          }
        }
      }
    });

Transisi playlist

Setiap kali pemutar beralih ke item media baru dalam playlist onMediaItemTransition(MediaItem mediaItem, @MediaItemTransitionReason int reason) dipanggil pada objek Player.Listener yang terdaftar. Alasan menunjukkan apakah ini adalah transisi otomatis, pencarian (misalnya setelah memanggil player.next()), pengulangan item yang sama, atau disebabkan oleh perubahan playlist (misalnya, jika item yang sedang diputar dihapus).

Metadata

Metadata yang ditampilkan dari player.getCurrentMediaMetadata() dapat berubah karena banyak alasan: transisi playlist, update metadata dalam streaming, atau update MediaItem saat ini di tengah pemutaran.

Jika Anda tertarik dengan perubahan metadata, misalnya untuk memperbarui UI yang menampilkan judul saat ini, Anda dapat memproses onMediaMetadataChanged.

Mencari

Memanggil metode Player.seekTo akan menghasilkan serangkaian callback ke instance Player.Listener yang terdaftar:

  1. onPositionDiscontinuity dengan reason=DISCONTINUITY_REASON_SEEK. Ini adalah hasil langsung dari panggilan Player.seekTo. Callback memiliki kolom PositionInfo untuk posisi sebelum dan setelah pencarian.
  2. onPlaybackStateChanged dengan perubahan status langsung yang terkait dengan pencarian. Perhatikan bahwa mungkin tidak ada perubahan seperti itu.

Callback individu versus onEvents

Pemroses dapat memilih antara menerapkan callback individual seperti onIsPlayingChanged(boolean isPlaying), dan callback onEvents(Player player, Events events) generik. Callback generik memberikan akses ke objek Player dan menentukan kumpulan events yang terjadi bersamaan. Callback ini selalu dipanggil setelah callback yang sesuai dengan setiap peristiwa.

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

Acara individual lebih disarankan dalam kasus berikut:

  • Pendengar tertarik dengan alasan perubahan. Misalnya, alasan yang diberikan untuk onPlayWhenReadyChanged atau onMediaItemTransition.
  • Pemroses hanya bertindak pada nilai baru yang diberikan melalui parameter callback atau memicu sesuatu yang lain yang tidak bergantung pada parameter callback.
  • Implementasi pemroses lebih memilih indikasi yang jelas dan mudah dibaca tentang apa yang memicu peristiwa dalam nama metode.
  • Pemroses melaporkan ke sistem analisis yang perlu mengetahui semua peristiwa individual dan perubahan status.

onEvents(Player player, Events events) generik harus lebih disukai dalam kasus berikut:

  • Pemroses ingin memicu logika yang sama untuk beberapa peristiwa. Misalnya, memperbarui UI untuk onPlaybackStateChanged dan onPlayWhenReadyChanged.
  • Pemroses memerlukan akses ke objek Player untuk memicu peristiwa lebih lanjut, misalnya, mencari setelah transisi item media.
  • Listener bermaksud menggunakan beberapa nilai status yang dilaporkan melalui callback terpisah secara bersamaan, atau dalam kombinasi dengan metode getter Player. Misalnya, penggunaan Player.getCurrentWindowIndex() dengan Timeline yang disediakan di onTimelineChanged hanya aman dari dalam callback onEvents.
  • Pemroses tertarik pada apakah peristiwa terjadi secara logis bersama-sama. Misalnya, onPlaybackStateChanged ke STATE_BUFFERING karena transisi item media.

Dalam beberapa kasus, pemroses mungkin perlu menggabungkan callback individual dengan callback onEvents generik, misalnya untuk merekam alasan perubahan item media dengan onMediaItemTransition, tetapi hanya bertindak setelah semua perubahan status dapat digunakan bersama-sama di onEvents.

Memproses peristiwa pemutaran menggunakan coroutine

Atau, Anda dapat meluncurkan coroutine Kotlin menggunakan Player.listenTo dan menentukan Player.Event yang relevan:

Perhatikan bahwa Player.listen dan Player.listenTo dapat dipanggil dari thread mana pun, sementara lambda callback selalu dipanggil di thread yang terkait dengan Player.getApplicationLooper. Oleh karena itu, aman untuk mengakses metode Player dan properti status di dalam lambda callback meskipun coroutine diluncurkan di thread yang berbeda.

Perubahan status pemutaran

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

Error saat pemutaran

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

Callback individu versus onEvents

Saat memproses peristiwa pemain di dalam coroutine, Anda akan selalu memberikan implementasi untuk callback onEvents, bukan callback individual. Anda dapat memilih antara Player.listen dan Player.listenTo, bergantung pada peristiwa mana yang harus memicu pemanggilan lambda Anda. Namun, fungsi-fungsi tersebut setara:

dengarkan

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

Jika tertarik dengan beberapa jenis peristiwa, Anda dapat meneruskan daftar peristiwa ke Player.listenTo. Lambda Anda akan dipanggil setiap kali peristiwa tersebut terjadi, dan Anda dapat memeriksa parameter Events untuk memeriksa peristiwa mana yang benar-benar dipicu:

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

Karena fungsi ini beroperasi pada onEvents, fungsi ini tidak memberikan akses ke argumen sementara yang diteruskan ke setiap callback, seperti alasan di onMediaItemTransition(..., int reason) atau oldPosition di onPositionDiscontinuity(...). Jika logika Anda bergantung pada argumen spesifik ini (dan tidak tersedia sebagai properti status di Player), Anda harus menggunakan antarmuka Player.Listener standar.

Gunakan AnalyticsListener

Saat menggunakan ExoPlayer, AnalyticsListener dapat didaftarkan dengan pemutar dengan memanggil addAnalyticsListener. Implementasi AnalyticsListener dapat memproses peristiwa mendetail yang mungkin berguna untuk tujuan analisis dan logging. Lihat halaman analisis untuk mengetahui detail selengkapnya.

Gunakan EventLogger

EventLogger adalah AnalyticsListener yang disediakan langsung oleh library untuk tujuan logging. Tambahkan EventLogger ke ExoPlayer untuk mengaktifkan logging tambahan yang berguna dengan satu baris:

Kotlin

player.addAnalyticsListener(EventLogger())

Java

player.addAnalyticsListener(new EventLogger());

Lihat halaman logging debug untuk mengetahui detail selengkapnya.

Mengaktifkan peristiwa pada posisi pemutaran yang ditentukan

Beberapa kasus penggunaan memerlukan peristiwa pemicuan pada posisi pemutaran yang ditentukan. Hal ini didukung menggunakan PlayerMessage. PlayerMessage dapat dibuat menggunakan ExoPlayer.createMessage. Posisi pemutaran saat perintah harus dieksekusi dapat ditetapkan menggunakan PlayerMessage.setPosition. Pesan dieksekusi di thread pemutaran secara default, tetapi hal ini dapat disesuaikan menggunakan PlayerMessage.setLooper. PlayerMessage.setDeleteAfterDelivery dapat digunakan untuk mengontrol apakah pesan akan dieksekusi setiap kali posisi pemutaran yang ditentukan ditemukan (hal ini dapat terjadi beberapa kali karena mode pencarian dan pengulangan), atau hanya pada saat pertama kali. Setelah dikonfigurasi, PlayerMessage dapat dijadwalkan menggunakan 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();