التحكّم في تشغيل الإعلان والإعلان عنه باستخدام MediaSession

توفّر جلسات الوسائط طريقة شاملة للتفاعل مع مشغّل الصوت أو الفيديو. في Media3، المشغّل التلقائي هو الفئة ExoPlayer التي تنفّذ الواجهة Player. يسمح ربط جلسة الوسائط بمشغّل الوسائط لأي تطبيق بالإعلان عن تشغيل الوسائط خارجيًا وتلقّي أوامر التشغيل من مصادر خارجية.

قد تنشأ الأوامر من أزرار مادية، مثل زر التشغيل على سماعة الرأس أو جهاز التحكّم عن بُعد في التلفزيون. وقد تأتي أيضًا من تطبيقات العميل التي تتضمّن أداة تحكّم في الوسائط، مثل إصدار الأمر "إيقاف مؤقت" إلى "مساعد Google". تفوّض جلسة الوسائط هذه الأوامر إلى مشغّل تطبيق الوسائط.

الحالات التي يجب فيها اختيار جلسة وسائط

عند تنفيذ MediaSession، يمكنك السماح للمستخدمين بالتحكّم في التشغيل من خلال:

  • من خلال سمّاعات الرأس غالبًا ما تتضمّن سمّاعات الرأس أزرارًا أو تفاعلات باللمس يمكن للمستخدم تنفيذها لتشغيل الوسائط أو إيقافها مؤقتًا أو الانتقال إلى المقطع الصوتي التالي أو السابق.
  • من خلال التحدّث إلى مساعد Google من الأنماط الشائعة قول "Ok Google، إيقاف مؤقت" لإيقاف أي وسائط يتم تشغيلها حاليًا على الجهاز.
  • من خلال ساعة Wear OS ويتيح ذلك الوصول بسهولة أكبر إلى عناصر التحكّم الأكثر شيوعًا أثناء تشغيل المحتوى على الهاتف.
  • من خلال أدوات التحكّم في الوسائط تعرض لوحة العرض الدوّارة هذه عناصر تحكّم لكل جلسة وسائط نشطة.
  • على التلفزيون تتيح تنفيذ إجراءات باستخدام أزرار التشغيل المادية وعناصر التحكّم في التشغيل على المنصة وإدارة الطاقة (على سبيل المثال، إذا تم إيقاف التلفزيون أو مكبّر الصوت أو جهاز استقبال الصوت والفيديو أو تم تبديل الإدخال، يجب إيقاف التشغيل في التطبيق).
  • من خلال عناصر التحكّم في الوسائط في Android Auto ويتيح ذلك التحكّم الآمن في التشغيل أثناء القيادة.
  • وأي عمليات خارجية أخرى يجب أن تؤثر في التشغيل

وهذا مفيد في العديد من حالات الاستخدام. على وجه الخصوص، ننصحك بشدة باستخدام MediaSession في الحالات التالية:

  • تبث محتوى فيديو طويل، مثل الأفلام أو البث التلفزيوني المباشر.
  • أنت تبث محتوى صوتيًا طويلاً، مثل برامج البودكاست أو قوائم تشغيل الموسيقى.
  • أنّك بصدد إنشاء تطبيق تلفزيون

ومع ذلك، لا تتوافق بعض حالات الاستخدام مع MediaSession. قد تحتاج إلى استخدام Player فقط في الحالات التالية:

  • تعرض محتوى قصير الشكل لا يتطلّب أي عناصر تحكّم خارجية أو تشغيل في الخلفية.
  • لا يتضمّن الفيديو نشاطًا واحدًا، مثل أن يتصفّح المستخدم قائمة ويتم عرض فيديوهات متعددة على الشاشة في الوقت نفسه.
  • تعرض فيديو تعريفيًا أو توضيحيًا لمرة واحدة، وتتوقّع أن يشاهده المستخدم بشكل نشط بدون الحاجة إلى عناصر تحكّم خارجية في التشغيل.
  • يكون المحتوى حسّاسًا من ناحية الخصوصية ولا تريد أن تتمكّن العمليات الخارجية من الوصول إلى البيانات الوصفية للوسائط (مثل وضع التصفّح المتخفي في المتصفّح).

