Analytics

ExoPlayer поддерживает широкий спектр потребностей в аналитике воспроизведения. В конечном счете, аналитика — это сбор, интерпретация, агрегирование и обобщение данных, полученных при воспроизведении. Эти данные могут использоваться либо на устройстве (например, для регистрации, отладки или для информирования о будущих решениях по воспроизведению), либо передаваться на сервер для мониторинга воспроизведения на всех устройствах.

Аналитической системе обычно необходимо сначала собирать события, а затем обрабатывать их, чтобы сделать их значимыми:

  • Сбор событий : это можно сделать, зарегистрировав AnalyticsListener в экземпляре ExoPlayer . Зарегистрированные прослушиватели аналитики получают события по мере их возникновения во время использования проигрывателя. Каждое событие связано с соответствующим медиа-элементом в списке воспроизведения, а также с метаданными позиции воспроизведения и метки времени.
  • Обработка событий . Некоторые аналитические системы загружают необработанные события на сервер, при этом вся обработка событий выполняется на стороне сервера. Также можно обрабатывать события на устройстве, и это может быть проще или уменьшить объем информации, которую необходимо загрузить. ExoPlayer предоставляет PlaybackStatsListener , который позволяет выполнять следующие этапы обработки:
    1. Интерпретация событий . Чтобы быть полезными для целей аналитики, события необходимо интерпретировать в контексте одного воспроизведения. Например, необработанное событие изменения состояния игрока на STATE_BUFFERING может соответствовать начальной буферизации, повторной буферизации или буферизации, которая происходит после поиска.
    2. Отслеживание состояния : на этом этапе события преобразуются в счетчики. Например, события изменения состояния можно преобразовать в счетчики, отслеживающие, сколько времени потрачено в каждом состоянии воспроизведения. Результатом является базовый набор значений аналитических данных для одного воспроизведения.
    3. Агрегация . На этом этапе аналитические данные из нескольких воспроизведений объединяются, обычно путем суммирования счетчиков.
    4. Расчет сводных показателей . Многие из наиболее полезных показателей — это те, которые вычисляют средние значения или объединяют значения основных аналитических данных другими способами. Сводные показатели можно рассчитать для одного или нескольких воспроизведений.

Сбор событий с помощью AnalyticsListener

Необработанные события воспроизведения из проигрывателя передаются реализациям AnalyticsListener . Вы можете легко добавить свой собственный прослушиватель и переопределить только те методы, которые вас интересуют:

Котлин

exoPlayer.addAnalyticsListener(
  object : AnalyticsListener {
    override fun onPlaybackStateChanged(
      eventTime: EventTime, @Player.State state: Int
    ) {}

    override fun onDroppedVideoFrames(
      eventTime: EventTime,
      droppedFrames: Int,
      elapsedMs: Long,
    ) {}
  }
)

Ява

exoPlayer.addAnalyticsListener(
    new AnalyticsListener() {
      @Override
      public void onPlaybackStateChanged(
          EventTime eventTime, @Player.State int state) {}

      @Override
      public void onDroppedVideoFrames(
          EventTime eventTime, int droppedFrames, long elapsedMs) {}
    });

EventTime , передаваемый каждому обратному вызову, связывает событие с медиа-элементом в списке воспроизведения, а также с метаданными позиции воспроизведения и метки времени:

  • realtimeMs : время события на настенных часах.
  • timeline , windowIndex и mediaPeriodId : определяет список воспроизведения и элемент в списке воспроизведения, которому принадлежит событие. mediaPeriodId содержит необязательную дополнительную информацию, например указывающую, принадлежит ли событие рекламе в элементе.
  • eventPlaybackPositionMs : позиция воспроизведения элемента в момент возникновения события.
  • currentTimeline , currentWindowIndex , currentMediaPeriodId и currentPlaybackPositionMs : то же, что и выше, но для воспроизводимого в данный момент элемента. Воспроизводимый в данный момент элемент может отличаться от элемента, которому принадлежит событие, например, если событие соответствует предварительной буферизации следующего воспроизводимого элемента.

Обработка событий с помощью PlaybackStatsListener

