Analytics

ExoPlayer mendukung berbagai kebutuhan analisis pemutaran. Pada akhirnya, analisis adalah tentang mengumpulkan, menafsirkan, menggabungkan, dan meringkas data dari pemutaran ulang. Data ini dapat digunakan di perangkat—misalnya untuk pencatatan, penelusuran bug, atau untuk membuat keputusan pemutaran di masa mendatang—atau dilaporkan ke server untuk memantau pemutaran di semua perangkat.

Sistem analisis biasanya perlu mengumpulkan peristiwa terlebih dahulu, lalu memprosesnya lebih lanjut agar bermakna:

  • Pengumpulan peristiwa: Hal ini dapat dilakukan dengan mendaftarkan AnalyticsListener di instance ExoPlayer. Pemroses Analytics terdaftar menerima peristiwa saat terjadi selama penggunaan pemutar. Setiap peristiwa dikaitkan dengan item media yang sesuai dalam playlist, serta metadata stempel waktu dan posisi pemutaran.
  • Pemrosesan peristiwa: Beberapa sistem analisis mengupload peristiwa mentah ke server, dengan semua pemrosesan peristiwa dilakukan di sisi server. Anda juga dapat memproses peristiwa di perangkat, dan melakukannya mungkin lebih sederhana atau mengurangi jumlah informasi yang perlu diupload. ExoPlayer menyediakan PlaybackStatsListener, yang memungkinkan Anda melakukan langkah-langkah pemrosesan berikut:
    1. Interpretasi peristiwa: Agar berguna untuk tujuan analisis, peristiwa perlu diinterpretasikan dalam konteks satu pemutaran. Misalnya, peristiwa mentah perubahan status pemutar menjadi STATE_BUFFERING dapat sesuai dengan penyanggaan awal, penyanggaan ulang, atau penyanggaan yang terjadi setelah penelusuran.
    2. Pelacakan status: Langkah ini mengonversi peristiwa menjadi penghitung. Misalnya, peristiwa perubahan status dapat dikonversi menjadi penghitung yang melacak berapa banyak waktu yang dihabiskan dalam setiap status pemutaran. Hasilnya adalah kumpulan nilai data analisis dasar untuk satu pemutaran.
    3. Agregasi: Langkah ini menggabungkan data analisis di beberapa pemutaran, biasanya dengan menjumlahkan penghitung.
    4. Penghitungan metrik ringkasan: Banyak metrik yang paling berguna adalah metrik yang menghitung rata-rata atau menggabungkan nilai data analytics dasar dengan cara lain. Metrik ringkasan dapat dihitung untuk satu atau beberapa pemutaran.

Pengumpulan peristiwa dengan AnalyticsListener

Peristiwa pemutaran mentah dari pemutar dilaporkan ke penerapan AnalyticsListener. Anda dapat dengan mudah menambahkan pendengar Anda sendiri dan mengganti hanya metode yang Anda minati:

Kotlin

exoPlayer.addAnalyticsListener(
  object : AnalyticsListener {
    override fun onPlaybackStateChanged(eventTime: EventTime, @Player.State state: Int) {}

    override fun onDroppedVideoFrames(
      eventTime: EventTime,
      droppedFrames: Int,
      elapsedMs: Long,
    ) {}
  }
)

Java

exoPlayer.addAnalyticsListener(
    new AnalyticsListener() {
      @Override
      public void onPlaybackStateChanged(EventTime eventTime, @Player.State int state) {}

      @Override
      public void onDroppedVideoFrames(
          EventTime eventTime, int droppedFrames, long elapsedMs) {}
    });

