أخبار المنتجات

تحسين تشغيل الوسائط: نظرة متعمّقة على PreloadManager في Media3 - الجزء الثاني

مدّة القراءة: 9 دقائق
عرض الملف الشخصي لـ Mayuri Khinvasara Khabya
Mayuri Khinvasara Khabya مهندسة علاقات المطوّرين

مرحبًا بك في الجزء الثاني من سلسلتنا المكوّنة من ثلاثة أجزاء حول التحميل المُسبق للوسائط باستخدام Media3. تم تصميم هذه السلسلة لإرشادك خلال عملية إنشاء تجارب وسائط عالية الاستجابة ومنخفضة وقت الاستجابة في تطبيقات Android.

  • الجزء 1: مقدّمة عن التحميل المُسبق باستخدام Media3 يتناول الأساسيات. واستكشفنا الفرق بين PreloadConfiguration لقوائم التشغيل البسيطة وDefaultPreloadManager الأكثر فعالية لواجهات المستخدم الديناميكية. تعرّفت على كيفية تنفيذ دورة حياة واجهة برمجة التطبيقات الأساسية: إضافة الوسائط باستخدام add()‎، واسترداد MediaSource مُعدّ باستخدام getMediaSource()‎، وإدارة الأولويات باستخدام setCurrentPlayingIndex()‎ وinvalidate()‎، وإخلاء الموارد باستخدام remove()‎ وrelease()‎.
  • الجزء الثاني (هذه المقالة): نستكشف في هذه المقالة الإمكانات المتقدّمة لـ DefaultPreloadManager. سنشرح كيفية الحصول على إحصاءات باستخدام PreloadManagerListener، وتطبيق أفضل الممارسات الجاهزة للإنتاج، مثل مشاركة المكوّنات الأساسية مع ExoPlayer، وإتقان نمط النافذة المنزلقة لإدارة الذاكرة بفعالية.
  • الجزء الثالث: سيتناول الجزء الأخير من هذه السلسلة كيفية دمج PreloadManager مع ذاكرة التخزين المؤقت للقرص الدائم، ما يتيح لك تقليل استهلاك البيانات من خلال إدارة الموارد وتوفير تجربة سلسة.

إذا كنت مبتدئًا في استخدام ميزة التحميل المُسبَق في Media3، ننصحك بشدة بقراءة الجزء الأول قبل المتابعة. بالنسبة إلى المطوّرين المستعدين لتجاوز الأساسيات، لنتعرّف على كيفية تحسين تنفيذ تشغيل الوسائط.

الاستماع: جلب الإحصاءات باستخدام PreloadManagerListener

عندما تريد إطلاق ميزة في مرحلة الإنتاج، بصفتك مطوّر تطبيقات، عليك أيضًا فهم الإحصاءات وراءها والاطّلاع عليها. كيف يمكنك التأكّد من أنّ استراتيجية التحميل المُسبَق فعّالة في بيئة حقيقية؟ تتطلب الإجابة عن هذا السؤال بيانات حول معدلات النجاح والإخفاق والأداء. وتُعدّ واجهة PreloadManagerListener الآلية الأساسية لجمع هذه البيانات.

يوفّر PreloadManagerListener عمليتَي استدعاء أساسيتَين تقدّمان إحصاءات مهمة حول عملية التحميل المُسبَق وحالته.

  • ‫onCompleted(MediaItem mediaItem): يتم استدعاء دالة ردّ الاتصال هذه عند اكتمال طلب التحميل المُسبَق بنجاح، على النحو المحدّد في TargetPreloadStatusControl.
  • ‫onError(PreloadException error): يمكن أن تكون دالة ردّ الاتصال هذه مفيدة لتصحيح الأخطاء والمراقبة. ويتم استدعاؤها عند تعذُّر التحميل المُسبَق، ما يؤدي إلى ظهور الاستثناء المرتبط بها.

يمكنك تسجيل مستمع من خلال طلب إجراء واحد كما هو موضّح في الرمز البرمجي في المثال التالي:

