ExoPlayer admite una amplia variedad de necesidades de estadísticas 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 el registro, la depuración o para informar decisiones de reproducción futuras) o informar a un servidor para supervisar las reproducciones en todos los dispositivos.
Por lo general, un sistema de estadísticas primero debe recopilar eventos y, luego, procesarlos para que sean significativos:
- Recopilación de eventos: Para ello, registra un
AnalyticsListener
en una instancia deExoPlayer
. 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 playlist, así como con la posición de reproducción y los metadatos de marca de tiempo. - Proceso de eventos:
Algunos sistemas de estadísticas suben eventos sin procesar a un servidor, y todo el proceso de eventos
se realiza del lado del servidor. También es posible procesar eventos en el dispositivo, y hacerlo puede ser más sencillo o reducir la cantidad de información que se debe subir. ExoPlayer proporciona
PlaybackStatsListener
, que te permite realizar los siguientes pasos de procesamiento:- Interpretación de eventos: Para que sean útiles con fines de análisis, los eventos 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 almacenamiento en búfer nuevo o al almacenamiento en búfer que se produce después de un salto. - Seguimiento de estado: En este paso, se convierten los eventos en contadores. Por ejemplo, los eventos de cambio de estado se pueden convertir en contadores que hacen un seguimiento de cuánto tiempo se pasa en cada estado de reproducción. El resultado es un conjunto básico de valores de datos de análisis para una sola reproducción.
- Agregación: En este paso, se combinan los datos de estadísticas de varias reproducciones, por lo general, sumando contadores.
- Cálculo de las métricas de resumen: muchas de las métricas más útiles son aquellas 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.
- Interpretación de eventos: Para que sean útiles con fines de análisis, los eventos deben interpretarse en el contexto de una sola reproducción. Por ejemplo, el evento sin procesar de un cambio de estado del reproductor a
Recopilació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) {} });
La EventTime
que se pasa a cada devolución de llamada asocia el evento a un elemento multimedia de la playlist, así como la posición de reproducción y los metadatos de marca de tiempo:
realtimeMs
: Es la hora del reloj del evento.timeline
,windowIndex
ymediaPeriodId
: Definen la playlist y el elemento dentro de la playlist a la que pertenece el evento.mediaPeriodId
contiene información adicional opcional, por ejemplo, que indica si el evento pertenece a un anuncio dentro del artículo.eventPlaybackPositionMs
: Es la posición de reproducción en el elemento cuando ocurrió el evento.currentTimeline
,currentWindowIndex
,currentMediaPeriodId
ycurrentPlaybackPositionMs
: 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, incluidas las siguientes:
- Métricas de resumen, por ejemplo, el tiempo de reproducción total
- Métricas de calidad de reproducción adaptativa, por ejemplo, la resolución de video promedio
- Métricas de calidad de renderización, por ejemplo, la tasa de fotogramas perdidos
- Métricas de uso de recursos, por ejemplo, la cantidad de bytes leídos a través de la red
Encontrarás una lista completa de los recuentos y las métricas derivadas disponibles en la Javadoc de PlaybackStats
.
PlaybackStatsListener
calcula PlaybackStats
independientes para cada elemento multimedia de la playlist y, también, para cada anuncio del cliente insertado dentro de estos elementos. Puedes proporcionar una devolución de llamada a PlaybackStatsListener
para obtener información sobre las reproducciones finalizadas y usar el EventTime
que se pasa a la devolución de llamada para identificar qué reproducción finalizó. Es posible agregar los datos de estadísticas para varias reproducciones. También es posible consultar PlaybackStats
para 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
brinda 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 activarlo si necesitas acceder al historial completo de los 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 reproducirlo y otra información más detallada, como por qué se interrumpió o finalizó la reproducción:
Estado de reproducción | Intención de jugar del usuario | No hay 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 del usuario de reproducir contenido es importante para distinguir los momentos en los que el usuario estaba esperando de forma activa que la reproducción continuara de los tiempos de espera pasivos. Por ejemplo,
PlaybackStats.getTotalWaitTimeMs
muestra el tiempo total transcurrido en los
estados JOINING_FOREGROUND
, BUFFERING
y SEEKING
, pero no el momento en el que
se detuvo la reproducción. De manera similar, PlaybackStats.getTotalPlayAndWaitTimeMs
mostrará el tiempo total con la intención del usuario de jugar, es decir, el tiempo de espera activo total y el tiempo total que se pasó en el estado PLAYING
.
Eventos procesados e interpretados
Puedes registrar eventos procesados e interpretados con PlaybackStatsListener
con keepHistory=true
. El PlaybackStats
resultante contendrá las siguientes listas de eventos:
playbackStateHistory
: Es una lista ordenada de estados de reproducción extendidos con elEventTime
en el que comenzaron a aplicarse. También puedes usarPlaybackStats.getPlaybackStateAtTime
para buscar el estado en un momento determinado.mediaTimeHistory
: Es un historial de horas reales y pares de horas de medios que te permiten reconstruir qué partes del contenido multimedia se reprodujeron en ese momento. También puedes usarPlaybackStats.getMediaTimeMsAtRealtimeMs
para buscar la posición de reproducción en una hora determinada.videoFormatHistory
yaudioFormatHistory
: Son listas ordenadas de formatos de audio y video que se usan durante la reproducción con elEventTime
en el que comenzaron a usarse.fatalErrorHistory
ynonFatalErrorHistory
: Son listas ordenadas de errores graves y no graves con elEventTime
en el que se produjeron. Los errores fatales son aquellos que finalizaron la reproducción, mientras que los errores recuperables podrían haberse recuperado.
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 la Javadoc de PlaybackStats
y las duraciones del estado de reproducción que muestra getPlaybackStateDurationMs
. Para tu comodidad, también encontrarás métodos como getTotalPlayTimeMs
y getTotalWaitTimeMs
que devuelven 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 estadísticas 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 los eventos de reproducción individuales, ya que no se pueden agregar.
PlaybackStatsListener.getCombinedPlaybackStats
se puede usar para obtener una vista agregada de todos los datos de estadísticas recopilados durante el ciclo de vida de un 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
Asocia datos de estadísticas 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 reproduce.
Se recomienda establecer metadatos específicos del contenido multimedia con MediaItem.Builder.setTag
.
La etiqueta de contenido multimedia forma parte de los EventTime
informados para los eventos sin procesar y cuando finalizan los PlaybackStats
, por lo que se puede recuperar fácilmente cuando se controlan 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. });
Cómo informar eventos de estadísticas personalizadas
En caso de que necesites agregar eventos personalizados a los datos de estadísticas, debes guardarlos
en tu propia estructura de datos y combinarlos con el PlaybackStats
informado más adelante. Si te resulta útil, puedes extender DefaultAnalyticsCollector
para poder 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();