Analytics

ExoPlayer รองรับความต้องการด้านการวิเคราะห์การเล่นที่หลากหลาย ท้ายที่สุดแล้ว ข้อมูลวิเคราะห์คือการรวบรวม ตีความ จัดกลุ่ม และสรุปข้อมูลจากการเล่น ข้อมูลนี้สามารถใช้ได้ทั้งในอุปกรณ์ เช่น สำหรับการบันทึก การแก้ไขข้อบกพร่อง หรือเพื่อแจ้งการตัดสินใจในการเล่นในอนาคต หรือรายงานไปยังเซิร์ฟเวอร์เพื่อตรวจสอบการเล่นในอุปกรณ์ทั้งหมด

โดยปกติแล้ว ระบบวิเคราะห์จะต้องรวบรวมเหตุการณ์ก่อน แล้วจึงประมวลผลเพิ่มเติมเพื่อให้เหตุการณ์มีความหมาย ดังนี้

  • การรวบรวมเหตุการณ์: ซึ่งทำได้โดยการลงทะเบียน AnalyticsListener ในอินสแตนซ์ ExoPlayer Listener ของ Analytics ที่ลงทะเบียนไว้จะได้รับเหตุการณ์เมื่อเกิดขึ้นระหว่างการใช้งานเพลเยอร์ แต่ละเหตุการณ์จะเชื่อมโยงกับรายการสื่อที่เกี่ยวข้องในเพลย์ลิสต์ รวมถึงตำแหน่งการเล่นและข้อมูลเมตาการประทับเวลา
  • การประมวลผลเหตุการณ์: ระบบวิเคราะห์บางระบบจะอัปโหลดเหตุการณ์ดิบไปยังเซิร์ฟเวอร์ โดยการประมวลผลเหตุการณ์ทั้งหมดจะดำเนินการฝั่งเซิร์ฟเวอร์ นอกจากนี้ คุณยังประมวลผลเหตุการณ์ในอุปกรณ์ได้ด้วย ซึ่งอาจทำได้ง่ายกว่าหรือลดปริมาณข้อมูลที่ต้องอัปโหลด ExoPlayer มี PlaybackStatsListener ซึ่งช่วยให้คุณทำตามขั้นตอนการประมวลผลต่อไปนี้ได้
    1. การตีความเหตุการณ์: เหตุการณ์ต้องได้รับการตีความในบริบทของการเล่นครั้งเดียวจึงจะเป็นประโยชน์ต่อวัตถุประสงค์ด้านการวิเคราะห์ ตัวอย่างเช่น เหตุการณ์ดิบของการเปลี่ยนสถานะเพลเยอร์เป็น STATE_BUFFERING อาจสอดคล้องกับการบัฟเฟอร์เริ่มต้น การบัฟเฟอร์ซ้ำ หรือการบัฟเฟอร์ที่เกิดขึ้นหลังจากการกรอ
    2. การติดตามสถานะ: ขั้นตอนนี้จะแปลงเหตุการณ์เป็นตัวนับ ตัวอย่างเช่น เหตุการณ์การเปลี่ยนแปลงสถานะสามารถแปลงเป็นตัวนับที่ติดตามระยะเวลาที่ใช้ในสถานะการเล่นแต่ละสถานะได้ ผลลัพธ์ที่ได้คือชุดค่าข้อมูลการวิเคราะห์พื้นฐานสำหรับการเล่น 1 ครั้ง
    3. การรวมข้อมูล: ขั้นตอนนี้จะรวมข้อมูลการวิเคราะห์จากการเล่นหลายครั้ง โดยปกติจะทำโดยการเพิ่มตัวนับ
    4. การคำนวณเมตริกสรุป: เมตริกที่มีประโยชน์มากที่สุดหลายรายการคือเมตริกที่คำนวณค่าเฉลี่ยหรือรวมค่าข้อมูลการวิเคราะห์พื้นฐานด้วยวิธีอื่นๆ ระบบจะคำนวณเมตริกสรุปสำหรับการเล่นครั้งเดียวหรือหลายครั้ง

การรวบรวมเหตุการณ์ด้วย AnalyticsListener