PlaybackStatsListener — это AnalyticsListener , реализующий обработку событий на устройстве. Он вычисляет PlaybackStats со счетчиками и производными показателями, включая:

  • Сводные показатели, например общее время воспроизведения.
  • Адаптивные показатели качества воспроизведения, например среднее разрешение видео.
  • Показатели качества рендеринга, например частота пропущенных кадров.
  • Показатели использования ресурсов, например количество байтов, прочитанных по сети.

Полный список доступных счетчиков и производных показателей можно найти в PlaybackStats Javadoc .

PlaybackStatsListener вычисляет отдельные PlaybackStats для каждого элемента мультимедиа в списке воспроизведения, а также для каждого клиентского объявления, вставленного в эти элементы. Вы можете предоставить обратный вызов PlaybackStatsListener чтобы получать информацию о завершенном воспроизведении, и использовать EventTime , передаваемый обратному вызову, чтобы определить, какое воспроизведение завершилось. Можно агрегировать аналитические данные для нескольких воспроизведений. Также можно в любой момент запросить PlaybackStats для текущего сеанса воспроизведения с помощью PlaybackStatsListener.getPlaybackStats() .

Котлин

exoPlayer.addAnalyticsListener(
  PlaybackStatsListener(/* keepHistory= */ true) {
    eventTime: EventTime?,
    playbackStats: PlaybackStats?,
    -> // Analytics data for the session started at `eventTime` is ready.
  }
)

Ява

exoPlayer.addAnalyticsListener(
    new PlaybackStatsListener(
        /* keepHistory= */ true,
        (eventTime, playbackStats) -> {
          // Analytics data for the session started at `eventTime` is ready.
        }));

Конструктор PlaybackStatsListener дает возможность сохранять полную историю обработанных событий. Обратите внимание, что это может привести к неизвестным затратам памяти в зависимости от продолжительности воспроизведения и количества событий. Поэтому включать его следует только в том случае, если вам нужен доступ к полной истории обработанных событий, а не только к окончательным данным аналитики.

Обратите внимание, что PlaybackStats использует расширенный набор состояний, чтобы указать не только состояние мультимедиа, но и намерение пользователя воспроизвести, а также более подробную информацию, например, почему воспроизведение было прервано или прекращено:

Состояние воспроизведения Намерение пользователя играть Нет намерения играть
Перед воспроизведением JOINING_FOREGROUND NOT_STARTED , JOINING_BACKGROUND
Активное воспроизведение PLAYING
Прерванное воспроизведение BUFFERING , SEEKING PAUSED , PAUSED_BUFFERING , SUPPRESSED , SUPPRESSED_BUFFERING , INTERRUPTED_BY_AD
Конечные состояния ENDED , STOPPED , FAILED , ABANDONED

Намерение пользователя воспроизвести важно для того, чтобы отличать времена, когда пользователь активно ждал продолжения воспроизведения, от времени пассивного ожидания. Например, PlaybackStats.getTotalWaitTimeMs возвращает общее время, проведенное в состояниях JOINING_FOREGROUND , BUFFERING и SEEKING , но не время, когда воспроизведение было приостановлено. Аналогичным образом, PlaybackStats.getTotalPlayAndWaitTimeMs вернет общее время, в течение которого пользователь намеревался играть, то есть общее время активного ожидания и общее время, проведенное в состоянии PLAYING .

Обработанные и интерпретированные события

Вы можете записывать обработанные и интерпретированные события с помощью PlaybackStatsListener с keepHistory=true . Полученный PlaybackStats будет содержать следующие списки событий:

  • playbackStateHistory : упорядоченный список расширенных состояний воспроизведения с EventTime , в котором они начали применяться. Вы также можете использовать PlaybackStats.getPlaybackStateAtTime для поиска состояния в заданное время настенных часов.
  • mediaTimeHistory : история пар времени настенных часов и времени мультимедиа, позволяющая реконструировать, какие части мультимедиа воспроизводились в какое время. Вы также можете использовать PlaybackStats.getMediaTimeMsAtRealtimeMs для поиска позиции воспроизведения в заданное время настенных часов.
  • videoFormatHistory и audioFormatHistory : упорядоченные списки видео- и аудиоформатов, используемых во время воспроизведения, с указанием EventTime , в котором они начали использоваться.
  • fatalErrorHistory и nonFatalErrorHistory : упорядоченные списки фатальных и нефатальных ошибок с указанием EventTime , в котором они произошли. Фатальные ошибки — это те, которые привели к прекращению воспроизведения, тогда как нефатальные ошибки можно было исправить.

