Analytics

ExoPlayer hỗ trợ nhiều nhu cầu phân tích hoạt động phát. Cuối cùng, phân tích là việc thu thập, diễn giải, tổng hợp và tóm tắt dữ liệu từ các lượt phát. Bạn có thể dùng dữ liệu này trên thiết bị (ví dụ: để ghi nhật ký, gỡ lỗi hoặc thông báo cho các quyết định phát trong tương lai) hoặc báo cáo cho một máy chủ để theo dõi hoạt động phát trên tất cả các thiết bị.

Thông thường, hệ thống phân tích cần thu thập sự kiện trước, sau đó xử lý thêm để các sự kiện đó có ý nghĩa:

  • Thu thập sự kiện: Bạn có thể thực hiện việc này bằng cách đăng ký một AnalyticsListener trên một thực thể ExoPlayer. Các trình nghe phân tích đã đăng ký sẽ nhận được các sự kiện khi chúng xảy ra trong quá trình sử dụng trình phát. Mỗi sự kiện được liên kết với mục nội dung nghe nhìn tương ứng trong danh sách phát, cũng như siêu dữ liệu về vị trí phát và dấu thời gian.
  • Xử lý sự kiện: Một số hệ thống phân tích tải sự kiện thô lên máy chủ, với tất cả hoạt động xử lý sự kiện được thực hiện phía máy chủ. Bạn cũng có thể xử lý các sự kiện trên thiết bị. Việc này có thể đơn giản hơn hoặc giảm lượng thông tin cần tải lên. ExoPlayer cung cấp PlaybackStatsListener, cho phép bạn thực hiện các bước xử lý sau:
    1. Diễn giải sự kiện: Để hữu ích cho mục đích phân tích, các sự kiện cần được diễn giải trong bối cảnh của một lần phát duy nhất. Ví dụ: sự kiện thô về việc thay đổi trạng thái trình phát thành STATE_BUFFERING có thể tương ứng với quá trình đệm ban đầu, đệm lại hoặc đệm xảy ra sau một thao tác tìm kiếm.
    2. Theo dõi trạng thái: Bước này chuyển đổi các sự kiện thành bộ đếm. Ví dụ: các sự kiện thay đổi trạng thái có thể được chuyển đổi thành các bộ đếm theo dõi thời gian đã dùng ở mỗi trạng thái phát. Kết quả là một tập hợp các giá trị dữ liệu phân tích cơ bản cho một lần phát.
    3. Tổng hợp: Bước này kết hợp dữ liệu phân tích trên nhiều lần phát, thường bằng cách cộng các bộ đếm.
    4. Tính toán chỉ số tóm tắt: Nhiều chỉ số hữu ích nhất là những chỉ số tính toán giá trị trung bình hoặc kết hợp các giá trị dữ liệu phân tích cơ bản theo những cách khác. Bạn có thể tính toán các chỉ số tóm tắt cho một hoặc nhiều lượt phát.

Thu thập sự kiện bằng AnalyticsListener

Các sự kiện phát thô từ trình phát được báo cáo cho các hoạt động triển khai AnalyticsListener. Bạn có thể dễ dàng thêm trình nghe của riêng mình và chỉ ghi đè các phương thức mà bạn quan tâm:

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 được truyền đến mỗi lệnh gọi lại sẽ liên kết sự kiện với một mục nội dung nghe nhìn trong danh sách phát, cũng như siêu dữ liệu về vị trí phát và dấu thời gian:

  • realtimeMs: Thời gian diễn ra sự kiện theo đồng hồ treo tường.
  • timeline, windowIndexmediaPeriodId: Xác định danh sách phát và mục trong danh sách phát mà sự kiện thuộc về. mediaPeriodId chứa thông tin bổ sung không bắt buộc, chẳng hạn như cho biết liệu sự kiện có thuộc về một quảng cáo trong mục hay không.
  • eventPlaybackPositionMs: Vị trí phát trong mục khi sự kiện xảy ra.
  • currentTimeline, currentWindowIndex, currentMediaPeriodIdcurrentPlaybackPositionMs: Tương tự như trên nhưng dành cho mục đang phát. Mục đang phát có thể khác với mục mà sự kiện thuộc về, ví dụ: nếu sự kiện tương ứng với việc đệm trước mục tiếp theo sẽ được phát.

