غالبًا ما يكون من المفضّل تشغيل الوسائط عندما لا يكون التطبيق في المقدّمة. على سبيل المثال، يواصل مشغِّل الموسيقى بشكل عام تشغيل الموسيقى عندما يقفل المستخدم جهازه أو يستخدم تطبيقًا آخر. وتوفّر مكتبة 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 منفصلة.