توفر جلسات الوسائط طريقة عالمية للتفاعل مع مشغّل صوت أو فيديو. في Media3، المشغِّل التلقائي هو الفئة ExoPlayer
التي تنفّذ واجهة Player
. إنّ ربط جلسة الوسائط بالمشغّل يتيح للتطبيق
الإعلان عن تشغيل الوسائط خارجيًا وتلقّي أوامر التشغيل من
مصادر خارجية.
وقد تصدر الأوامر من الأزرار الخارجية، مثل زر التشغيل على سماعة الرأس أو وحدة التحكم عن بُعد بالتلفزيون. وقد تأتي أيضًا من تطبيقات العميل التي تتضمّن وحدة تحكّم في الوسائط، مثل توجيه "إيقاف مؤقت" إلى "مساعد Google". تفوض جلسة الوسائط هذه الأوامر إلى مشغل تطبيق الوسائط.
الوقت المناسب لاختيار جلسة وسائط
عند تنفيذ MediaSession
، تسمح للمستخدمين بالتحكّم في التشغيل:
- من خلال سماعات الرأس. وغالبًا ما تكون هناك أزرار أو تفاعلات باللمس يمكن للمستخدم إجراؤها على سماعات الرأس لتشغيل الوسائط أو إيقافها مؤقتًا أو الانتقال إلى المسار التالي أو السابق.
- من خلال التحدّث إلى مساعد Google ومن الأنماط الشائعة قول "OK Google، إيقاف مؤقت" لإيقاف أي وسائط يتم تشغيلها حاليًا على الجهاز مؤقتًا.
- من خلال ساعة Wear OS ويتيح ذلك سهولة الوصول إلى عناصر التحكم في التشغيل الأكثر شيوعًا أثناء التشغيل على هواتفهم.
- من خلال عناصر التحكّم في الوسائط تعرض منصة العرض هذه عناصر التحكم في كل جلسة تشغيل وسائط.
- على التلفزيون يتيح تنفيذ إجراءات من خلال أزرار التشغيل الفعلية، وعناصر التحكم في تشغيل النظام الأساسي، وإدارة الطاقة (على سبيل المثال، في حال إيقاف تشغيل التلفزيون أو مكبّر الصوت العمودي أو جهاز استقبال الصوت والفيديو أو تبديل الإدخال، يجب إيقاف التشغيل في التطبيق).
- وأي عمليات خارجية أخرى تحتاج إلى التأثير في التشغيل
وهذا أمر رائع للعديد من حالات الاستخدام. وبشكلٍ خاص، ننصحك بشدّة باستخدام
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
المقدَّم
لتحديد ما إذا كنت تريد قبول
الطلب أو رفضه. يمكنك الاطِّلاع على مثال على قبول طلب ربط في القسم توضيح الأوامر المتاحة.
بعد الاتصال، يمكن لوحدة التحكّم إرسال أوامر التشغيل إلى الجلسة. تفوض الجلسة بعد ذلك هذه الأوامر إلى المشغّل. تعالج الجلسة تلقائيًا أوامر التشغيل وقوائم التشغيل المحدّدة في واجهة Player
.
تتيح لك طرق معاودة الاتصال الأخرى معالجة طلبات أوامر التشغيل المخصّصة وتعديل قائمة التشغيل مثلاً).
تتضمّن عمليات الاستدعاء هذه أيضًا عنصر ControllerInfo
حتى تتمكّن من تعديل
كيفية ردّك على كل طلب على أساس كل مسؤول من التحكّم بالبيانات.
تعديل قائمة التشغيل
يمكن لجلسة وسائط تعديل قائمة التشغيل الخاصة بالمشغّل مباشرةً كما هو موضّح في
دليل ExoPlayer لقوائم التشغيل.
يمكن لوحدات التحكّم أيضًا تعديل قائمة التشغيل في حال توفّر
COMMAND_SET_MEDIA_ITEM
أو COMMAND_CHANGE_MEDIA_ITEMS
لوحدة التحكّم.
عند إضافة عناصر جديدة إلى قائمة التشغيل، يحتاج المشغّل عادةً إلى نسخ MediaItem
ذات
معرّف موارد منتظم (URI) محدّد
لإتاحة تشغيلها. تتم تلقائيًا إعادة توجيه العناصر المضافة حديثًا
إلى طرق اللاعبين مثل player.addMediaItem
إذا كان لها معرّف موارد منتظم (URI) محدّد.
إذا أردت تخصيص مثيلات الـ 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)
. في هذه الحالة، يمكن أو يحتاج التطبيق
إلى معالجة جميع تفاصيل واجهة برمجة التطبيقات من تلقاء نفسه.