إذا لم تتطابق حالة الاستخدام مع أي من الحالات المذكورة أعلاه، عليك تحديد ما إذا كنت توافق على أن يواصل تطبيقك تشغيل المحتوى عندما لا يتفاعل المستخدم معه بشكل نشط. إذا كانت الإجابة بنعم، من المحتمل أنّك تريد اختيار MediaSession. إذا كانت الإجابة لا، من المفترض أن تستخدم Player بدلاً من ذلك.

إنشاء جلسة وسائط

تعمل جلسة الوسائط جنبًا إلى جنب مع المشغّل الذي تديره. يمكنك إنشاء جلسة وسائط باستخدام العنصرَين Context وPlayer. عليك إنشاء جلسة وسائط وتهيئتها عند الحاجة إليها، مثل طريقة دورة حياة onStart() أو onResume() في Activity أو Fragment، أو طريقة onCreate() في Service التي تملك جلسة الوسائط ومشغّلها المرتبط.

لإنشاء جلسة وسائط، عليك تهيئة Player وتوفيرها إلى MediaSession.Builder على النحو التالي:

Kotlin

val player = ExoPlayer.Builder(context).build()
val mediaSession = MediaSession.Builder(context, player).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();
MediaSession mediaSession = new MediaSession.Builder(context, player).build();

التعامل التلقائي مع الحالة

تعدّل مكتبة Media3 جلسة الوسائط تلقائيًا باستخدام حالة المشغّل. وبالتالي، لن تحتاج إلى التعامل يدويًا مع عملية الربط بين اللاعب والجلسة.

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

معرّف الجلسة الفريد

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

إذا كان أحد التطبيقات يريد إدارة عدة مثيلات لجلسات في الوقت نفسه، يجب أن يضمن التطبيق أنّ معرّف الجلسة لكل جلسة فريد. يمكن ضبط معرّف الجلسة عند إنشاء الجلسة باستخدام MediaSession.Builder.setId(String id).

إذا ظهر لك الخطأ IllegalStateException الذي يؤدي إلى تعطُّل تطبيقك مع رسالة الخطأ IllegalStateException: Session ID must be unique. ID=، من المحتمل أنّه تم إنشاء جلسة بشكل غير متوقّع قبل إصدار مثيل تم إنشاؤه سابقًا باستخدام المعرّف نفسه. ولتجنُّب تسرُّب الجلسات بسبب خطأ برمجي، يتم رصد هذه الحالات وإرسال إشعار بشأنها من خلال طرح استثناء.

منح إذن التحكّم لبرامج أخرى

جلسة الوسائط هي المفتاح للتحكّم في التشغيل. تتيح لك هذه الواجهة توجيه الأوامر من مصادر خارجية إلى مشغّل الوسائط الذي يتولّى تشغيل الوسائط. يمكن أن تكون مصادر هذه الأحداث أزرارًا فعلية، مثل زر التشغيل على سماعة رأس أو جهاز تحكّم عن بُعد في التلفزيون، أو طلبات غير مباشرة، مثل توجيه طلب "إيقاف مؤقت" إلى "مساعد Google". وبالمثل، قد ترغب في منح إذن الوصول إلى نظام Android لتسهيل تلقّي الإشعارات واستخدام عناصر التحكّم في شاشة القفل، أو إلى ساعة Wear OS لتتمكّن من التحكّم في التشغيل من خلفية الساعة. يمكن للعملاء الخارجيين استخدام أداة تحكّم في الوسائط لإرسال أوامر تشغيل إلى تطبيق الوسائط، وتتلقّى جلسة الوسائط هذه الأوامر، ثم تفوّضها في النهاية إلى مشغّل الوسائط.

مخطّط بياني يوضّح التفاعل بين MediaSession وMediaController
الشكل 1: يسهّل عنصر التحكّم في الوسائط تمرير الأوامر من مصادر خارجية إلى جلسة الوسائط.

