ExoPlayer obsługuje szeroki zakres potrzeb związanych z analizą odtwarzania. Analityka polega na zbieraniu, interpretowaniu, agregowaniu i podsumowywaniu danych z odtworzeń. Te dane mogą być używane na urządzeniu, np. do logowania, debugowania lub informowania o przyszłych decyzjach dotyczących odtwarzania, albo zgłaszane na serwerze w celu monitorowania odtwarzania na wszystkich urządzeniach.
System analityczny zwykle musi najpierw zebrać zdarzenia, a następnie je przetworzyć, aby nadać im znaczenie:
- Zbieranie zdarzeń: można to zrobić, rejestrując
AnalyticsListenerw instancjiExoPlayer. Zarejestrowane detektory statystyk otrzymują zdarzenia, gdy występują one podczas korzystania z odtwarzacza. Każde zdarzenie jest powiązane z odpowiednim elementem multimedialnym na playliście, a także z pozycją odtwarzania i metadanymi 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żna też przetwarzać zdarzenia na urządzeniu, co może być prostsze lub zmniejszyć ilość informacji, które trzeba przesłać. ExoPlayer udostępnia interfejs
PlaybackStatsListener, który umożliwia wykonanie tych czynności:- Interpretacja zdarzeń: aby zdarzenia były przydatne do celów analitycznych, muszą być interpretowane w kontekście pojedynczego odtworzenia. Na przykład nieprzetworzone zdarzenie zmiany stanu odtwarzacza na
STATE_BUFFERINGmoże odpowiadać początkowemu buforowaniu, ponownemu buforowaniu lub buforowaniu, które następuje po przewinięciu. - Śledzenie stanu: ten krok przekształca zdarzenia w liczniki. Na przykład zdarzenia zmiany stanu można przekonwertować na liczniki śledzące czas spędzony w każdym stanie odtwarzania. Wynikiem jest podstawowy zestaw wartości danych analitycznych dotyczących pojedynczego odtworzenia.
- Agregacja: ten krok polega na połączeniu danych analitycznych z wielu odtworzeń, zwykle przez dodanie liczników.
- Obliczanie danych podsumowujących: wiele z najbardziej przydatnych danych to te, które obliczają średnie lub łączą podstawowe wartości danych analitycznych w inny sposób. Dane podsumowujące można obliczyć dla pojedynczych lub wielu odtworzeń.
- Interpretacja zdarzeń: aby zdarzenia były przydatne do celów analitycznych, muszą być interpretowane w kontekście pojedynczego odtworzenia. Na przykład nieprzetworzone zdarzenie zmiany stanu odtwarzacza na
Zbieranie zdarzeń za pomocą AnalyticsListener
Nieprzetworzone zdarzenia odtwarzania z odtwarzacza są zgłaszane do implementacji AnalyticsListener. Możesz 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) {} });
Obiekt EventTime przekazywany do każdej funkcji wywołania zwrotnego wiąże zdarzenie z elementem multimedialnym na playliście, a także z pozycją odtwarzania i metadanymi sygnatury czasowej:
realtimeMs: czas zegarowy zdarzenia.timeline,windowIndeximediaPeriodId: definiują playlistę i element na playliście, do którego należy zdarzenie.mediaPeriodIdzawiera opcjonalne dodatkowe informacje, na przykład wskazujące, czy zdarzenie należy do reklamy w elemencie.eventPlaybackPositionMs: pozycja odtwarzania w elemencie, gdy wystąpiło zdarzenie.currentTimeline,currentWindowIndex,currentMediaPeriodIdicurrentPlaybackPositionMs: jak opisano wcześniej, ale w odniesieniu do 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 do odtworzenia.
Przetwarzanie zdarzeń za pomocą PlaybackStatsListener
PlaybackStatsListener to AnalyticsListener, który implementuje przetwarzanie zdarzeń na urządzeniu. Oblicza PlaybackStats, podając liczniki i dane pochodne, w tym:
- Dane podsumowania, 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.
- Wskaźniki wykorzystania zasobów, np. liczba bajtów odczytanych przez sieć.
Pełną listę dostępnych liczb i pochodnych danych znajdziesz w PlaybackStats Javadoc.
PlaybackStatsListener oblicza osobne wartości PlaybackStats dla każdego elementu multimedialnego na playliście, a także dla każdej reklamy po stronie klienta wstawionej w tych elementach. Możesz podać wywołanie zwrotne do PlaybackStatsListener, aby otrzymywać informacje o zakończonych odtworzeniach, i użyć parametru EventTime przekazanego do wywołania zwrotnego, aby określić, które odtworzenie zostało zakończone. Możesz zbierać dane analityczne dotyczące 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 może to spowodować nieznane obciążenie pamięci w zależności od długości odtwarzania i liczby zdarzeń. Dlatego należy ją włączyć 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 wskazać nie tylko stan multimediów, ale też intencję użytkownika dotyczącą odtwarzania oraz bardziej szczegółowe informacje, takie jak powód przerwania lub zakończenia odtwarzania:
| Stan odtwarzania | Intencja użytkownika dotycząca odtworzenia | Brak zamiaru odtworzenia |
|---|---|---|
| Przed odtworzeniem | 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ć czas, gdy użytkownik aktywnie czekał na kontynuację odtwarzania, od czasu oczekiwania pasywnego. Na przykład PlaybackStats.getTotalWaitTimeMs zwraca łączny czas spędzony w stanach JOINING_FOREGROUND, BUFFERING i SEEKING, ale nie czas, gdy odtwarzanie było wstrzymane. Podobnie PlaybackStats.getTotalPlayAndWaitTimeMs zwróci łączny czas z intencją użytkownika odtworzenia, czyli łączny czas aktywnego oczekiwania i łączny czas spędzony w stanie PLAYING.
Przetworzone i zinterpretowane zdarzenia
Możesz rejestrować przetworzone i zinterpretowane zdarzenia, używając PlaybackStatsListener z keepHistory=true. Wynikowy PlaybackStats będzie zawierać te listy zdarzeń:
playbackStateHistory: lista uporządkowana rozszerzonych stanów odtwarzania zEventTime, w którym zaczęły obowiązywać. Możesz też użyćPlaybackStats.getPlaybackStateAtTime, aby sprawdzić stan w danym czasie zegarowym.mediaTimeHistory: historia par czasu zegarowego i czasu multimediów, która pozwala odtworzyć, które części multimediów zostały odtworzone w danym momencie. Możesz też użyćPlaybackStats.getMediaTimeMsAtRealtimeMs, aby sprawdzić pozycję odtwarzania w danym czasie zegarowym.videoFormatHistoryiaudioFormatHistory: uporządkowane listy formatów wideo i audio używanych podczas odtwarzania wraz zEventTime, w którym zaczęły być używane.fatalErrorHistoryinonFatalErrorHistory: listy uporządkowane błędów krytycznych i niekrytycznych zEventTime, w których wystąpiły. Błędy krytyczne to takie, które zakończyły odtwarzanie, natomiast błędy niekrytyczne mogły zostać naprawione.
Dane analityczne dotyczące pojedynczego odtworzenia
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 Javadoc, oraz czasy trwania stanu odtwarzania zwracane przez getPlaybackStateDurationMs. Dla wygody znajdziesz też 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);
Zbiorcze dane analityczne dotyczące wielu odtworzeń
Możesz połączyć kilka obiektów PlaybackStats, wywołując PlaybackStats.merge. Wynikowy plik PlaybackStats będzie zawierać zagregowane dane wszystkich scalonych odtworzeń. Pamiętaj, że nie będzie on zawierać historii poszczególnych zdarzeń odtwarzania, ponieważ nie można ich zagregować.
PlaybackStatsListener.getCombinedPlaybackStats można używać do uzyskiwania zagregowanego widoku wszystkich danych analitycznych zebranych w całym okresie istnienia PlaybackStatsListener.
Obliczone wskaźniki 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
Podczas zbierania danych analitycznych dotyczących poszczególnych odtworzeń możesz powiązać te dane z metadanymi odtwarzanych multimediów.
Zalecamy ustawienie metadanych dotyczących multimediów za pomocą MediaItem.Builder.setTag.
Tag multimediów jest częścią EventTime zgłaszanego w przypadku nieprzetworzonych zdarzeń i po zakończeniu PlaybackStats, więc można go 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 niestandardowych zdarzeń analitycznych
Jeśli chcesz dodać zdarzenia niestandardowe do danych analitycznych, musisz zapisać te zdarzenia we własnej strukturze danych i później połączyć je z raportowanymi zdarzeniami PlaybackStats. Jeśli to pomoże, możesz rozszerzyć klasę DefaultAnalyticsCollector, aby móc generować instancje EventTime dla zdarzeń niestandardowych i wysyłać je do zarejestrowanych już detektoró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(); }