يكون من المستحسن غالبًا تشغيل الوسائط عندما لا يعمل التطبيق في المقدّمة. على سبيل المثال، يستمر مشغّل الموسيقى بشكل عام في تشغيل الموسيقى عندما يقفل المستخدم جهازه أو يستخدم تطبيقًا آخر. وتوفّر مكتبة Media3 سلسلة من الواجهات التي تتيح لك دعم التشغيل في الخلفية.
استخدام MediaSessionService
لتفعيل ميزة التشغيل في الخلفية، يجب تضمين Player
وMediaSession
داخل خدمة منفصلة.
ويتيح هذا للجهاز مواصلة عرض الوسائط حتى في حال عدم تشغيل تطبيقك في المقدّمة.
عند استضافة مشغّل داخل إحدى الخدمات، يجب استخدام MediaSessionService
.
ولإجراء ذلك، أنشئ فئة تمتد MediaSessionService
` وأنشِئ جلسة الوسائط الخاصة بك داخلها.
فاستخدام MediaSessionService
يتيح للعملاء الخارجيين، مثل "مساعد Google" أو عناصر التحكّم في وسائط النظام أو الأجهزة المصاحبة مثل Wear OS، اكتشاف خدمتك والاتصال بها والتحكّم في التشغيل، وكل ذلك بدون الوصول إلى نشاط واجهة المستخدم للتطبيق على الإطلاق. في الواقع، يمكن ربط عدة تطبيقات عملاء
بنفس MediaSessionService
في الوقت نفسه، لكل تطبيق
MediaController
خاص به.
تنفيذ دورة حياة الخدمة
تحتاج إلى تنفيذ ثلاث طرق لدورة حياة الخدمة:
- يتم استدعاء
onCreate()
عندما تكون وحدة التحكم الأولى على وشك الاتصال، ويتم إنشاء مثيل للخدمة وبدء تشغيلها. إنه أفضل مكان لإنشاءPlayer
وMediaSession
. - يتم استدعاء "
onTaskRemoved(Intent)
" عندما يرفض المستخدِم التطبيق من المهام الأخيرة. إذا كان التشغيل مستمرًا، يمكن للتطبيق اختيار إبقاء الخدمة قيد التشغيل في المقدّمة. إذا كان المشغل متوقفًا مؤقتًا، فلن تكون الخدمة في المقدمة ويجب إيقافها. - يتم استدعاء "
onDestroy()
" عند إيقاف الخدمة. يجب إصدار جميع الموارد بما في ذلك اللاعب والجلسة.
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() } // The user dismissed the app from the recent tasks override fun onTaskRemoved(rootIntent: Intent?) { val player = mediaSession?.player!! if (!player.playWhenReady || player.mediaItemCount == 0 || player.playbackState == Player.STATE_ENDED) { // Stop the service if not playing, continue playing in the background // otherwise. stopSelf() } } // 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(); } // The user dismissed the app from the recent tasks @Override public void onTaskRemoved(@Nullable Intent rootIntent) { Player player = mediaSession.getPlayer(); if (!player.getPlayWhenReady() || player.getMediaItemCount() == 0 || player.getPlaybackState() == Player.STATE_ENDED) { // Stop the service if not playing, continue playing in the background // otherwise. stopSelf(); } } // 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?) { val player = mediaSession.player if (player.playWhenReady) { // Make sure the service is not in foreground. player.pause() } stopSelf() }
Java
@Override public void onTaskRemoved(@Nullable Intent rootIntent) { Player player = mediaSession.getPlayer(); if (player.getPlayWhenReady()) { // Make sure the service is not in foreground. player.pause(); } stopSelf(); }
توفير الوصول إلى جلسة الوسائط
يمكنك إلغاء طريقة 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
في البيان، وإذا كنت تستهدف الإصدار 34 من واجهة برمجة التطبيقات
والأعلى أيضًا FOREGROUND_SERVICE_MEDIA_PLAYBACK
:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
عليك أيضًا الإعلان عن فئة Service
في البيان باستخدام فلتر الأهداف MediaSessionService
.
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
يجب تحديد سمة foregroundServiceType
تتضمّن mediaPlayback
عند تشغيل تطبيقك على جهاز Android 10 (المستوى 29 من واجهة برمجة التطبيقات) أو الإصدارات الأحدث.
التحكّم في التشغيل باستخدام 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();
يمكن للتطبيقات تخصيص أزرار الأوامر لعناصر التحكّم في وسائط Android. تعرَّف على المزيد من المعلومات عن تخصيص عناصر التحكُّم في وسائط Android.
تخصيص الإشعارات
لتخصيص الإشعار، أنشِئ MediaNotification.Provider
باستخدام DefaultMediaNotificationProvider.Builder
أو من خلال إنشاء عملية تنفيذ مخصّصة لواجهة الموفّر. يمكنك إضافة
المزوّد إلى "MediaSessionService
" من خلال
setMediaNotificationProvider
.
استئناف التشغيل
أزرار الوسائط هي أزرار الأجهزة الموجودة على أجهزة Android والأجهزة الملحقة الأخرى، مثل زر التشغيل أو الإيقاف المؤقت في سماعة رأس بلوتوث. تتعامل Media3 مع إدخالات زر الوسائط نيابة عنك عندما تكون الخدمة قيد التشغيل.
تعريف استقبال زر الوسائط Media3
يشتمل 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 على إعداد المشغّل وبدء التشغيل عند
اكتمال معاودة الاتصال.
الإعدادات المتقدّمة لوحدة التحكّم والتوافق مع الأنظمة القديمة
أحد السيناريوهات الشائعة هو استخدام MediaController
في واجهة المستخدم الخاصة بالتطبيق للتحكم في
تشغيل قائمة التشغيل وعرضها. في الوقت نفسه، يتم عرض الجلسة لعملاء خارجيين، مثل عناصر التحكّم في وسائط Android و"مساعد Google" على الجهاز الجوّال أو التلفزيون
وWear OS للساعات وAndroid Auto في السيارات. يُعد التطبيق التجريبي لجلسة Media3
مثالاً على تطبيق ينفّذ مثل هذا السيناريو.
قد تستخدم هذه البرامج الخارجية واجهات برمجة تطبيقات، مثل MediaControllerCompat
من مكتبة AndroidX القديمة أو android.media.session.MediaController
من إطار عمل Android. تتوافق منصة Media3 تمامًا مع الأنظمة القديمة مع المكتبة القديمة وتوفِّر إمكانية التشغيل التفاعلي مع واجهة برمجة التطبيقات لإطار عمل Android.
استخدام وحدة التحكّم بإشعارات الوسائط
من المهم فهم أنّ وحدات التحكّم القديمة أو وحدات التحكّم في إطار العمل هذه تقرأ
القيم نفسها الواردة في إطار العمل PlaybackState.getActions()
و
PlaybackState.getCustomActions()
. لتحديد الإجراءات والإجراءات المخصّصة لجلسة إطار العمل، يمكن للتطبيق استخدام وحدة التحكّم في إشعارات الوسائط وضبط أوامره المتاحة والتنسيق المخصّص. تربط الخدمة وحدة التحكّم بإشعارات الوسائط بجلستك، وتستخدم الجلسة ConnectionResult
الذي يعرضه onConnect()
في معاودة الاتصال لضبط الإجراءات والإجراءات المخصّصة لجلسة إطار العمل.
وفقًا لسيناريو للأجهزة الجوّالة فقط، يمكن أن يوفّر أحد التطبيقات تنفيذ 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 layout and available commands to configure the legacy/framework session. return AcceptedResultBuilder(session) .setCustomLayout( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward)) ) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build() } // Default commands with default custom layout 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 layout and available commands to configure the legacy/framework session. return new AcceptedResultBuilder(session) .setCustomLayout( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward))) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build(); } // Default commands without default custom layout 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 with default custom layout 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 without default custom layout for all other controllers. return new AcceptedResultBuilder(session).build(); }
يحتوي التطبيق التجريبي للجلسة على وحدة سيارات توضح توافق نظام التشغيل Automotive الذي يتطلب حزمة APK منفصلة.