عندما يكون جهاز التحكّم على وشك الاتصال بجلسة الوسائط، يتم استدعاء الطريقة onConnect(). يمكنك استخدام ControllerInfo المتاح لتحديد ما إذا كنت تريد قبول الطلب أو رفضه. يمكنك الاطّلاع على مثال على قبول طلب ربط في قسم تعريف الأوامر المخصّصة.

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

تتيح لك طرق معاودة الاتصال الأخرى التعامل مع طلبات الأوامر المخصّصة وتعديل قائمة التشغيل، على سبيل المثال. تتضمّن عمليات الاسترجاع هذه أيضًا عنصر ControllerInfo، ما يتيح لك تعديل طريقة الرد على كل طلب على أساس كل وحدة تحكّم.

تعديل قائمة التشغيل

يمكن لجلسة الوسائط تعديل قائمة تشغيل المشغّل مباشرةً كما هو موضّح في دليل ExoPlayer لقوائم التشغيل. يمكن أيضًا لمسؤولي التحكّم تعديل قائمة التشغيل إذا كان بإمكانهم استخدام COMMAND_SET_MEDIA_ITEM أو COMMAND_CHANGE_MEDIA_ITEMS.

عند إضافة عناصر جديدة إلى قائمة التشغيل، يحتاج المشغّل عادةً إلى MediaItem مثيلات مع معرّف موارد موحّد (URI) محدّد لإتاحة تشغيلها. يتم تلقائيًا إعادة توجيه العناصر المضافة حديثًا إلى طرق المشغّل، مثل player.addMediaItem، إذا كان لديها معرّف موارد موحّد محدّد.

إذا أردت تخصيص مثيلات MediaItem المضافة إلى المشغّل، يمكنك إلغاء onAddMediaItems(). هذه الخطوة مطلوبة عندما تريد توفير إمكانية استخدام أدوات تحكّم تطلب الوسائط بدون معرّف URI محدّد. بدلاً من ذلك، يحتوي MediaItem عادةً على حقل واحد أو أكثر من الحقول التالية التي تم ضبطها لوصف الوسائط المطلوبة:

  • MediaItem.id: معرّف عام يحدّد الوسائط.
  • MediaItem.RequestMetadata.mediaUri: عنوان URI للطلب قد يستخدم مخططًا مخصّصًا وليس بالضرورة أن يكون قابلاً للتشغيل مباشرةً من خلال المشغّل.
  • MediaItem.RequestMetadata.searchQuery: طلب بحث نصي، مثلاً من "مساعد Google"
  • MediaItem.MediaMetadata: بيانات وصفية منظَّمة، مثل "العنوان" أو "الفنان"

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

إدارة الإعدادات المفضّلة لأزرار الوسائط

يمكن لكل وحدة تحكّم، مثل واجهة مستخدم النظام أو Android Auto أو Wear OS، اتّخاذ قراراتها الخاصة بشأن الأزرار التي يجب عرضها للمستخدم. لتحديد عناصر التحكّم في التشغيل التي تريد عرضها للمستخدم، يمكنك تحديد إعدادات المفاتيح الخاصة بالوسائط في MediaSession. تتألف هذه الإعدادات المفضّلة من قائمة مرتّبة من مثيلات CommandButton، يحدّد كل منها إعدادًا مفضّلاً لزر في واجهة المستخدم.

تحديد أزرار الأوامر

يتم استخدام مثيلات CommandButton لتحديد الإعدادات المفضّلة لأزرار الوسائط. يحدّد كل زر ثلاثة جوانب من عنصر واجهة المستخدم المطلوب:

  1. الرمز الذي يحدّد المظهر المرئي يجب ضبط الرمز على إحدى الثوابت المحدّدة مسبقًا عند إنشاء CommandButton.Builder. يُرجى العِلم أنّ هذا ليس مصدرًا فعليًا لملف Bitmap أو صورة. تساعد الثوابت العامة وحدات التحكّم في اختيار مورد مناسب للحصول على مظهر متسق في واجهة المستخدِم الخاصة بها. إذا لم يتطابق أي من ثوابت الرموز المحدّدة مسبقًا مع حالة الاستخدام، يمكنك استخدام setCustomIconResId بدلاً من ذلك.
  2. الأمر: يحدّد الإجراء الذي يتم تنفيذه عندما يتفاعل المستخدم مع الزر. يمكنك استخدام setPlayerCommand لـ Player.Command، أو setSessionCommand لـ SessionCommand محدّد مسبقًا أو مخصّص.
  3. الفتحة: تحدّد المكان الذي يجب وضع الزر فيه في واجهة مستخدم وحدة التحكّم. هذا الحقل اختياري ويتم ضبطه تلقائيًا استنادًا إلى الرمز والأمر. على سبيل المثال، يتيح تحديد أنّه يجب عرض زر في منطقة التنقّل "للأمام" في واجهة المستخدم بدلاً من منطقة "القائمة المنسدلة" التلقائية.