val preloadManagerListener = object : PreloadManagerListener {
    override fun onCompleted(mediaItem: MediaItem) {
        // Log success for analytics. 
        Log.d("PreloadAnalytics", "Preload completed for $mediaItem")
    }

    override fun onError( preloadError: PreloadException) {
        // Log the specific error for debugging and monitoring.
        Log.e("PreloadAnalytics", "Preload error ", preloadError)
    }
}

preloadManager.addListener(preloadManagerListener)

استخراج الإحصاءات من المستمع 

يمكن ربط عمليات الاستدعاء هذه بمسار الإحالة الناجحة في الإحصاءات. من خلال إعادة توجيه هذه الأحداث إلى محرّك الإحصاءات، يمكنك الإجابة عن أسئلة رئيسية مثل:

  • ما هو معدّل نجاح التحميل المُسبَق؟ (نسبة أحداث onCompleted إلى إجمالي محاولات التحميل المُسبَق)
  • ما هي شبكات توصيل المحتوى أو تنسيقات الفيديو التي تسجّل أعلى معدلات خطأ؟ (من خلال تحليل الاستثناءات من onError)
  • ما هو معدّل أخطاء التحميل المُسبَق؟ (نسبة أحداث onError إلى إجمالي محاولات التحميل المُسبق)

يمكن أن تمنحك هذه البيانات ملاحظات كمية حول استراتيجية التحميل المُسبق، ما يتيح لك إجراء اختبارات A/B وتحسينات مستندة إلى البيانات لتجربة المستخدم. يمكن أن تساعدك هذه البيانات أيضًا في ضبط مُدد التحميل المُسبق بذكاء وعدد الفيديوهات التي تريد تحميلها مُسبقًا، بالإضافة إلى وحدات التخزين المؤقت التي تخصّصها.

ما وراء تصحيح الأخطاء: استخدام onError للرجوع إلى واجهة مستخدم بديلة بسلاسة

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

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

نظام الرفيق: لماذا تُعدّ مشاركة المكوّنات مع ExoPlayer ضرورية؟

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

تم تصميم DefaultPreloadManager.Builder لتسهيل هذه المشاركة، وهو يتضمّن واجهات برمجة تطبيقات لإنشاء كل من PreloadManager ومثيل مشغّل مرتبط. لنتعرّف على سبب ضرورة مشاركة مكوّنات مثل BandwidthMeter وLoadControl وTrackSelector وLooper. يمكنك الاطّلاع على التمثيل المرئي لكيفية تفاعل هذه المكوّنات مع ميزة "تشغيل ExoPlayer".

preloadManager2.png

منع تعارضات معدّل نقل البيانات باستخدام BandwidthMeter مشترَك

يوفّر BandwidthMeter تقديرًا لمعدّل نقل البيانات المتاح في الشبكة استنادًا إلى معدّلات النقل السابقة. إذا كان PreloadManager والمشغّل يستخدمان نُسخًا منفصلة، فلن يكونا على دراية بنشاط الشبكة لدى بعضهما، ما قد يؤدي إلى حدوث حالات تعذُّر. على سبيل المثال، لنفترض أنّ مستخدمًا يشاهد فيديو، ثم ينخفض مستوى الاتصال بالشبكة، ويبدأ MediaSource الذي يحمّل المحتوى مسبقًا في الوقت نفسه عملية تنزيل مكثّفة لفيديو مستقبلي. سيؤدي نشاط MediaSource الذي يتم تحميله مسبقًا إلى استهلاك معدل نقل البيانات الذي يحتاجه المشغّل النشط، ما يؤدي إلى توقّف الفيديو الحالي. ويُعدّ توقُّف التشغيل مؤقتًا من المشاكل الكبيرة التي تؤثر في تجربة المستخدم.

من خلال مشاركة BandwidthMeter واحد، يمكن لـ TrackSelector اختيار المقاطع الصوتية بأعلى جودة وفقًا لظروف الشبكة الحالية وحالة المخزن المؤقت، أثناء التحميل المُسبق أو التشغيل. ويمكنه بعد ذلك اتخاذ قرارات ذكية لحماية جلسة التشغيل النشطة وضمان تجربة سلسة.

preloadManagerBuilder.setBandwidthMeter(customBandwidthMeter)

