Analytics

ExoPlayer obsługuje wiele potrzeb związanych z analizą odtwarzania. W podstawie 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 decyzji dotyczących odtwarzania w przyszłości) lub przekazywane na serwer w celu monitorowania odtwarzania na wszystkich urządzeniach.

System analityczny musi najpierw rejestrować zdarzenia, a potem przetwarzać je dalej, aby zapewnić ich znaczenie:

  • Zbieranie zdarzeń: w tym celu należy zarejestrować AnalyticsListener w instancji ExoPlayer. Zarejestrowane funkcje analityczne odbiornika otrzymują zdarzenia w miarę ich występowania podczas korzystania z odtwarzacza. Każde zdarzenie jest powiązane z odpowiednim elementem multimedialnym na playliście, a także z metadanymi dotyczącymi pozycji odtwarzania i sygnatury czasowej.
  • Przetwarzanie zdarzeń: niektóre systemy analityczne przesyłają nieprzetworzone zdarzenia na serwer, a wszystkie przetwarzanie zdarzeń odbywa się po stronie serwera. Możliwe jest też przetwarzanie zdarzeń na urządzeniu, co może być prostsze lub też zmniejszyć ilość informacji do przesłania. ExoPlayer udostępnia interfejs PlaybackStatsListener, który umożliwia wykonanie tych czynności:
    1. Interpretacja zdarzeń: aby zdarzenia były przydatne do celów analitycznych, muszą być interpretowane w kontekście pojedynczego odtwarzania. Na przykład nieprzetworzony zdarzenie zmiany stanu odtwarzacza na STATE_BUFFERING może odpowiadać początkowemu buforowaniu, ponownemu buforowaniu lub buforowaniu po przesunięciu.
    2. Śledzenie stanu: na tym etapie zdarzenia są przekształcane w liczniki. Na przykład zdarzenia zmiany stanu można przekształcić w liczniki, które śledzą, ile czasu zostało spędzone w każdym stanie odtwarzania. Wynikiem jest podstawowy zestaw wartości danych analitycznych dotyczących pojedynczego odtwarzania.
    3. Sumowanie: na tym etapie dane analityczne są łączone z danymi z wielu odtworzeń, zwykle przez dodawanie liczników.
    4. Obliczanie danych podsumowujących: wiele z najbardziej przydatnych danych to dane, które obliczają średnie lub łączą podstawowe wartości danych Analytics na inne sposoby. Dane podsumowujące można obliczać dla pojedynczych lub wielu odtworzeń.

Zbieranie zdarzeń za pomocą AnalyticsListener

Nieprzetworzone zdarzenia odtwarzania z odtwarzacza są zgłaszane do implementacji AnalyticsListener. Możesz łatwo dodać własnego słuchacza 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) {}
    });

Wartość EventTime przekazywana do każdego wywołania zwrotnego łączy zdarzenie z elementem multimedialnym na playliście, a także z pozycją odtwarzania i metadanymi sygnatury czasowej:

  • realtimeMs: czas zegarowy zdarzenia.
  • timeline, windowIndex i mediaPeriodId: określają playlistę i element na playliście, do której należy zdarzenie. mediaPeriodId zawiera opcjonalne informacje dodatkowe, np. wskazujące, czy zdarzenie należy do reklamy w danym elemencie.
  • eventPlaybackPositionMs: pozycja odtwarzania w produkcie w momencie wystąpienia zdarzenia.
  • currentTimeline, currentWindowIndex, currentMediaPeriodId i currentPlaybackPositionMs: jak powyżej, ale dla aktualnie odtwarzanego elementu. Aktualnie odtwarzany element może się różnić od elementu, do którego należy zdarzenie, np. jeśli odpowiada ono wstępnemu buforowaniu następnego elementu do odtworzenia.

Przetwarzanie zdarzeń za pomocą PlaybackStatsListener

PlaybackStatsListener to AnalyticsListener, który implementuje przetwarzanie zdarzeń na urządzeniu. Oblicza PlaybackStats, z licznikami i danymi pochodnymi, takimi jak:

  • Dane podsumowujące, np. łączny czas odtwarzania.
  • dane o jakości odtwarzania z wykorzystaniem adaptacji, np. średnia rozdzielczość filmu;
  • Dane o jakości renderowania, na przykład współczynnik utraty klatek.
  • Dane o wykorzystaniu zasobów, np. liczba bajtów odczytanych przez sieć.

Pełną listę dostępnych zliczań i wyprowadzonych danych znajdziesz w dokumentacji PlaybackStatsJavadoc.

PlaybackStatsListener oblicza osobne PlaybackStats dla każdego elementu multimedialnego w playlistzie, a także dla każdej reklamy po stronie klienta wstawionej w tych elementach. Aby otrzymywać informacje o zakończonych odtworzeniach, możesz podać adres PlaybackStatsListener, a aby określić, które odtwarzanie zostało zakończone, możesz użyć parametru EventTime przekazanego do wywołania zwrotnego. Możesz zagregować dane analityczne dotyczące wielu odtworzeń. W każdej chwili możesz też wysłać zapytanie do PlaybackStats dotyczące bieżącej sesji odtwarzania za pomocą 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 udostępnia opcję przechowywania pełnej historii przetworzonych zdarzeń. Pamiętaj, że może to spowodować nieznane obciążenie pamięci w zależności od długości odtwarzania i liczby zdarzeń. Dlatego włącz ją tylko wtedy, gdy potrzebujesz dostępu do pełnej historii przetworzonych zdarzeń, a nie tylko do końcowych danych analitycznych.

