تحسين تشغيل الوسائط: نظرة متعمّقة على PreloadManager في Media3 - الجزء 2
مدّة القراءة: 9 دقائق
مرحبًا بك في الجزء الثاني من سلسلتنا المكوّنة من ثلاثة أجزاء حول التحميل المُسبَق للوسائط باستخدام Media3. تم تصميم هذه السلسلة لإرشادك خلال عملية إنشاء تجارب وسائط سريعة الاستجابة ومنخفضة الكمون في تطبيقات Android.
- تناول الجزء 1: مقدّمة عن التحميل المُسبَق باستخدام Media3 الأساسيات. استكشفنا الفرق بين PreloadConfiguration لقوائم التشغيل البسيطة وDefaultPreloadManager الأكثر فعالية لواجهات المستخدم الديناميكية. تعرّفت على كيفية تنفيذ دورة حياة واجهة برمجة التطبيقات الأساسية: إضافة وسائط باستخدام add()، واسترداد MediaSource مُعدّة باستخدام getMediaSource()، وإدارة الأولويات باستخدام setCurrentPlayingIndex() وinvalidate()، وإتاحة الموارد باستخدام remove() وrelease().
- الجزء 2 (هذه المشاركة): في هذه المدونة، نستكشف الإمكانات المتقدّمة لفئة DefaultPreloadManager. نتناول كيفية الحصول على إحصاءات باستخدام PreloadManagerListener، وتنفيذ أفضل الممارسات الجاهزة للاستخدام في مرحلة الإنتاج، مثل مشاركة المكوّنات الأساسية مع ExoPlayer، وإتقان نمط النافذة المنزلقة لإدارة الذاكرة بفعالية.
- الجزء 3: سيتناول الجزء الأخير من هذه السلسلة دمج PreloadManager مع ذاكرة التخزين المؤقت للقرص، ما يتيح لك تقليل استهلاك البيانات من خلال إدارة الموارد وتقديم تجربة سلسة.
إذا كنت لا تعرف ميزة التحميل المُسبَق في Media3، ننصحك بشدة بقراءة الجزء 1 قبل المتابعة. بالنسبة إلى المستخدمين المستعدين للانتقال إلى مستوى أعلى من الأساسيات، لنستكشف كيفية تحسين عملية تنفيذ تشغيل الوسائط.
الاستماع إلى الأحداث: استرداد الإحصاءات باستخدام 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 Playback.
تجنُّب تعارضات معدّل نقل البيانات مع BandwidthMeter مشترَك
تقدّم أداة BandwidthMeter تقديرًا لمعدّل نقل البيانات المتوفر على الشبكة استنادًا إلى معدّلات النقل السابقة. إذا كان كلّ من PreloadManager والمشغّل يستخدمان مثيلات منفصلة، لن يكون أيّ منهما على دراية بنشاط الشبكة للآخر، ما قد يؤدي إلى سيناريوهات تعذُّر. على سبيل المثال، لنفترض أنّ المستخدم يشاهد فيديو، ثم تنخفض جودة اتصال الشبكة، وتبدأ MediaSource المحمَّلة مسبقًا في الوقت نفسه عملية تنزيل مكثّفة لفيديو مستقبلي. سيؤدي نشاط MediaSource الذي يتم تحميله مسبقًا إلى استهلاك معدل نقل البيانات الذي يحتاج إليه المشغّل النشط، ما يؤدي إلى توقّف الفيديو الحالي مؤقتًا. يُعدّ حدوث توقّف مؤقت أثناء التشغيل مشكلة كبيرة في تجربة المستخدم.
من خلال مشاركة BandwidthMeter واحد، يمكن لـ TrackSelector اختيار المقاطع الصوتية بأعلى جودة وفقًا لحالات الشبكة الحالية وحالة المخزن المؤقت، وذلك أثناء التحميل المُسبَق أو التشغيل. يمكنه بعد ذلك اتّخاذ قرارات ذكية لحماية جلسة التشغيل النشطة وضمان تجربة سلسة.
preloadManagerBuilder.setBandwidthMeter(customBandwidthMeter)
ضمان التوافق مع مكوّنات LoadControl وTrackSelector وRenderer المشترَكة في ExoPlayer
- LoadControl: يحدّد هذا المكوّن سياسة التخزين المؤقت، مثل مقدار البيانات التي يجب تخزينها مؤقتًا قبل بدء التشغيل ووقت بدء تحميل المزيد من البيانات أو إيقافه. تضمن ميزة "مشاركة LoadControl" توجيه استهلاك الذاكرة لكل من Player وPreloadManager من خلال استراتيجية تخزين مؤقت واحدة ومنسَّقة في كل من الوسائط التي تم تحميلها مسبقًا والوسائط التي يتم تشغيلها حاليًا، ما يمنع تعارض الموارد. عليك تخصيص حجم ذاكرة التخزين المؤقت بذكاء بالتنسيق مع عدد العناصر التي يتم التحميل المُسبَق لها ومدة التحميل، وذلك لضمان الاتساق. في حال حدوث تعارض، سيعطي المشغّل الأولوية لتشغيل العنصر الحالي المعروض على الشاشة. باستخدام LoadControl مشترَك، سيواصل مدير التحميل المُسبَق التحميل المُسبَق طالما أنّ عدد وحدات البايت المخزَّنة مؤقتًا والمخصَّصة للتحميل المُسبَق لم يبلغ الحدّ الأقصى، ولن ينتظر إلى أن يكتمل التحميل للتشغيل.
ملاحظة: تضمن مشاركة LoadControl في أحدث إصدار من Media3 (1.8) إمكانية مشاركة أداة التخصيص بشكل صحيح مع 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 عند إنشاء المشغّل. يمكن طلب البحث عن أداة معالجة الرسائل في سلسلة التعليمات التي يجب الوصول إلى المشغّل منها باستخدام 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، الذي يخصّص مخازن مؤقتة للذاكرة. وعندما تتراكم هذه البيانات، يمكن أن تستنفد مساحة الذاكرة المخصّصة للتطبيق. الحلّ هو خوارزمية قد تكون على دراية بها، تُعرف باسم "النافذة المنزلقة". يحافظ نمط النافذة المنزلقة على مجموعة صغيرة وقابلة للإدارة من العناصر في الذاكرة تكون متجاورة منطقيًا مع موضع المستخدم الحالي في الخلاصة. أثناء تنقّل المستخدم، تنزلق هذه "النافذة" من العناصر المُدارة معه، ما يؤدي إلى إضافة عناصر جديدة تظهر في طريقة العرض، وإزالة العناصر البعيدة.
تنفيذ نمط النافذة المنزلقة
من الضروري معرفة أنّ 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: التخزين المؤقت باستخدام الوسائط المحمَّلة مسبقًا؟
يوفّر التحميل المُسبَق للبيانات في الذاكرة فائدة فورية من حيث الأداء، ولكن قد يكون له بعض العيوب. وبعد إغلاق التطبيق أو إزالة الوسائط المحمَّلة مسبقًا من أداة الإدارة، يتم حذف البيانات. لتحقيق مستوى أكثر استدامة من التحسين، يمكننا الجمع بين التحميل المُسبَق والتخزين المؤقت على القرص. هذه الميزة قيد التطوير النشط وستتوفّر قريبًا خلال بضعة أشهر.
هل لديك أي ملاحظات تودّ مشاركتها؟ نتطلّع إلى تلقّي ردّك.
ترقَّبوا المزيد من الأخبار، وابدأوا بتسريع فيديوهاتكم. 🚀
-
أخبار المنتجاتفي تطبيقات الوسائط الحالية، يُعدّ توفير تجربة تشغيل سلسة ومتواصلة أمرًا أساسيًا لتقديم تجربة ممتعة للمستخدمين. يتوقّع المستخدمون أن تبدأ فيديوهاتهم على الفور وأن يتم تشغيلها بسلاسة بدون توقّف.
Mayuri Khinvasara Khabya • 8 دقائق للقراءة -
أخبار المنتجاتعلى Google Play، نعمل باستمرار على توسيع منصة الاشتراكات لمساعدتك في تعزيز النمو والتكيّف مع نماذج الأعمال الجديدة وتلبية احتياجات المستخدمين أينما كانوا.
Sheenam Mittal • مدّة القراءة: 4 دقائق -
أخبار المنتجاتفي العام الماضي، أصبح بإمكان "استوديو Android" استخدام أي نموذج للذكاء الاصطناعي. واليوم، نتّخذ الخطوة التالية من خلال توفير إمكانية اختيار وكلاء الترميز.
Matthew Warner • مدّة القراءة: 3 دقائق
يمكنك تلقّي أحدث الإحصاءات حول تطوير تطبيقات Android في بريدك الوارد أسبوعيًا.