توفّر Media3 واجهة PlayerView تلقائية تتيح بعض خيارات التخصيص.
تجاوز العناصر القابلة للرسم
يستخدم PlayerView PlayerControlView لعرض عناصر التحكّم في التشغيل وشريط التقدم. يمكن استبدال العناصر القابلة للرسم التي يستخدمها PlayerControlView بعناصر قابلة للرسم تحمل الأسماء نفسها المحدّدة في تطبيقك. يمكنك الاطّلاع على مستندات PlayerControlView للحصول على قائمة بعناصر التحكّم القابلة للرسم والتي يمكن إلغاؤها.
لإجراء أي تخصيص إضافي، من المتوقّع أن ينفّذ مطوّرو التطبيقات مكوّنات واجهة المستخدم الخاصة بهم. ومع ذلك، إليك بعض أفضل الممارسات التي يمكن أن تساعدك في البدء.
أفضل الممارسات
عند تنفيذ واجهة مستخدم للوسائط تتصل بـ Media3 Player (على سبيل المثال،
ExoPlayer أو MediaController أو تنفيذ Player مخصّص)، ننصح التطبيقات
باتّباع أفضل الممارسات التالية للحصول على أفضل تجربة لواجهة المستخدم.
زر التشغيل/الإيقاف المؤقت
لا يتوافق زر التشغيل والإيقاف المؤقت مباشرةً مع حالة مشغّل واحد. على سبيل المثال، يجب أن يتمكّن المستخدم من إعادة تشغيل المحتوى بعد انتهائه أو تعذُّر تشغيله، حتى إذا لم يتم إيقاف المشغّل مؤقتًا.
لتبسيط عملية التنفيذ، توفّر Media3 طرقًا مفيدة لتحديد الزر الذي يجب عرضه (Util.shouldShowPlayButton) والتعامل مع الضغطات على الأزرار (Util.handlePlayPauseButtonAction):
Kotlin
val shouldShowPlayButton: Boolean = Util.shouldShowPlayButton(player) playPauseButton.setImageDrawable(if (shouldShowPlayButton) playDrawable else pauseDrawable) playPauseButton.setOnClickListener { Util.handlePlayPauseButtonAction(player) }
Java
boolean shouldShowPlayButton = Util.shouldShowPlayButton(player); playPauseButton.setImageDrawable(shouldShowPlayButton ? playDrawable : pauseDrawable); playPauseButton.setOnClickListener(view -> Util.handlePlayPauseButtonAction(player));
الاستماع إلى آخر الأخبار عن الولاية
يجب أن يضيف مكوّن واجهة المستخدم Player.Listener ليتم إعلامه بتغييرات الحالة التي تتطلّب تحديثًا مطابقًا لواجهة المستخدم. لمزيد من التفاصيل، يُرجى الاطّلاع على الاستماع إلى أحداث التشغيل.
قد تكون إعادة تحميل واجهة المستخدم مكلفة، وغالبًا ما تصل أحداث اللاعبين المتعددة معًا. لتجنُّب إعادة تحميل واجهة المستخدم بشكل متكرر في فترة زمنية قصيرة، من الأفضل بشكل عام الاستماع إلى onEvents فقط وتشغيل تحديثات واجهة المستخدم من هناك:
Kotlin
player.addListener( object : Player.Listener { override fun onEvents(player: Player, events: Player.Events) { if ( events.containsAny( Player.EVENT_PLAY_WHEN_READY_CHANGED, Player.EVENT_PLAYBACK_STATE_CHANGED, Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED, ) ) { updatePlayPauseButton() } if (events.containsAny(Player.EVENT_REPEAT_MODE_CHANGED)) { updateRepeatModeButton() } } } )
Java
player.addListener( new Player.Listener() { @Override public void onEvents(Player player, Player.Events events) { if (events.containsAny( Player.EVENT_PLAY_WHEN_READY_CHANGED, Player.EVENT_PLAYBACK_STATE_CHANGED, Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED)) { updatePlayPauseButton(); } if (events.containsAny(Player.EVENT_REPEAT_MODE_CHANGED)) { updateRepeatModeButton(); } } });
التعامل مع الطلبات المتاحة
يجب أن يتحقّق مكوّن واجهة المستخدم للأغراض العامة الذي قد يحتاج إلى العمل مع عمليات تنفيذ Player مختلفة من أوامر المشغّل المتاحة لإظهار الأزرار أو إخفائها ولتجنُّب استدعاء الطرق غير المتوافقة:
Kotlin
nextButton.isEnabled = player.isCommandAvailable(COMMAND_SEEK_TO_NEXT)
Java
nextButton.setEnabled(player.isCommandAvailable(COMMAND_SEEK_TO_NEXT));
التقاط الإطار الأول وعرض الصورة
عندما يعرض أحد مكوّنات واجهة المستخدم فيديو أو صورًا، فإنّه يستخدم عادةً عرض حاجب مؤقت إلى أن يتوفّر الإطار أو الصورة الأولى الحقيقية. بالإضافة إلى ذلك، يتطلب تشغيل الفيديو والصور المختلطة إخفاء عرض الصورة وإظهاره في الأوقات المناسبة.
من الأنماط الشائعة للتعامل مع هذه التعديلات الاستماع إلى
Player.Listener.onEvents() لأي تغيير في المقاطع المحدّدة
(EVENT_TRACKS_CHANGED) وعند عرض أول إطار فيديو
(EVENT_RENDERED_FIRST_FRAME)، بالإضافة إلى ImageOutput.onImageAvailable()
عند توفّر صورة جديدة:
Kotlin
override fun onEvents(player: Player, events: Player.Events) { if (events.contains(Player.EVENT_TRACKS_CHANGED)) { // If no video or image track: show shutter, hide image view. // Otherwise: do nothing to wait for first frame or image. } if (events.contains(Player.EVENT_RENDERED_FIRST_FRAME)) { // Hide shutter, hide image view. } } override fun onImageAvailable(presentationTimeUs: Long, bitmap: Bitmap) { // Show shutter, set image and show image view. }
Java
@Override public void onEvents(Player player, Player.Events events) { if (events.contains(Player.EVENT_TRACKS_CHANGED)) { // If no video or image track: show shutter, hide image view. // Otherwise: do nothing to wait for first frame or image. } if (events.contains(Player.EVENT_RENDERED_FIRST_FRAME)) { // Hide shutter, hide image view. } } @Override public void onImageAvailable(long presentationTimeUs, Bitmap bitmap) { // Show shutter, set image and show image view. }
عناصر التحكّم في شريط التمرير وشريط التقديم/الترجيع
عند تنفيذ شريط تقديم/ترجيع أو شريط تمرير مخصّص يتصل بمثيل ExoPlayer، فعِّل وضع التمرير باستخدام ExoPlayer.setScrubbingModeEnabled عندما يبدأ المستخدم بسحب مؤشر شريط التمرير، وأرسِل تعديلات Player.seekTo أثناء السحب، وأوقِف وضع التمرير فور انتهاء الإيماءة أو إلغائها. تجنَّب
ترك وضع التصفّح السريع مفعَّلاً أثناء التشغيل العادي أو عمليات البحث الفردية، لأنّه
يمنع التشغيل ويزيد من استخدام موارد وحدة فك الترميز.
ننصح أيضًا بضبط نسبة سماح للبحث الجزئي استنادًا إلى dp من عرض شريط التمرير في واجهة المستخدم باستخدام ScrubbingModeParameters.Builder.setFractionalSeekTolerance لكي يتمكّن ExoPlayer من الانتقال إلى الإطارات الرئيسية القريبة ضمن دقة اللمس لشريط التمرير. للاطّلاع على أمثلة على الرموز البرمجية والتفاصيل، يُرجى مراجعة وضع التصفّح السريع.