EventTime yang diteruskan ke setiap callback mengaitkan peristiwa dengan item media dalam playlist, serta metadata stempel waktu dan posisi pemutaran:

  • realtimeMs: Waktu jam dinding peristiwa.
  • timeline, windowIndex, dan mediaPeriodId: Menentukan playlist dan item dalam playlist yang terkait dengan peristiwa. mediaPeriodId berisi informasi tambahan opsional, misalnya menunjukkan apakah peristiwa termasuk dalam iklan dalam item.
  • eventPlaybackPositionMs: Posisi pemutaran dalam item saat peristiwa terjadi.
  • currentTimeline, currentWindowIndex, currentMediaPeriodId, dan currentPlaybackPositionMs: Seperti di atas, tetapi untuk item yang sedang diputar. Item yang sedang diputar mungkin berbeda dengan item yang memiliki peristiwa tersebut, misalnya jika peristiwa tersebut sesuai dengan pra-penyangga item berikutnya yang akan diputar.

Pemrosesan peristiwa dengan PlaybackStatsListener

PlaybackStatsListener adalah AnalyticsListener yang mengimplementasikan pemrosesan peristiwa di perangkat. Metrik ini menghitung PlaybackStats, dengan penghitung dan metrik turunan, termasuk:

  • Metrik ringkasan, misalnya total waktu pemutaran.
  • Metrik kualitas pemutaran adaptif, misalnya resolusi video rata-rata.
  • Metrik kualitas rendering, misalnya kecepatan frame yang dihilangkan.
  • Metrik penggunaan resource, misalnya jumlah byte yang dibaca melalui jaringan.

Anda akan menemukan daftar lengkap jumlah dan metrik turunan yang tersedia di PlaybackStats Javadoc.

PlaybackStatsListener menghitung PlaybackStats terpisah untuk setiap item media dalam playlist, dan juga setiap iklan sisi klien yang disisipkan dalam item ini. Anda dapat memberikan callback ke PlaybackStatsListener untuk diberi tahu tentang pemutaran yang selesai, dan menggunakan EventTime yang diteruskan ke callback untuk mengidentifikasi pemutaran mana yang selesai. Anda dapat mengagregasi data analisis untuk beberapa pemutaran. Anda juga dapat membuat kueri PlaybackStats untuk sesi pemutaran saat ini kapan saja menggunakan PlaybackStatsListener.getPlaybackStats().

Kotlin

exoPlayer.addAnalyticsListener(
  PlaybackStatsListener(/* keepHistory= */ true) {
    eventTime: EventTime?,
    playbackStats: PlaybackStats?
    -> // Analytics data for the session started at `eventTime` is ready.
  }
)

Java

exoPlayer.addAnalyticsListener(
    new PlaybackStatsListener(
        /* keepHistory= */ true,
        (eventTime, playbackStats) -> {
          // Analytics data for the session started at `eventTime` is ready.
        }));

Konstruktor PlaybackStatsListener memberikan opsi untuk menyimpan histori lengkap peristiwa yang diproses. Perhatikan bahwa hal ini dapat menimbulkan overhead memori yang tidak diketahui, bergantung pada durasi pemutaran dan jumlah peristiwa. Oleh karena itu, Anda hanya boleh mengaktifkannya jika memerlukan akses ke histori lengkap peristiwa yang diproses, bukan hanya ke data analisis akhir.

Perhatikan bahwa PlaybackStats menggunakan serangkaian status yang diperluas untuk menunjukkan tidak hanya status media, tetapi juga niat pengguna untuk memutar dan informasi yang lebih mendetail seperti alasan pemutaran terganggu atau berakhir:

Status pemutaran Niat pengguna untuk bermain Tidak ada niat untuk bermain
Sebelum pemutaran JOINING_FOREGROUND NOT_STARTED, JOINING_BACKGROUND
Pemutaran aktif PLAYING
Pemutaran terganggu BUFFERING, SEEKING PAUSED, PAUSED_BUFFERING, SUPPRESSED, SUPPRESSED_BUFFERING, INTERRUPTED_BY_AD
Status akhir ENDED, STOPPED, FAILED, ABANDONED

Niat pengguna untuk memutar penting untuk membedakan waktu saat pengguna secara aktif menunggu pemutaran dilanjutkan dari waktu tunggu pasif. Misalnya, PlaybackStats.getTotalWaitTimeMs menampilkan total waktu yang dihabiskan dalam status JOINING_FOREGROUND, BUFFERING, dan SEEKING, tetapi bukan waktu saat pemutaran dijeda. Demikian pula, PlaybackStats.getTotalPlayAndWaitTimeMs akan menampilkan total waktu dengan niat pengguna untuk bermain, yaitu total waktu tunggu aktif dan total waktu yang dihabiskan dalam status PLAYING.

