Analytics

ExoPlayer 支持各种播放分析需求。归根结底 分析就是收集、解读、汇总和汇总数据 。这些数据可以在设备上使用,例如用于 记录、调试或告知未来的播放决策,或向 服务器来监控所有设备上的播放。

分析系统通常需要首先收集事件,然后再对其进行处理 让它们更有意义:

  • 事件收集: 这可以通过在 ExoPlayer 上注册 AnalyticsListener 来实现 实例。注册的分析监听器会在以下事件发生时接收事件: 播放器的使用情况每个事件都与相应的媒体相关联 以及播放位置和时间戳元数据。
  • 事件处理: 一些分析系统会将原始事件(包括所有事件) 处理。您也可以在 而且这样做可能更简单,或能够减少 需要上传。ExoPlayer 提供 PlaybackStatsListener, 允许您执行以下处理步骤: <ph type="x-smartling-placeholder">
      </ph>
    1. 事件解读:为了便于进行分析,事件需要 以便在单次播放的上下文中解读例如,原始的 事件发生时,播放器状态更改为 STATE_BUFFERING 初始缓冲、重新缓冲或在跳转后发生的缓冲。
    2. 状态跟踪:此步骤会将事件转换为计数器。例如: 可将状态变化事件转换为计数器, 每个播放状态下所耗费的时间收集的结果就是一组基本的分析数据。 值。
    3. 汇总 (Aggregation):此步骤可汇总多个 通常通过将计数器相加来实现。
    4. 摘要指标的计算:许多最实用的指标都是 计算平均值或将基本分析数据值组合到一起, 其他方式可以针对单个或多个汇总指标计算 次播放。

使用 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:事件的时钟时间。
  • timelinewindowIndexmediaPeriodId:定义播放列表和 活动所属的播放列表。mediaPeriodId 包含可选的其他信息,例如,指明 事件属于某个广告。
  • eventPlaybackPositionMs:事件发生时,内容中的播放位置 错误。
  • currentTimelinecurrentWindowIndexcurrentMediaPeriodIdcurrentPlaybackPositionMs:同上,但针对当前播放的项。通过 当前播放的项可能不同于事件发送到的项 例如,如果相应事件对应于下一个预先缓冲的 要播放的项。

使用 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_STARTEDJOINING_BACKGROUND
当前播放 PLAYING
播放中断 BUFFERINGSEEKING PAUSEDPAUSED_BUFFERINGSUPPRESSEDSUPPRESSED_BUFFERINGINTERRUPTED_BY_AD
结束状态 ENDEDSTOPPEDFAILEDABANDONED

用户玩游戏的意图非常重要 即主动等待播放,从而从被动等待时间继续播放。例如: PlaybackStats.getTotalWaitTimeMs 会返回在 JOINING_FOREGROUNDBUFFERINGSEEKING 状态,但不是 已暂停播放。同样,PlaybackStats.getTotalPlayAndWaitTimeMs 将 返回有用户想要玩游戏的总时间,也就是总的 等待时间和处于 PLAYING 状态的总时间。

已处理和解释的事件

您可以使用 PlaybackStatsListener 记录已处理的事件和经过解释的事件 和keepHistory=true。生成的 PlaybackStats 将包含 以下事件列表:

  • playbackStateHistory:扩展播放状态的有序列表,其中包含 开始申请的EventTime。您还可以使用 PlaybackStats.getPlaybackStateAtTime,用于查询给定墙的状态 时钟时间。
  • mediaTimeHistory:挂钟时间和媒体时间对的历史记录,允许 您可以重新构建当时播放了媒体的哪些部分。您可以 还使用 PlaybackStats.getMediaTimeMsAtRealtimeMs 查找播放 。
  • videoFormatHistoryaudioFormatHistory:视频和 通过开始播放的 EventTime 播放期间使用的音频格式。 使用。
  • fatalErrorHistorynonFatalErrorHistory:严重错误和 发生非严重错误的 EventTime。严重错误包括 结束播放的错误,而非严重错误可能是可以恢复的。

单场播放分析数据

如果您使用 PlaybackStatsListener,系统会自动收集这些数据,即使 和keepHistory=false。最终的值是您可以 可在 PlaybackStats Javadoc 中找到相应视频以及播放状态时长 由 getPlaybackStateDurationMs 返回。为方便起见,我们还提供了 方法(如 getTotalPlayTimeMsgetTotalWaitTimeMs),这些方法会返回 特定播放状态组合的时长。

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