أحداث اللاعبين

الاستماع إلى أحداث التشغيل

ويتم الإبلاغ عن الأحداث، مثل التغييرات في الحالة وأخطاء التشغيل، في حالات Player.Listener المسجّلة. ولتسجيل المستمع لتلقّي مثل هذه الأحداث:

Kotlin

// Add a listener to receive events from the player.
player.addListener(listener)

Java

// Add a listener to receive events from the player.
player.addListener(listener);

تتضمن Player.Listener طُرقًا تلقائية فارغة، لذلك ما عليك سوى تنفيذ الطرق التي تهمّك. راجِع Javadoc للحصول على وصف كامل للأساليب وأوقات استدعائها. تم وصف بعض أهم الطرق بمزيد من التفصيل أدناه.

ويمكن للمستمعين الاختيار بين تنفيذ عمليات استدعاء لحدث فردي أو معاودة اتصال عامة باستخدام onEvents يتم استدعاءها بعد وقوع حدث واحد أو أكثر معًا. يمكنك الانتقال إلى Individual callbacks vs onEvents للحصول على توضيح بشأن الخيار الذي يجب تفضيله لحالات الاستخدام المختلفة.

التغييرات في حالة التشغيل

يمكن تلقّي التغييرات في حالة المشغّل من خلال تنفيذ onPlaybackStateChanged(@State int state) في Player.Listener مسجَّل. يمكن أن يكون المشغّل إحدى حالات التشغيل الأربع:

  • Player.STATE_IDLE: هذه هي الحالة الأولية وحالة توقف المشغّل وتعذّر التشغيل. سيحتفظ اللاعب بموارد محدودة فقط في هذه الحالة.
  • Player.STATE_BUFFERING: لا يمكن للمشغّل التشغيل فورًا من موضعه الحالي. يحدث هذا في الغالب بسبب الحاجة إلى تحميل المزيد من البيانات.
  • Player.STATE_READY: يمكن للّاعب التشغيل مباشرةً من موضعه الحالي.
  • Player.STATE_ENDED: أنهى المشغّل تشغيل كل الوسائط.

بالإضافة إلى هذه الحالات، يتضمّن المشغّل علامة playWhenReady للإشارة إلى نية المستخدم في اللعب. يمكن تلقّي التغييرات في هذه العلامة من خلال تنفيذ onPlayWhenReadyChanged(playWhenReady, @PlayWhenReadyChangeReason int reason).

يلعب اللاعب (أي أن موضعه يتقدم ويتم تقديم الوسائط للمستخدم) عند استيفاء الشروط الثلاثة التالية:

  • حالة المشغّل Player.STATE_READY
  • سيُقام playWhenReady true
  • لا يتم منع التشغيل لسبب من الأسباب المعروضة من قِبل "Player.getPlaybackSuppressionReason".

بدلاً من التحقّق من هذه الخصائص بشكل فردي، يمكن استدعاء السمة Player.isPlaying. يمكن تلقّي التغييرات التي يتم إجراؤها على هذه الحالة من خلال تنفيذ onIsPlayingChanged(boolean isPlaying):

Kotlin

player.addListener(
  object : Player.Listener {
    override fun onIsPlayingChanged(isPlaying: Boolean) {
      if (isPlaying) {
        // Active playback.
      } else {
        // Not playing because playback is paused, ended, suppressed, or the player
        // is buffering, stopped or failed. Check player.playWhenReady,
        // player.playbackState, player.playbackSuppressionReason and
        // player.playerError for details.
      }
    }
  }
)

Java

player.addListener(
    new Player.Listener() {
      @Override
      public void onIsPlayingChanged(boolean isPlaying) {
        if (isPlaying) {
          // Active playback.
        } else {
          // Not playing because playback is paused, ended, suppressed, or the player
          // is buffering, stopped or failed. Check player.getPlayWhenReady,
          // player.getPlaybackState, player.getPlaybackSuppressionReason and
          // player.getPlaybackError for details.
        }
      }
    });

أخطاء التشغيل

يمكنك العثور على الأخطاء التي تؤدي إلى تعذُّر التشغيل من خلال تنفيذ onPlayerError(PlaybackException error) في Player.Listener مسجَّل. وعند حدوث عطل، سيتم استدعاء هذه الطريقة فورًا قبل أن تنتقل حالة التشغيل إلى Player.STATE_IDLE. يمكنك إعادة محاولة إجراء عمليات التشغيل التي تعذّر إتمامها أو تم إيقافها من خلال الاتصال بـ ExoPlayer.prepare.

