Analytics

ExoPlayer répond à un large éventail de besoins en matière d'analyse de la lecture. En fin de compte, l'analyse consiste à collecter, interpréter, agréger et résumer les données issues des lectures. Ces données peuvent être utilisées sur l'appareil (par exemple, pour la journalisation, le débogage ou pour éclairer les futures décisions de lecture) ou être transmises à un serveur pour surveiller les lectures sur tous les appareils.

Un système d'analyse doit généralement collecter des événements, puis les traiter pour leur donner du sens :

  • Collecte d'événements : vous pouvez le faire en enregistrant un AnalyticsListener sur une instance ExoPlayer. Les écouteurs d'analyse enregistrés reçoivent des événements au fur et à mesure qu'ils se produisent lors de l'utilisation du lecteur. Chaque événement est associé à l'élément multimédia correspondant de la playlist, ainsi qu'aux métadonnées de position de lecture et d'horodatage.
  • Traitement des événements : certains systèmes d'analyse importent des événements bruts sur un serveur, et tout le traitement des événements est effectué côté serveur. Il est également possible de traiter les événements sur l'appareil. Cela peut être plus simple ou réduire la quantité d'informations à importer. ExoPlayer fournit PlaybackStatsListener, qui vous permet d'effectuer les étapes de traitement suivantes :
    1. Interprétation des événements : pour être utiles à des fins d'analyse, les événements doivent être interprétés dans le contexte d'une seule lecture. Par exemple, l'événement brut d'un changement d'état du lecteur vers STATE_BUFFERING peut correspondre à une mise en mémoire tampon initiale, à une remise en mémoire tampon ou à une mise en mémoire tampon qui se produit après une recherche.
    2. Suivi de l'état : cette étape convertit les événements en compteurs. Par exemple, les événements de changement d'état peuvent être convertis en compteurs qui suivent le temps passé dans chaque état de lecture. Vous obtenez ainsi un ensemble de valeurs de données analytiques de base pour une seule lecture.
    3. Agrégation : cette étape consiste à combiner les données analytiques de plusieurs lectures, généralement en additionnant les compteurs.
    4. Calcul des métriques récapitulatives : de nombreuses métriques utiles sont celles qui calculent des moyennes ou combinent les valeurs de données analytiques de base d'autres manières. Les métriques récapitulatives peuvent être calculées pour une ou plusieurs lectures.

Collecte d'événements avec AnalyticsListener

Les événements de lecture bruts du lecteur sont signalés aux implémentations AnalyticsListener. Vous pouvez facilement ajouter votre propre écouteur et remplacer uniquement les méthodes qui vous intéressent :

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) {}
    });

Le EventTime transmis à chaque rappel associe l'événement à un élément multimédia de la playlist, ainsi qu'aux métadonnées de position de lecture et d'horodatage :

  • realtimeMs : heure de l'événement.
  • timeline, windowIndex et mediaPeriodId : définissent la playlist et l'élément de la playlist auquel appartient l'événement. mediaPeriodId contient des informations supplémentaires facultatives, par exemple pour indiquer si l'événement appartient à une annonce dans l'élément.
  • eventPlaybackPositionMs : position de lecture dans l'élément au moment où l'événement s'est produit.
  • currentTimeline, currentWindowIndex, currentMediaPeriodId et currentPlaybackPositionMs : comme ci-dessus, mais pour l'élément en cours de lecture. L'élément en cours de lecture peut être différent de celui auquel appartient l'événement, par exemple si l'événement correspond à la pré-mise en mémoire tampon de l'élément suivant à lire.

Traitement des événements avec PlaybackStatsListener

PlaybackStatsListener est un AnalyticsListener qui implémente le traitement des événements sur l'appareil. Elle calcule PlaybackStats, avec des compteurs et des métriques dérivées, y compris :

  • Métriques récapitulatives, par exemple la durée totale de lecture.
  • Métriques sur la qualité de lecture adaptative, par exemple la résolution vidéo moyenne.
  • Métriques de qualité du rendu, par exemple le taux d'images abandonnées.
  • Métriques d'utilisation des ressources, par exemple le nombre d'octets lus sur le réseau.

Vous trouverez la liste complète des métriques et des nombres dérivés disponibles dans la Javadoc PlaybackStats.

PlaybackStatsListener calcule des PlaybackStats distincts pour chaque élément multimédia de la playlist, ainsi que pour chaque annonce insérée côté client dans ces éléments. Vous pouvez fournir un rappel à PlaybackStatsListener pour être informé des lectures terminées et utiliser le EventTime transmis au rappel pour identifier la lecture terminée. Il est possible d'agréger les données analytiques pour plusieurs lectures. Il est également possible d'interroger PlaybackStats pour la session de lecture en cours à tout moment à l'aide de 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.
        }));

Le constructeur de PlaybackStatsListener permet de conserver l'historique complet des événements traités. Notez que cela peut entraîner une surcharge de mémoire inconnue en fonction de la durée de la lecture et du nombre d'événements. Par conséquent, vous ne devez l'activer que si vous avez besoin d'accéder à l'historique complet des événements traités, et pas seulement aux données analytiques finales.