Kotlin

val button =
  CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15)
    .setSessionCommand(SessionCommand(CUSTOM_ACTION_ID, Bundle.EMPTY))
    .setSlots(CommandButton.SLOT_FORWARD)
    .build()

Java

CommandButton button =
    new CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15)
        .setSessionCommand(new SessionCommand(CUSTOM_ACTION_ID, Bundle.EMPTY))
        .setSlots(CommandButton.SLOT_FORWARD)
        .build();

عند تحديد الإعدادات المفضّلة لأزرار الوسائط، يتم تطبيق الخوارزمية التالية:

  1. بالنسبة إلى كل CommandButton في إعدادات زر الوسائط، ضَع الزر في أول خانة متاحة ومسموح بها.
  2. إذا لم يتم ملء أي من الفتحات الوسطى والأمامية والخلفية بزر، أضِف أزرارًا تلقائية لهذه الفتحة.

يمكنك استخدام CommandButton.DisplayConstraints لإنشاء معاينة لكيفية تحديد الإعدادات المفضّلة لأزرار الوسائط استنادًا إلى قيود عرض واجهة المستخدم.

ضبط الإعدادات المفضّلة لزر الوسائط

أسهل طريقة لضبط الإعدادات المفضّلة لزر الوسائط هي تحديد القائمة عند إنشاء MediaSession. بدلاً من ذلك، يمكنك إلغاء MediaSession.Callback.onConnect لتخصيص الإعدادات المفضّلة لأزرار الوسائط لكل وحدة تحكّم متصلة.

Kotlin

val mediaSession =
  MediaSession.Builder(context, player)
    .setMediaButtonPreferences(ImmutableList.of(likeButton, favoriteButton))
    .build()

Java

MediaSession mediaSession =
  new MediaSession.Builder(context, player)
      .setMediaButtonPreferences(ImmutableList.of(likeButton, favoriteButton))
      .build();

تعديل إعدادات أزرار الوسائط المفضّلة بعد تفاعل المستخدم

بعد التعامل مع تفاعل مع المشغّل، قد تحتاج إلى تعديل الأزرار المعروضة في واجهة مستخدم وحدة التحكّم. ومن الأمثلة الشائعة على ذلك زر التبديل الذي يغيّر رمزه وإجراءه بعد تنفيذ الإجراء المرتبط بهذا الزر. لتعديل إعدادات زر الوسائط المفضّلة، يمكنك استخدام MediaSession.setMediaButtonPreferences لتعديل الإعدادات المفضّلة لجميع أدوات التحكّم أو لأداة تحكّم معيّنة:

Kotlin

// Handle "favoritesButton" action, replace by opposite button
mediaSession.setMediaButtonPreferences(
  ImmutableList.of(likeButton, removeFromFavoritesButton))

Java

// Handle "favoritesButton" action, replace by opposite button
mediaSession.setMediaButtonPreferences(
    ImmutableList.of(likeButton, removeFromFavoritesButton));

إضافة أوامر مخصّصة وتخصيص السلوك التلقائي

يمكن توسيع نطاق أوامر المشغّل المتاحة باستخدام أوامر مخصّصة، ويمكن أيضًا اعتراض أوامر المشغّل وأزرار الوسائط الواردة لتغيير السلوك التلقائي.

تعريف الطلبات المخصّصة والتعامل معها

