Analytics

ExoPlayer obsługuje szeroki zakres potrzeb związanych ze statystykami odtwarzania. Statystyki dotyczą przede wszystkim zbierania, interpretowania, agregowania i podsumowywania danych z odtworzeń. Dane te mogą być używane na urządzeniu – na przykład do rejestrowania, debugowania lub do podejmowania przyszłych decyzji dotyczących odtwarzania – lub przekazywane do serwera w celu monitorowania odtwarzania na wszystkich urządzeniach.

System analityczny zwykle musi najpierw zarejestrować zdarzenia, a potem je dokładniej przetworzyć, aby nadać im znaczenie:

  • Rejestrowanie zdarzeń: można to zrobić, rejestrując obiekt AnalyticsListener w instancji ExoPlayer. Zarejestrowani detektorzy statystyk otrzymują zdarzenia w chwili, gdy występują podczas korzystania z odtwarzacza. Każde zdarzenie jest powiązane z odpowiadającym mu elementem multimedialnym na playliście, a także z metadanymi pozycji odtwarzania i sygnatury czasowej.
  • Przetwarzanie zdarzeń: niektóre systemy analityczne przesyłają na serwer nieprzetworzone zdarzenia, a cała przetwarzanie zdarzeń odbywa się po stronie serwera. Można również przetwarzać zdarzenia na urządzeniu, co pozwala uprościć proces lub zmniejszyć ilość informacji do przesłania. ExoPlayer udostępnia pakiet PlaybackStatsListener, który pozwala na wykonanie tych czynności:
    1. Interpretacja zdarzeń: aby były przydatne do celów analitycznych, zdarzenia muszą być interpretowane w kontekście pojedynczego odtworzenia. Na przykład nieprzetworzone zdarzenie zmiany stanu odtwarzacza na STATE_BUFFERING może odpowiadać wstępnemu buforowaniu, wstrzykiwaniu lub buforowaniu, które ma miejsce po wyszukiwaniu.
    2. Śledzenie stanu: w tym kroku zdarzenia są konwertowane na liczniki. Na przykład zdarzenia zmiany stanu można przekształcić w liczniki śledzące ilość czasu spędzonego w poszczególnych stanach odtwarzania. W efekcie powstaje podstawowy zestaw wartości danych statystycznych dotyczących pojedynczego odtworzenia.
    3. Agregacja: na tym etapie połączono dane analityczne z wielu odtworzeń. Zwykle odbywa się to przez zsumowanie liczników.
    4. Obliczanie danych podsumowujących: wiele najbardziej przydatnych danych to te, które obliczają średnie wartości lub łączą podstawowe wartości danych statystycznych na inne sposoby. Podsumowanie można wyliczyć 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łasny detektor 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) {}
    });

Element EventTime przekazywany do każdego wywołania zwrotnego wiąże zdarzenie z elementem multimedialnym na playliście, a także metadane pozycji odtwarzania i sygnatury czasowej:

  • realtimeMs: zegar zegarowy wydarzenia.
  • timeline, windowIndex i mediaPeriodId: definiuje playlistę i jej element, do którego należy zdarzenie. mediaPeriodId zawiera opcjonalne informacje dodatkowe, np. wskazujące, czy zdarzenie należy do reklamy w elemencie.
  • eventPlaybackPositionMs: pozycja odtwarzania w elemencie, w którym wystąpiło zdarzenie.
  • currentTimeline, currentWindowIndex, currentMediaPeriodId i currentPlaybackPositionMs: jak wyżej, ale dotyczy aktualnie odtwarzanego elementu. Aktualnie odtwarzany element może się różnić od elementu, do którego należy zdarzenie, np. jeśli zdarzenie odpowiada wstępnemu buforowaniu następnego elementu, który ma zostać odtworzony.

Przetwarzanie zdarzeń za pomocą PlaybackStatsListener

PlaybackStatsListener to element typu AnalyticsListener, który implementuje przetwarzanie zdarzeń na urządzeniu. Oblicza wartość PlaybackStats, korzystając z liczników i danych pochodnych, takich jak:

  • Dane podsumowania, np. łączny czas odtwarzania.
  • Dane dotyczące jakości odtwarzania adaptacyjnego, np. średnia rozdzielczość wideo.
  • danych o jakości renderowania, np. liczbie pominiętych klatek;
  • Wskaźniki wykorzystania zasobów, na przykład liczba bajtów odczytanych przez sieć.

Pełną listę dostępnych liczb i danych pochodnych znajdziesz w dokumencie Java PlaybackStats.

PlaybackStatsListener oblicza osobne wartości PlaybackStats dla każdego elementu multimedialnego na playliście oraz dla każdej reklamy po stronie klienta wstawionej do tych elementów. Możesz podać wywołanie zwrotne do PlaybackStatsListener, które informuje o zakończonych odtworzeniach, i użycie EventTime przekazane do wywołania zwrotnego, aby określić, które odtwarzanie się zakończyło. Możesz zagregować dane statystyczne dla wielu odtworzeń. W każdej chwili możesz też wysłać zapytanie do PlaybackStats dotyczące bieżącej sesji odtwarzania, korzystając z metody 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ć zużycie pamięci. Dlatego też warto włączyć ją tylko wtedy, gdy potrzebujesz dostępu do pełnej historii przetworzonych zdarzeń, a nie tylko do ostatecznych danych analitycznych.

