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
AnalyticsListenerw instancjiExoPlayer. 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:- 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_BUFFERINGmoże odpowiadać początkowemu buforowaniu, ponownemu buforowaniu lub buforowaniu po przewinięciu. - Ś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.
- Agregacja: ten krok łączy dane analityczne z wielu odtworzeń, zwykle przez zsumowanie liczników.
- 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ń.
- 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
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,windowIndeximediaPeriodId: określa playlistę i element na playliście, do którego należy zdarzenie.mediaPeriodIdzawiera 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,currentMediaPeriodIdicurrentPlaybackPositionMs: 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, BUFFERING i SEEKING, 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ć ikonyPlaybackStats.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ć aplikacjiPlaybackStats.getMediaTimeMsAtRealtimeMs, aby sprawdzić pozycję odtwarzania w danym momencie.videoFormatHistoryiaudioFormatHistory: uporządkowane listy formatów wideo i audio używanych podczas odtwarzania z podaniemEventTime, w którym zaczęto ich używać.fatalErrorHistoryinonFatalErrorHistory: uporządkowane listy błędów krytycznych i niekrytycznych z informacją oEventTime, 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 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ć 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(); }