Аналитические данные однократного воспроизведения

Эти данные собираются автоматически, если вы используете PlaybackStatsListener , даже если keepHistory=false . Окончательные значения — это общедоступные поля, которые вы можете найти в Javadoc PlaybackStats , а также длительность состояния воспроизведения, возвращаемая getPlaybackStateDurationMs . Для удобства вы также найдете такие методы, как getTotalPlayTimeMs и getTotalWaitTimeMs , которые возвращают продолжительность определенных комбинаций состояний воспроизведения.

Котлин

Log.d(
  "DEBUG",
  "Playback summary: " +
    "play time = " +
    playbackStats.totalPlayTimeMs +
    ", rebuffers = " +
    playbackStats.totalRebufferCount
)

Ява

Log.d(
    "DEBUG",
    "Playback summary: "
        + "play time = "
        + playbackStats.getTotalPlayTimeMs()
        + ", rebuffers = "
        + playbackStats.totalRebufferCount);

Совокупные аналитические данные нескольких воспроизведений

Вы можете объединить несколько PlaybackStats вместе, вызвав PlaybackStats.merge . Полученная PlaybackStats будет содержать агрегированные данные всех объединенных воспроизведений. Обратите внимание, что он не будет содержать историю отдельных событий воспроизведения, поскольку их нельзя агрегировать.

PlaybackStatsListener.getCombinedPlaybackStats можно использовать для получения агрегированного представления всех аналитических данных, собранных за время существования PlaybackStatsListener .

Рассчитываемые сводные показатели

Помимо базовых аналитических данных, PlaybackStats предоставляет множество методов расчета сводных показателей.

Котлин

Log.d(
  "DEBUG",
  "Additional calculated summary metrics: " +
    "average video bitrate = " +
    playbackStats.meanVideoFormatBitrate +
    ", mean time between rebuffers = " +
    playbackStats.meanTimeBetweenRebuffers
)

Ява

Log.d(
    "DEBUG",
    "Additional calculated summary metrics: "
        + "average video bitrate = "
        + playbackStats.getMeanVideoFormatBitrate()
        + ", mean time between rebuffers = "
        + playbackStats.getMeanTimeBetweenRebuffers());

Расширенные темы

Связывание аналитических данных с метаданными воспроизведения

При сборе аналитических данных для отдельных воспроизведений вы можете связать данные аналитики воспроизведения с метаданными о воспроизводимом медиафайле.

Рекомендуется установить метаданные, специфичные для носителя, с помощью MediaItem.Builder.setTag . Медиа-тег является частью EventTime , сообщаемого для необработанных событий и после завершения работы PlaybackStats , поэтому его можно легко получить при обработке соответствующих аналитических данных:

Котлин

PlaybackStatsListener(/* keepHistory= */ false) {
  eventTime: EventTime,
  playbackStats: PlaybackStats ->
  val mediaTag =
    eventTime.timeline
      .getWindow(eventTime.windowIndex, Timeline.Window())
      .mediaItem
      .localConfiguration
      ?.tag
    // Report playbackStats with mediaTag metadata.
}

Ява

new PlaybackStatsListener(
    /* keepHistory= */ false,
    (eventTime, playbackStats) -> {
      Object mediaTag =
          eventTime.timeline.getWindow(eventTime.windowIndex, new Timeline.Window())
              .mediaItem
              .localConfiguration
              .tag;
      // Report playbackStats with mediaTag metadata.
    });

Отчеты о событиях пользовательской аналитики

Если вам нужно добавить пользовательские события к аналитическим данным, вам необходимо сохранить эти события в своей собственной структуре данных и позже объединить их с отчетами PlaybackStats . Если это поможет, вы можете расширить DefaultAnalyticsCollector чтобы иметь возможность создавать экземпляры EventTime для ваших пользовательских событий и отправлять их уже зарегистрированным прослушивателям, как показано в следующем примере.

Котлин

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

Ява

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