Memproses dan menafsirkan peristiwa

Anda dapat merekam peristiwa yang diproses dan ditafsirkan menggunakan PlaybackStatsListener dengan keepHistory=true. PlaybackStats yang dihasilkan akan berisi daftar acara berikut:

  • playbackStateHistory: Daftar berurutan status pemutaran yang diperluas dengan EventTime saat status tersebut mulai diterapkan. Anda juga dapat menggunakan PlaybackStats.getPlaybackStateAtTime untuk mencari status pada waktu dinding tertentu.
  • mediaTimeHistory: Histori pasangan waktu jam dinding dan waktu media yang memungkinkan Anda merekonstruksi bagian media mana yang diputar pada waktu tertentu. Anda juga dapat menggunakan PlaybackStats.getMediaTimeMsAtRealtimeMs untuk mencari posisi pemutaran pada waktu jam dinding tertentu.
  • videoFormatHistory dan audioFormatHistory: Daftar berurutan format video dan audio yang digunakan selama pemutaran dengan EventTime saat format tersebut mulai digunakan.
  • fatalErrorHistory dan nonFatalErrorHistory: Daftar berurutan kesalahan fatal dan tidak fatal dengan EventTime saat terjadinya. Error fatal adalah error yang mengakhiri pemutaran, sedangkan error non-fatal mungkin dapat dipulihkan.

Data analisis pemutaran tunggal

Data ini dikumpulkan secara otomatis jika Anda menggunakan PlaybackStatsListener, bahkan dengan keepHistory=false. Nilai akhir adalah kolom publik yang dapat Anda temukan di Javadoc PlaybackStats dan durasi status pemutaran yang ditampilkan oleh getPlaybackStateDurationMs. Untuk mempermudah, Anda juga akan menemukan metode seperti getTotalPlayTimeMs dan getTotalWaitTimeMs yang menampilkan durasi kombinasi status pemutaran tertentu.

Kotlin

Log.d(
  "DEBUG",
  "Playback summary: " +
    "play time = " +
    playbackStats.totalPlayTimeMs +
    ", rebuffers = " +
    playbackStats.totalRebufferCount,
)

Java

Log.d(
    "DEBUG",
    "Playback summary: "
        + "play time = "
        + playbackStats.getTotalPlayTimeMs()
        + ", rebuffers = "
        + playbackStats.totalRebufferCount);

Menggabungkan data analisis beberapa pemutaran

Anda dapat menggabungkan beberapa PlaybackStats dengan memanggil PlaybackStats.merge. PlaybackStats yang dihasilkan akan berisi data gabungan dari semua pemutaran yang digabungkan. Perhatikan bahwa laporan ini tidak akan berisi histori peristiwa pemutaran individual, karena peristiwa ini tidak dapat digabungkan.

PlaybackStatsListener.getCombinedPlaybackStats dapat digunakan untuk mendapatkan tampilan gabungan semua data analisis yang dikumpulkan selama masa aktif PlaybackStatsListener.

Metrik ringkasan yang dihitung

Selain data analisis dasar, PlaybackStats menyediakan banyak metode untuk menghitung metrik ringkasan.

Kotlin

Log.d(
  "DEBUG",
  "Additional calculated summary metrics: " +
    "average video bitrate = " +
    playbackStats.meanVideoFormatBitrate +
    ", mean time between rebuffers = " +
    playbackStats.meanTimeBetweenRebuffers,
)

Java

Log.d(
    "DEBUG",
    "Additional calculated summary metrics: "
        + "average video bitrate = "
        + playbackStats.getMeanVideoFormatBitrate()
        + ", mean time between rebuffers = "
        + playbackStats.getMeanTimeBetweenRebuffers());

Topik lanjutan

Mengaitkan data analisis dengan metadata pemutaran