يمكن لتطبيقات الوسائط تحديد أوامر مخصّصة يمكن استخدامها مثلاً في إعدادات زر الوسائط المفضّلة. على سبيل المثال، يمكنك تنفيذ أزرار تتيح للمستخدم حفظ عنصر وسائط في قائمة بالعناصر المفضّلة. يرسل MediaController أوامر مخصّصة ويتلقّاها MediaSession.Callback.

لتحديد الأوامر المخصّصة، عليك إلغاء MediaSession.Callback.onConnect() لضبط الأوامر المخصّصة المتاحة لكل وحدة تحكّم متصلة.

Kotlin

private class CustomMediaSessionCallback: MediaSession.Callback {
  // Configure commands available to the controller in onConnect()
  override fun onConnect(
    session: MediaSession,
    controller: MediaSession.ControllerInfo
  ): MediaSession.ConnectionResult {
    val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
        .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY))
        .build()
    return AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build()
  }
}

Java

class CustomMediaSessionCallback implements MediaSession.Callback {
  // Configure commands available to the controller in onConnect()
  @Override
  public ConnectionResult onConnect(
    MediaSession session,
    ControllerInfo controller) {
    SessionCommands sessionCommands =
        ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
            .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle()))
            .build();
    return new AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
}

لتلقّي طلبات الأوامر المخصّصة من MediaController، عليك تجاهل طريقة onCustomCommand() في Callback.

Kotlin

private class CustomMediaSessionCallback: MediaSession.Callback {
  ...
  override fun onCustomCommand(
    session: MediaSession,
    controller: MediaSession.ControllerInfo,
    customCommand: SessionCommand,
    args: Bundle
  ): ListenableFuture<SessionResult> {
    if (customCommand.customAction == SAVE_TO_FAVORITES) {
      // Do custom logic here
      saveToFavorites(session.player.currentMediaItem)
      return Futures.immediateFuture(
        SessionResult(SessionResult.RESULT_SUCCESS)
      )
    }
    ...
  }
}

Java

class CustomMediaSessionCallback implements MediaSession.Callback {
  ...
  @Override
  public ListenableFuture<SessionResult> onCustomCommand(
    MediaSession session, 
    ControllerInfo controller,
    SessionCommand customCommand,
    Bundle args
  ) {
    if(customCommand.customAction.equals(SAVE_TO_FAVORITES)) {
      // Do custom logic here
      saveToFavorites(session.getPlayer().getCurrentMediaItem());
      return Futures.immediateFuture(
        new SessionResult(SessionResult.RESULT_SUCCESS)
      );
    }
    ...
  }
}

يمكنك تتبُّع وحدة التحكّم في الوسائط التي تُرسل طلبًا باستخدام السمة packageName الخاصة بالكائن MediaSession.ControllerInfo الذي يتم تمريره إلى طرق Callback. يتيح لك ذلك تخصيص سلوك تطبيقك استجابةً لأمر معيّن إذا كان مصدره النظام أو تطبيقك أو تطبيقات أخرى.

تخصيص أوامر مشغّل الفيديو التلقائية

يتم تفويض جميع الأوامر التلقائية ومعالجة الحالة إلى Player الموجودة في MediaSession. لتخصيص سلوك أمر محدّد في واجهة Player، مثل play() أو seekToNext()، ضَع Player بين علامات ForwardingSimpleBasePlayer قبل تمريره إلى MediaSession:

Kotlin

val player = (logic to build a Player instance)

val forwardingPlayer = object : ForwardingSimpleBasePlayer(player) {
  // Customizations
}

val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()

Java

ExoPlayer player = (logic to build a Player instance)

ForwardingSimpleBasePlayer forwardingPlayer =
    new ForwardingSimpleBasePlayer(player) {
      // Customizations
    };

MediaSession mediaSession =
  new MediaSession.Builder(context, forwardingPlayer).build();

لمزيد من المعلومات حول ForwardingSimpleBasePlayer، يُرجى الاطّلاع على دليل ExoPlayer في قسم التخصيص.

تحديد وحدة التحكّم التي أرسلت أمرًا للاعب

