أخبار المنتجات
تحسين تشغيل الوسائط: نظرة معمّقة على 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 توجيه استهلاك الذاكرة في كلّ من المشغّل وPreloadManager من خلال استراتيجية تخزين مؤقت واحدة ومنسّقة في كلّ من الوسائط التي تم تحميلها مسبقًا والوسائط التي يتم تشغيلها بشكل نشط، ما يمنع تعارض الموارد. عليك تخصيص حجم ذاكرة التخزين المؤقت بذكاء بالتنسيق مع عدد العناصر التي يتم التحميل المُسبَق لها ومدة التحميل، وذلك لضمان الاتساق. في حال حدوث تعارض، سيعطي المشغّل الأولوية لتشغيل العنصر الحالي المعروض على الشاشة. باستخدام LoadControl مشترَك، سيواصل مدير التحميل المُسبَق التحميل المُسبَق طالما أنّ عدد بايتات المخزن المؤقت المستهدَفة المخصّصة للتحميل المُسبَق لم يبلغ الحدّ الأقصى، ولن ينتظر إلى أن يكتمل التحميل لتشغيل المحتوى.
ملاحظة: تضمن مشاركة LoadControl في أحدث إصدار من Media3 (1.8) إمكانية مشاركة Allocator بشكلٍ صحيح مع PreloadManager والمشغّل. استخدام LoadControl للتحكّم بفعالية في التحميل المُسبَق هو إحدى الميزات التي ستتوفّر في إصدار Media3 1.9 القادم.
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 دقائق
-
أخبار المنتجات
أصبح الإصدار 4 من استوديو Android Panda ثابتًا وجاهزًا للاستخدام في الإنتاج. يتضمّن هذا الإصدار "وضع التخطيط" و"توقّع التعديل التالي" والمزيد، ما يسهّل إنشاء تطبيقات Android عالية الجودة أكثر من أي وقت مضى.
Matt Dyor • مدة القراءة: 5 دقائق
-
أخبار المنتجات
إذا كنت من مطوّري تطبيقات Android وتتطلّع إلى دمج ميزات مبتكرة تستند إلى الذكاء الاصطناعي في تطبيقك، أطلقنا مؤخرًا تحديثات جديدة وفعّالة.
Thomas Ezan • قراءة لمدة 3 دقائق
البقاء على اطّلاع على آخر التحديثات
يمكنك تلقّي أحدث الإحصاءات حول تطوير تطبيقات Android في بريدك الوارد أسبوعيًا.