تجدر الإشارة إلى أنّ بعض عمليات تنفيذ Player تجتاز مثيلات للفئات الفرعية من PlaybackException لتوفير معلومات إضافية عن تعذُّر إكمال العملية. على سبيل المثال، تمرّر ExoPlayer ExoPlaybackException التي تحتوي على type وrendererIndex وحقول أخرى خاصة بـ ExoPlayer.

يوضح المثال التالي كيفية اكتشاف إخفاق التشغيل بسبب مشكلة في شبكة HTTP:

Kotlin

player.addListener(
  object : Player.Listener {
    override fun onPlayerError(error: PlaybackException) {
      val cause = error.cause
      if (cause is HttpDataSourceException) {
        // An HTTP error occurred.
        val httpError = cause
        // It's possible to find out more about the error both by casting and by querying
        // the cause.
        if (httpError is InvalidResponseCodeException) {
          // Cast to InvalidResponseCodeException and retrieve the response code, message
          // and headers.
        } else {
          // Try calling httpError.getCause() to retrieve the underlying cause, although
          // note that it may be null.
        }
      }
    }
  }
)

Java

player.addListener(
    new Player.Listener() {
      @Override
      public void onPlayerError(PlaybackException error) {
        @Nullable Throwable cause = error.getCause();
        if (cause instanceof HttpDataSourceException) {
          // An HTTP error occurred.
          HttpDataSourceException httpError = (HttpDataSourceException) cause;
          // It's possible to find out more about the error both by casting and by querying
          // the cause.
          if (httpError instanceof HttpDataSource.InvalidResponseCodeException) {
            // Cast to InvalidResponseCodeException and retrieve the response code, message
            // and headers.
          } else {
            // Try calling httpError.getCause() to retrieve the underlying cause, although
            // note that it may be null.
          }
        }
      }
    });

عمليات نقل قوائم التشغيل

عند تغيير المشغّل إلى عنصر وسائط جديد في قائمة التشغيل يتم استدعاء onMediaItemTransition(MediaItem mediaItem, @MediaItemTransitionReason int reason) على عناصر Player.Listener المسجَّلة. يشير السبب إلى ما إذا كان ذلك نقلاً تلقائيًا أو عملية تقديم (على سبيل المثال بعد استدعاء player.next()) أو تكرارًا للعنصر نفسه أو بسبب تغيير في قائمة التشغيل (على سبيل المثال، إذا تمت إزالة العنصر قيد التشغيل حاليًا).

البيانات الوصفية

قد تتغيّر البيانات الوصفية المعروضة من player.getCurrentMediaMetadata() لأسباب متعدّدة، مثل عمليات نقل قوائم التشغيل أو تعديل البيانات الوصفية أثناء عرض الفيديو أو تعديل سياسة MediaItem الحالية في منتصف التشغيل.

إذا كنت مهتمًا بالتغييرات في البيانات الوصفية، مثل تعديل واجهة مستخدم تعرض العنوان الحالي، يمكنك الاستماع إلى onMediaMetadataChanged.

جارٍ تفعيل عناصر الانتقال

يؤدي استدعاء طرق Player.seekTo إلى إنشاء سلسلة من عمليات معاودة الاتصال إلى Player.Listener حالات مسجَّلة:

  1. onPositionDiscontinuity مع reason=DISCONTINUITY_REASON_SEEK. هذه النتيجة المباشرة لاستدعاء Player.seekTo. يتضمّن معاودة الاتصال PositionInfo حقلاً للموضع قبل شريط التقديم وبعده.
  2. onPlaybackStateChanged مع أي تغيير فوري للحالة متعلّق بطلب البحث يُرجى العلم أنّه قد لا يحدث مثل هذا التغيير.

عمليات معاودة الاتصال الفردية مقابل onEvents

يمكن للمستمعين الاختيار بين تنفيذ استدعاءات فردية، مثل onIsPlayingChanged(boolean isPlaying)، ومعاودة الاتصال العامة onEvents(Player player, Events events). يوفر رد الاستدعاء العام إمكانية الوصول إلى الكائن Player ويحدد مجموعة events التي حدثت معًا. دائمًا ما يتم استدعاء معاودة الاتصال هذه بعد عمليات معاودة الاتصال التي تتوافق مع الأحداث الفردية.

Kotlin

override fun onEvents(player: Player, events: Player.Events) {
  if (
    events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED) ||
      events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)
  ) {
    uiModule.updateUi(player)
  }
}

Java

@Override
public void onEvents(Player player, Events events) {
  if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)
      || events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)) {
    uiModule.updateUi(player);
  }
}

