Analytics

ExoPlayer obsługuje wiele potrzeb związanych z analityką odtwarzania. Analityka polega na zbieraniu, interpretowaniu, agregowaniu i podsumowywaniu danych z odtworzeń. Dane te mogą być używane na urządzeniu, np. do rejestrowania, debugowania lub podejmowania przyszłych decyzji dotyczących odtwarzania, albo przesyłane na serwer w celu monitorowania odtwarzania na wszystkich urządzeniach.

System analityczny zwykle najpierw zbiera zdarzenia, a potem je przetwarza, aby były przydatne:

  • Zbieranie zdarzeń: można to zrobić, rejestrując AnalyticsListener w instancji ExoPlayer. Zarejestrowane detektory analityczne otrzymują zdarzenia w miarę ich występowania podczas korzystania z odtwarzacza. Każde zdarzenie jest powiązane z odpowiednim elementem multimedialnym na liście odtwarzania, a także z metadanymi pozycji odtwarzania i sygnatury czasowej.
  • Przetwarzanie zdarzeń: niektóre systemy analityczne przesyłają nieprzetworzone zdarzenia na serwer, a całe przetwarzanie zdarzeń odbywa się po stronie serwera. Możesz też przetwarzać zdarzenia na urządzeniu. Może to być prostsze lub zmniejszyć ilość informacji, które trzeba przesłać. ExoPlayer udostępnia PlaybackStatsListener, który umożliwia wykonanie tych kroków przetwarzania:
    1. Interpretacja zdarzeń: aby zdarzenia były przydatne do celów analitycznych, muszą być interpretowane w kontekście pojedynczego odtwarzania. Na przykład surowe zdarzenie zmiany stanu odtwarzacza na STATE_BUFFERING może odpowiadać początkowemu buforowaniu, ponownemu buforowaniu lub buforowaniu po przewinięciu.
    2. Śledzenie stanu: ten krok przekształca zdarzenia w liczniki. Na przykład zdarzenia zmiany stanu można przekształcić w liczniki śledzące czas spędzony w każdym stanie odtwarzania. Wynikiem jest podstawowy zestaw wartości danych analitycznych dotyczących jednego odtworzenia.
    3. Agregacja: ten krok łączy dane analityczne z wielu odtworzeń, zwykle przez zsumowanie liczników.
    4. Obliczanie danych podsumowujących: wiele z najbardziej przydatnych rodzajów danych to te, które obliczają średnie lub łączą podstawowe wartości danych analitycznych w inny sposób. Podsumowujące dane mogą być obliczane dla pojedynczych lub wielu odtworzeń.

Zbieranie zdarzeń za pomocą AnalyticsListener

Surowe zdarzenia odtwarzania z odtwarzacza są zgłaszane do implementacji AnalyticsListener. Możesz łatwo dodać własnego odbiorcę i zastąpić tylko te metody, które Cię interesują:

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 przekazywany do każdego wywołania zwrotnego przypisuje zdarzenie do elementu multimedialnego na liście odtwarzania, a także do metadanych pozycji odtwarzania i sygnatury czasowej:

  • realtimeMs: czas zdarzenia według zegara.
  • timeline, windowIndexmediaPeriodId: określa playlistę i element na playliście, do którego należy zdarzenie. mediaPeriodId zawiera opcjonalne dodatkowe informacje, np. czy zdarzenie należy do reklamy w ramach produktu.
  • eventPlaybackPositionMs: Pozycja odtwarzania w elemencie, w której wystąpiło zdarzenie.
  • currentTimeline, currentWindowIndex, currentMediaPeriodIdcurrentPlaybackPositionMs: jak wyżej, ale w przypadku aktualnie odtwarzanego elementu. Obecnie odtwarzany element może różnić się od elementu, do którego należy zdarzenie, np. jeśli zdarzenie odpowiada wstępnemu buforowaniu następnego elementu do odtworzenia.

Przetwarzanie zdarzeń za pomocą interfejsu PlaybackStatsListener

PlaybackStatsListener to AnalyticsListener, który implementuje przetwarzanie zdarzeń na urządzeniu. Oblicza PlaybackStats, a liczniki i dane pochodne obejmują:

  • Dane podsumowujące, np. łączny czas odtwarzania.
  • Dane dotyczące adaptacyjnej jakości odtwarzania, np. średnia rozdzielczość filmu.
  • Dane dotyczące jakości renderowania, np. odsetek pominiętych klatek.
  • Dane o wykorzystaniu zasobów, np. liczba bajtów odczytanych przez sieć.

Pełną listę dostępnych wartości i danych pochodnych znajdziesz w PlaybackStatsdokumentacji Javadoc.

PlaybackStatsListener oblicza osobne wartości PlaybackStats dla każdego elementu multimedialnego na liście odtwarzania, a także dla każdej reklamy wstawionej po stronie klienta w tych elementach. Możesz podać wywołanie zwrotne do PlaybackStatsListener, aby otrzymywać informacje o zakończonych odtwarzaniach, i użyć parametru EventTime przekazanego do wywołania zwrotnego, aby określić, które odtwarzanie zostało zakończone. Możesz zbierać dane analityczne z wielu odtworzeń. W każdej chwili możesz też wysłać zapytanie do PlaybackStats dotyczące bieżącej sesji odtwarzania, używając 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 umożliwia zachowanie pełnej historii przetworzonych zdarzeń. Pamiętaj, że w zależności od długości odtwarzania i liczby zdarzeń może to spowodować nieznany narzut pamięci. Dlatego należy włączać tę opcję tylko wtedy, gdy potrzebujesz dostępu do pełnej historii przetworzonych zdarzeń, a nie tylko do końcowych danych analitycznych.