Pamiętaj, że PlaybackStats używa rozszerzonego zestawu stanów, aby pokazywać nie tylko stan multimediów, ale także zamiar odtwarzania treści i bardziej szczegółowe informacje, np. o przyczynach przerwania lub zakończenia odtwarzania:

Stan odtwarzania Zamiar użytkownika Brak zamiaru gry
Przed rozpoczęciem odtwarzania JOINING_FOREGROUND NOT_STARTED, JOINING_BACKGROUND
Aktywne odtwarzanie PLAYING
Przerwane odtwarzanie BUFFERING, SEEKING PAUSED, PAUSED_BUFFERING, SUPPRESSED, SUPPRESSED_BUFFERING, INTERRUPTED_BY_AD
Stany zakończenia ENDED, STOPPED, FAILED, ABANDONED

Zamiar użytkownika pozwala odróżnić czas oczekiwania na kontynuację odtwarzania od czasu pasywnego oczekiwania. Na przykład PlaybackStats.getTotalWaitTimeMs zwraca łączny czas podany w stanach JOINING_FOREGROUND, BUFFERING i SEEKING, ale nie zwraca czasu wstrzymania odtwarzania. Analogicznie PlaybackStats.getTotalPlayAndWaitTimeMs zwraca łączny czas, jeśli użytkownik zamierza grać w grze, czyli łączny czas oczekiwania i łączny czas w stanie PLAYING.

Przetworzone i zinterpretowane zdarzenia

Aby rejestrować przetworzone i interpretowane zdarzenia, użyj funkcji PlaybackStatsListener z ustawieniem keepHistory=true. Powstały w ten sposób PlaybackStats będzie zawierał te listy zdarzeń:

  • playbackStateHistory: uporządkowana lista stanów rozszerzonego odtwarzania – EventTime, od której zaczęły obowiązywać. Możesz też użyć funkcji PlaybackStats.getPlaybackStateAtTime, aby sprawdzić stan o danej godzinie zegara.
  • mediaTimeHistory: historia par czasu zegara i czasu multimediów, co pozwala zrekonstruować, które fragmenty multimediów były w danym momencie odtwarzane. Możesz też użyć funkcji PlaybackStats.getMediaTimeMsAtRealtimeMs, aby sprawdzić pozycję odtwarzania o danej godzinie.
  • videoFormatHistory i audioFormatHistory: uporządkowane listy formatów wideo i audio używanych podczas odtwarzania za pomocą właściwości EventTime, od których zaczęły być używane.
  • fatalErrorHistory i nonFatalErrorHistory: uporządkowane listy błędów krytycznych i niekrytycznych (EventTime), w których wystąpiły. Błędy krytyczne to te, które zakończyły odtwarzanie, natomiast błędy niekrytyczne mogły zostać naprawione.

Dane analityczne dotyczące pojedynczego odtworzenia

Jeśli używasz PlaybackStatsListener, te dane są zbierane automatycznie, nawet jeśli używasz keepHistory=false. Ostateczne wartości to pola publiczne, które można znaleźć w pliku Javadoc PlaybackStats, i czasy trwania stanu odtwarzania zwracane przez parametr getPlaybackStateDurationMs. Dla wygody podajemy też metody takie jak getTotalPlayTimeMs i getTotalWaitTimeMs, 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ć ze sobą kilka elementów PlaybackStats, wywołując metodę PlaybackStats.merge. Powstały w ten sposób PlaybackStats będzie zawierał dane zbiorcze o wszystkich scalonych odtworzeniach. Nie będzie on zawierać historii poszczególnych zdarzeń odtwarzania, ponieważ nie można ich gromadzić.

PlaybackStatsListener.getCombinedPlaybackStats może posłużyć do uzyskania zagregowanego podglądu wszystkich danych analitycznych zgromadzonych w okresie objętym 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ązanie danych analitycznych z metadanymi odtwarzania

Jeśli zbierasz dane statystyczne dotyczące poszczególnych odtworzeń, możesz je powiązać z metadanymi dotyczącymi odtwarzanych multimediów.

Zalecamy ustawienie metadanych specyficznych dla multimediów za pomocą parametru MediaItem.Builder.setTag. Tag multimediów jest częścią zbioru danych EventTime raportowanych dla nieprzetworzonych zdarzeń i po zakończeniu PlaybackStats, więc można go łatwo pobrać przy obsłudze 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 niestandardowych zdarzeń Analytics

Jeśli musisz dodać do danych Analytics zdarzenia niestandardowe, musisz je zapisać w swojej strukturze danych, a potem połączyć z raportowanymi zdarzeniami typu PlaybackStats. Jeśli to pomoże, możesz rozszerzyć zakres DefaultAnalyticsCollector, aby móc generować EventTime instancje zdarzeń niestandardowych i wysyłać je do już zarejestrowanych detektorów, jak pokazano w poniższym 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();