Saat mengumpulkan data analisis untuk setiap pemutaran, Anda mungkin ingin mengaitkan data analisis pemutaran dengan metadata tentang media yang diputar.

Sebaiknya tetapkan metadata khusus media dengan MediaItem.Builder.setTag. Tag media adalah bagian dari EventTime yang dilaporkan untuk peristiwa mentah dan saat PlaybackStats selesai, sehingga dapat diambil dengan mudah saat menangani data analisis yang sesuai:

Kotlin

PlaybackStatsListener(/* keepHistory= */ false) {
  eventTime: EventTime,
  playbackStats: PlaybackStats ->
  val mediaTag =
    eventTime.timeline
      .getWindow(eventTime.windowIndex, Timeline.Window())
      .mediaItem
      .localConfiguration
      ?.tag
  // Report playbackStats with mediaTag metadata.
}

Java

new PlaybackStatsListener(
    /* keepHistory= */ false,
    (eventTime, playbackStats) -> {
      Object mediaTag =
          eventTime.timeline.getWindow(eventTime.windowIndex, new Timeline.Window())
              .mediaItem
              .localConfiguration
              .tag;
      // Report playbackStats with mediaTag metadata.
    });

Melaporkan peristiwa analisis kustom

Jika perlu menambahkan peristiwa kustom ke data analisis, Anda harus menyimpan peristiwa ini dalam struktur data Anda sendiri dan menggabungkannya dengan PlaybackStats yang dilaporkan nanti. Jika perlu, Anda dapat memperluas DefaultAnalyticsCollector agar dapat membuat instance EventTime untuk peristiwa kustom dan mengirimkannya ke pemroses yang sudah terdaftar seperti yang ditunjukkan dalam contoh berikut.

Kotlin

@OptIn(UnstableApi::class)
private interface ExtendedListener : AnalyticsListener {
  fun onCustomEvent(eventTime: EventTime)
}

@OptIn(UnstableApi::class)
private class ExtendedCollector : DefaultAnalyticsCollector(Clock.DEFAULT) {

  fun customEvent() {
    val eventTime = super.generateCurrentPlayerMediaPeriodEventTime()
    super.sendEvent(eventTime, CUSTOM_EVENT_ID) { listener: AnalyticsListener ->
      if (listener is ExtendedListener) {
        listener.onCustomEvent(eventTime)
      }
    }
  }
}

@OptIn(UnstableApi::class)
fun useExtendedAnalyticsCollector(context: Context) {
  // Usage - Setup and listener registration.
  val player = ExoPlayer.Builder(context).setAnalyticsCollector(ExtendedCollector()).build()
  player.addAnalyticsListener(
    object : ExtendedListener {
      override fun onCustomEvent(eventTime: EventTime) {
        // Save custom event for analytics data.
      }
    }
  )
  // Usage - Triggering the custom event.
  (player.analyticsCollector as ExtendedCollector).customEvent()
}

Java

@OptIn(markerClass = UnstableApi.class)
private interface ExtendedListener extends AnalyticsListener {
  void onCustomEvent(EventTime eventTime);
}

@OptIn(markerClass = UnstableApi.class)
private static class ExtendedCollector extends DefaultAnalyticsCollector {
  public ExtendedCollector() {
    super(Clock.DEFAULT);
  }

  public void customEvent() {
    AnalyticsListener.EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
    sendEvent(
        eventTime,
        CUSTOM_EVENT_ID,
        listener -> {
          if (listener instanceof ExtendedListener) {
            ((ExtendedListener) listener).onCustomEvent(eventTime);
          }
        });
  }
}

@OptIn(markerClass = UnstableApi.class)
public static void useExtendedAnalyticsCollector(Context context) {
  // Usage - Setup and listener registration.
  ExoPlayer player =
      new ExoPlayer.Builder(context).setAnalyticsCollector(new ExtendedCollector()).build();
  player.addAnalyticsListener(
      (ExtendedListener)
          eventTime -> {
            // Save custom event for analytics data.
          });
  // Usage - Triggering the custom event.
  ((ExtendedCollector) player.getAnalyticsCollector()).customEvent();
}