Analytics

ExoPlayer supporta un'ampia gamma di esigenze di analisi della riproduzione. In definitiva, l'analisi consiste nel raccogliere, interpretare, aggregare e riassumere i dati delle riproduzioni. Questi dati possono essere utilizzati sul dispositivo, ad esempio per il logging, il debug o per prendere decisioni future sulla riproduzione, oppure possono essere segnalati 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 di eventi: può essere eseguita registrando un AnalyticsListener su un'istanza ExoPlayer. I listener 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 della posizione di riproduzione e del 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 ti consente di eseguire i seguenti passaggi di elaborazione:
    1. 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 un cambio di stato del player in STATE_BUFFERING può corrispondere al buffering iniziale, a un nuovo buffering o al buffering che si verifica dopo una ricerca.
    2. 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 di dati di analisi per una singola riproduzione.
    3. Aggregazione: questo passaggio combina i dati di analisi di più riproduzioni, in genere sommando i contatori.
    4. Calcolo delle metriche di riepilogo: molte delle metriche più utili sono quelle che calcolano le medie o combinano i valori dei dati di analisi di base in altri modi. Le metriche di riepilogo possono essere calcolate per una o più riproduzioni.

Raccolta di eventi con AnalyticsListener

Gli eventi di riproduzione non elaborati del player vengono segnalati alle implementazioni di AnalyticsListener. Puoi aggiungere il tuo listener ed eseguire l'override solo dei 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 parametro EventTime passato a ogni callback associa l'evento a un elemento multimediale nella playlist, nonché ai metadati della posizione di riproduzione e del timestamp:

  • realtimeMs: l'ora dell'evento.
  • timeline, windowIndex e mediaPeriodId: definiscono la playlist e l'elemento all'interno della playlist a cui appartiene l'evento. mediaPeriodId contiene informazioni aggiuntive facoltative, ad esempio se l'evento appartiene a un annuncio all'interno dell'elemento.
  • eventPlaybackPositionMs: la posizione di riproduzione nell'elemento quando si è verificato l'evento.
  • currentTimeline, currentWindowIndex, currentMediaPeriodId e currentPlaybackPositionMs: come descritto in precedenza, 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 on-device. 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 sulla qualità del rendering, ad esempio la frequenza di frame persi.
  • Metriche di utilizzo delle risorse, ad esempio il numero di byte letti sulla rete.

Troverai un elenco completo dei conteggi e delle metriche derivate disponibili nel Javadoc di PlaybackStats.

PlaybackStatsListener calcola PlaybackStats separati per ogni elemento multimediale nella playlist e per ogni annuncio lato client inserito all'interno di questi elementi. Puoi fornire un callback a PlaybackStatsListener per ricevere informazioni sulle riproduzioni terminate e utilizzare il EventTime passato al callback per identificare la riproduzione terminata. È possibile aggregare i dati di analisi 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 l'opzione di conservare la cronologia completa degli eventi elaborati. Tieni presente che ciò potrebbe comportare un overhead di memoria sconosciuto a seconda della durata della riproduzione e del numero di eventi. Pertanto, devi attivarlo 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 della 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, INTERRUPTED_BY_AD
Stati finali ENDED, STOPPED, FAILED, ABANDONED

L'intenzione dell'utente di riprodurre contenuti è importante per distinguere i momenti in cui l'utente attendeva attivamente la continuazione della 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 messa in pausa. Analogamente, PlaybackStats.getTotalPlayAndWaitTimeMs restituirà il tempo totale con l'intenzione dell'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. La risorsa PlaybackStats risultante conterrà i seguenti elenchi di eventi:

  • playbackStateHistory: un elenco ordinato di stati di riproduzione estesa con EventTime in cui hanno iniziato a essere applicati. Puoi anche utilizzare PlaybackStats.getPlaybackStateAtTime per cercare lo stato in un determinato momento.
  • mediaTimeHistory: una cronologia delle coppie di tempo reale e tempo multimediale che ti consente di ricostruire quali parti dei contenuti multimediali sono state riprodotte in quale momento. Puoi anche utilizzare PlaybackStats.getMediaTimeMsAtRealtimeMs per cercare la posizione di riproduzione in un determinato momento.
  • videoFormatHistory e audioFormatHistory: elenchi ordinati dei formati video e audio utilizzati durante la riproduzione con il EventTime in cui hanno iniziato a essere utilizzati.
  • fatalErrorHistory e nonFatalErrorHistory: elenchi ordinati di errori irreversibili e non irreversibili con il EventTime in 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 singola riproduzione

Questi dati vengono raccolti automaticamente se utilizzi PlaybackStatsListener, anche con keepHistory=false. I valori finali sono i campi pubblici che puoi trovare nel Javadoc PlaybackStats e le durate degli stati 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);

Dati di analisi aggregati 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, poiché non possono essere aggregati.

PlaybackStatsListener.getCombinedPlaybackStats può essere utilizzato per ottenere una visualizzazione aggregata di tutti i dati di analisi raccolti nel corso della vita di un PlaybackStatsListener.

Metriche di riepilogo calcolate

Oltre ai dati di analisi di base, 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 in riproduzione.

È consigliabile impostare metadati specifici per i contenuti multimediali con MediaItem.Builder.setTag. Il tag multimediale fa parte del EventTime segnalato per gli eventi non elaborati e quando i PlaybackStats sono terminati, in modo che possa essere recuperato 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.
    });

Generazione di report sugli eventi di analisi personalizzati

Se devi aggiungere eventi personalizzati ai dati di analisi, devi salvarli nella tua struttura di dati e combinarli con i PlaybackStats segnalati in un secondo momento. Se può essere utile, puoi estendere DefaultAnalyticsCollector per poter generare istanze EventTime per i tuoi eventi personalizzati e inviarle ai listener 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();
}

Monitoraggio dei flag di funzionalità e della modalità Canary

Quando esegui test A/B o valuti l'impatto delle nuove ottimizzazioni di Media3, puoi correlare i dati di analisi della riproduzione con i flag di funzionalità attivi e la modalità Canary.

Puoi eseguire query su Flags.getLogString o ispezionare override di flag specifici utilizzando Flags.getEnabledOverrides e Flags.getDisabledOverrides per includere gli stati dei flag nei payload di analisi personalizzati o nei metadati della sessione.