أخبار المنتجات
تحسين تشغيل الوسائط: نظرة متعمّقة على 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 إلى إجمالي محاولات التحميل المُسبَق)
يمكن أن تمنحك هذه البيانات ملاحظات كمّية عن استراتيجية التحميل المُسبَق، ما يتيح لك إجراء الاختبارات الثنائية وتحسين تجربة المستخدم استنادًا إلى البيانات. يمكن أن تساعدك هذه البيانات أيضًا في تعديل مدة التحميل المُسبَق وعدد الفيديوهات التي تريد تحميلها مُسبقًا بالإضافة إلى المخازن المؤقتة التي تخصّصها بذكاء.
ما وراء تصحيح الأخطاء: استخدام onError لتوفير حلّ احتياطي سلس لواجهة المستخدم
يشير تعذُّر التحميل المُسبَق إلى حدث وشيك لتخزين البيانات مؤقتًا للمستخدم. يسمح لك ردّ الاستدعاء onError بالرد بشكل تفاعلي. بدلاً من مجرد تسجيل الخطأ، يمكنك تعديل واجهة المستخدم. على سبيل المثال، إذا تعذّر التحميل المُسبَق للفيديو القادم، يمكن أن يوقف تطبيقك ميزة التشغيل التلقائي للتمرير السريع التالي، ما يتطلب من المستخدم النقر لبدء التشغيل.
بالإضافة إلى ذلك، من خلال فحص نوع PreloadException، يمكنك تحديد استراتيجية أكثر ذكاءً لإعادة المحاولة. يمكن أن يختار التطبيق إزالة مصدر فاشل على الفور من المدير استنادًا إلى رسالة الخطأ أو رمز حالة HTTP. يجب إزالة العنصر من واجهة المستخدم وفقًا لذلك حتى لا تؤثر مشاكل التحميل في تجربة المستخدم. يمكنك أيضًا الحصول على بيانات أكثر تفصيلاً من PreloadException، مثل HttpDataSourceException، للبحث أكثر في الأخطاء. مزيد من المعلومات عن تحديد المشاكل في ExoPlayer وحلّها
نظام الرفيق: ما هي ضرورة مشاركة المكوّنات مع ExoPlayer؟
تم تصميم DefaultPreloadManager وExoPlayer للعمل معًا. لضمان الاستقرار والكفاءة، يجب أن يشاركا عدة مكوّنات أساسية components. إذا كانا يعملان باستخدام مكوّنات منفصلة وغير منسّقة، فقد يؤثر ذلك في أمان الخيط وسهولة استخدام المقاطع الصوتية التي تم تحميلها مُسبقًا على المشغّل، لأنّنا بحاجة إلى التأكّد من تشغيل المقاطع الصوتية التي تم تحميلها مُسبقًا على المشغّل الصحيح. يمكن أن تتنافس المكوّنات المنفصلة أيضًا على موارد محدودة، مثل معدّل نقل البيانات والذاكرة، ما قد يؤدي إلى تدهور الأداء. يُعد التعامل مع الإزالة المناسبة جزءًا مهمًا من دورة الحياة، والترتيب المقترَح للإزالة هو إزالة PreloadManager أولاً، ثم ExoPlayer.
تم تصميم DefaultPreloadManager.Builder لتسهيل هذه المشاركة ويتضمّن واجهات برمجة تطبيقات لـ إنشاء كل من PreloadManager ومثيل مشغّل مرتبط. لنطّلع على سبب ضرورة مشاركة مكوّنات مثل BandwidthMeter وLoadControl وTrackSelector ومشغّل الرسائل. يمكنك الاطّلاع على التمثيل المرئي لكيفية تفاعل هذه المكوّنات مع تشغيل ExoPlayer.
منع تعارضات معدّل نقل البيانات باستخدام BandwidthMeter مشترك
تقدّم BandwidthMeter تقديرًا لمعدّل نقل البيانات المتاح استنادًا إلى معدّلات النقل السابقة. إذا كان كل من PreloadManager والمشغّل يستخدمان مثيلَين منفصلَين، لن يكونا على علم بنشاط الشبكة الخاص بكل منهما، ما قد يؤدي إلى حالات تعذُّر. على سبيل المثال، لنفترض أنّ المستخدم يشاهد فيديو، ويتدهور اتصال الشبكة، ويبدأ MediaSource للتحميل المُسبَق في الوقت نفسه عملية تنزيل مكثّفة لفيديو مستقبلي. سيستهلك نشاط MediaSource للتحميل المُسبَق معدّل نقل البيانات الذي يحتاجه المشغّل النشط، ما يؤدي إلى توقّف الفيديو الحالي. يُعد التوقّف أثناء التشغيل خطأً كبيرًا في تجربة المستخدم.
من خلال مشاركة BandwidthMeter واحد، يمكن أن يختار TrackSelector المقاطع الصوتية بأعلى جودة في ظل ظروف الشبكة الحالية وحالة المخزن المؤقت، أثناء التحميل المُسبَق أو التشغيل. يمكنه بعد ذلك اتخاذ قرارات ذكية لحماية جلسة التشغيل النشطة وضمان تجربة سلسة.
preloadManagerBuilder.setBandwidthMeter(customBandwidthMeter)
ضمان الاتساق باستخدام مكوّنات LoadControl وTrackSelector وRenderer المشتركة في ExoPlayer
- LoadControl: يحدّد هذا المكوّن سياسة التخزين المؤقت، مثل مقدار البيانات التي يجب تخزينها مؤقتًا قبل بدء التشغيل ومتى يجب بدء تحميل المزيد من البيانات أو إيقافه. تضمن مشاركة LoadControl توجيه استهلاك الذاكرة للمشغّل وPreloadManager من خلال استراتيجية تخزين مؤقت منسّقة واحدة على كل من الوسائط التي تم تحميلها مُسبقًا والوسائط التي يتم تشغيلها حاليًا، ما يمنع التنافس على الموارد. عليك تخصيص حجم ذاكرة التخزين المؤقت بذكاء بالتنسيق مع عدد العناصر التي يتم تحميلها مُسبقًا والمدة، لضمان الاتساق. في أوقات التنافس، سيمنح المشغّل الأولوية لتشغيل العنصر الحالي المعروض على الشاشة. باستخدام LoadControl مشترك، سيواصل مدير التحميل المُسبَق التحميل المُسبَق طالما لم تصل وحدات البايت للمخزن المؤقت المستهدَف المخصّصة للتحميل المُسبَق إلى الحدّ الأعلى، ولن ينتظر إلى أن يكتمل التحميل للتشغيل.
ملاحظة: تضمن مشاركة LoadControl في أحدث إصدار من Media3 (1.8) إمكانية مشاركة أداة التخصيص بشكل صحيح مع PreloadManager والمشغّل. إنّ استخدام LoadControl للتحكّم بفعالية في التحميل المُسبَق هي ميزة ستتوفّر في إصدار Media3 1.9 القادم.
preloadManagerBuilder.setLoadControl(customLoadControl)
- TrackSelector: هذا المكوّن مسؤول عن اختيار المقاطع الصوتية (على سبيل المثال، فيديو بدقة معيّنة، أو صوت بلغة معيّنة) التي سيتم تحميلها وتشغيلها. تضمن المشاركة أنّ المقاطع الصوتية التي تم اختيارها أثناء التحميل المُسبَق هي نفسها التي سيستخدمها المشغّل. يؤدي ذلك إلى تجنُّب سيناريو غير مجدٍ يتم فيه تحميل مقطع فيديو بدقة 480p مُسبقًا، ثم يتجاهله المشغّل على الفور ويسترد مقطعًا بدقة 720p عند التشغيل.< br /> يجب ألا يشارك مدير التحميل المُسبَق المثيل نفسه من TrackSelector مع المشغّل. بدلاً من ذلك، يجب أن يستخدم كل منهما مثيلاً مختلفًا من TrackSelector ولكن من التنفيذ نفسه. لهذا السبب، نضبط TrackSelectorFactory بدلاً من TrackSelector في DefaultPreloadManager.Builder.
preloadManagerBuilder.setTrackSelectorFactory(customTrackSelectorFactory)
- Renderer: هذا المكوّن مسؤول عن فهم إمكانات المشغّل بدون إنشاء أدوات العرض الكاملة. يفحص هذا المخطط لمعرفة تنسيقات الفيديو والصوت والنص التي سيدعمها المشغّل النهائي. يسمح له ذلك باختيار وتنزيل مقطع الوسائط المتوافق فقط بذكاء ويمنع إضاعة معدّل نقل البيانات على المحتوى الذي لا يمكن للمشغّل تشغيله فعليًا.
preloadManagerBuilder.setRenderersFactory(customRenderersFactory)
مزيد من المعلومات عن مكوّنات Exoplayer
القاعدة الذهبية: استخدام Playback Looper مشترك للجميع
يمكن تحديد الخيط الذي يمكن الوصول إلى مثيل ExoPlayer عليه بشكل صريح من خلال تمرير Looper عند إنشاء المشغّل. يمكن طلب البحث عن Looper الخاص بالخيط الذي يجب الوصول إلى المشغّل منه باستخدام Player.getApplicationLooper. من خلال الحفاظ على مشغّل رسائل مشترك بين المشغّل و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 دقائق
-
أخبار المنتجات
أعلنا اليوم خلال The Android Show أنّ Android ينتقل من نظام تشغيل إلى نظام ذكاء اصطناعي، ما يخلق المزيد من الفرص للتفاعل مع تطبيقاتك.
Matthew McCullough • مدّة القراءة: 4 دقائق
-
أخبار المنتجات
تتطوّر المنظومة المتكاملة للأجهزة الجوّالة باستمرار، ما يوفّر فرصًا جديدة ويفرض تهديدات جديدة. من خلال هذه التغييرات، يظلّ كل من Android وGoogle Play ملتزمَين بضمان استمرار مليارات المستخدمين في الاستمتاع بتطبيقاتهم بثقة وازدهار ابتكارات المطوّرين.
Vijaya Kaza • مدّة القراءة: 3 دقائق
البقاء على اطّلاع على آخر التحديثات
يمكنك تلقّي أحدث الإحصاءات حول تطوير تطبيقات Android في بريدك الوارد أسبوعيًا.