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

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

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

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

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

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

وهذا مفيد في العديد من حالات الاستخدام. وعلى وجه الخصوص، ننصحك بشدة باستخدام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 المتوفر لتحديد ما إذا كنت تريد قبول الطلب أو رفضه. يمكنك الاطّلاع على مثال لقبول طلب ربط في قسم Declare الطلبات المخصّصة.

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

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

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

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

عند إضافة عناصر جديدة إلى قائمة التشغيل، يتطلّب المشغّل عادةً MediaItem نُسخًا تتضمّن معرّف موارد موحّد لتشغيلها. يتم تلقائيًا إعادة توجيه العناصر التي تمت إضافتها حديثًا إلى طرق المشغّل، مثل player.addMediaItem، إذا تم تحديد عنوان URL لها.

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

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

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

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

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

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

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

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

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. إذا لم يتم ملء أي من المواضع المركزية والاتجاه إلى الأمام والخلف بأحد buttons، أضِف أزرارًا تلقائية لهذه الموضع.

يمكنك استخدام 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));

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

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

تحديد الأوامر المخصّصة ومعالجتها

يمكن لتطبيقات الوسائط تحديد أوامر مخصّصة يمكن استخدامها مثلاً في الإعدادات المفضّلة لأزرار الوسائط. على سبيل المثال، قد تحتاج إلى تنفيذ أزرار تتيح للمستخدم حفظ عنصر وسائط في قائمة بالعناصر المفضّلة. يُرسِل 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 المناسبة في مشغّل الجلسة.

ننصحك بمعالجة جميع أحداث زرّات الوسائط الواردة في Playercorresponding method. بالنسبة إلى حالات الاستخدام الأكثر تقدمًا، يمكن اعتراض أحداث buttons في 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.
              }
            });