Notez que PlaybackStats utilise un ensemble étendu d'états pour indiquer non seulement l'état du contenu multimédia, mais aussi l'intention de l'utilisateur de le lire et des informations plus détaillées, par exemple la raison pour laquelle la lecture a été interrompue ou s'est terminée :

État de la lecture Intention de l'utilisateur de lire un contenu Pas d'intention de jouer
Avant la lecture JOINING_FOREGROUND NOT_STARTED, JOINING_BACKGROUND
Lecture active PLAYING
Lecture interrompue BUFFERING, SEEKING PAUSED, PAUSED_BUFFERING, SUPPRESSED, SUPPRESSED_BUFFERING, INTERRUPTED_BY_AD
États finaux ENDED, STOPPED, FAILED, ABANDONED

L'intention de l'utilisateur de lire du contenu est importante pour distinguer les moments où il attendait activement la reprise de la lecture des temps d'attente passifs. Par exemple, PlaybackStats.getTotalWaitTimeMs renvoie le temps total passé dans les états JOINING_FOREGROUND, BUFFERING et SEEKING, mais pas le temps pendant lequel la lecture a été mise en pause. De même, PlaybackStats.getTotalPlayAndWaitTimeMs renverra le temps total avec une intention de lecture de l'utilisateur, c'est-à-dire le temps d'attente actif total et le temps total passé dans l'état PLAYING.

Événements traités et interprétés

Vous pouvez enregistrer les événements traités et interprétés à l'aide de PlaybackStatsListener avec keepHistory=true. Le PlaybackStats obtenu contiendra les listes d'événements suivantes :

  • playbackStateHistory : liste ordonnée des états de lecture étendus avec le EventTime auquel ils ont commencé à s'appliquer. Vous pouvez également utiliser PlaybackStats.getPlaybackStateAtTime pour rechercher l'état à une heure donnée.
  • mediaTimeHistory : historique des paires heure de l'horloge murale et heure du contenu multimédia qui vous permet de reconstituer les parties du contenu multimédia qui ont été lues à un moment donné. Vous pouvez également utiliser PlaybackStats.getMediaTimeMsAtRealtimeMs pour rechercher la position de lecture à une heure donnée.
  • videoFormatHistory et audioFormatHistory : listes ordonnées des formats vidéo et audio utilisés pendant la lecture, avec le EventTime auquel ils ont commencé à être utilisés.
  • fatalErrorHistory et nonFatalErrorHistory : listes ordonnées des erreurs fatales et non fatales avec le EventTime auquel elles se sont produites. Les erreurs fatales sont celles qui ont interrompu la lecture, tandis que les erreurs non fatales ont pu être récupérées.

Données analytiques sur les lectures uniques

Ces données sont collectées automatiquement si vous utilisez PlaybackStatsListener, même avec keepHistory=false. Les valeurs finales sont les champs publics que vous pouvez trouver dans la Javadoc PlaybackStats et les durées de l'état de lecture renvoyées par getPlaybackStateDurationMs. Pour plus de commodité, vous trouverez également des méthodes telles que getTotalPlayTimeMs et getTotalWaitTimeMs qui renvoient la durée de combinaisons spécifiques d'états de lecture.

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);

Agréger les données analytiques de plusieurs lectures

Vous pouvez combiner plusieurs PlaybackStats en appelant PlaybackStats.merge. La PlaybackStats résultante contiendra les données agrégées de toutes les lectures fusionnées. Notez qu'il ne contient pas l'historique des événements de lecture individuels, car ils ne peuvent pas être agrégés.

PlaybackStatsListener.getCombinedPlaybackStats peut être utilisé pour obtenir une vue agrégée de toutes les données analytiques collectées au cours de la durée de vie d'un PlaybackStatsListener.

Métriques récapitulatives calculées

En plus des données analytiques de base, PlaybackStats fournit de nombreuses méthodes pour calculer les métriques récapitulatives.

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());

Rubriques avancées

Associer des données analytiques à des métadonnées de lecture

Lorsque vous collectez des données analytiques pour des lectures individuelles, vous pouvez associer ces données à des métadonnées sur le contenu multimédia lu.

Il est conseillé de définir des métadonnées spécifiques au contenu multimédia avec MediaItem.Builder.setTag. La balise média fait partie de EventTime signalé pour les événements bruts et lorsque PlaybackStats sont terminés. Elle peut donc être facilement récupérée lors du traitement des données analytiques correspondantes :

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.
    });

Créer des rapports sur les événements Analytics personnalisés

Si vous devez ajouter des événements personnalisés aux données analytiques, vous devez les enregistrer dans votre propre structure de données et les combiner avec les PlaybackStats signalés ultérieurement. Si cela peut vous aider, vous pouvez étendre DefaultAnalyticsCollector pour pouvoir générer des instances EventTime pour vos événements personnalisés et les envoyer aux écouteurs déjà enregistrés, comme indiqué dans l'exemple suivant.

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();
}