ระบบจะรายงานเหตุการณ์การเล่นดิบจากเพลเยอร์ไปยังการติดตั้งใช้งาน AnalyticsListener คุณสามารถเพิ่ม Listener ของคุณเองและลบล้างเฉพาะเมธอดที่คุณสนใจได้ ดังนี้

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 ที่ส่งไปยัง Callback แต่ละรายการจะเชื่อมโยงเหตุการณ์กับรายการสื่อในเพลย์ลิสต์ รวมถึงข้อมูลเมตาของตำแหน่งการเล่นและการประทับเวลา ดังนี้

  • realtimeMs: เวลาตามนาฬิกาของเหตุการณ์
  • timeline, windowIndex และ mediaPeriodId: กำหนดเพลย์ลิสต์และรายการภายในเพลย์ลิสต์ที่เหตุการณ์นั้นเป็นส่วนหนึ่ง mediaPeriodId มีข้อมูลเพิ่มเติมที่ไม่บังคับ เช่น การระบุว่าเหตุการณ์เป็นของโฆษณาภายในรายการหรือไม่
  • eventPlaybackPositionMs: ตำแหน่งการเล่นในรายการเมื่อเกิดเหตุการณ์
  • currentTimeline, currentWindowIndex, currentMediaPeriodId และ currentPlaybackPositionMs: ตามที่อธิบายไว้ก่อนหน้านี้ แต่สำหรับรายการที่กำลังเล่นอยู่ รายการที่กำลังเล่นอยู่อาจแตกต่างจากรายการที่เป็นของเหตุการณ์ เช่น หากเหตุการณ์สอดคล้องกับการบัฟเฟอร์ล่วงหน้าของรายการถัดไปที่จะเล่น

การประมวลผลเหตุการณ์ด้วย PlaybackStatsListener

PlaybackStatsListener คือ AnalyticsListener ที่ใช้การประมวลผลเหตุการณ์ในอุปกรณ์ โดยจะคำนวณ PlaybackStats ด้วยตัวนับและเมตริกที่ได้มา ซึ่งรวมถึงรายการต่อไปนี้

  • เมตริกสรุป เช่น เวลาในการเล่นทั้งหมด
  • เมตริกคุณภาพการเล่นแบบปรับอัตโนมัติ เช่น ความละเอียดวิดีโอโดยเฉลี่ย
  • เมตริกคุณภาพการแสดงผล เช่น อัตราเฟรมที่หายไป
  • เมตริกการใช้ทรัพยากร เช่น จำนวนไบต์ที่อ่านผ่านเครือข่าย

คุณจะเห็นรายการจำนวนและเมตริกที่ได้มาทั้งหมดใน PlaybackStats Javadoc

PlaybackStatsListener จะคำนวณ PlaybackStats แยกกันสำหรับรายการสื่อแต่ละรายการในเพลย์ลิสต์ และโฆษณาฝั่งไคลเอ็นต์แต่ละรายการที่แทรกภายในรายการเหล่านี้ด้วย คุณสามารถระบุ Callback ไปยัง PlaybackStatsListener เพื่อรับแจ้งเกี่ยวกับการเล่นที่เสร็จสิ้น และใช้ EventTime ที่ส่งไปยัง Callback เพื่อระบุการเล่นที่เสร็จสิ้น คุณสามารถรวบรวมข้อมูลวิเคราะห์สำหรับการเล่นหลายรายการได้ นอกจากนี้ คุณยังค้นหา 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.
    });

การรายงานเหตุการณ์ Analytics ที่กำหนดเอง

ในกรณีที่ต้องการเพิ่มเหตุการณ์ที่กำหนดเองลงในข้อมูลการวิเคราะห์ คุณต้องบันทึกเหตุการณ์เหล่านี้ในโครงสร้างข้อมูลของคุณเอง แล้วรวมเข้ากับ PlaybackStats ที่รายงานในภายหลัง หากเป็นประโยชน์ คุณสามารถขยาย DefaultAnalyticsCollector เพื่อให้สามารถสร้างอินสแตนซ์ EventTime สำหรับเหตุการณ์ที่กำหนดเองและส่งไปยัง Listener ที่ลงทะเบียนไว้แล้วได้ ดังที่แสดงในตัวอย่างต่อไปนี้

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

แฟล็กฟีเจอร์การติดตามและโหมด Canary

เมื่อทำการทดสอบ A/B หรือประเมินผลกระทบของการเพิ่มประสิทธิภาพ Media3 ใหม่ คุณสามารถเชื่อมโยงข้อมูลการวิเคราะห์การเล่นกับแฟล็กฟีเจอร์ที่ใช้งานอยู่และโหมด Canary ได้

คุณสามารถค้นหา Flags.getLogString หรือตรวจสอบการลบล้างแฟล็กที่เฉพาะเจาะจงได้โดยใช้ Flags.getEnabledOverrides และ Flags.getDisabledOverrides เพื่อรวมสถานะแฟล็กไว้ในเพย์โหลดการวิเคราะห์ที่กำหนดเองหรือข้อมูลเมตาของเซสชัน