ضمان الاتساق مع مكوّنات LoadControl وTrackSelector وRenderer المشتركة في ExoPlayer

  • ‫LoadControl: يحدّد هذا المكوّن سياسة التخزين المؤقت، مثل مقدار البيانات التي يجب تخزينها مؤقتًا قبل بدء التشغيل ووقت بدء أو إيقاف تحميل المزيد من البيانات. تضمن مشاركة LoadControl أن يتم توجيه استهلاك الذاكرة للمشغّل وPreloadManager من خلال استراتيجية تخزين مؤقت واحدة ومنسّقة لكل من الوسائط التي تم تحميلها مسبقًا والوسائط التي يتم تشغيلها بشكل نشط، ما يمنع تضارب الموارد. عليك تخصيص حجم ذاكرة التخزين المؤقت بذكاء بالتنسيق مع عدد العناصر التي يتم تحميلها مسبقًا ومدتها، لضمان الاتساق. في أوقات التنافس، سيعطي المشغّل الأولوية لتشغيل العنصر الحالي المعروض على الشاشة. باستخدام LoadControl مشترَك، سيواصل مدير التحميل المُسبَق عملية التحميل المُسبَق طالما أنّ وحدات بايت المخزن المؤقت المستهدَفة المخصّصة للتحميل المُسبَق لم تصل إلى الحدّ الأقصى، ولا ينتظر حتى يكتمل التحميل للتشغيل.

ملاحظة: تضمن مشاركة LoadControl في أحدث إصدار من Media3 (1.8) إمكانية مشاركة Allocator بشكل صحيح مع PreloadManager والمشغّل. إنّ استخدام LoadControl للتحكّم بفعالية في التحميل المُسبق هو ميزة ستتوفّر في الإصدار 1.9 القادم من Media3.

preloadManagerBuilder.setLoadControl(customLoadControl)

  • ‫TrackSelector: هذا المكوّن مسؤول عن اختيار المقاطع التي سيتم تحميلها وتشغيلها (على سبيل المثال، فيديو بدقة معيّنة أو صوت بلغة معيّنة). تضمن المشاركة أنّ المقاطع الصوتية التي تم اختيارها أثناء التحميل المُسبق هي نفسها التي سيستخدمها المشغّل. يؤدي ذلك إلى تجنُّب سيناريو غير مفيد يتم فيه تحميل مقطع فيديو بدقة 480p مسبقًا، ليتم تجاهله فورًا من قِبل المشغّل وجلب مقطع بدقة 720p عند التشغيل.< br /> يجب ألا يشارك مدير التحميل المُسبق المثيل نفسه من TrackSelector مع المشغّل. بدلاً من ذلك، يجب استخدام مثيل TrackSelector مختلف ولكن من التنفيذ نفسه. لهذا السبب، نضبط TrackSelectorFactory بدلاً من TrackSelector في DefaultPreloadManager.Builder.

preloadManagerBuilder.setTrackSelectorFactory(customTrackSelectorFactory)

  • العارض: هذا المكوّن مسؤول عن فهم إمكانات المشغّل بدون إنشاء عارضين كاملين. ويتحقّق من هذا المخطط لمعرفة تنسيقات الفيديو والصوت والنص التي سيتيحها المشغّل النهائي. يتيح ذلك للمشغّل اختيار وتنزيل المقطع الصوتي المتوافق فقط بذكاء، ويمنع إهدار معدل نقل البيانات على محتوى لا يمكن للمشغّل تشغيله فعليًا.

preloadManagerBuilder.setRenderersFactory(customRenderersFactory)

مزيد من المعلومات حول مكوّنات ExoPlayer

القاعدة الذهبية: تكرار التشغيل هو الحلّ الأمثل

يمكن تحديد سلسلة التعليمات التي يمكن من خلالها الوصول إلى مثيل ExoPlayer بشكل صريح من خلال تمرير Looper عند إنشاء المشغّل. يمكن الاستعلام عن Looper لسلسلة المحادثات التي يجب الوصول إلى المشغّل منها باستخدام Player.getApplicationLooper. من خلال الاحتفاظ بـ Looper مشترَك بين المشغّل وPreloadManager، نضمن تسلسل جميع العمليات على عناصر الوسائط المشترَكة هذه في قائمة انتظار رسائل سلسلة محادثات واحدة. يمكن أن يقلل هذا من أخطاء التزامن.

