توفّر جلسات الوسائط طريقة عالمية للتفاعل مع مشغّل ملف صوتي أو فيديو. في 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 جلسة الوسائط تلقائيًا باستخدام حالة المشغّل. وبالتالي، لا تحتاج إلى معالجة عملية الربط يدويًا من المشغّل إلى الجلسة.
يختلف هذا النهج عن النهج القديم الذي كان يتطلّب إنشاء
PlaybackStateCompat
وإدارته بشكل مستقل عن المشغّل نفسه، على سبيل المثال لتحديد
أي أخطاء.
معرّف الجلسة الفريد
ينشئ MediaSession.Builder
تلقائيًا جلسة باستخدام سلسلة فارغة كأحد مكونات
معرّف الجلسة. وهذا كافٍ إذا كان التطبيق يريد إنشاء مثيل واحد فقط
للجلسة، وهو الإجراء الأكثر شيوعًا.
إذا أراد التطبيق إدارة عدّة نُسخ من الجلسة في الوقت نفسه، عليه
التأكّد من أنّ رقم تعريف الجلسة لكلّ جلسة فريد. يمكن
ضبط معرّف الجلسة عند إنشاء الجلسة باستخدام MediaSession.Builder.setId(String id)
.
إذا ظهرت لك رسالة خطأ IllegalStateException
تشير إلى تعطُّل تطبيقك IllegalStateException: Session ID must be unique. ID=
، من المرجّح أنّه تم إنشاء جلسة بشكل غير متوقّع قبل إنهاء جلسة سابقة تم إنشاؤها باستخدام رقم التعريف نفسه. لتجنّب تسرُّب الجلسات بسبب خطأ في المعالجة، يتم رصد هذه الحالات وإرسال إشعار بها من خلال طرح أحد
الاستثناءات.
منح عناصر التحكّم لعملاء آخرين
جلسة الوسائط هي مفتاح التحكّم في التشغيل. ويتيح لك توجيه الطلبات من مصادر خارجية إلى المشغّل الذي يشغّل الوسائط. يمكن أن تكون هذه المصادر أزرارًا مادية، مثل زر التشغيل في سماعات الرأس أو جهاز التحكّم عن بُعد في التلفزيون، أو طلبات غير مباشرة، مثل توجيه "إيقاف مؤقت" إلى "مساعد Google". وبالمثل، قد تحتاج إلى منح الإذن بالوصول إلى نظام Android لتسهيل استخدام عناصر التحكّم في الإشعارات وشاشة القفل، أو إلى ساعة Wear OS لكي تتمكّن من التحكّم في التشغيل من خلفية شاشة الساعة. يمكن للعملاء الخارجيين استخدام وحدة تحكّم في الوسائط لإصدار أوامر التشغيل إلى تطبيق الوسائط. ويتم تلقّي هذه الأوامر من خلال جلسة الوسائط التي تفوض الأوامر في نهاية المطاف إلى مشغّل الوسائط.
عندما يكون جهاز التحكّم على وشك الاتصال بجلسة الوسائط، يتمّ استدعاء الأسلوب
onConnect()
. يمكنك استخدام رمز ControllerInfo
المتوفر لتحديد ما إذا كنت تريد قبول
الطلب أو رفضه. يمكنك الاطّلاع على مثال لقبول طلب ربط في قسم Declare
available commands (تعريف الأوامر المتاحة).
بعد الاتصال، يمكن لوحدة التحكّم إرسال أوامر التشغيل إلى الجلسة. بعد ذلك، تفوِّض الجلسة هذه الأوامر إلى المشغّل. تعالج الجلسة تلقائيًا تعليمات التشغيل وقائمة التشغيل
المحدّدة في واجهة 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()
مع هذه الميزة في تطبيق الجلسة التجريبي.
إدارة التنسيق المخصّص والأوامر المخصّصة
توضِّح الأقسام التالية كيفية الإعلان عن تنسيق مخصّص لأزرار التحكم المخصّصة في التطبيقات العميلة وتفويض وحدات التحكّم بإرسال الطلبات المخصّصة.
تحديد تنسيق مخصّص للجلسة
للإشارة إلى تطبيقات العميل التي تريد عرض عناصر التحكّم في التشغيل أمام العميل، اضبط التنسيق المخصّص للجلسة عند إنشاء MediaSession
في طريقة onCreate()
للخدمة.
Kotlin
override fun onCreate() { super.onCreate() val likeButton = CommandButton.Builder() .setDisplayName("Like") .setIconResId(R.drawable.like_icon) .setSessionCommand(SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING)) .build() val favoriteButton = CommandButton.Builder() .setDisplayName("Save to favorites") .setIconResId(R.drawable.favorite_icon) .setSessionCommand(SessionCommand(SAVE_TO_FAVORITES, Bundle())) .build() session = MediaSession.Builder(this, player) .setCallback(CustomMediaSessionCallback()) .setCustomLayout(ImmutableList.of(likeButton, favoriteButton)) .build() }
Java
@Override public void onCreate() { super.onCreate(); CommandButton likeButton = new CommandButton.Builder() .setDisplayName("Like") .setIconResId(R.drawable.like_icon) .setSessionCommand(new SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING)) .build(); CommandButton favoriteButton = new CommandButton.Builder() .setDisplayName("Save to favorites") .setIconResId(R.drawable.favorite_icon) .setSessionCommand(new SessionCommand(SAVE_TO_FAVORITES, new Bundle())) .build(); Player player = new ExoPlayer.Builder(this).build(); mediaSession = new MediaSession.Builder(this, player) .setCallback(new CustomMediaSessionCallback()) .setCustomLayout(ImmutableList.of(likeButton, favoriteButton)) .build(); }
تحديد الأوامر المتاحة للمشغّل والأوامر المخصّصة
يمكن لتطبيقات الوسائط تحديد أوامر مخصّصة يمكن استخدامها مثلاً في
تنسيق مخصّص. على سبيل المثال، قد تحتاج إلى تنفيذ أزرار تسمح للمستخدم بحفظ ملف وسائط في قائمة بالعناصر المفضّلة. يُرسِل MediaController
أوامر مخصّصة ويتلقّاها MediaSession.Callback
.
يمكنك تحديد أوامر الجلسات المخصّصة المتاحة لجهاز
MediaController
عند اتصاله بجلسة الوسائط. يمكنك إجراء ذلك من خلال
تجاوز MediaSession.Callback.onConnect()
. يمكنك ضبط مجموعة الأوامر المتاحة وإعادتها عند قبول طلب اتصال من MediaController
في طريقة معاودة الاتصال onConnect
:
Kotlin
private inner 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 inner 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
. يتيح لك ذلك تخصيص سلوك
تطبيقك استجابةً لأمر معيّن إذا كان مصدره هو النظام أو
تطبيقك الخاص أو تطبيقات العميل الأخرى.
تعديل التنسيق المخصّص بعد تفاعل المستخدِم
بعد معالجة أمر مخصّص أو أي تفاعل آخر مع المشغّل،
قد تحتاج إلى تعديل التنسيق المعروض في واجهة مستخدِم وحدة التحكّم. ومن الأمثلة المعتادة على ذلك
زرّ تبديل يغيّر رمزه بعد بدء الإجراء المرتبط
بهذا الزر. لتعديل التنسيق، يمكنك استخدام
MediaSession.setCustomLayout
:
Kotlin
val removeFromFavoritesButton = CommandButton.Builder() .setDisplayName("Remove from favorites") .setIconResId(R.drawable.favorite_remove_icon) .setSessionCommand(SessionCommand(REMOVE_FROM_FAVORITES, Bundle())) .build() mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton))
Java
CommandButton removeFromFavoritesButton = new CommandButton.Builder() .setDisplayName("Remove from favorites") .setIconResId(R.drawable.favorite_remove_icon) .setSessionCommand(new SessionCommand(REMOVE_FROM_FAVORITES, new Bundle())) .build(); mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton));
تخصيص سلوك أوامر التشغيل
لتخصيص سلوك أمر محدّد في واجهة Player
، مثل
play()
أو seekToNext()
، عليك تضمين Player
في ForwardingPlayer
.
Kotlin
val player = ExoPlayer.Builder(context).build() val forwardingPlayer = object : ForwardingPlayer(player) { override fun play() { // Add custom logic super.play() } override fun setPlayWhenReady(playWhenReady: Boolean) { // Add custom logic super.setPlayWhenReady(playWhenReady) } } val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()
Java
ExoPlayer player = new ExoPlayer.Builder(context).build(); ForwardingPlayer forwardingPlayer = new ForwardingPlayer(player) { @Override public void play() { // Add custom logic super.play(); } @Override public void setPlayWhenReady(boolean playWhenReady) { // Add custom logic super.setPlayWhenReady(playWhenReady); } }; MediaSession mediaSession = new MediaSession.Builder(context, forwardingPlayer).build();
لمزيد من المعلومات حول ForwardingPlayer
، اطّلِع على دليل ExoPlayer حول
التخصيص.
تحديد وحدة التحكّم التي تطلب أمرًا من اللاعب
عندما يبدأ طلب Player
من MediaController
، يمكنك
تحديد مصدره باستخدام MediaSession.controllerForCurrentRequest
والحصول على ControllerInfo
للطلب الحالي:
Kotlin
class CallerAwareForwardingPlayer(player: Player) : ForwardingPlayer(player) { override fun seekToNext() { Log.d( "caller", "seekToNext called from package ${session.controllerForCurrentRequest?.packageName}" ) super.seekToNext() } }
Java
public class CallerAwareForwardingPlayer extends ForwardingPlayer { public CallerAwareForwardingPlayer(Player player) { super(player); } @Override public void seekToNext() { Log.d( "caller", "seekToNext called from package: " + session.getControllerForCurrentRequest().getPackageName()); super.seekToNext(); } }
الاستجابة لأزرار الوسائط
أزرار الوسائط هي أزرار أجهزة خارجية موجودة على أجهزة Android وغيرها من الأجهزة الملحقة، مثل زر التشغيل/الإيقاف المؤقت على سماعة رأس بلوتوث. يعالج Media3
أحداث أزرار الوسائط نيابةً عنك عند وصولها إلى الجلسة ويُطلِب
طريقة Player
المناسبة في مشغّل الجلسة.
يمكن للتطبيق إلغاء السلوك التلقائي من خلال إلغاء
MediaSession.Callback.onMediaButtonEvent(Intent)
. في هذه الحالة، يمكن للتطبيق التعامل مع جميع تفاصيل واجهة برمجة التطبيقات بنفسه أو يحتاج إلى ذلك.
معالجة الأخطاء والإبلاغ عنها
هناك نوعان من الأخطاء التي تُرسِلها الجلسة وتُبلغ بها أدوات التحكّم. تشير الأخطاء المميتة إلى حدوث خطأ فني في تشغيل جلسة المشغّل يؤدي إلى مقاطعة التشغيل. يتم تلقائيًا إبلاغ وحدة التحكّم بالأخطاء المميتة عند حدوثها. الأخطاء غير الفادحة هي أخطاء غير فنية أو أخطاء تتعلّق بالسياسة ولا تؤدي إلى إيقاف التشغيل، ويتم إرسالها إلى وحدات التحكّم من قِبل التطبيق يدويًا.
أخطاء فادحة في التشغيل
يُبلغ المشغّل الجلسة عن خطأ تشغيل خطير، ثم يتم إعلام وحدات التحكّم بالخطأ لإجراء مكالمة من خلال Player.Listener.onPlayerError(PlaybackException)
وPlayer.Listener.onPlayerErrorChanged(@Nullable PlaybackException)
.
في هذه الحالة، يتم نقل حالة التشغيل إلى STATE_IDLE
وMediaController.getPlaybackError()
يعرض PlaybackException
الذي تسبب في النقل. يمكن لجهاز التحكّم فحص PlayerException.errorCode
للحصول على
معلومات عن سبب الخطأ.
من أجل التشغيل التفاعلي، يتم تكرار الخطأ الفادح في PlaybackStateCompat
لجلسة المنصة من خلال نقل حالتها إلى 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();
يسجِّل مشغّل التقديم/الترجيع Player.Listener
للمشغّل الفعلي ويعترض عمليات الاستدعاء التي تُبلغ عن خطأ. بعد ذلك، يتم تفويض
PlaybackException
مخصّص للمستمعين الذين
تم تسجيلهم على مشغّل الإعادة. لكي يعمل هذا الإجراء، يجب أن تلغي وحدة إعادة التوجيه
العنصرَين Player.addListener
وPlayer.removeListener
للوصول إلى
المستمعين الذين يمكنهم إرسال رمز خطأ أو رسالة أو عناصر إضافية مخصّصة:
Kotlin
class ErrorForwardingPlayer(private val context: Context, player: Player) : ForwardingPlayer(player) { private val listeners: MutableList<Player.Listener> = mutableListOf() private var customizedPlaybackException: PlaybackException? = null init { player.addListener(ErrorCustomizationListener()) } override fun addListener(listener: Player.Listener) { listeners.add(listener) } override fun removeListener(listener: Player.Listener) { listeners.remove(listener) } override fun getPlayerError(): PlaybackException? { return customizedPlaybackException } private inner class ErrorCustomizationListener : Player.Listener { override fun onPlayerErrorChanged(error: PlaybackException?) { customizedPlaybackException = error?.let { customizePlaybackException(it) } listeners.forEach { it.onPlayerErrorChanged(customizedPlaybackException) } } override fun onPlayerError(error: PlaybackException) { listeners.forEach { it.onPlayerError(customizedPlaybackException!!) } } private 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) } // Apps can customize further error messages by adding more branches. 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) } override fun onEvents(player: Player, events: Player.Events) { listeners.forEach { it.onEvents(player, events) } } // Delegate all other callbacks to all listeners without changing arguments like onEvents. } }
Java
private static class ErrorForwardingPlayer extends ForwardingPlayer { private final Context context; private List<Player.Listener> listeners; @Nullable private PlaybackException customizedPlaybackException; public ErrorForwardingPlayer(Context context, Player player) { super(player); this.context = context; listeners = new ArrayList<>(); player.addListener(new ErrorCustomizationListener()); } @Override public void addListener(Player.Listener listener) { listeners.add(listener); } @Override public void removeListener(Player.Listener listener) { listeners.remove(listener); } @Nullable @Override public PlaybackException getPlayerError() { return customizedPlaybackException; } private class ErrorCustomizationListener implements Listener { @Override public void onPlayerErrorChanged(@Nullable PlaybackException error) { customizedPlaybackException = error != null ? customizePlaybackException(error, context) : null; for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onPlayerErrorChanged(customizedPlaybackException); } } @Override public void onPlayerError(PlaybackException error) { for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onPlayerError(checkNotNull(customizedPlaybackException)); } } private PlaybackException customizePlaybackException( PlaybackException error, Context context) { 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; // Apps can customize further error messages by adding more case statements. 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); } @Override public void onEvents(Player player, Events events) { for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onEvents(player, events); } } // Delegate all other callbacks to all listeners without changing arguments like onEvents. } }
الأخطاء غير المميتة
يمكن أن يرسل التطبيق أخطاء غير قاتلة لا تنشأ عن استثناء فني، ويرسلها إلى جميع وحدات التحكّم أو إلى وحدة تحكّم معيّنة:
Kotlin
val sessionError = SessionError( SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED, context.getString(R.string.error_message_authentication_expired), ) // Sending a nonfatal error to all controllers. mediaSession.sendError(sessionError) // Interoperability: Sending a nonfatal error to the media notification controller 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)); // Sending a nonfatal error to all controllers. mediaSession.sendError(sessionError); // Interoperability: Sending a nonfatal error to the media notification controller 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); }
يتم تكرار خطأ غير مميت تم إرساله إلى وحدة التحكّم في إشعارات الوسائط إلى
PlaybackStateCompat
من جلسة المنصة. وبالتالي، يتم ضبط رمز الخطأ و
رسالة الخطأ فقط على PlaybackStateCompat
وفقًا لذلك، بينما
لا يتم تغيير PlaybackStateCompat.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. } });