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

MediaSessionService
بتشغيل جلسة
تشغيل الوسائط بشكل منفصل عن نشاط التطبيقعند استضافة مشغّل داخل إحدى الخدمات، يجب استخدام MediaSessionService
.
لإجراء ذلك، أنشئ فئة تمديد MediaSessionService
وأنشئ
جلسة الوسائط داخلها.
يتيح استخدام MediaSessionService
للعملاء الخارجيين، مثل "مساعد Google" أو عناصر التحكّم في الوسائط في النظام أو أزرار الوسائط على الأجهزة الطرفية أو الأجهزة المصاحبة، مثل Wear OS، اكتشاف خدمتك والاتصال بها والتحكّم في التشغيل، وكل ذلك بدون الوصول إلى نشاط واجهة المستخدم في تطبيقك على الإطلاق. في الواقع،
يمكن أن تكون هناك تطبيقات عملاء متعددة متصلة بالحساب نفسه على MediaSessionService
في الوقت نفسه، مع كل تطبيق لديه MediaController
خاص به.
تنفيذ دورة حياة الخدمة
عليك تنفيذ طريقتَي دورة حياة لخدمتك:
- يتمّ استدعاء
onCreate()
عندما يكون جهاز التحكّم الأوّل على وشك الاتصال ويتمّ إنشاء مثيل للخدمة وبدءها. وهو أفضل مكان لإنشاءPlayer
وMediaSession
. - يتمّ استدعاء
onDestroy()
عند إيقاف الخدمة. يجب تحرير جميع الموارد بما في ذلك المشغّل والجلسة.
يمكنك اختياريًا إلغاء onTaskRemoved(Intent)
لتخصيص ما يحدث
عندما يغلِق المستخدم التطبيق من المهام الأخيرة. يتم تلقائيًا إبقاء الخدمة
مشغّلة إذا كان التشغيل مستمرًا ويتم إيقافها في حال عدم تشغيل المحتوى.
Kotlin
class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null // Create your player and media session in the onCreate lifecycle event override fun onCreate() { super.onCreate() val player = ExoPlayer.Builder(this).build() mediaSession = MediaSession.Builder(this, player).build() } // Remember to release the player and media session in onDestroy override fun onDestroy() { mediaSession?.run { player.release() release() mediaSession = null } super.onDestroy() } }
Java
public class PlaybackService extends MediaSessionService { private MediaSession mediaSession = null; // Create your Player and MediaSession in the onCreate lifecycle event @Override public void onCreate() { super.onCreate(); ExoPlayer player = new ExoPlayer.Builder(this).build(); mediaSession = new MediaSession.Builder(this, player).build(); } // Remember to release the player and media session in onDestroy @Override public void onDestroy() { mediaSession.getPlayer().release(); mediaSession.release(); mediaSession = null; super.onDestroy(); } }
بدلاً من إبقاء التشغيل مستمرًا في الخلفية، يمكنك إيقاف الخدمة في أي حال عندما يغلق المستخدم التطبيق:
Kotlin
override fun onTaskRemoved(rootIntent: Intent?) { pauseAllPlayersAndStopSelf() }
Java
@Override public void onTaskRemoved(@Nullable Intent rootIntent) { pauseAllPlayersAndStopSelf(); }
لأي عملية تنفيذ يدوية أخرى من onTaskRemoved
، يمكنك استخدام
isPlaybackOngoing()
للتحقّق مما إذا كان التشغيل مستمرًا وبدء
الخدمة التي تعمل في المقدّمة.
توفير إمكانية الوصول إلى جلسة الوسائط
يمكنك إلغاء طريقة onGetSession()
لمنح عملاء آخرين إذن الوصول إلى جلسة إعلام
التي تم إنشاؤها عند إنشاء الخدمة.
Kotlin
class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null // [...] lifecycle methods omitted override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession }
Java
public class PlaybackService extends MediaSessionService { private MediaSession mediaSession = null; // [...] lifecycle methods omitted @Override public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) { return mediaSession; } }
تحديد الخدمة في البيان
يتطلّب أحد التطبيقات إذنَي FOREGROUND_SERVICE
وFOREGROUND_SERVICE_MEDIA_PLAYBACK
لتشغيل خدمة تعمل في المقدّمة لتشغيل المحتوى:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
يجب أيضًا الإفصاح عن فئة Service
في البيان باستخدام فلتر أهداف
MediaSessionService
وforegroundServiceType
يتضمّنmediaPlayback
.
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
التحكّم في التشغيل باستخدام MediaController
في النشاط أو المقتطف الذي يحتوي على واجهة مستخدم المشغّل، يمكنك إنشاء رابط
بين واجهة المستخدم وجلسة الوسائط باستخدام MediaController
. تستخدِم واجهة المستخدم
وحدة التحكّم في الوسائط لإرسال الأوامر من واجهة المستخدم إلى المشغّل ضمن
الجلسة. اطّلِع على دليل إنشاء MediaController
لمعرفة تفاصيل عن إنشاء MediaController
واستخدامه.
تنفيذ أوامر MediaController
يتلقّى MediaSession
الأوامر من وحدة التحكّم من خلال
MediaSession.Callback
. يؤدي تفعيل MediaSession
إلى إنشاء تنفيذ تلقائي
لـ MediaSession.Callback
يعالج تلقائيًا جميع
الطلبات التي يرسلها MediaController
إلى مشغّلك.
إشعار
ينشئ MediaSessionService
تلقائيًا MediaNotification
من أجلك
من المفترض أن يعمل في معظم الحالات. يكون الإشعار المنشور تلقائيًا هو
إشعار MediaStyle
يتم تعديله باستمرار ليعرض أحدث
المعلومات من جلسة الوسائط ويعرِض عناصر التحكّم في التشغيل. يرصد
MediaNotification
جلستك ويمكن استخدامه للتحكّم في التشغيل
لأي تطبيقات أخرى مرتبطة بالجلسة نفسها.
على سبيل المثال، سينشئ تطبيق لبث الموسيقى يستخدم MediaSessionService
ملفًا
MediaNotification
يعرض عنوان الوسائط الحالية التي يتم تشغيلها واسم الفنّان وصورة الألبوم، بالإضافة إلى عناصر التحكّم في التشغيل استنادًا إلى إعدادات
MediaSession
.
يمكن تقديم البيانات الوصفية المطلوبة في الوسائط أو الإفصاح عنها كجزء من عنصر الوسائط كما هو موضّح في المقتطف التالي:
Kotlin
val mediaItem = MediaItem.Builder() .setMediaId("media-1") .setUri(mediaUri) .setMediaMetadata( MediaMetadata.Builder() .setArtist("David Bowie") .setTitle("Heroes") .setArtworkUri(artworkUri) .build() ) .build() mediaController.setMediaItem(mediaItem) mediaController.prepare() mediaController.play()
Java
MediaItem mediaItem = new MediaItem.Builder() .setMediaId("media-1") .setUri(mediaUri) .setMediaMetadata( new MediaMetadata.Builder() .setArtist("David Bowie") .setTitle("Heroes") .setArtworkUri(artworkUri) .build()) .build(); mediaController.setMediaItem(mediaItem); mediaController.prepare(); mediaController.play();
مراحل الإشعارات
يتم إنشاء الإشعار فور توفّر Player
MediaItem
مثيل
في قائمة التشغيل.
تحدث جميع تعديلات الإشعارات تلقائيًا استنادًا إلى حالة Player
و
MediaSession
.
لا يمكن إزالة الإشعار أثناء تشغيل الخدمة التي تعمل في المقدّمة. لإزالة الإشعار على الفور، يجب الاتصال بالرقم Player.release()
أو محو المحتوى من المحتوى باستخدام Player.clearMediaItems()
.
إذا تم إيقاف المشغّل مؤقتًا أو إيقافه أو تعذّر تشغيله لأكثر من 10 دقائق بدون مزيد من تفاعلات المستخدم، يتم نقل الخدمة تلقائيًا من حالة الخدمة التي تعمل في المقدّمة حتى يتم إغلاقها من قِبل النظام. يمكنك تنفيذ ميزة استئناف التشغيل للسماح للمستخدم بإعادة بدء دورة حياة الخدمة واستئناف التشغيل في وقت لاحق.
تخصيص الإشعارات
يمكن تخصيص البيانات الوصفية عن المحتوى الذي يتم تشغيله حاليًا من خلال تعديل MediaItem.MediaMetadata
. إذا أردت تعديل البيانات الوصفية لمحتوى حالي، يمكنك استخدام Player.replaceMediaItem
لتعديل البيانات الوصفية بدون
إيقاف التشغيل.
يمكنك أيضًا تخصيص بعض الأزرار المعروضة في الإشعار من خلال ضبط الإعدادات المفضَّلة المخصّصة لزر الوسائط في عناصر التحكّم في الوسائط على Android. مزيد من المعلومات حول تخصيص عناصر التحكّم في الوسائط على Android
لمزيد من تخصيص الإشعار نفسه، يمكنك إنشاء
MediaNotification.Provider
باستخدام DefaultMediaNotificationProvider.Builder
أو من خلال إنشاء عملية تنفيذ مخصّصة لواجهة مقدّم الخدمة. أضِف
موفّر الخدمة إلى MediaSessionService
باستخدام
setMediaNotificationProvider
.
استئناف التشغيل
بعد إنهاء MediaSessionService
، وحتى بعد إعادة تشغيل
الجهاز، من الممكن توفير ميزة استئناف التشغيل للسماح للمستخدمين
بإعادة تشغيل الخدمة واستئناف التشغيل من حيث توقفوا. يتم إيقاف ميزة
استئناف التشغيل تلقائيًا، ما يعني أنّه لا يمكن للمستخدم استئناف التشغيل
عندما لا تكون خدمتك قيد التشغيل. للموافقة على هذه الميزة، عليك الإفصاح عن
جهاز استقبال أزرار الوسائط وتنفيذ الطريقة onPlaybackResumption
.
الإفصاح عن جهاز استقبال أزرار الوسائط Media3
ابدأ بتعريف MediaButtonReceiver
في البيان:
<receiver android:name="androidx.media3.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
تنفيذ دالة الاستدعاء لاستئناف التشغيل
عندما يطلب جهاز بلوتوث أو ميزة استئناف التشغيل في واجهة مستخدم نظام Android
استئناف التشغيل، يتم استدعاء onPlaybackResumption()
طريقة طلب التعليقات.
Kotlin
override fun onPlaybackResumption( mediaSession: MediaSession, controller: ControllerInfo ): ListenableFuture<MediaItemsWithStartPosition> { val settable = SettableFuture.create<MediaItemsWithStartPosition>() scope.launch { // Your app is responsible for storing the playlist and the start position // to use here val resumptionPlaylist = restorePlaylist() settable.set(resumptionPlaylist) } return settable }
Java
@Override public ListenableFuture<MediaItemsWithStartPosition> onPlaybackResumption( MediaSession mediaSession, ControllerInfo controller ) { SettableFuture<MediaItemsWithStartPosition> settableFuture = SettableFuture.create(); settableFuture.addListener(() -> { // Your app is responsible for storing the playlist and the start position // to use here MediaItemsWithStartPosition resumptionPlaylist = restorePlaylist(); settableFuture.set(resumptionPlaylist); }, MoreExecutors.directExecutor()); return settableFuture; }
إذا كنت قد حفظت مَعلمات أخرى، مثل سرعة التشغيل أو وضع التكرار أو
وضع التشغيل العشوائي، يُعدّ onPlaybackResumption()
مكانًا جيدًا لضبط المشغّل
باستخدام هذه المَعلمات قبل أن يهيئ Media3 المشغّل ويبدأ التشغيل عند اكتمال callback.
إعدادات وحدة التحكّم المتقدّمة والتوافق مع الإصدارات القديمة
ومن الأمثلة الشائعة على ذلك استخدام MediaController
في واجهة مستخدم التطبيق للتحكّم في
التشغيل وعرض قائمة التشغيل. وفي الوقت نفسه، يتم عرض الجلسة
للعملاء الخارجيين، مثل عناصر التحكّم في الوسائط على Android و"مساعد Google" على الأجهزة الجوّالة أو التلفزيون،
وWear OS للساعات وAndroid Auto في السيارات. يُعدّ تطبيق Media3 التجريبي للجلسة
مثالاً على تطبيق ينفذ هذا السيناريو.
قد تستخدم هذه التطبيقات الخارجية واجهات برمجة تطبيقات مثل MediaControllerCompat
من مكتبة
AndroidX القديمة أو android.media.session.MediaController
من منصّة
Android. تتوافق Media3 بالكامل مع الإصدارات القديمة من المكتبة، وتوفر إمكانية التشغيل التفاعلي مع واجهة برمجة تطبيقات نظام Android الأساسي.
استخدام وحدة التحكّم في إشعارات الوسائط
من المهم معرفة أنّ أدوات التحكّم القديمة وأدوات التحكّم في المنصة تشتركان في
الحالة نفسها ولا يمكن تخصيص مستوى العرض حسب أداة التحكّم (على سبيل المثال،
PlaybackState.getActions()
وPlaybackState.getCustomActions()
المتاحان).
يمكنك استخدام أداة التحكّم في إشعارات الوسائط لمحاولة
ضبط الحالة المحدّدة في جلسة وسائط المنصة من أجل التوافق مع أدوات التحكّم
القديمة وأدوات التحكّم في المنصة.
على سبيل المثال، يمكن للتطبيق توفير تنفيذ لبرمجة MediaSession.Callback.onConnect()
لضبط الأوامر المتاحة وإعدادات الخيارات المفضّلة لزرّي الوسائط على وجه التحديد لجلسة المنصة على النحو التالي:
Kotlin
override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): ConnectionResult { if (session.isMediaNotificationController(controller)) { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build() val playerCommands = ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon() .remove(COMMAND_SEEK_TO_PREVIOUS) .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) .remove(COMMAND_SEEK_TO_NEXT) .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) .build() // Custom button preferences and commands to configure the platform session. return AcceptedResultBuilder(session) .setMediaButtonPreferences( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward)) ) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build() } // Default commands with default button preferences for all other controllers. return AcceptedResultBuilder(session).build() }
Java
@Override public ConnectionResult onConnect( MediaSession session, MediaSession.ControllerInfo controller) { if (session.isMediaNotificationController(controller)) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS .buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build(); Player.Commands playerCommands = ConnectionResult.DEFAULT_PLAYER_COMMANDS .buildUpon() .remove(COMMAND_SEEK_TO_PREVIOUS) .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) .remove(COMMAND_SEEK_TO_NEXT) .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) .build(); // Custom button preferences and commands to configure the platform session. return new AcceptedResultBuilder(session) .setMediaButtonPreferences( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward))) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build(); } // Default commands with default button preferences for all other controllers. return new AcceptedResultBuilder(session).build(); }
تفويض Android Auto لإرسال أوامر مخصّصة
عند استخدام MediaLibraryService
ولإتاحة Android Auto مع التطبيق المتوافق مع الأجهزة الجوّالة، يجب أن يتلقّى جهاز التحكّم في Android Auto
الأوامر المتاحة المناسبة، وإلا سيرفض Media3
الأوامر المخصّصة الواردة من جهاز التحكّم هذا:
Kotlin
override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): ConnectionResult { val sessionCommands = ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build() if (session.isMediaNotificationController(controller)) { // [...] See above. } else if (session.isAutoCompanionController(controller)) { // Available session commands to accept incoming custom commands from Auto. return AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build() } // Default commands for all other controllers. return AcceptedResultBuilder(session).build() }
Java
@Override public ConnectionResult onConnect( MediaSession session, MediaSession.ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS .buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build(); if (session.isMediaNotificationController(controller)) { // [...] See above. } else if (session.isAutoCompanionController(controller)) { // Available commands to accept incoming custom commands from Auto. return new AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build(); } // Default commands for all other controllers. return new AcceptedResultBuilder(session).build(); }
يحتوي التطبيق التجريبي للجلسة على وحدة سيارات، توضّح توافقه مع نظام التشغيل Automotive الذي يتطلّب حزمة APK منفصلة.