تخصيصات واجهة المستخدم

توفّر 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(Player.COMMAND_SEEK_TO_NEXT)

Java

nextButton.setEnabled(player.isCommandAvailable(Player.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, 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.
}