ExoPlayer supporta un'ampia gamma di esigenze di analisi della riproduzione. In definitiva, l'analisi consiste nella raccolta, nell'interpretazione, nell'aggregazione e nel riepilogo dei dati delle riproduzioni. Questi dati possono essere utilizzati sul dispositivo, ad esempio per la registrazione, il debug o per informare le future decisioni di riproduzione, oppure possono essere inviati a un server per monitorare le riproduzioni su tutti i dispositivi.
Un sistema di analisi di solito deve prima raccogliere gli eventi e poi elaborarli ulteriormente per renderli significativi:
- Raccolta degli eventi:
può essere eseguita registrando un
AnalyticsListenersu un'istanzaExoPlayer. Gli ascoltatori di analisi registrati ricevono gli eventi man mano che si verificano durante l'utilizzo del player. Ogni evento è associato all'elemento multimediale corrispondente nella playlist, nonché ai metadati relativi alla posizione di riproduzione e al timestamp. - Elaborazione degli eventi:
alcuni sistemi di analisi caricano gli eventi non elaborati su un server, con tutta l'elaborazione degli eventi eseguita lato server. È anche possibile elaborare gli eventi sul dispositivo, il che potrebbe essere più semplice o ridurre la quantità di informazioni da caricare. ExoPlayer fornisce
PlaybackStatsListener, che consente di eseguire i seguenti passaggi di elaborazione:- Interpretazione degli eventi: per essere utili ai fini dell'analisi, gli eventi devono
essere interpretati nel contesto di una singola riproduzione. Ad esempio, l'evento
non elaborato di una modifica dello stato del player in
STATE_BUFFERINGpuò corrispondere a un buffering iniziale, un rebuffer o un buffering che si verifica dopo una ricerca. - Monitoraggio dello stato: questo passaggio converte gli eventi in contatori. Ad esempio, gli eventi di modifica dello stato possono essere convertiti in contatori che monitorano il tempo trascorso in ogni stato di riproduzione. Il risultato è un insieme di base di valori dei dati di analisi per una singola riproduzione.
- Aggregazione: questo passaggio combina i dati di analisi su più riproduzioni, in genere sommando i contatori.
- Calcolo delle metriche riepilogative: molte delle metriche più utili sono quelle che calcolano le medie o combinano i valori di base dei dati di Analytics in altri modi. Le metriche riepilogative possono essere calcolate per una o più riproduzioni.
- Interpretazione degli eventi: per essere utili ai fini dell'analisi, gli eventi devono
essere interpretati nel contesto di una singola riproduzione. Ad esempio, l'evento
non elaborato di una modifica dello stato del player in
Raccolta di eventi con AnalyticsListener
Gli eventi di riproduzione non elaborati del player vengono segnalati alle implementazioni di AnalyticsListener. Puoi aggiungere facilmente il tuo listener e sostituire solo i
metodi che ti interessano:
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) {} });
Il EventTime passato a ogni callback associa l'evento a un elemento multimediale
nella playlist, nonché ai metadati relativi alla posizione di riproduzione e al timestamp:
realtimeMs: l'ora dell'evento.timeline,windowIndexemediaPeriodId: definisce la playlist e l'elemento all'interno della playlist a cui appartiene l'evento.mediaPeriodIdcontiene informazioni aggiuntive facoltative, ad esempio indica se l'evento appartiene a un annuncio all'interno dell'elemento.eventPlaybackPositionMs: La posizione di riproduzione nell'elemento in cui si è verificato l'evento.currentTimeline,currentWindowIndex,currentMediaPeriodIdecurrentPlaybackPositionMs: come sopra, ma per l'elemento in riproduzione. L'elemento attualmente in riproduzione potrebbe essere diverso da quello a cui appartiene l'evento, ad esempio se l'evento corrisponde al pre-buffering dell'elemento successivo da riprodurre.
Elaborazione degli eventi con PlaybackStatsListener
PlaybackStatsListener è un AnalyticsListener che implementa l'elaborazione degli eventi
sul dispositivo. Calcola PlaybackStats, con contatori e metriche derivate, tra cui:
- Metriche di riepilogo, ad esempio il tempo di riproduzione totale.
- Metriche sulla qualità di riproduzione adattiva, ad esempio la risoluzione video media.
- Metriche di qualità del rendering, ad esempio la frequenza di frame eliminati.
- Metriche di utilizzo delle risorse, ad esempio il numero di byte letti tramite la rete.
Troverai un elenco completo dei conteggi e delle metriche derivate disponibili nella
PlaybackStats documentazione Javadoc.
PlaybackStatsListener calcola un PlaybackStats separato per ogni elemento multimediale
nella playlist, nonché per ogni annuncio lato client inserito in questi elementi. Puoi
fornire un callback a PlaybackStatsListener per ricevere una notifica al termine
delle riproduzioni e utilizzare EventTime passato al callback per identificare quale
riproduzione è terminata. È possibile aggregare i dati analitici per
più riproduzioni. È anche possibile eseguire query su PlaybackStats per la
sessione di riproduzione corrente in qualsiasi momento utilizzando
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. }));
Il costruttore di PlaybackStatsListener offre la possibilità di conservare l'intera cronologia degli eventi elaborati. Tieni presente che ciò potrebbe comportare un sovraccarico di memoria sconosciuto
a seconda della durata della riproduzione e del numero di eventi. Pertanto, dovresti
attivarla solo se hai bisogno di accedere alla cronologia completa degli eventi
elaborati, anziché solo ai dati di analisi finali.
Tieni presente che PlaybackStats utilizza un insieme esteso di stati per indicare non solo
lo stato dei contenuti multimediali, ma anche l'intenzione dell'utente di riprodurli e informazioni
più dettagliate, ad esempio il motivo per cui la riproduzione è stata interrotta o terminata:
| Stato di riproduzione | Intenzione dell'utente di giocare | Nessuna intenzione di giocare |
|---|---|---|
| Prima della riproduzione | JOINING_FOREGROUND |
NOT_STARTED, JOINING_BACKGROUND |
| Riproduzione attiva | PLAYING |
|
| Riproduzione interrotta | BUFFERING, SEEKING |
PAUSED, PAUSED_BUFFERING, SUPPRESSED, SUPPRESSED_BUFFERING e INTERRUPTED_BY_AD |
| Stati finali | ENDED, STOPPED, FAILED, ABANDONED |
L'intenzione dell'utente di riprodurre è importante per distinguere i momenti in cui l'utente
attende attivamente la riproduzione dai tempi di attesa passivi. Ad esempio,
PlaybackStats.getTotalWaitTimeMs restituisce il tempo totale trascorso negli stati
JOINING_FOREGROUND, BUFFERING e SEEKING, ma non il tempo in cui
la riproduzione è stata sospesa. Analogamente, PlaybackStats.getTotalPlayAndWaitTimeMs restituirà
il tempo totale con l'intenzione di un utente di giocare, ovvero il tempo di attesa
attivo totale e il tempo totale trascorso nello stato PLAYING.
Eventi elaborati e interpretati
Puoi registrare gli eventi elaborati e interpretati utilizzando PlaybackStatsListener
con keepHistory=true. Il PlaybackStats risultante conterrà
i seguenti elenchi di eventi:
playbackStateHistory: un elenco ordinato di stati di riproduzione estesi con ilEventTimein cui hanno iniziato a essere applicati. Puoi anche utilizzarePlaybackStats.getPlaybackStateAtTimeper cercare lo stato a una determinata ora dell'orologio a muro.mediaTimeHistory: una cronologia delle coppie di ora dell'orologio a muro e ora dei contenuti multimediali che ti consente di ricostruire quali parti dei contenuti multimediali sono state riprodotte in quale momento. Puoi anche utilizzarePlaybackStats.getMediaTimeMsAtRealtimeMsper cercare la posizione di riproduzione in un determinato momento.videoFormatHistoryeaudioFormatHistory: elenchi ordinati di formati video e audio utilizzati durante la riproduzione con ilEventTimein cui hanno iniziato a essere utilizzati.fatalErrorHistoryenonFatalErrorHistory: elenchi ordinati di errori irreversibili e non irreversibili con ilEventTimein cui si sono verificati. Gli errori irreversibili sono quelli che hanno interrotto la riproduzione, mentre gli errori non irreversibili potrebbero essere stati recuperabili.
Dati di analisi della riproduzione singola
Questi dati vengono raccolti automaticamente se utilizzi PlaybackStatsListener, anche
con keepHistory=false. I valori finali sono i campi pubblici che puoi
trovare nella Javadoc di PlaybackStats e le durate dello stato di riproduzione
restituite da getPlaybackStateDurationMs. Per comodità, troverai anche
metodi come getTotalPlayTimeMs e getTotalWaitTimeMs che restituiscono la
durata di combinazioni specifiche di stati di riproduzione.
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);
Aggregare i dati di analisi di più riproduzioni
Puoi combinare più PlaybackStats chiamando
PlaybackStats.merge. Il PlaybackStats risultante conterrà i dati aggregati
di tutte le riproduzioni unite. Tieni presente che non conterrà la cronologia dei singoli eventi di riproduzione, in quanto non possono essere aggregati.
PlaybackStatsListener.getCombinedPlaybackStats può essere utilizzato per ottenere una
visualizzazione aggregata di tutti i dati di analisi raccolti nel ciclo di vita di un
PlaybackStatsListener.
Metriche di riepilogo calcolate
Oltre ai dati di base di Analytics, PlaybackStats fornisce molti metodi
per calcolare le metriche di riepilogo.
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());
Argomenti avanzati
Associazione dei dati di analisi ai metadati di riproduzione
Quando raccogli i dati di analisi per le singole riproduzioni, potresti voler associare i dati di analisi della riproduzione ai metadati relativi ai contenuti multimediali riprodotti.
È consigliabile impostare metadati specifici per i contenuti multimediali con MediaItem.Builder.setTag.
Il tag media fa parte di EventTime segnalato per gli eventi non elaborati e al termine di PlaybackStats, quindi può essere recuperato facilmente durante la gestione dei dati di analisi corrispondenti:
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. });
Report sugli eventi di analisi personalizzati
Se devi aggiungere eventi personalizzati ai dati di analisi, devi salvarli
nella tua struttura dei dati e combinarli con i PlaybackStats riportati in un secondo momento. Se può esserti utile, puoi estendere DefaultAnalyticsCollector
per poter generare EventTime istanze per i tuoi eventi personalizzati e inviarle
agli ascoltatori già registrati, come mostrato nell'esempio seguente.
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(); }