يجب أن تتم جميع التفاعلات بين PreloadManager والمشغّل مع مصادر الوسائط التي سيتم تحميلها أو تحميلها مسبقًا على سلسلة عمليات التشغيل نفسها. تُعدّ مشاركة Looper ضرورية لضمان أمان سلاسل التعليمات، وبالتالي يجب مشاركة PlaybackLooper بين PreloadManager والمشغّل.

يُعدّ PreloadManager عنصر MediaSource يتضمّن حالة في الخلفية. عندما يستدعي رمز واجهة المستخدم player.setMediaSource(mediaSource)، يتم نقل هذا العنصر المعقّد الذي يتضمّن حالة من MediaSource الذي يتم تحميله مسبقًا إلى المشغّل. في هذا السيناريو، يتم نقل PreloadMediaSource بالكامل من المدير إلى المشغّل. يجب أن تتم جميع هذه التفاعلات وعمليات التسليم على PlaybackLooper نفسه.

إذا كان PreloadManager وExoPlayer يعملان على سلاسل محادثات مختلفة، قد تحدث حالة سباق. قد يعدّل مؤشر ترابط PreloadManager الحالة الداخلية لـ MediaSource (مثل كتابة بيانات جديدة في مخزن مؤقت) في اللحظة نفسها التي يحاول فيها مؤشر ترابط المشغّل القراءة منه. ويؤدي ذلك إلى سلوك غير متوقّع، وهو IllegalStateException الذي يصعب تصحيحه.

preloadManagerBuilder.setPreloadLooper(playbackLooper)

لنتعرّف على كيفية مشاركة جميع المكوّنات المذكورة أعلاه بين ExoPlayer وDefaultPreloadManager في الإعداد نفسه.

val preloadManagerBuilder =
DefaultPreloadManager.Builder(context, targetPreloadStatusControl)

// Optional - Share components between ExoPlayer and DefaultPreloadManager
preloadManagerBuilder
     .setBandwidthMeter(customBandwidthMeter)
     .setLoadControl(customLoadControl)
     .setMediaSourceFactory(customMediaSourceFactory)
     .setTrackSelectorFactory(customTrackSelectorFactory)
     .setRenderersFactory(customRenderersFactory)
     .setPreloadLooper(playbackLooper)

val preloadManager = val preloadManagerBuilder.build()

ملاحظة: إذا كنت تستخدم المكوّنات التلقائية في ExoPlayer، مثل DefaultLoadControl وما إلى ذلك، لن تحتاج إلى مشاركتها صراحةً مع DefaultPreloadManager. عند إنشاء مثيل ExoPlayer من خلال buildExoPlayer من DefaultPreloadManager.Builder، تتم الإشارة إلى هذه المكوّنات تلقائيًا مع بعضها البعض، إذا كنت تستخدم عمليات التنفيذ التلقائية مع الإعدادات التلقائية. ولكن إذا كنت تستخدم مكوّنات أو إعدادات مخصّصة، عليك إبلاغ DefaultPreloadManager بها صراحةً من خلال واجهات برمجة التطبيقات المذكورة أعلاه.

التحميل المُسبق الجاهز للإنتاج: نمط النافذة المنزلقة

في الخلاصة الديناميكية، يمكن للمستخدم الانتقال إلى كمية غير محدودة من المحتوى. إذا واصلت إضافة فيديوهات إلى DefaultPreloadManager بدون استراتيجية إزالة مقابلة، سيؤدي ذلك حتمًا إلى حدوث خطأ OutOfMemoryError. يحتفظ كل مصدر MediaSource محمَّل مسبقًا بـ SampleQueue، الذي يخصّص مخازن مؤقتة للذاكرة. ومع تراكم هذه العناصر، يمكن أن تستنفد مساحة الذاكرة المؤقتة للتطبيق. الحل هو خوارزمية قد تكون على دراية بها بالفعل، تسمى النافذة المنزلقة. يحافظ نمط النافذة المنزلقة على مجموعة صغيرة من العناصر التي يمكن إدارتها في الذاكرة، والتي تكون مجاورة منطقيًا لموضع المستخدم الحالي في الخلاصة. أثناء تمرير المستخدم، تنزلق هذه "النافذة" من العناصر المُدارة معه، وتضيف عناصر جديدة تظهر في العرض، وتزيل أيضًا العناصر البعيدة الآن.

