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