Analytics

ExoPlayer admite una amplia variedad de necesidades de análisis de reproducción. En última instancia, el análisis consiste en recopilar, interpretar, agregar y resumir datos de las reproducciones. Estos datos se pueden usar en el dispositivo (por ejemplo, para registro, depuración o para informar decisiones de reproducción futuras) o se pueden informar a un servidor para supervisar las reproducciones en todos los dispositivos.

Por lo general, un sistema de estadísticas necesita recopilar eventos primero y, luego, procesarlos aún más para que sean significativos:

  • Recopilación de eventos: Para ello, se debe registrar un AnalyticsListener en una instancia de ExoPlayer. Los objetos de escucha de estadísticas registrados reciben eventos a medida que ocurren durante el uso del reproductor. Cada evento se asocia con el elemento multimedia correspondiente de la lista de reproducción, así como con los metadatos de la marca de tiempo y la posición de reproducción.
  • Procesamiento de eventos: Algunos sistemas de estadísticas suben eventos sin procesar a un servidor, y todo el procesamiento de eventos se realiza en el servidor. También es posible procesar eventos en el dispositivo, lo que puede ser más simple o reducir la cantidad de información que debe subirse. ExoPlayer proporciona PlaybackStatsListener, que te permite realizar los siguientes pasos de procesamiento:
    1. Interpretación de eventos: Deben ser útiles con fines estadísticos, y deben interpretarse en el contexto de una sola reproducción. Por ejemplo, el evento sin procesar de un cambio de estado del reproductor a STATE_BUFFERING puede corresponder al almacenamiento en búfer inicial, a un realmacenamiento en el búfer o al almacenamiento en búfer que se produce después de una búsqueda.
    2. Seguimiento de estado: Este paso convierte los eventos en contadores. Por ejemplo, los eventos de cambio de estado se pueden convertir en contadores que rastrean cuánto tiempo se dedica en cada estado de reproducción. El resultado es un conjunto básico de valores de datos de estadísticas para una sola reproducción.
    3. Agregación: En este paso, se combinan los datos de estadísticas en varias reproducciones, por lo general, mediante la suma de contadores.
    4. Cálculo de métricas de resumen: muchas de las métricas más útiles son las que calculan promedios o combinan los valores de datos de estadísticas básicos de otras maneras. Las métricas de resumen se pueden calcular para una o varias reproducciones.

Colección de eventos con AnalyticsListener

Los eventos de reproducción sin procesar del reproductor se informan a las implementaciones de AnalyticsListener. Puedes agregar fácilmente tu propio objeto de escucha y anular solo los métodos que te interesan:

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

El EventTime que se pasa a cada devolución de llamada asocia el evento a un elemento multimedia de la lista de reproducción, así como a la posición de reproducción y los metadatos de la marca de tiempo:

  • realtimeMs: Indica la hora real del evento.
  • timeline, windowIndex y mediaPeriodId: Definen la lista de reproducción y el elemento dentro de la lista de reproducción al que pertenece el evento. El mediaPeriodId contiene información adicional opcional, por ejemplo, si el evento pertenece a un anuncio dentro del elemento.
  • eventPlaybackPositionMs: Es la posición de reproducción en el elemento cuando ocurrió el evento.
  • currentTimeline, currentWindowIndex, currentMediaPeriodId y currentPlaybackPositionMs: Igual que lo anterior, pero para el elemento que se está reproduciendo. El elemento que se está reproduciendo puede ser diferente del elemento al que pertenece el evento, por ejemplo, si el evento corresponde al almacenamiento en búfer previo del siguiente elemento que se reproducirá.

Procesamiento de eventos con PlaybackStatsListener

PlaybackStatsListener es un AnalyticsListener que implementa el procesamiento de eventos en el dispositivo. Calcula PlaybackStats, con contadores y métricas derivadas, que incluyen lo siguiente:

  • Las métricas de resumen, como el tiempo total de reproducción
  • Métricas de calidad de reproducción adaptables (por ejemplo, la resolución de video promedio)
  • Métricas de calidad del procesamiento, por ejemplo, la tasa de disminución de fotogramas
  • Métricas de uso de recursos, como la cantidad de bytes leídos en la red.

Encontrarás una lista completa de los recuentos disponibles y las métricas derivadas en el Javadoc de PlaybackStats.

PlaybackStatsListener calcula PlaybackStats independientes para cada elemento multimedia en la lista de reproducción y también cada anuncio del cliente insertado dentro de estos elementos. Puedes proporcionar una devolución de llamada a PlaybackStatsListener para recibir información sobre las reproducciones finalizadas y usar el EventTime que se pasó a la devolución de llamada para identificar qué reproducción finalizó. Es posible agregar los datos de estadísticas de varias reproducciones. También es posible consultar el PlaybackStats de la sesión de reproducción actual en cualquier momento con 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.
        }));

El constructor de PlaybackStatsListener ofrece la opción de conservar el historial completo de los eventos procesados. Ten en cuenta que esto puede generar una sobrecarga de memoria desconocida según la duración de la reproducción y la cantidad de eventos. Por lo tanto, solo debes activarla si necesitas acceder al historial completo de eventos procesados, en lugar de solo a los datos finales de estadísticas.