عندما يتم بدء طلب استدعاء الطريقة Player من خلال MediaController، يمكنك تحديد مصدر الطلب باستخدام MediaSession.controllerForCurrentRequest والحصول على ControllerInfo للطلب الحالي:

Kotlin

class CallerAwarePlayer(player: Player) :
  ForwardingSimpleBasePlayer(player) {

  override fun handleSeek(
    mediaItemIndex: Int,
    positionMs: Long,
    seekCommand: Int,
  ): ListenableFuture<*> {
    Log.d(
      "caller",
      "seek operation from package ${session.controllerForCurrentRequest?.packageName}",
    )
    return super.handleSeek(mediaItemIndex, positionMs, seekCommand)
  }
}

Java

public class CallerAwarePlayer extends ForwardingSimpleBasePlayer {
  public CallerAwarePlayer(Player player) {
    super(player);
  }

  @Override
  protected ListenableFuture<?> handleSeek(
        int mediaItemIndex, long positionMs, int seekCommand) {
    Log.d(
        "caller",
        "seek operation from package: "
            + session.getControllerForCurrentRequest().getPackageName());
    return super.handleSeek(mediaItemIndex, positionMs, seekCommand);
  }
}

تخصيص طريقة التعامل مع زر الوسائط

أزرار الوسائط هي أزرار مادية متوفّرة على أجهزة Android والأجهزة الطرفية الأخرى، مثل زر التشغيل/الإيقاف المؤقت على سماعة رأس مزوّدة بتقنية البلوتوث. تتعامل Media3 مع أحداث أزرار الوسائط نيابةً عنك عند وصولها إلى الجلسة، وتستدعي طريقة Player المناسبة في مشغّل الجلسة.

يُنصح بمعالجة جميع أحداث أزرار الوسائط الواردة في طريقة Player المناسبة. بالنسبة إلى حالات الاستخدام الأكثر تقدّمًا، يمكن اعتراض أحداث زر الوسائط في MediaSession.Callback.onMediaButtonEvent(Intent).

معالجة الأخطاء والإبلاغ عنها

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

أخطاء فادحة في التشغيل

يُبلغ المشغّل الجلسة عن حدوث خطأ فادح في التشغيل، ثم يتم إبلاغ أدوات التحكّم بهذا الخطأ من خلال استدعاء Player.Listener.onPlayerError(PlaybackException) وPlayer.Listener.onPlayerErrorChanged(@Nullable PlaybackException).

في هذه الحالة، يتم نقل حالة التشغيل إلى STATE_IDLE وتعرض الدالة MediaController.getPlaybackError() قيمة PlaybackException التي تسبّبت في عملية النقل. يمكن لوحدة التحكّم فحص PlayerException.errorCode للحصول على معلومات حول سبب الخطأ.

لتحقيق التشغيل التفاعلي، يتم تكرار الخطأ الفادح في جلسة المنصة من خلال نقل حالتها إلى STATE_ERROR وتعيين رمز الخطأ ورسالته وفقًا PlaybackException.

تخصيص الأخطاء الخطيرة

لتوفير معلومات مترجمة وواضحة للمستخدم، يمكن تخصيص رمز الخطأ ورسالة الخطأ وإضافات الخطأ الخاصة بخطأ تشغيل فادح من خلال استخدام ForwardingPlayer عند إنشاء الجلسة:

Kotlin

val forwardingPlayer = ErrorForwardingPlayer(player)
val session = MediaSession.Builder(context, forwardingPlayer).build()

Java

Player forwardingPlayer = new ErrorForwardingPlayer(player);
MediaSession session =
    new MediaSession.Builder(context, forwardingPlayer).build();

يمكن لمشغّل إعادة التوجيه استخدام ForwardingSimpleBasePlayer لاعتراض الخطأ وتخصيص رمز الخطأ أو الرسالة أو الإضافات. وبالطريقة نفسها، يمكنك أيضًا إنشاء أخطاء جديدة غير متوفّرة في المشغّل الأصلي:

Kotlin

