Analytics

O ExoPlayer oferece suporte a uma ampla variedade de necessidades de análise de reprodução. No final das contas, a análise tem a ver com 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 para informar futuras decisões de reprodução, ou relatado a um para monitorar reproduções em todos os dispositivos.

Um sistema de análise geralmente precisa coletar eventos primeiro e depois processá-los mais para torná-los significativos:

  • Coleta do evento: Para isso, registre um AnalyticsListener em um ExoPlayer instância. Os listeners de análise registrados recebem eventos à medida que eles ocorrem durante pelo uso do player. Cada evento é associado à mídia correspondente na lista de reprodução, bem como metadados de posição de reprodução e marcação de tempo.
  • Processamento de eventos: Alguns sistemas de análise fazem upload de eventos brutos para um servidor, com todos os o processamento é realizado no lado do servidor. Também é possível processar eventos no e fazer isso pode ser mais simples ou reduzir a quantidade de informações que que precisa ser carregada. O ExoPlayer fornece PlaybackStatsListener, que permite que você realize as seguintes etapas de processamento:
    1. Interpretação de eventos: para serem úteis para fins de análise, os eventos precisam seja interpretada no contexto de uma única reprodução. Por exemplo, os dados brutos evento de mudança de estado do player para STATE_BUFFERING pode corresponder a armazenamento em buffer inicial, buffering ou armazenamento em buffer que ocorre após uma busca.
    2. Rastreamento de estado: essa etapa converte eventos em contadores. Por exemplo: eventos de alteração 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 dados de análise valores para uma única reprodução.
    3. Agregação: esta etapa combina os dados de análise em vários reproduções, normalmente somando contadores.
    4. Cálculo de métricas resumidas: muitas das métricas mais úteis são aqueles que calculam médias ou combinam os valores básicos dos dados analíticos de outras maneiras. As métricas resumidas podem ser calculadas para um ou vários reproduções.

Coleta de eventos com o AnalyticsListener

Eventos de reprodução brutos do player são informados ao AnalyticsListener e implementações. É possível adicionar facilmente seu próprio listener e substituir apenas os métodos em que você tem interesse:

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 transmitido para cada callback associa o evento a uma mídia na playlist, assim como metadados de posição de reprodução e marcação de tempo:

  • realtimeMs: o horário real do evento.
  • timeline, windowIndex e mediaPeriodId: definem a playlist e o item na playlist à qual o evento pertence. O mediaPeriodId contém informações adicionais opcionais, por exemplo, indicando se o evento pertence a um anúncio no item.
  • eventPlaybackPositionMs: a posição da reprodução no item quando o evento o incidente.
  • currentTimeline, currentWindowIndex, currentMediaPeriodId e currentPlaybackPositionMs: igual ao item acima, mas para o item em reprodução. A item em reprodução pode ser diferente do item ao qual o evento por exemplo, se o evento corresponder ao armazenamento prévio em buffer do item a ser reproduzido.

Processamento de eventos com PlaybackStatsListener

PlaybackStatsListener é uma AnalyticsListener que implementa no dispositivo processamento de eventos. Ela calcula PlaybackStats, com contadores e incluindo:

  • métricas de resumo, por exemplo, o tempo total de reprodução;
  • Métricas adaptáveis de qualidade de reprodução, como a resolução média do vídeo.
  • Renderizar métricas de qualidade, como a taxa de frames perdidos.
  • Métricas de uso de recursos, por exemplo, o número de bytes lidos na rede.

Você encontrará uma lista completa das contagens e métricas derivadas disponíveis na Javadoc PlaybackStats.

O PlaybackStatsListener calcula PlaybackStats separados para cada item de mídia na lista de reprodução e cada anúncio do cliente inserido nesses itens. Você pode fornecer um callback para PlaybackStatsListener para ser informado sobre a conclusão reproduções e usar o EventTime transmitido ao callback para identificar quais a reprodução for concluída. É possível agregar os dados de análise para várias reproduções. Também é possível consultar o PlaybackStats do 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 a classe histórico 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, você só devem ativá-la se você precisar de acesso ao histórico completo de e não apenas nos dados finais de análise.

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 jogar e informações informações como por que a reprodução foi interrompida ou encerrada:

Estado da 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 aguardando ativamente a reprodução para continuar a partir dos tempos de espera passivas. Por exemplo: PlaybackStats.getTotalWaitTimeMs retorna o tempo total gasto no Estados JOINING_FOREGROUND, BUFFERING e SEEKING, mas não a hora em que a reprodução foi pausada. Da mesma forma, PlaybackStats.getTotalPlayAndWaitTimeMs vai retornar o tempo total com a intenção de um usuário de jogar, ou seja, o tempo total de tempo de espera 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 o seguintes listas de eventos:

  • playbackStateHistory: uma lista ordenada de estados de reprodução estendidos com o EventTime em que começaram a ser usados. Você também pode usar PlaybackStats.getPlaybackStateAtTime para procurar o estado em uma determinada parede no horário do relógio.
  • mediaTimeHistory: um histórico de relógios de parede e pares de tempo de mídia que permite que você reconstrua quais partes da mídia foram tocadas e em qual momento. Você pode use PlaybackStats.getMediaTimeMsAtRealtimeMs para procurar a reprodução posição em um determinado horário de parede.
  • videoFormatHistory e audioFormatHistory: listas ordenadas de vídeos e formatos de áudio usados durante a reprodução com o EventTime em que foram iniciados que serão usadas.
  • fatalErrorHistory e nonFatalErrorHistory: listas ordenadas de erros fatais e erros não fatais pelo EventTime em que 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 o PlaybackStatsListener, mesmo que com keepHistory=false. Os valores finais são os campos públicos que você pode encontrar no PlaybackStats Javadoc e as durações do estado de reprodução retornado por getPlaybackStateDurationMs. Para sua comodidade, você também métodos como getTotalPlayTimeMs e getTotalWaitTimeMs, que retornam o 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);

Agregar dados de análise de várias reproduções

É possível combinar vários PlaybackStats ao chamar PlaybackStats.merge. O PlaybackStats resultante vai conter o dados de todas as reproduções mescladas. Observe que ele não conterá o histórico do eventos de reprodução individuais, pois eles não podem ser agregados.

PlaybackStatsListener.getCombinedPlaybackStats pode ser usado para receber visualização agregada de todos os dados de análise coletados durante a vida útil de um PlaybackStatsListener.

Métricas de resumo calculadas

Além dos dados básicos de análise, PlaybackStats fornece muitos métodos para calcular as métricas resumidas.

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

Como associar dados de análise a metadados de reprodução

Ao coletar dados de análise para reproduções individuais, recomendamos associar os dados de análise de reprodução a metadados sobre a mídia que está sendo tocado.

Recomendamos 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 são concluídas, para que possam ser facilmente recuperadas ao processar o 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.
    });

Como gerar relatórios de eventos de análise personalizados

Caso precise adicionar eventos personalizados aos dados de análise, você terá que salvar esses eventos na sua própria estrutura de dados e combiná-los com o PlaybackStats depois. Se isso ajudar, estenda DefaultAnalyticsCollector para gerar instâncias de EventTime para seus eventos personalizados e enviar aos listeners já registrados, como mostrado no exemplo a seguir.

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