Pamiętaj, że PlaybackStats korzysta z rozszerzonego zestawu stanów, aby wskazać nie tylko stan multimediów, ale także zamiar odtwarzania przez użytkownika, a także bardziej szczegółowe informacje, np. o przyczynach przerwania lub zakończenia odtwarzania:

Stan odtwarzania Chęć grania Brak zamiaru grania
Przed odtworzeniem JOINING_FOREGROUND NOT_STARTED, JOINING_BACKGROUND
Aktywne odtwarzanie PLAYING
Odtwarzanie przerwane BUFFERING, SEEKING PAUSED, PAUSED_BUFFERING, SUPPRESSED, SUPPRESSED_BUFFERING, INTERRUPTED_BY_AD
Stany końcowe ENDED, STOPPED, FAILED, ABANDONED

Informacje o intencji odtwarzania są ważne, ponieważ pozwalają odróżnić czas aktywnego oczekiwania na wznowienie odtwarzania od czasu pasywnego oczekiwania. Na przykład: PlaybackStats.getTotalWaitTimeMs zwraca łączny czas spędzony w stanach JOINING_FOREGROUND, BUFFERINGSEEKING, ale nie uwzględnia czasu, gdy odtwarzanie było wstrzymane. Podobnie PlaybackStats.getTotalPlayAndWaitTimeMs zwróci łączny czas, w którym użytkownik miał zamiar zagrać, czyli łączny czas aktywnego oczekiwania i łączny czas spędzony w stanie PLAYING.

Przetworzone i zinterpretowane zdarzenia

Zdarzenia przetworzone i zinterpretowane możesz rejestrować za pomocą funkcji PlaybackStatsListener z parametrem keepHistory=true. Wynikowa tabela PlaybackStats będzie zawierać te listy zdarzeń:

  • playbackStateHistory: uporządkowana lista rozszerzonych stanów odtwarzania z parametrem EventTime, od którego te stany zaczęły być stosowane. Możesz też użyć opcji PlaybackStats.getPlaybackStateAtTime, aby sprawdzić stan w określonym czasie.
  • mediaTimeHistory: historia par czasów zegara 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ć PlaybackStats.getMediaTimeMsAtRealtimeMs, aby sprawdzić pozycję odtwarzania w danym czasie.
  • videoFormatHistory i audioFormatHistory: uporządkowane listy formatów wideo i audio używanych podczas odtwarzania wraz z EventTime, w którym zaczęto ich używać.
  • fatalErrorHistorynonFatalErrorHistory: uporządkowane listy krytycznych i niekrytycznych błędów z poziomami EventTime, w których wystąpiły. Błędy krytyczne to te, które spowodowały zakończenie odtwarzania, natomiast błędy niekrytyczne mogły być odwracalne.

Dane analityczne dotyczące pojedynczego odtwarzania

Te dane są zbierane automatycznie, jeśli korzystasz z narzędzia PlaybackStatsListener, nawet keepHistory=false. Ostateczne wartości to pola publiczne, które można znaleźć w PlaybackStats Javadoc, oraz czas trwania stanu odtwarzania zwracany przez getPlaybackStateDurationMs. Dla wygody dostępne są również metody takie jak getTotalPlayTimeMs i getTotalWaitTimeMs, które zwracają czas trwania określonych kombinacji stanu 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);

Dane analityczne zbiorcze z wielu odtworzeń

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

PlaybackStatsListener.getCombinedPlaybackStats można używać do wyświetlania danych analitycznych zebranych w całym okresie istnienia PlaybackStatsListener w ujęciu zbiorczym.

Obliczone dane podsumowania

Oprócz podstawowych danych analitycznych usługa 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ązanie danych analitycznych z metadanymi odtwarzania

Podczas zbierania danych analitycznych dotyczących poszczególnych odtwarzań możesz powiązać dane analityczne dotyczące odtwarzania z metadanymi o odtwarzanych treściach.

Zalecamy ustawienie metadanych dotyczących poszczególnych mediów za pomocą MediaItem.Builder.setTag. Tag mediów jest częścią EventTime raportowanych w przypadku nieprzetworzonych zdarzeń i czasu zakończenia zdarzenia PlaybackStats. Można go więc łatwo pobrać podczas obsługi 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ń analitycznych niestandardowych

Jeśli chcesz dodać do danych analitycznych zdarzenia niestandardowe, musisz je zapisać w własnej strukturze danych i później połączyć z zgłoszonymi zdarzeniami PlaybackStats. Jeśli chcesz, możesz rozszerzyć DefaultAnalyticsCollector, aby generować wystąpienia EventTime dla zdarzeń niestandardowych i przesyłać je do już zarejestrowanych słuchaczy, jak pokazano w tym przykładzie.

Kotlin

private interface ExtendedListener : AnalyticsListener {
  fun onCustomEvent(eventTime: EventTime)
}

private class ExtendedCollector : DefaultAnalyticsCollector(Clock.DEFAULT) {
  fun customEvent() {
    val eventTime = generateCurrentPlayerMediaPeriodEventTime()
    sendEvent(eventTime, CUSTOM_EVENT_ID) { listener: AnalyticsListener ->
      if (listener is ExtendedListener) {
        listener.onCustomEvent(eventTime)
      }
    }
  }
}

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

private interface ExtendedListener extends AnalyticsListener {
  void onCustomEvent(EventTime eventTime);
}

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

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