Analytics

يدعم ExoPlayer مجموعة كبيرة من احتياجات إحصاءات التشغيل. في النهاية، تتمحور الإحصاءات حول جمع البيانات من عمليات التشغيل وتفسيرها وتجميعها وتلخيصها. ويمكن استخدام هذه البيانات إما على الجهاز، مثل تسجيل المحتوى أو تصحيح الأخطاء أو للإبلاغ عن قرارات التشغيل المستقبلية، أو إرسال تقارير إلى أحد الخوادم لمراقبة عمليات التشغيل على جميع الأجهزة.

يحتاج نظام التحليلات عادةً إلى جمع الأحداث أولاً، ثم معالجتها بشكل أكبر لجعلها ذات مغزى:

  • مجموعة الفعاليات: يمكن إجراء ذلك من خلال تسجيل AnalyticsListener في مثيل ExoPlayer. يتلقّى مستمعو التحليلات المسجّلون الأحداث عند حدوثها أثناء استخدام المشغّل. ويتم ربط كل حدث بعنصر الوسائط المقابل في قائمة التشغيل، بالإضافة إلى موضع التشغيل والبيانات الوصفية للطابع الزمني.
  • معالجة الأحداث: تُحمِّل بعض أنظمة الإحصاءات أحداثًا أولية إلى خادم، ويتم تنفيذ جميع عمليات معالجة الأحداث من جهة الخادم. من الممكن أيضًا معالجة الأحداث على الجهاز، وقد يكون ذلك أكثر بساطة أو يقلل من مقدار المعلومات التي يجب تحميلها. يوفّر ExoPlayer إمكانية استخدام PlaybackStatsListener التي تتيح لك تنفيذ خطوات المعالجة التالية:
    1. تفسير الأحداث: لتحقيق الاستفادة من أغراض الإحصاءات، يجب تفسير الأحداث في سياق عملية تشغيل واحدة. على سبيل المثال، إنّ الحدث الأولي لتغيير حالة المشغّل إلى STATE_BUFFERING قد يتطابق مع عملية تخزين مؤقت مبدئية أو عملية إعادة تخزين مؤقت أو عملية تخزين مؤقت تحدث بعد عملية التقديم والترجيع.
    2. تتبُّع الحالة: تحوِّل هذه الخطوة الأحداث إلى عدّادات. على سبيل المثال، يمكن تحويل أحداث تغيير الحالة إلى عدّادات تتّبع الوقت المستغرَق في كل حالة تشغيل. والنتيجة هي مجموعة أساسية من قيم بيانات الإحصاءات لعملية تشغيل واحدة.
    3. التجميع: تجمع هذه الخطوة بين بيانات الإحصاءات على مستوى عمليات التشغيل المتعددة، وذلك عادةً من خلال إضافة عدّادات.
    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: وقت الساعة الجدارية للحدث
  • 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

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