يجب تفضيل الأحداث الفردية في الحالات التالية:

  • المستمع مهتم بأسباب التغييرات. على سبيل المثال، يمكنك الاطّلاع على الأسباب المقدَّمة للسمة onPlayWhenReadyChanged أو onMediaItemTransition.
  • لا يتعامل المستمع إلّا مع القيم الجديدة المقدَّمة من خلال مَعلمات معاودة الاتصال أو يشغّل شيئًا آخر لا يعتمد على مَعلمات معاودة الاتصال.
  • يُفضّل تنفيذ المستمع إشارة واضحة سهلة القراءة لما أدّى إلى بدء الحدث في اسم الطريقة.
  • ويقدم المستمع تقارير إلى نظام تحليل يحتاج إلى معرفة جميع الأحداث الفردية وتغييرات الحالة.

يجب تفضيل استخدام onEvents(Player player, Events events) العام في الحالات التالية:

  • يريد المستمع تشغيل المنطق نفسه لأحداث متعددة. على سبيل المثال، تحديث واجهة المستخدم لكل من onPlaybackStateChanged وonPlayWhenReadyChanged.
  • ويحتاج المستمع إلى الوصول إلى الكائن Player لتشغيل المزيد من الأحداث، مثل البحث بعد نقل عنصر وسائط.
  • ويهدف المستمع إلى استخدام قيم حالة متعدّدة يتمّ الإبلاغ عنها من خلال عمليات استدعاء منفصلة معًا، أو بالاقتران مع أساليب getter Player. على سبيل المثال، يكون استخدام Player.getCurrentWindowIndex() مع Timeline المذكور في onTimelineChanged إجراءً آمنًا فقط من داخل معاودة الاتصال في onEvents.
  • يهتم المستمع بمعرفة ما إذا كانت الأحداث قد حدثت معًا بشكل منطقي. على سبيل المثال، من onPlaybackStateChanged إلى STATE_BUFFERING بسبب نقل عنصر الوسائط.

في بعض الحالات، قد يحتاج المستمعون إلى دمج عمليات معاودة الاتصال الفردية مع استدعاء onEvents العام، مثلاً لتسجيل أسباب تغيير عناصر الوسائط باستخدام onMediaItemTransition، ولكن يجب اتّخاذ إجراء فقط بعد استخدام جميع تغييرات الحالة معًا في onEvents.

جارٍ استخدام AnalyticsListener

عند استخدام ExoPlayer، يمكن تسجيل AnalyticsListener في المشغّل من خلال الاتصال بـ addAnalyticsListener. يمكن لعمليات تنفيذ AnalyticsListener الاستماع إلى الأحداث التفصيلية التي قد تكون مفيدة لأغراض الإحصاءات وتسجيل البيانات. يُرجى الرجوع إلى صفحة الإحصاءات للاطّلاع على مزيد من التفاصيل.

جارٍ استخدام EventLogger

EventLogger هو AnalyticsListener الذي تقدّمه المكتبة مباشرةً لأغراض التسجيل. أضِف EventLogger إلى ExoPlayer لتفعيل تسجيل إضافي مفيد باستخدام سطر واحد:

Kotlin

player.addAnalyticsListener(EventLogger())

Java

player.addAnalyticsListener(new EventLogger());

يمكنك الاطّلاع على صفحة تسجيل تصحيح الأخطاء لمعرفة المزيد من التفاصيل.

تنشيط الأحداث في مواضع تشغيل محددة

تتطلب بعض حالات الاستخدام تنشيط الأحداث في مواضع تشغيل محدّدة. هذا الإجراء متوافق باستخدام PlayerMessage. يمكن إنشاء PlayerMessage باستخدام ExoPlayer.createMessage. يمكن ضبط موضع التشغيل الذي يجب تنفيذه باستخدام PlayerMessage.setPosition. يتم تنفيذ الرسائل في سلسلة محادثات التشغيل تلقائيًا، ولكن يمكن تخصيص ذلك باستخدام PlayerMessage.setLooper. يمكن استخدام PlayerMessage.setDeleteAfterDelivery للتحكّم في ما إذا كان سيتم تنفيذ الرسالة في كل مرة يتم فيها العثور على موضع التشغيل المحدّد (قد يحدث ذلك عدة مرات بسبب أوضاع التقديم/الترجيع) أو في المرة الأولى فقط. وبعد ضبط السمة PlayerMessage، يمكن جدولتها باستخدام السمة PlayerMessage.send.

Kotlin

player
  .createMessage { messageType: Int, payload: Any? -> }
  .setLooper(Looper.getMainLooper())
  .setPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 120000)
  .setPayload(customPayloadData)
  .setDeleteAfterDelivery(false)
  .send()

Java

player
    .createMessage(
        (messageType, payload) -> {
          // Do something at the specified playback position.
        })
    .setLooper(Looper.getMainLooper())
    .setPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 120_000)
    .setPayload(customPayloadData)
    .setDeleteAfterDelivery(false)
    .send();