Ten en cuenta que PlaybackStats usa un conjunto extendido de estados para indicar no solo el estado del contenido multimedia, sino también la intención del usuario de reproducir el contenido e información más detallada, como por qué se interrumpió o finalizó la reproducción:

Estado de reproducción Intención del usuario de jugar No tengo intención de jugar
Antes de la reproducción JOINING_FOREGROUND NOT_STARTED, JOINING_BACKGROUND
Reproducción activa PLAYING
Reproducción interrumpida BUFFERING, SEEKING PAUSED, PAUSED_BUFFERING, SUPPRESSED, SUPPRESSED_BUFFERING, INTERRUPTED_BY_AD
Estados finales ENDED, STOPPED, FAILED, ABANDONED

La intención de reproducción del usuario es importante para distinguir los momentos en los que el usuario estaba esperando activamente a que continuara la reproducción del tiempo de espera pasivo. Por ejemplo, PlaybackStats.getTotalWaitTimeMs muestra el tiempo total empleado en los estados JOINING_FOREGROUND, BUFFERING y SEEKING, pero no el tiempo en el que se pausó la reproducción. De manera similar, PlaybackStats.getTotalPlayAndWaitTimeMs mostrará el tiempo total con la intención de jugar por parte del usuario, es decir, el tiempo de espera activo total y el tiempo total empleado en el estado PLAYING.

Eventos interpretados y procesados

Puedes registrar eventos interpretados y procesados mediante PlaybackStatsListener con keepHistory=true. El PlaybackStats resultante contendrá las siguientes listas de eventos:

  • playbackStateHistory: Es una lista ordenada de los estados de reproducción extendidas con el EventTime en el que se comenzaron a aplicar. También puedes usar PlaybackStats.getPlaybackStateAtTime para buscar el estado en una hora determinada.
  • mediaTimeHistory: Es un historial de pares de horas reales y de horas multimedia que te permiten reconstruir qué partes del contenido multimedia se reprodujeron en qué momento. También puedes usar PlaybackStats.getMediaTimeMsAtRealtimeMs para buscar la posición de reproducción en un horario específico.
  • videoFormatHistory y audioFormatHistory: Listas ordenadas de formatos de video y audio utilizados durante la reproducción con el EventTime en el que se comenzaron a usar.
  • fatalErrorHistory y nonFatalErrorHistory: Listas ordenadas de errores irrecuperables y recuperables con el EventTime en el que se produjeron. Los errores fatales son aquellos que finalizaron la reproducción, mientras que los errores recuperables se pudieron recuperar.

Datos de estadísticas de reproducción única

Estos datos se recopilan automáticamente si usas PlaybackStatsListener, incluso con keepHistory=false. Los valores finales son los campos públicos que puedes encontrar en el Javadoc PlaybackStats y las duraciones de los estados de reproducción que muestra getPlaybackStateDurationMs. Para mayor comodidad, también encontrarás métodos como getTotalPlayTimeMs y getTotalWaitTimeMs que muestran la duración de combinaciones específicas de estados de reproducción.

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

Datos de análisis agregados de varias reproducciones

Puedes combinar varios PlaybackStats llamando a PlaybackStats.merge. El PlaybackStats resultante contendrá los datos agregados de todas las reproducciones combinadas. Ten en cuenta que no contendrá el historial de eventos de reproducción individuales, ya que no se pueden agregar.

Se puede usar PlaybackStatsListener.getCombinedPlaybackStats para obtener una vista agregada de todos los datos de estadísticas recopilados durante la vida útil de una PlaybackStatsListener.

Métricas de resumen calculadas

Además de los datos de estadísticas básicos, PlaybackStats proporciona muchos métodos para calcular las métricas de resumen.

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

Temas avanzados

Cómo asociar datos de análisis con metadatos de reproducción

Cuando recopiles datos de estadísticas de reproducciones individuales, te recomendamos que asocies los datos de estadísticas de reproducción con metadatos sobre el contenido multimedia que se está reproduciendo.

Se recomienda establecer metadatos específicos del contenido multimedia con MediaItem.Builder.setTag. La etiqueta de medios es parte de los EventTime informados para los eventos sin procesar y cuando finaliza PlaybackStats, de modo que se pueda recuperar fácilmente cuando se manejen los datos de estadísticas correspondientes:

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

Genera informes de eventos de estadísticas personalizados

En caso de que necesites agregar eventos personalizados a los datos de Analytics, debes guardarlos en tu propia estructura de datos y combinarlos con el PlaybackStats informado más adelante. Si te resulta útil, puedes extender DefaultAnalyticsCollector a fin de generar instancias de EventTime para tus eventos personalizados y enviarlas a los objetos de escucha ya registrados, como se muestra en el siguiente ejemplo.

Kotlin

private interface ExtendedListener : AnalyticsListener {
  fun onCustomEvent(eventTime: EventTime)
}

private class ExtendedCollector : DefaultAnalyticsCollector(Clock.DEFAULT) {
  fun customEvent() {
    val eventTime = generateCurrentPlayerMediaPeriodEventTime()
    sendEvent(eventTime, CUSTOM_EVENT_ID) { listener: AnalyticsListener ->
      if (listener is ExtendedListener) {
        listener.onCustomEvent(eventTime)
      }
    }
  }
}

// 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

private interface ExtendedListener extends AnalyticsListener {
  void onCustomEvent(EventTime eventTime);
}

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

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