ExoPlayer поддерживает широкий спектр потребностей в аналитике воспроизведения. Аналитика позволяет собирать, интерпретировать, агрегировать и обобщать данные о воспроизведении. Эти данные можно использовать на устройстве, например для ведения журналов, отладки или принятия решений о воспроизведении в будущем, или передавать на сервер для отслеживания воспроизведения на всех устройствах.
Система аналитики обычно сначала собирает события, а затем обрабатывает их, чтобы получить полезные данные:
- Сбор данных о событиях. Это можно сделать, зарегистрировав
AnalyticsListenerв экземпляреExoPlayer. Зарегистрированные прослушиватели аналитики получают события по мере их возникновения во время использования проигрывателя. Каждое событие связано с соответствующим медиаобъектом в плейлисте, а также с метаданными о позиции воспроизведения и временной меткой. - Обработка событий. Некоторые системы аналитики загружают необработанные события на сервер, где и выполняется вся обработка. Также можно обрабатывать события на устройстве. Это может быть проще и позволит сократить объем загружаемой информации. ExoPlayer предоставляет
PlaybackStatsListener, с помощью которого можно выполнять следующие действия:- Интерпретация событий. Чтобы события можно было использовать для анализа, их нужно интерпретировать в контексте одного воспроизведения. Например, необработанное событие изменения состояния проигрывателя на
STATE_BUFFERINGможет соответствовать начальной буферизации, повторной буферизации или буферизации после перемотки. - Отслеживание состояния. На этом этапе события преобразуются в счетчики. Например, события изменения состояния можно преобразовать в счетчики, отслеживающие, сколько времени было потрачено на каждое состояние воспроизведения. В результате вы получите базовый набор данных аналитики для одного воспроизведения.
- Агрегирование. На этом этапе данные Аналитики объединяются по нескольким воспроизведениям, как правило, путем суммирования счетчиков.
- Расчет сводных показателей. Многие из наиболее полезных показателей представляют собой средние значения или другие комбинации базовых данных Аналитики. Сводные показатели можно рассчитывать для одного или нескольких воспроизведений.
- Интерпретация событий. Чтобы события можно было использовать для анализа, их нужно интерпретировать в контексте одного воспроизведения. Например, необработанное событие изменения состояния проигрывателя на
Сбор событий с помощью AnalyticsListener
События воспроизведения, полученные от проигрывателя, передаются в реализации AnalyticsListener. Вы можете легко добавить собственный прослушиватель и переопределить только те методы, которые вас интересуют:
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) {} });
Объект EventTime, передаваемый в каждый обратный вызов, связывает событие с медиаконтентом в плейлисте, а также с метаданными о позиции воспроизведения и временной метке:
realtimeMs– время события по настенным часам.timeline,windowIndexиmediaPeriodId– элементы, определяющие плейлист и объект в нем, к которому относится событие;mediaPeriodIdсодержит дополнительную информацию, например о том, относится ли событие к объявлению в объекте.eventPlaybackPositionMs– позиция воспроизведения элемента в момент события.currentTimeline,currentWindowIndex,currentMediaPeriodIdиcurrentPlaybackPositionMs– то же, что и выше, но для текущего воспроизводимого объекта. Текущий воспроизводимый объект может отличаться от объекта, к которому относится событие, например если событие соответствует предварительной буферизации следующего объекта.
Обработка событий с помощью PlaybackStatsListener
PlaybackStatsListener – это AnalyticsListener, которое реализует обработку событий на устройстве. Он рассчитывает PlaybackStats, используя счетчики и производные показатели, в том числе:
- Сводные показатели, например общее время воспроизведения.
- Показатели адаптивного качества воспроизведения, например среднее разрешение видео.
- Показатели качества отрисовки, например частота пропущенных кадров.
- Показатели использования ресурсов, например количество байтов, считанных из сети.
Полный список доступных счетчиков и производных показателей можно найти в PlaybackStats Javadoc.
PlaybackStatsListener рассчитывает отдельные PlaybackStats для каждого мультимедийного объекта в плейлисте, а также для каждого клиентского объявления, вставленного в эти объекты. Вы можете предоставить обратный вызов для PlaybackStatsListener, чтобы получать уведомления о завершении воспроизведения, и использовать EventTime, переданный в обратный вызов, чтобы определить, какое воспроизведение завершилось. Вы можете агрегировать данные аналитики для нескольких воспроизведений. Кроме того, в любой момент можно запросить у PlaybackStats информацию о текущем сеансе воспроизведения с помощью 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. }));
Конструктор 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. Окончательные значения – это общедоступные поля, которые можно найти в PlaybackStats Javadoc, а также длительность состояний воспроизведения, возвращаемая функцией getPlaybackStateDurationMs. Для удобства также доступны методы getTotalPlayTimeMs и getTotalWaitTimeMs, которые возвращают продолжительность определенных комбинаций состояний воспроизведения.
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);
Агрегирование данных аналитики о нескольких воспроизведениях
Вы можете объединить несколько объектов PlaybackStats, вызвав метод PlaybackStats.merge. В результате PlaybackStats будет содержать агрегированные данные всех объединенных воспроизведений. Обратите внимание, что в нем не будет истории отдельных событий воспроизведения, поскольку их нельзя агрегировать.
PlaybackStatsListener.getCombinedPlaybackStats можно использовать для получения агрегированных данных аналитики, собранных за все время существования PlaybackStatsListener.
Вычисляемые сводные показатели
Помимо базовых данных аналитики, PlaybackStats предлагает множество способов расчета сводных показателей.
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());
Дополнительные темы
Как связать данные аналитики с метаданными воспроизведения
При сборе данных о воспроизведении может потребоваться связать их с метаданными о контенте.
Рекомендуем задавать метаданные для медиаконтента с помощью MediaItem.Builder.setTag.
Медиатег является частью EventTime, который передается для необработанных событий и после завершения PlaybackStats. Поэтому его легко извлечь при обработке соответствующих данных аналитики:
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. });
Как создавать отчеты о специальных событиях аналитики
Если вам нужно добавить в данные аналитики специальные события, сохраните их в собственной структуре данных и объедините с полученными отчетами PlaybackStats позже. При необходимости вы можете расширить DefaultAnalyticsCollector, чтобы создавать экземпляры EventTime для специальных событий и отправлять их зарегистрированным слушателям, как показано в следующем примере.
Kotlin
@OptIn(UnstableApi::class) private interface ExtendedListener : AnalyticsListener { fun onCustomEvent(eventTime: EventTime) } @OptIn(UnstableApi::class) private class ExtendedCollector : DefaultAnalyticsCollector(Clock.DEFAULT) { fun customEvent() { val eventTime = super.generateCurrentPlayerMediaPeriodEventTime() super.sendEvent(eventTime, CUSTOM_EVENT_ID) { listener: AnalyticsListener -> if (listener is ExtendedListener) { listener.onCustomEvent(eventTime) } } } } @OptIn(UnstableApi::class) fun useExtendedAnalyticsCollector(context: Context) { // 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
@OptIn(markerClass = UnstableApi.class) private interface ExtendedListener extends AnalyticsListener { void onCustomEvent(EventTime eventTime); } @OptIn(markerClass = UnstableApi.class) 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); } }); } } @OptIn(markerClass = UnstableApi.class) public static void useExtendedAnalyticsCollector(Context context) { // 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(); }