class ErrorForwardingPlayer (private val context: Context, player: Player) :
    ForwardingSimpleBasePlayer(player) {

  override fun getState(): State {
    var state = super.getState()
    if (state.playerError != null) {
      state =
        state.buildUpon()
          .setPlayerError(customizePlaybackException(state.playerError!!))
          .build()
    }
    return state
  }

  fun customizePlaybackException(error: PlaybackException): PlaybackException {
    val buttonLabel: String
    val errorMessage: String
    when (error.errorCode) {
      PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW -> {
        buttonLabel = context.getString(R.string.err_button_label_restart_stream)
        errorMessage = context.getString(R.string.err_msg_behind_live_window)
      }
      else -> {
        buttonLabel = context.getString(R.string.err_button_label_ok)
        errorMessage = context.getString(R.string.err_message_default)
      }
    }
    val extras = Bundle()
    extras.putString("button_label", buttonLabel)
    return PlaybackException(errorMessage, error.cause, error.errorCode, extras)
  }
}

Java

class ErrorForwardingPlayer extends ForwardingSimpleBasePlayer {

  private final Context context;

  public ErrorForwardingPlayer(Context context, Player player) {
    super(player);
    this.context = context;
  }

  @Override
  protected State getState() {
    State state = super.getState();
    if (state.playerError != null) {
      state =
          state.buildUpon()
              .setPlayerError(customizePlaybackException(state.playerError))
              .build();
    }
    return state;
  }

  private PlaybackException customizePlaybackException(PlaybackException error) {
    String buttonLabel;
    String errorMessage;
    switch (error.errorCode) {
      case PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW:
        buttonLabel = context.getString(R.string.err_button_label_restart_stream);
        errorMessage = context.getString(R.string.err_msg_behind_live_window);
        break;
      default:
        buttonLabel = context.getString(R.string.err_button_label_ok);
        errorMessage = context.getString(R.string.err_message_default);
        break;
    }
    Bundle extras = new Bundle();
    extras.putString("button_label", buttonLabel);
    return new PlaybackException(errorMessage, error.getCause(), error.errorCode, extras);
  }
}

الأخطاء غير الخطيرة

يمكن للتطبيق إرسال الأخطاء غير المميتة التي لا تنشأ من استثناء فني إلى جميع وحدات التحكّم أو إلى وحدة تحكّم معيّنة:

Kotlin

val sessionError = SessionError(
  SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED,
  context.getString(R.string.error_message_authentication_expired),
)

// Option 1: Sending a nonfatal error to all controllers.
mediaSession.sendError(sessionError)

// Option 2: Sending a nonfatal error to the media notification controller only
// to set the error code and error message in the playback state of the platform
// media session.
mediaSession.mediaNotificationControllerInfo?.let {
  mediaSession.sendError(it, sessionError)
}

Java

SessionError sessionError = new SessionError(
    SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED,
    context.getString(R.string.error_message_authentication_expired));

// Option 1: Sending a nonfatal error to all controllers.
mediaSession.sendError(sessionError);

// Option 2: Sending a nonfatal error to the media notification controller only
// to set the error code and error message in the playback state of the platform
// media session.
ControllerInfo mediaNotificationControllerInfo =
    mediaSession.getMediaNotificationControllerInfo();
if (mediaNotificationControllerInfo != null) {
  mediaSession.sendError(mediaNotificationControllerInfo, sessionError);
}

عند إرسال خطأ غير فادح إلى وحدة التحكّم في إشعارات الوسائط، يتم تكرار رمز الخطأ ورسالة الخطأ في جلسة الوسائط على النظام الأساسي، بينما لا يتم تغيير PlaybackState.state إلى STATE_ERROR.

تلقّي الأخطاء غير الخطيرة

يتلقّى MediaController خطأ غير خطير من خلال تنفيذ MediaController.Listener.onError:

Kotlin

val future = MediaController.Builder(context, sessionToken)
  .setListener(object : MediaController.Listener {
    override fun onError(controller: MediaController, sessionError: SessionError) {
      // Handle nonfatal error.
    }
  })
  .buildAsync()

Java

MediaController.Builder future =
    new MediaController.Builder(context, sessionToken)
        .setListener(
            new MediaController.Listener() {
              @Override
              public void onError(MediaController controller, SessionError sessionError) {
                // Handle nonfatal error.
              }
            });