O ExoPlayer oferece suporte a uma ampla variedade de necessidades de análise de reprodução. Em última análise, a análise consiste em coletar, interpretar, agregar e resumir os dados das reproduções. Esses dados podem ser usados no dispositivo, por exemplo, para geração de registros, depuração ou informar decisões de reprodução futuras, ou informados a um servidor para monitorar as reproduções em todos os dispositivos.
Um sistema analítico geralmente precisa coletar eventos primeiro e depois processá-los ainda mais para torná-los significativos:
- Coleta de eventos:
para fazer isso, registre um
AnalyticsListener
em uma instância deExoPlayer
. Os listeners de análise registrados recebem eventos à medida que eles ocorrem durante o uso do player. Cada evento é associado ao item de mídia correspondente na playlist, bem como aos metadados de posição da reprodução e de carimbo de data/hora. - Processamento de eventos: alguns sistemas de análise fazem o upload de eventos brutos para um servidor, e todo o processamento é realizado no lado do servidor. Também é possível processar eventos no
dispositivo. Fazer isso pode ser mais simples ou reduzir a quantidade de informações que
precisam ser enviadas. O ExoPlayer fornece
PlaybackStatsListener
, que permite executar as seguintes etapas de processamento:- Interpretação de eventos: para ser útil para fins de análise, os eventos precisam
ser interpretados no contexto de uma única reprodução. Por exemplo, o evento
bruto de uma mudança de estado do player para
STATE_BUFFERING
pode corresponder ao armazenamento em buffer inicial, a um novo buffer ou ao armazenamento em buffer que acontece após uma busca. - Rastreamento de estado: esta etapa converte eventos em contadores. Por exemplo, eventos de mudança de estado podem ser convertidos em contadores que rastreiam quanto tempo é gasto em cada estado de reprodução. O resultado é um conjunto básico de valores de dados de análise para uma única reprodução.
- Agregação: esta etapa combina os dados de análise de várias reproduções, normalmente somando contadores.
- Cálculo de métricas resumidas: muitas das métricas mais úteis são aquelas que calculam médias ou combinam os valores básicos de dados de análise de outras maneiras. As métricas de resumo podem ser calculadas para uma ou várias reproduções.
- Interpretação de eventos: para ser útil para fins de análise, os eventos precisam
ser interpretados no contexto de uma única reprodução. Por exemplo, o evento
bruto de uma mudança de estado do player para
Coleta de eventos com AnalyticsListener
Os eventos brutos de reprodução do player são informados às implementações
AnalyticsListener
. Você pode adicionar facilmente o próprio listener e modificar apenas os
métodos em que está interessado:
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) {} });
O EventTime
que é transmitido para cada callback associa o evento a um item de mídia
na playlist, além da posição da reprodução e dos metadados do carimbo de data/hora:
realtimeMs
: o horário convencional do evento.timeline
,windowIndex
emediaPeriodId
: definem a playlist e o item na playlist a que o evento pertence.mediaPeriodId
contém outras informações opcionais, por exemplo, indicando se o evento pertence a um anúncio no item.eventPlaybackPositionMs
: a posição de reprodução no item quando o evento ocorreu.currentTimeline
,currentWindowIndex
,currentMediaPeriodId
ecurrentPlaybackPositionMs
: conforme mostrado acima, mas para o item em reprodução no momento. O item em reprodução no momento pode ser diferente do item a que o evento pertence, por exemplo, se o evento corresponder ao armazenamento em buffer do próximo item a ser reproduzido.
Processamento de eventos com PlaybackStatsListener
PlaybackStatsListener
é um AnalyticsListener
que implementa o processamento
de eventos no dispositivo. Ela calcula PlaybackStats
, com contadores e métricas derivadas, incluindo:
- Métricas de resumo, por exemplo, o tempo total de reprodução.
- Métricas de qualidade de reprodução adaptável, por exemplo, a resolução média de vídeo.
- Métricas de qualidade da renderização, como a taxa de frames perdidos.
- Métricas de uso de recursos, como o número de bytes lidos na rede.
Você encontra uma lista completa das contagens disponíveis e métricas derivadas no
Javadoc PlaybackStats
.
PlaybackStatsListener
calcula PlaybackStats
separados para cada item de mídia
na playlist, além de cada anúncio do lado do cliente inserido nesses itens. Você
pode fornecer um callback a PlaybackStatsListener
para ser informado sobre as reproduções concluídas
e usar o EventTime
transmitido ao callback para identificar qual
reprodução terminou. É possível agregar os dados de análise para
várias reproduções. Também é possível consultar o PlaybackStats
para a
sessão de reprodução atual a qualquer momento usando
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. }));
O construtor de PlaybackStatsListener
oferece a opção de manter o histórico completo
de eventos processados. Isso pode gerar uma sobrecarga de memória desconhecida,
dependendo da duração da reprodução e do número de eventos. Portanto, ative-a apenas se precisar de acesso ao histórico completo de eventos processados, e não apenas aos dados de análise finais.
Observe que PlaybackStats
usa um conjunto estendido de estados para indicar não apenas
o estado da mídia, mas também a intenção do usuário de reproduzir e informações mais detalhadas,
como por que a reprodução foi interrompida ou finalizada:
Estado de reprodução | Intenção do usuário de jogar | Sem intenção de jogar |
---|---|---|
Antes da reprodução | JOINING_FOREGROUND |
NOT_STARTED , JOINING_BACKGROUND |
Reprodução ativa | PLAYING |
|
Reprodução interrompida | BUFFERING , SEEKING |
PAUSED , PAUSED_BUFFERING , SUPPRESSED , SUPPRESSED_BUFFERING , INTERRUPTED_BY_AD |
Estados finais | ENDED , STOPPED , FAILED , ABANDONED |
A intenção do usuário de jogar é importante para distinguir os momentos em que o usuário estava
esperando ativamente a reprodução para continuar dos tempos de espera passivos. Por exemplo,
PlaybackStats.getTotalWaitTimeMs
retorna o tempo total gasto nos estados
JOINING_FOREGROUND
, BUFFERING
e SEEKING
, mas não o tempo em que
a reprodução foi pausada. Da mesma forma, PlaybackStats.getTotalPlayAndWaitTimeMs
retornará
o tempo total com uma intenção do usuário de jogar, ou seja, o tempo de espera total
ativo e o tempo total gasto no estado PLAYING
.
Eventos processados e interpretados
É possível registrar eventos processados e interpretados usando PlaybackStatsListener
com keepHistory=true
. O PlaybackStats
resultante vai conter as
seguintes listas de eventos:
playbackStateHistory
: uma lista ordenada de estados de reprodução estendida com aEventTime
em que eles começaram a ser aplicados. Também é possível usarPlaybackStats.getPlaybackStateAtTime
para procurar o estado em um determinado horário.mediaTimeHistory
: um histórico de pares de horário de parede e mídia que permite reconstruir quais partes da mídia foram reproduzidas em que momento. Você também pode usarPlaybackStats.getMediaTimeMsAtRealtimeMs
para procurar a posição da reprodução em um determinado horário de parede.videoFormatHistory
eaudioFormatHistory
: listas ordenadas de formatos de vídeo e áudio usados durante a reprodução com oEventTime
em que começaram a ser usados.fatalErrorHistory
enonFatalErrorHistory
: listas ordenadas de erros fatais e não fatais com oEventTime
em que eles ocorreram. Erros fatais são aqueles que encerraram a reprodução, enquanto erros não fatais podem ter sido recuperáveis.
Dados de análise de reprodução única
Esses dados serão coletados automaticamente se você usar PlaybackStatsListener
, mesmo
com keepHistory=false
. Os valores finais são os campos públicos que podem
ser encontrados no Javadoc PlaybackStats
e as durações do estado de reprodução
retornadas por getPlaybackStateDurationMs
. Por conveniência, você também encontrará
métodos como getTotalPlayTimeMs
e getTotalWaitTimeMs
, que retornam a
duração de combinações específicas de estados de reprodução.
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);
Dados de análise agregados de várias reproduções
É possível combinar várias PlaybackStats
chamando
PlaybackStats.merge
. O PlaybackStats
resultante conterá os dados
agregados de todas as reproduções mescladas. Ele não contém o histórico de
eventos de reprodução individuais, já que eles não podem ser agregados.
PlaybackStatsListener.getCombinedPlaybackStats
pode ser usado para ter uma
visualização agregada de todos os dados de análise coletados durante o ciclo de vida de uma
PlaybackStatsListener
.
Resumo das métricas calculadas
Além dos dados básicos de análise, o PlaybackStats
fornece muitos métodos
para calcular as métricas de resumo.
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 avançados
Associar dados de análise a metadados de reprodução
Ao coletar dados de análise para reproduções individuais, é recomendável associar os dados de análise de reprodução aos metadados sobre a mídia que está sendo reproduzida.
É aconselhável definir metadados específicos de mídia com MediaItem.Builder.setTag
.
A tag de mídia faz parte do EventTime
informado para eventos brutos e quando
PlaybackStats
é concluído. Dessa forma, ela pode ser facilmente extraída ao processar os
dados de análise correspondentes:
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. });
Gerar relatórios de eventos de análise personalizada
Se você precisar adicionar eventos personalizados aos dados de análise, salve-os
na sua própria estrutura de dados e combine-os com o PlaybackStats
informado
posteriormente. Se isso ajudar, você poderá estender DefaultAnalyticsCollector
para gerar instâncias de EventTime
para seus eventos personalizados e enviá-las
aos listeners já registrados, como mostrado no exemplo abaixo.
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();