Pamiętaj, że PlaybackStats używa rozszerzonego zestawu stanów, aby wskazywać nie tylko stan multimediów, ale także zamiar użytkownika dotyczący odtwarzania oraz bardziej szczegółowe informacje, np. dlaczego odtwarzanie zostało przerwane lub zakończone:

Stan odtwarzania Intencja użytkownika dotycząca odtwarzania Brak zamiaru grania
Przed odtwarzaniem JOINING_FOREGROUND NOT_STARTED, JOINING_BACKGROUND
Aktywne odtwarzanie PLAYING
Przerwane odtwarzanie BUFFERING, SEEKING PAUSED, PAUSED_BUFFERING, SUPPRESSED, SUPPRESSED_BUFFERING, INTERRUPTED_BY_AD
Stany końcowe ENDED, STOPPED, FAILED, ABANDONED

Intencja użytkownika dotycząca odtwarzania jest ważna, aby odróżnić okresy, w których użytkownik aktywnie czekał na wznowienie odtwarzania, od okresów pasywnego oczekiwania. Na przykład funkcja PlaybackStats.getTotalWaitTimeMs zwraca łączny czas spędzony w stanach JOINING_FOREGROUND, BUFFERINGSEEKING, ale nie czas, w którym odtwarzanie było wstrzymane. Podobnie PlaybackStats.getTotalPlayAndWaitTimeMs zwróci łączny czas, w którym użytkownik miał zamiar grać, czyli łączny czas aktywnego oczekiwania i łączny czas spędzony w stanie PLAYING.

Przetworzone i zinterpretowane zdarzenia

Przetworzone i zinterpretowane zdarzenia możesz rejestrować za pomocą funkcji PlaybackStatsListener z funkcją keepHistory=true. Wynikowy PlaybackStats będzie zawierać te listy zdarzeń:

  • playbackStateHistory: uporządkowana lista rozszerzonych stanów odtwarzania z wartością EventTime, od której zaczęły obowiązywać. Możesz też użyć ikony PlaybackStats.getPlaybackStateAtTime, aby sprawdzić stan w danym momencie.
  • mediaTimeHistory: historia par czasu zegarowego i czasu multimediów, która pozwala odtworzyć, które części multimediów były odtwarzane w danym momencie. Możesz też użyć aplikacji PlaybackStats.getMediaTimeMsAtRealtimeMs, aby sprawdzić pozycję odtwarzania w danym momencie.
  • videoFormatHistoryaudioFormatHistory: uporządkowane listy formatów wideo i audio używanych podczas odtwarzania z podaniem EventTime, w którym zaczęto ich używać.
  • fatalErrorHistorynonFatalErrorHistory: uporządkowane listy błędów krytycznych i niekrytycznych z informacją o EventTime, w którym wystąpiły. Błędy krytyczne to błędy, które spowodowały zakończenie odtwarzania, a błędy niekrytyczne mogły być możliwe do naprawienia.

Dane analityczne dotyczące odtwarzania jednokrotnego

Te dane są zbierane automatycznie, jeśli używasz PlaybackStatsListener, nawet w przypadku keepHistory=false. Wartości końcowe to pola publiczne, które można znaleźć w PlaybackStats dokumentacji Javadoc i czasach trwania stanu odtwarzania zwracanych przez getPlaybackStateDurationMs. Dla wygody znajdziesz też metody takie jak getTotalPlayTimeMsgetTotalWaitTimeMs, które zwracają czas trwania określonych kombinacji stanów odtwarzania.

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

zbiorcze dane analityczne dotyczące wielu odtworzeń,

Możesz połączyć kilka PlaybackStats, wywołując PlaybackStats.merge. Wynikowy PlaybackStats będzie zawierać zagregowane dane wszystkich połączonych odtworzeń. Pamiętaj, że nie będzie ona zawierać historii poszczególnych zdarzeń odtwarzania, ponieważ nie można ich agregować.

PlaybackStatsListener.getCombinedPlaybackStats może służyć do uzyskiwania zbiorczego widoku wszystkich danych analitycznych zebranych w okresie istnienia PlaybackStatsListener.

Obliczone dane podsumowujące

Oprócz podstawowych danych analitycznych PlaybackStats udostępnia wiele metod obliczania danych podsumowujących.

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

Tematy zaawansowane

Powiązywanie danych analitycznych z metadanymi odtwarzania

Podczas zbierania danych analitycznych dotyczących poszczególnych odtworzeń możesz chcieć powiązać dane analityczne odtwarzania z metadanymi odtwarzanych multimediów.

Zalecamy ustawianie metadanych dotyczących konkretnych mediów za pomocą MediaItem.Builder.setTag. Tag multimediów jest częścią EventTime raportowanych w przypadku surowych zdarzeń i gdy PlaybackStats są zakończone, więc można go łatwo pobrać podczas przetwarzania odpowiednich danych analitycznych:

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

Raportowanie zdarzeń analiz niestandardowych

Jeśli chcesz dodać do danych analitycznych zdarzenia niestandardowe, musisz zapisać je we własnej strukturze danych i później połączyć z raportowanymi danymi PlaybackStats. W razie potrzeby możesz rozszerzyć DefaultAnalyticsCollector, aby generować instancje EventTime dla zdarzeń niestandardowych i wysyłać je do zarejestrowanych już odbiorców, jak pokazano w tym przykładzie.

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