slidingwindow.png

تنفيذ نمط النافذة المنزلقة

من المهم معرفة أنّ PreloadManager لا يوفّر طريقة setWindowSize()‎ مضمّنة. النافذة المنزلقة هي نمط تصميم تقع على عاتقك كمطوّر مسؤولية تنفيذه باستخدام الطريقتَين الأساسيتَين add()‎ وremove()‎. يجب أن يربط منطق تطبيقك أحداث واجهة المستخدم، مثل الانتقال إلى محتوى معيّن أو تغيير الصفحة، بطلبات البيانات هذه من واجهة برمجة التطبيقات. إذا أردت مرجعًا برمجيًا لذلك، يتوفّر لدينا نمط النافذة المنزلقة هذا في نموذج socialite الذي يتضمّن أيضًا PreloadManagerWrapper يحاكي النافذة المنزلقة.

لا تنسَ إضافة preloadManager.remove(mediaItem) في عملية التنفيذ عندما لا يكون من المرجّح أن يظهر العنصر قريبًا في مشاهدة المستخدم. إنّ عدم إزالة العناصر التي لم تعُد قريبة من المستخدم هو السبب الرئيسي لمشاكل الذاكرة في عمليات التحميل المُسبَق. يضمن استدعاء remove()‎ تحرير الموارد التي تساعدك في الحفاظ على استخدام ذاكرة تطبيقك بشكل محدود ومستقر.

ضبط استراتيجية التحميل المُسبق المصنّفة باستخدام TargetPreloadStatusControl

بعد أن حدّدنا ما يجب تحميله مسبقًا (العناصر في النافذة)، يمكننا تطبيق استراتيجية محدّدة جيدًا لمقدار التحميل المسبق لكل عنصر. لقد رأينا سابقًا كيفية تحقيق هذه الدقة من خلال إعداد TargetPreloadStatusControl في الجزء 1.

للتذكير، قد يكون احتمال تشغيل عنصر في الموضع +/- 1 أعلى من احتمال تشغيل عنصر في الموضع +/- 4. يمكنك تخصيص المزيد من الموارد (الشبكة ووحدة المعالجة المركزية والذاكرة) للعناصر التي من المرجّح أن يشاهدها المستخدم بعد ذلك. يؤدي ذلك إلى إنشاء استراتيجية "تحميل مسبق" استنادًا إلى التقارب، وهو المفتاح للموازنة بين التشغيل الفوري والاستخدام الفعّال للموارد.

يمكنك استخدام بيانات الإحصاءات من خلال PreloadManagerListener كما هو موضّح في الأقسام السابقة لتحديد استراتيجية مدة التحميل المُسبَق.

الخاتمة والخطوات التالية

أصبحت الآن ملمًّا بالمعرفة المتقدّمة لإنشاء خلاصات وسائط سريعة ومستقرة وفعّالة في استخدام الموارد باستخدام DefaultPreloadManager في Media3.

لنراجع معًا أبرز النقاط:

  • استخدِم PreloadManagerListener لجمع إحصاءات وتنفيذ معالجة فعّالة للأخطاء.
  • استخدِم دائمًا DefaultPreloadManager.Builder واحدًا لإنشاء مثيلات المدير والمشغّل لضمان مشاركة المكوّنات المهمة.
  • يمكنك تنفيذ نمط النافذة المنزلقة من خلال إدارة طلبات add()‎ وremove()‎ بشكل نشط لمنع حدوث خطأ OutOfMemoryError.
  • استخدِم TargetPreloadStatusControl لإنشاء استراتيجية ذكية ومتعدّدة المستويات للتحميل المُسبَق توازن بين الأداء واستهلاك الموارد.

الخطوات التالية في الجزء 3: التخزين المؤقت باستخدام الوسائط المُحمَّلة مسبقًا

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

هل لديك أي ملاحظات لمشاركتها؟ نحن في انتظار ردّك.

ترقَّبوا المزيد من الميزات، واستمتِعوا بتشغيل الفيديوهات بشكل أسرع. 🚀

من كتابة:
متابعة القراءة