توفّر جلسات الوسائط طريقة شاملة للتفاعل مع مشغّل الصوت أو الفيديو. في Media3، المشغّل التلقائي هو فئة ExoPlayer التي تنفّذ واجهة Player. يتيح ربط جلسة الوسائط بالمشغّل للتطبيق الإعلان عن تشغيل الوسائط خارجيًا وتلقّي أوامر التشغيل من مصادر خارجية.
قد تصدر الأوامر من أزرار مادية، مثل زر التشغيل في سماعة رأس أو جهاز التحكّم عن بعد للتلفزيون. وقد تأتي أيضًا من تطبيقات العملاء التي تحتوي على وحدة تحكّم في الوسائط، مثل إعطاء تعليمات "إيقاف مؤقت" للمساعدين المستندين إلى الذكاء الاصطناعي، بما في ذلك Gemini أو "مساعد Google". وتفوّض جلسة الوسائط هذه الأوامر إلى مشغّل تطبيق الموسيقى.
حالات اختيار جلسة وسائط
عند تنفيذ MediaSession، يمكنك السماح للمستخدمين بالتحكّم في التشغيل:
- من خلال سماعات الرأس غالبًا ما تكون هناك أزرار أو تفاعلات باللمس يمكن للمستخدم إجراؤها على سماعات الرأس لتشغيل الوسائط أو إيقافها مؤقتًا أو الانتقال إلى المقطع الصوتي التالي أو السابق.
- من خلال التحدّث إلى المساعدين المستندين إلى الذكاء الاصطناعي، بما في ذلك Gemini أو "مساعد 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=، من المحتمل أنّه تم إنشاء جلسة بشكل غير متوقّع قبل إصدار مثيل تم إنشاؤه سابقًا بالرقم التعريفي نفسه. لتجنُّب تسرُّب الجلسات بسبب خطأ في البرمجة، يتم رصد هذه الحالات وإرسال إشعار بها من خلال طرح استثناء.
منح إمكانية التحكّم لعملاء آخرين
جلسة الوسائط هي مفتاح التحكّم في التشغيل. فهي تتيح لك توجيه الأوامر من مصادر خارجية إلى المشغّل الذي يؤدي مهمة تشغيل الوسائط. ويمكن أن تكون هذه المصادر أزرارًا مادية، مثل زر التشغيل في سماعة رأس أو جهاز التحكّم عن بعد للتلفزيون، أو أوامر غير مباشرة، مثل طلب "الإيقاف المؤقت" من Gemini أو "مساعد Google". وبالمثل، قد ترغب في منح حق الوصول إلى نظام Android لعناصر التحكم في الإشعارات وشاشة القفل، أو إلى ساعة Wear OS حتى تتمكن من التحكم في التشغيل من واجهة الساعة. يمكن للعملاء الخارجيين استخدام وحدة تحكّم في الوسائط لإصدار أوامر تشغيل لتطبيق الموسيقى. تتلقّى جلسة الوسائط هذه الأوامر وتفوّضها إلى مشغّل الوسائط.
عندما يكون جهاز التحكّم على وشك الاتصال بجلسة الوسائط، تطلب الجلسة
onConnectAsync(). يمكنك استخدام ControllerInfo المقدَّم لتحديد ما إذا كنت تريد قبول الطلب أو رفضه. للاطّلاع على مثال على قبول طلب اتصال، يُرجى مراجعة تعريف الطلبات المخصّصة ومعالجتها.
بعد الربط، يمكن لوحدة التحكّم إرسال أوامر تشغيل إلى الجلسة. بعد ذلك، تفوّض الجلسة هذه الأوامر إلى المشغّل. تتم معالجة أوامر التشغيل وقائمة التشغيل المحدّدة في واجهة Player تلقائيًا من خلال الجلسة.
تتيح لك طرق رد الاتصال الأخرى التعامل مع طلبات الأوامر المخصّصة وتعديل قائمة التشغيل، على سبيل المثال. تتضمّن عمليات الاستدعاء هذه أيضًا عنصر ControllerInfo لتتمكّن من تعديل طريقة الردّ على كل طلب على أساس كل وحدة تحكّم.
تعديل قائمة التشغيل
يمكن لجلسة الوسائط تعديل قائمة تشغيل المشغّل مباشرةً كما هو موضّح في دليل ExoPlayer لقوائم التشغيل.
يمكن للمتحكّمين أيضًا تعديل قائمة التشغيل إذا كان بإمكانهم الوصول إلى COMMAND_SET_MEDIA_ITEM أو COMMAND_CHANGE_MEDIA_ITEMS.
عند إضافة عناصر جديدة إلى قائمة التشغيل، يتطلّب المشغّل عادةً MediaItem
مثيل مع معرّف موارد منتظم (URI) محدّد لتشغيلها. بشكل تلقائي، تتم إعادة توجيه العناصر المُضافة حديثًا تلقائيًا إلى طرق المشغّل مثل player.addMediaItem إذا كان لديها معرّف موارد منتظم (URI) محدّد.
إذا أردت تخصيص مثيلات MediaItem المضافة إلى المشغّل، يمكنك
تجاوز onAddMediaItems(). هذه الخطوة ضرورية إذا أردت إتاحة وحدات التحكّم التي تطلب تشغيل وسائط بدون معرّف موارد منتظم (URI) محدّد. بدلاً من ذلك،
يحتوي MediaItem عادةً على حقل واحد أو أكثر من الحقول التالية التي تم ضبطها
لوصف الوسائط المطلوبة:
MediaItem.mediaId: معرّف عام يحدّد الوسائط-
MediaItem.RequestMetadata.mediaUri: عنوان URI للطلب قد يستخدم مخططًا مخصّصًا ولا يمكن بالضرورة تشغيله مباشرةً من خلال المشغّل. -
MediaItem.RequestMetadata.searchQuery: طلب بحث نصي، على سبيل المثال من Gemini أو "مساعد Google". -
MediaItem.mediaMetadata: البيانات الوصفية المنظَّمة، مثل "العنوان" أو "الفنان"
للحصول على المزيد من خيارات التخصيص لقوائم التشغيل الجديدة تمامًا، يمكنك
أيضًا إلغاء onSetMediaItems()، ما يتيح لك
تحديد عنصر البداية والموضع في قائمة التشغيل. على سبيل المثال، يمكنك توسيع
عنصر واحد مطلوب إلى قائمة تشغيل كاملة وإرشاد المشغّل للبدء
من فهرس العنصر المطلوب في الأصل. للاطّلاع على
نموذج لتنفيذ onSetMediaItems() باستخدام
هذه الميزة، يمكنك مراجعة التطبيق التجريبي للجلسة.
نشر البيانات الوصفية لقائمة التشغيل
توفّر Media3 واجهات برمجة تطبيقات لنشر البيانات الوصفية المنظَّمة لقوائم التشغيل إلى جانب تشغيل المقاطع الصوتية النشطة. من خلال توحيد طريقة تعبئة تطبيقك لبيانات العضوية في قوائم التشغيل والبيانات الوصفية الوصفية في جميع جلسات الوسائط، يتيح Android عرض نقاط دخول على مستوى النظام للتشغيل في تطبيقك.
في هذا السياق، يشير المصطلح قائمة تشغيل إلى أي مجموعة مرتبة من الأغاني يتم تشغيلها بالتسلسل. لا ينطبق ذلك على قوائم العناصر الفردية القابلة للتشغيل التي يحدّدها المستخدم أو التطبيق فحسب، بل ينطبق أيضًا على المجموعات، مثل ألبومات الموسيقى وحلقات البودكاست وفصول الكتب المسموعة.
الإفصاح عن رقم تعريف قائمة التشغيل وبياناتها الوصفية
في Media3، يتم تقسيم بيانات تعريف قائمة التشغيل وبياناتها الوصفية الوصفية
إلى نطاقَين MediaMetadata:
- رقم تعريف قائمة التشغيل على مستوى السلعة (
MediaItem.mediaMetadata.playlistId):- يمكنك وضع علامة على
MediaItemقيد التشغيل كجزء من قائمة تشغيل من خلال استدعاءMediaMetadata.Builder.setPlaylistIdعلىMediaMetadataللعنصر، بينما يحدّدMediaItem.mediaIdبشكل فريد المقطع الصوتي الفردي. -
playlistIdهي سلسلة غير شفافة يحدّدها التطبيق وتعرّف قائمة التشغيل بشكل فريد على مستوى الجلسات طالما أنّه يمكن استئناف قائمة التشغيل (على سبيل المثال،37i9dQZF1DX76Wlfdnj7AP)، ولا يجب إعادة استخدامها لقائمة تشغيل مختلفة. لا تفسّر وحدات التحكّم المتصلة هذا المعرّف، بل تخزّنه وترسله مرة أخرى إلى تطبيقك لاستئناف قائمة التشغيل. - تحمل كل أغنية تنتمي إلى قائمة التشغيل السمة
playlistIdنفسها. يجب تركplaylistIdبدون ضبط (null) للأغاني التي لا يتم تشغيلها من سياق قائمة تشغيل، مثل أغنية مستقلة واحدة.
- يمكنك وضع علامة على
- البيانات الوصفية لقائمة التشغيل على مستوى المشغّل (
Player.getPlaylistMetadata):- املأ مثيل
MediaMetadataمخصّصًا بسمات قائمة التشغيل الوصفية (setTitleوsetArtworkUriوsetMediaType) واضبطه على المشغّل باستخدامPlayer.setPlaylistMetadata. - عندما ترصد وحدة تحكّم متصلة قيمة غير فارغة في
currentMediaItem.mediaMetadata.playlistId، يتم فحصMediaController.getPlaylistMetadataلاسترداد عنوان قائمة التشغيل ومعرّف الموارد المنتظم (URI) للعمل الفني. - يجب تعديل البيانات الوصفية لقائمة التشغيل كلما تغيّرت قائمة التشغيل النشطة (على سبيل المثال، عندما يبدأ المستخدم قائمة تشغيل مختلفة) بحيث يصف
getPlaylistMetadataدائمًا قائمة التشغيل التي يشير إليهاplaylistIdللعنصر الحالي.
- املأ مثيل
يوضّح المثال التالي كيفية نشر البيانات الوصفية لقائمة التشغيل على Player
وربط المقطع الصوتي MediaItem بقائمة التشغيل هذه:
Kotlin
// 1. Declare the playlist descriptive metadata on the Player val playlistMetadata = MediaMetadata.Builder() .setTitle("Morning Vibes") .setArtworkUri(Uri.parse("https://example.com/image/morning-vibes")) .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST) .build() player.setPlaylistMetadata(playlistMetadata) // 2. Mark the playing track(s) with the playlistId it belongs to val trackItem = MediaItem.Builder() .setMediaId("foobar:track:6rqhFgbbKwnb9MLmUQDhG6") .setMediaMetadata( MediaMetadata.Builder() .setTitle("Example Track") .setArtist("Example Artist") .setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC) .setPlaylistId("37i9dQZF1DX76Wlfdnj7AP") .build() ) .build() player.setMediaItem(trackItem)
Java
// 1. Declare the playlist descriptive metadata on the Player MediaMetadata playlistMetadata = new MediaMetadata.Builder() .setTitle("Morning Vibes") .setArtworkUri(Uri.parse("https://example.com/image/morning-vibes")) .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST) .build(); player.setPlaylistMetadata(playlistMetadata); // 2. Mark the playing track(s) with the playlistId it belongs to MediaItem trackItem = new MediaItem.Builder() .setMediaId("foobar:track:6rqhFgbbKwnb9MLmUQDhG6") .setMediaMetadata( new MediaMetadata.Builder() .setTitle("Example Track") .setArtist("Example Artist") .setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC) .setPlaylistId("37i9dQZF1DX76Wlfdnj7AP") .build()) .build(); player.setMediaItem(trackItem);
استئناف تشغيل قائمة تشغيل
عندما يختار المستخدم قائمة تشغيل لاستئنافها، تتصل وحدة التحكّم بجلسة الوسائط في تطبيقك وترسل قائمة تشغيل MediaItem تم ضبط mediaId على playlistId المخزَّن:
Kotlin
val playlistItem = MediaItem.Builder() .setMediaId("37i9dQZF1DX76Wlfdnj7AP") .setMediaMetadata( MediaMetadata.Builder() .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST) .setIsPlayable(true) .build() ) .build() controller.setMediaItem(playlistItem) controller.prepare() controller.play()
Java
MediaItem playlistItem = new MediaItem.Builder() .setMediaId("37i9dQZF1DX76Wlfdnj7AP") .setMediaMetadata( new MediaMetadata.Builder() .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST) .setIsPlayable(true) .build()) .build(); controller.setMediaItem(playlistItem); controller.prepare(); controller.play();
يوسّع تطبيقك قائمة التشغيل MediaItem إلى المقاطع الصوتية القابلة للتشغيل داخل
MediaSession.Callback.onSetMediaItems (أو
onAddMediaItems)، وينشر playlistId و
playlistMetadata مرة أخرى كما هو موضّح في القسم السابق.
إدارة الإعدادات المفضّلة لأزرار الوسائط
يمكن لكل وحدة تحكّم، مثل واجهة مستخدم النظام أو Android Auto أو Wear OS، اتخاذ قراراتها الخاصة بشأن الأزرار التي تظهر للمستخدم. لتحديد عناصر التحكّم في التشغيل التي تريد عرضها للمستخدم، يمكنك تحديد الإعدادات المفضّلة لأزرار الوسائط في MediaSession. تتألف هذه الإعدادات المفضّلة من قائمة مع ترتيب
من مثيلات CommandButton، يحدد كل منها إعدادًا مفضلاً لزر في
واجهة المستخدم.
تحديد أزرار الأوامر
يتم استخدام مثيلات CommandButton لتحديد الإعدادات المفضّلة لأزرار الوسائط. يحدّد كل زر ثلاثة جوانب لعنصر في واجهة المستخدم المطلوب:
- الرمز، الذي يحدّد المظهر المرئي يجب ضبط الرمز على أحد الثوابت المحدّدة مسبقًا عند إنشاء
CommandButton.Builder. يُرجى العِلم أنّ هذا ليس موردًا فعليًا لصورة نقطية أو صورة عادية. يساعد الثابت العام وحدات التحكّم في اختيار مورد مناسب للحصول على مظهر متّسق ضمن واجهة المستخدم الخاصة بها. إذا لم يكن أي من ثوابت الرموز المحدّدة مسبقًا مناسبًا لحالة الاستخدام، يمكنك استخدامsetCustomIconResIdبدلاً من ذلك. - الأمر: يحدّد الإجراء الذي يتم تنفيذه عندما يتفاعل المستخدم مع الزر. يمكنك استخدام
setPlayerCommandلـPlayer.Command، أوsetSessionCommandلـSessionCommandمحدّد مسبقًا أو مخصّص. - Slot، لتحديد مكان الزر في واجهة مستخدم وحدة التحكّم هذا الحقل اختياري ويتم ضبطه تلقائيًا استنادًا إلى الرمز والأمر. على سبيل المثال، تتيح هذه السمة تحديد أنّه يجب عرض زر في منطقة التنقّل "للأمام" في واجهة المستخدم بدلاً من منطقة "تجاوز السعة" التلقائية.
Kotlin
val button = CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15) .setPlayerCommand(Player.COMMAND_SEEK_FORWARD) .setSlots(CommandButton.SLOT_FORWARD) .build()
Java
CommandButton button = new CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15) .setPlayerCommand(Player.COMMAND_SEEK_FORWARD) .setSlots(CommandButton.SLOT_FORWARD) .build();
عند حلّ مشكلة الإعدادات المفضّلة لأزرار الوسائط، يتم تطبيق الخوارزمية التالية:
- بالنسبة إلى كل
CommandButtonفي إعدادات أزرار الوسائط المفضّلة، ضَع الزر في أول موضع متاح ومسموح به. - إذا لم يتم ملء أي من الخانات المركزية والأمامية والخلفية بزر، أضِف أزرارًا تلقائية لهذه الخانة.
يمكنك استخدام CommandButton.DisplayConstraints لإنشاء معاينة لكيفية
حلّ الإعدادات المفضّلة لأزرار الوسائط استنادًا إلى قيود
عرض واجهة المستخدم.
ضبط الإعدادات المفضّلة لأزرار الوسائط
أسهل طريقة لضبط الإعدادات المفضّلة لأزرار الوسائط هي تحديد القائمة عند إنشاء MediaSession. يمكنك بدلاً من ذلك إلغاء
MediaSession.Callback.onConnectAsync() لتخصيص إعدادات زر الوسائط المفضّلة
لكل وحدة تحكّم متصلة.
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.onConnectAsync() لضبط الطلبات المخصّصة المتاحة
لكل وحدة تحكّم متصلة.
Kotlin
private class CustomMediaSessionCallback : MediaSession.Callback { // Configure commands available to the controller in onConnect() override fun onConnectAsync( session: MediaSession, controller: ControllerInfo, ): ListenableFuture<ConnectionResult> { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY)) .build() return Futures.immediateFuture( AcceptedResultBuilder(session, controller) .setAvailableSessionCommands(sessionCommands) .build() ) } }
Java
private static class CustomMediaSessionCallback implements MediaSession.Callback { // Configure commands available to the controller in onConnect() @Override public ListenableFuture<ConnectionResult> onConnectAsync( MediaSession session, ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS .buildUpon() .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle())) .build(); return Futures.immediateFuture( new AcceptedResultBuilder(session, controller) .setAvailableSessionCommands(sessionCommands) .build()); } }
لتلقّي طلبات الأوامر المخصّصة من MediaController، يجب إلغاء طريقة onCustomCommand() في Callback.
Kotlin
private class CustomCallback : MediaSession.Callback { // ... override fun onCustomCommand( session: MediaSession, controller: 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)) } // ... return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) } }
Java
private static class CustomCallback 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)); } // ... return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS)); } }
يمكنك تتبُّع وحدة التحكّم في الوسائط التي تُرسل طلبًا باستخدام السمة packageName لعنصر MediaSession.ControllerInfo الذي يتم تمريره إلى طرق Callback. يتيح لك ذلك تخصيص سلوك تطبيقك استجابةً لأمر معيّن إذا كان مصدره النظام أو تطبيقك أو تطبيقات عملاء أخرى.
تخصيص أوامر المشغّل التلقائية
يتم تفويض جميع الأوامر التلقائية ومعالجة الحالة إلى Player الموجود على
MediaSession. لتخصيص سلوك أمر محدّد في واجهة Player، مثل play() أو seekToNext()، يمكنك تضمين Player في ForwardingSimpleBasePlayer قبل تمريره إلى MediaSession:
Kotlin
val forwardingPlayer = object : ForwardingSimpleBasePlayer(player) { // Customizations } val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()
Java
ForwardingSimpleBasePlayer forwardingPlayer = new ForwardingSimpleBasePlayer(player) { // Customizations }; MediaSession mediaSession = new MediaSession.Builder(context, forwardingPlayer).build();
لمزيد من المعلومات حول ForwardingSimpleBasePlayer، يُرجى الاطّلاع على دليل ExoPlayer حول التخصيص.
تحديد وحدة التحكّم التي تطلب أمرًا من المشغّل
عندما يتم إجراء طلب إلى طريقة Player من خلال MediaController، يمكنك
تحديد المصدر باستخدام MediaSession.controllerForCurrentRequest
والحصول على ControllerInfo للطلب الحالي:
Kotlin
private class CallerAwarePlayer(player: Player) : ForwardingSimpleBasePlayer(player) { private lateinit var session: MediaSession 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
private static final class CallerAwarePlayer extends ForwardingSimpleBasePlayer { private MediaSession session; 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().
معالجة الأخطاء والإبلاغ عنها
هناك نوعان من الأخطاء التي تصدرها الجلسة وتبلغ عنها وحدات التحكّم. تشير الأخطاء الفادحة إلى حدوث عطل فني في تشغيل الجلسة يؤدي إلى إيقاف التشغيل. يتم إبلاغ وحدة التحكّم بالأخطاء الفادحة تلقائيًا عند حدوثها. الأخطاء غير الفادحة هي أخطاء غير فنية أو أخطاء في السياسة لا تؤدي إلى مقاطعة التشغيل ويتم إرسالها إلى وحدات التحكّم من خلال التطبيق يدويًا.
أخطاء فادحة في التشغيل
يتم إبلاغ المشغّل عن خطأ فادح في التشغيل إلى الجلسة، ثم يتم إبلاغ وحدات التحكّم به من خلال
Player.Listener.onPlayerError(PlaybackException) وPlayer.Listener.onPlayerErrorChanged(@Nullable PlaybackException).
في هذه الحالة، يتم نقل حالة التشغيل إلى STATE_IDLE،
وتعرض MediaController.getPlayerError() القيمة PlaybackException التي تسبّبت في
عملية النقل. يمكن لوحدة التحكّم فحص PlaybackException.errorCode للحصول على معلومات حول سبب الخطأ.
إعداد خطأ مخصّص في المشغّل
بالإضافة إلى الأخطاء الفادحة التي يبلّغ عنها المشغّل، يمكن للتطبيق
ضبط PlaybackException مخصّص على مستوى MediaSession باستخدام
MediaSession.setPlaybackException(PlaybackException). يتيح ذلك للتطبيق إرسال إشارة إلى وحدات التحكّم المتصلة بوجود حالة خطأ. ويمكن ضبط الاستثناء لجميع وحدات التحكّم المتصلة أو لوحدة تحكّم ControllerInfo معيّنة.
عندما يضبط تطبيق PlaybackException باستخدام واجهة برمجة التطبيقات هذه:
سيتم إرسال إشعار إلى مثيلات
MediaControllerالمرتبطة. سيتم استدعاء عمليات الاستدعاءListener.onPlayerError(PlaybackException)وListener.onPlayerErrorChanged(@Nullable PlaybackException)على وحدة التحكّم مع الاستثناء المقدَّم.ستعرض الطريقة
MediaController.getPlayerError()السمةPlaybackExceptionالتي تم ضبطها من خلال التطبيق.ستتغيّر حالة التشغيل لوحدات التحكّم المتأثرة إلى
Player.STATE_IDLE.ستتم إزالة الطلبات المتاحة، ولن يتبقّى سوى طلبات القراءة، مثل
COMMAND_GET_TIMELINE، في حال سبق منحها. على سبيل المثال، يتم تجميد حالةTimelineإلى الحالة التي تم فيها تطبيق الاستثناء على وحدة التحكّم. تتم إزالة الأوامر التي تحاول تغيير حالة المشغّل، مثلCOMMAND_PLAY، إلى أن يزيل التطبيق استثناء التشغيل لوحدة التحكّم المحدّدة.
لمحو PlaybackException مخصّص تم ضبطه سابقًا واستعادة
التقارير العادية لحالة المشغّل، يمكن للتطبيق استدعاء
MediaSession.setPlaybackException(/* playbackException= */ null) أو
MediaSession.setPlaybackException(ControllerInfo,
/* playbackException= */ null).
تخصيص الأخطاء الفادحة
لتقديم معلومات مفيدة ومترجَمة للمستخدم، يمكنك تخصيص
رمز الخطأ ورسالة الخطأ والإضافات الخاصة بخطأ التشغيل الفادح
الناتج عن المشغّل الفعلي. لإجراء ذلك، استخدِم ForwardingSimpleBasePlayer
عند إنشاء الجلسة:
Kotlin
val session = MediaSession.Builder(context, ErrorForwardingPlayer(context, player)).build()
Java
MediaSession session = new MediaSession.Builder(context, new ErrorForwardingPlayer(context, player)).build();
يمكن للفئة الفرعية ForwardingSimpleBasePlayer إلغاء getState() لاعتراض الخطأ وتخصيص رمز الخطأ أو الرسالة أو الإضافات. وبالطريقة نفسها، يمكنك أيضًا إنشاء أخطاء جديدة غير موجودة في المشغّل الأصلي:
Kotlin
private 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 } 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) } 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
private static 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. } });