Xử lý sự kiện bằng PlaybackStatsListener

PlaybackStatsListener là một AnalyticsListener triển khai quy trình xử lý sự kiện trên thiết bị. Chỉ số này tính toán PlaybackStats, với các bộ đếm và chỉ số phái sinh, bao gồm:

  • Chỉ số tóm tắt, chẳng hạn như tổng thời gian phát.
  • Các chỉ số về chất lượng phát thích ứng, chẳng hạn như độ phân giải trung bình của video.
  • Chỉ số về chất lượng kết xuất, chẳng hạn như tốc độ khung hình bị bỏ qua.
  • Các chỉ số về mức sử dụng tài nguyên, chẳng hạn như số byte được đọc qua mạng.

Bạn sẽ thấy danh sách đầy đủ về số lượt truy cập và chỉ số phái sinh có sẵn trong PlaybackStatsJavadoc.

PlaybackStatsListener tính toán PlaybackStats riêng biệt cho từng mục nội dung nghe nhìn trong danh sách phát, cũng như từng quảng cáo phía máy khách được chèn trong các mục này. Bạn có thể cung cấp một lệnh gọi lại cho PlaybackStatsListener để được thông báo về các lượt phát đã hoàn tất và sử dụng EventTime được truyền đến lệnh gọi lại để xác định lượt phát nào đã hoàn tất. Bạn có thể tổng hợp dữ liệu phân tích cho nhiều lượt phát. Bạn cũng có thể truy vấn PlaybackStats cho phiên phát hiện tại bất cứ lúc nào bằng cách sử dụng 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.
        }));

Hàm khởi tạo của PlaybackStatsListener cho phép giữ lại toàn bộ nhật ký của các sự kiện đã xử lý. Xin lưu ý rằng điều này có thể làm tăng mức sử dụng bộ nhớ không xác định, tuỳ thuộc vào thời lượng phát và số lượng sự kiện. Do đó, bạn chỉ nên bật tính năng này nếu cần truy cập vào toàn bộ nhật ký các sự kiện đã xử lý, thay vì chỉ truy cập vào dữ liệu phân tích cuối cùng.

Xin lưu ý rằng PlaybackStats sử dụng một nhóm trạng thái mở rộng để cho biết không chỉ trạng thái của nội dung nghe nhìn mà còn cả ý định phát của người dùng và thông tin chi tiết hơn, chẳng hạn như lý do khiến quá trình phát bị gián đoạn hoặc kết thúc:

Trạng thái phát Ý định phát của người dùng Không có ý định chơi
Trước khi phát JOINING_FOREGROUND NOT_STARTED, JOINING_BACKGROUND
Đang phát PLAYING
Bị gián đoạn khi phát BUFFERING, SEEKING PAUSED, PAUSED_BUFFERING, SUPPRESSED, SUPPRESSED_BUFFERING, INTERRUPTED_BY_AD
Trạng thái kết thúc ENDED, STOPPED, FAILED, ABANDONED

Ý định phát của người dùng là yếu tố quan trọng để phân biệt thời điểm người dùng đang chủ động chờ quá trình phát tiếp tục với thời gian chờ thụ động. Ví dụ: PlaybackStats.getTotalWaitTimeMs trả về tổng thời gian đã dùng ở các trạng thái JOINING_FOREGROUND, BUFFERINGSEEKING, nhưng không phải là thời gian khi quá trình phát bị tạm dừng. Tương tự, PlaybackStats.getTotalPlayAndWaitTimeMs sẽ trả về tổng thời gian người dùng có ý định phát, tức là tổng thời gian chờ hoạt động và tổng thời gian ở trạng thái PLAYING.

Các sự kiện đã được xử lý và diễn giải

