ExoPlayer 支持各种播放分析需求。归根结底 分析就是收集、解读、汇总和汇总数据 。这些数据可以在设备上使用,例如用于 记录、调试或告知未来的播放决策,或向 服务器来监控所有设备上的播放。
分析系统通常需要首先收集事件,然后再对其进行处理 让它们更有意义:
- 事件收集:
这可以通过在
ExoPlayer
上注册AnalyticsListener
来实现 实例。注册的分析监听器会在以下事件发生时接收事件: 播放器的使用情况每个事件都与相应的媒体相关联 以及播放位置和时间戳元数据。 - 事件处理:
一些分析系统会将原始事件(包括所有事件)
处理。您也可以在
而且这样做可能更简单,或能够减少
需要上传。ExoPlayer 提供
PlaybackStatsListener
, 允许您执行以下处理步骤: <ph type="x-smartling-placeholder">- </ph>
- 事件解读:为了便于进行分析,事件需要
以便在单次播放的上下文中解读例如,原始的
事件发生时,播放器状态更改为
STATE_BUFFERING
初始缓冲、重新缓冲或在跳转后发生的缓冲。 - 状态跟踪:此步骤会将事件转换为计数器。例如: 可将状态变化事件转换为计数器, 每个播放状态下所耗费的时间收集的结果就是一组基本的分析数据。 值。
- 汇总 (Aggregation):此步骤可汇总多个 通常通过将计数器相加来实现。
- 摘要指标的计算:许多最实用的指标都是 计算平均值或将基本分析数据值组合到一起, 其他方式可以针对单个或多个汇总指标计算 次播放。
- 事件解读:为了便于进行分析,事件需要
以便在单次播放的上下文中解读例如,原始的
事件发生时,播放器状态更改为
使用 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
、计数器和推导
指标,包括:
- 摘要指标,例如总播放时间。
- 自适应播放质量指标,例如平均视频分辨率。
- 渲染质量指标,例如丢帧率。
- 资源用量指标,例如通过网络读取的字节数。
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
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();