من المفيد غالبًا تشغيل الوسائط عندما لا يكون التطبيق في المقدّمة. على سبيل المثال، يواصل مشغّل الموسيقى تشغيل الموسيقى عادةً عندما يقفل المستخدم جهازه أو يستخدم تطبيقًا آخر. توفّر مكتبة 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, metadata (like title // and artwork) of the current item 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, metadata (like title // and artwork) of the current item and the start position to use here. MediaItemsWithStartPosition resumptionPlaylist = restorePlaylist(); settableFuture.set(resumptionPlaylist); }, MoreExecutors.directExecutor()); return settableFuture; }
إذا كنت قد خزّنت مَعلمات أخرى، مثل سرعة التشغيل أو وضع التكرار أو وضع الترتيب العشوائي، فإنّ onPlaybackResumption()
هو المكان المناسب لضبط المشغّل باستخدام هذه المَعلمات قبل أن تجهّز Media3 المشغّل وتبدأ التشغيل عند اكتمال معاودة الاتصال.
يتم استدعاء هذا الإجراء أثناء وقت التشغيل لإنشاء إشعار استئناف واجهة مستخدم نظام Android بعد إعادة تشغيل الجهاز. بالنسبة إلى الإشعارات التفاعلية، يُنصح بملء حقول MediaMetadata
مثل title
وartworkData
أو artworkUri
للعنصر الحالي بقيم متاحة محليًا، لأنّه قد لا يكون الوصول إلى الشبكة متاحًا بعد. يمكنك أيضًا إضافة
MediaConstants.EXTRAS_KEY_COMPLETION_STATUS
و
MediaConstants.EXTRAS_KEY_COMPLETION_PERCENTAGE
إلى MediaMetadata.extras
للإشارة إلى موضع استئناف التشغيل.
إعدادات متقدّمة لوحدة التحكّم والتوافق مع الأنظمة القديمة
من السيناريوهات الشائعة استخدام 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 توضّح إمكانية استخدام نظام التشغيل Automotive الذي يتطلّب حِزمة APK منفصلة.