Bạn có thể ghi lại các sự kiện đã xử lý và diễn giải bằng cách sử dụng PlaybackStatsListener với keepHistory=true. PlaybackStats kết quả sẽ chứa các danh sách sự kiện sau:

  • playbackStateHistory: Danh sách có thứ tự gồm các trạng thái phát mở rộng cùng với EventTime mà tại đó các trạng thái này bắt đầu được áp dụng. Bạn cũng có thể dùng PlaybackStats.getPlaybackStateAtTime để tra cứu trạng thái tại một thời điểm nhất định trên đồng hồ treo tường.
  • mediaTimeHistory: Nhật ký về các cặp thời gian trên đồng hồ treo tường và thời gian phát nội dung nghe nhìn, cho phép bạn dựng lại những phần nào của nội dung nghe nhìn đã được phát vào thời điểm nào. Bạn cũng có thể dùng PlaybackStats.getMediaTimeMsAtRealtimeMs để tra cứu vị trí phát tại một thời điểm cụ thể.
  • videoFormatHistoryaudioFormatHistory: Danh sách có thứ tự của các định dạng video và âm thanh được dùng trong quá trình phát, cùng với EventTime mà các định dạng này bắt đầu được dùng.
  • fatalErrorHistorynonFatalErrorHistory: Danh sách có thứ tự gồm các lỗi nghiêm trọng và không nghiêm trọng cùng với EventTime mà tại đó các lỗi này xảy ra. Lỗi nghiêm trọng là những lỗi khiến quá trình phát kết thúc, trong khi lỗi không nghiêm trọng có thể khắc phục được.

Dữ liệu phân tích về lượt phát riêng lẻ

Dữ liệu này được thu thập tự động nếu bạn sử dụng PlaybackStatsListener, ngay cả khi có keepHistory=false. Giá trị cuối cùng là các trường công khai mà bạn có thể tìm thấy trong PlaybackStats Javadoc và thời lượng trạng thái phát do getPlaybackStateDurationMs trả về. Để thuận tiện, bạn cũng sẽ tìm thấy các phương thức như getTotalPlayTimeMsgetTotalWaitTimeMs trả về thời lượng của các tổ hợp trạng thái phát cụ thể.

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

Tổng hợp dữ liệu phân tích của nhiều lượt phát

Bạn có thể kết hợp nhiều PlaybackStats với nhau bằng cách gọi PlaybackStats.merge. PlaybackStats thu được sẽ chứa dữ liệu tổng hợp của tất cả các lượt phát đã hợp nhất. Xin lưu ý rằng báo cáo này sẽ không chứa nhật ký của các sự kiện phát riêng lẻ vì những sự kiện này không thể được tổng hợp.

PlaybackStatsListener.getCombinedPlaybackStats có thể được dùng để xem tổng hợp tất cả dữ liệu phân tích được thu thập trong suốt thời gian tồn tại của một PlaybackStatsListener.

Chỉ số tóm lược được tính toán

Ngoài dữ liệu phân tích cơ bản, PlaybackStats còn cung cấp nhiều phương thức để tính toán các chỉ số tóm tắt.

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

Chủ đề nâng cao

Liên kết dữ liệu phân tích với siêu dữ liệu phát

Khi thu thập dữ liệu phân tích cho từng lượt phát, bạn có thể muốn liên kết dữ liệu phân tích lượt phát với siêu dữ liệu về nội dung nghe nhìn đang được phát.

Bạn nên đặt siêu dữ liệu dành riêng cho nội dung nghe nhìn bằng MediaItem.Builder.setTag. Thẻ nội dung nghe nhìn là một phần của EventTime được báo cáo cho các sự kiện thô và khi PlaybackStats hoàn tất, thẻ này có thể dễ dàng truy xuất khi xử lý dữ liệu phân tích tương ứng:

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.
    });

Báo cáo sự kiện phân tích tuỳ chỉnh

Trong trường hợp cần thêm sự kiện tuỳ chỉnh vào dữ liệu phân tích, bạn cần lưu các sự kiện này trong cấu trúc dữ liệu của riêng mình và kết hợp chúng với PlaybackStats được báo cáo sau này. Nếu cần, bạn có thể mở rộng DefaultAnalyticsCollector để có thể tạo các thực thể EventTime cho các sự kiện tuỳ chỉnh và gửi chúng đến các trình nghe đã đăng ký như minh hoạ trong ví dụ sau.

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