غالبًا ما يكون من الأفضل تشغيل الوسائط عندما لا يكون التطبيق في المقدّمة. على سبيل المثال، يستمر مشغّل الموسيقى في تشغيل الموسيقى عندما يقفل المستخدم جهازه أو يستخدم تطبيقًا آخر. توفّر مكتبة 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 MediaSession 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() } override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession }
Java
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(); } @Override public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) { return mediaSession; } }
بدلاً من مواصلة التشغيل في الخلفية، يمكنك إيقاف الخدمة في أي حالة يغلق فيها المستخدم التطبيق:
Kotlin
@OptIn(UnstableApi::class) override fun onTaskRemoved(rootIntent: Intent?) { pauseAllPlayersAndStopSelf() }
Java
@OptIn(markerClass = UnstableApi.class) @Override public void onTaskRemoved(@Nullable Intent rootIntent) { pauseAllPlayersAndStopSelf(); }
بالنسبة إلى أي تنفيذ يدوي آخر لـ onTaskRemoved، يمكنك استخدام
isPlaybackOngoing() للتحقّق مما إذا كان التشغيل مستمرًا وبدء
الخدمة التي تعمل في المقدّمة.
توفير إمكانية الوصول إلى جلسة الوسائط
يمكنك إلغاء طريقة onGetSession() لمنح العملاء الآخرين إمكانية الوصول إلى جلسة الوسائط التي تم إنشاؤها عند إنشاء الخدمة.
Kotlin
class PlaybackService : MediaSessionService() { // [...] lifecycle methods omitted override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession }
Java
class PlaybackService extends MediaSessionService { // [...] 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 في ملف البيان باستخدام intent filter 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();
مراحل نشاط الإشعارات
يتم إنشاء الإشعار فور توفّر MediaItem نسخة من Player في قائمة التشغيل.
يتم إجراء جميع تعديلات الإشعارات تلقائيًا استنادًا إلى حالة 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: MediaSession.ControllerInfo, isForPlayback: Boolean, ): ListenableFuture<MediaSession.MediaItemsWithStartPosition> { val settableFuture = SettableFuture.create<MediaSession.MediaItemsWithStartPosition>() 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. val resumptionPlaylist = restorePlaylist() settableFuture.set(resumptionPlaylist) }, MoreExecutors.directExecutor(), ) return settableFuture }
Java
@Override public ListenableFuture<MediaItemsWithStartPosition> onPlaybackResumption( MediaSession mediaSession, ControllerInfo controller, boolean isForPlayback) { 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 بعد إعادة تشغيل الجهاز مع ضبط isForPlayback على false. بالنسبة إلى الإشعارات الغنية، يُنصح بملء حقول 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 الأساسي.
تحديد وحدات التحكّم الموثوق بها
يمكن لأي تطبيق محاولة الاتصال بجلسة الوسائط أو المكتبة. إذا أردت حصر إمكانية الوصول بوحدات التحكّم في النظام ووحدات التحكّم التي تملك إذن التحكّم في محتوى الوسائط وتطبيقك، يمكنك استخدام ControllerInfo.isTrusted() لإجراء فحص أساسي لإمكانية الوصول. بدلاً من ذلك، يمكنك تحديد عناصر تحكّم أكثر تحديدًا، مثل عنصر التحكّم في إشعارات الوسائط أو عناصر التحكّم في Android Auto، كما هو موضّح في الأقسام التالية.
استخدام وحدة التحكّم في إشعارات الوسائط
من المهم فهم أنّ وحدات التحكّم القديمة ووحدات التحكّم في المنصة تتشارك
الحالة نفسها ولا يمكن تخصيص مستوى العرض حسب وحدة التحكّم (على سبيل المثال،
PlaybackState.getActions() وPlaybackState.getCustomActions() المتاحان).
يمكنك استخدام وحدة التحكّم في إشعارات الوسائط
لضبط الحالة المحدّدة في جلسة الوسائط على المنصة من أجل التوافق مع وحدات التحكّم
القديمة ووحدات التحكّم في المنصة.
على سبيل المثال، يمكن أن يوفّر التطبيق تنفيذًا لـ MediaSession.Callback.onConnect() لضبط الأوامر المتاحة وإعدادات أزرار الوسائط المفضّلة خصيصًا لجلسة المنصة على النحو التالي:
Kotlin
override fun onConnectAsync( session: MediaSession, controller: MediaSession.ControllerInfo, ): ListenableFuture<ConnectionResult> { if (session.isMediaNotificationController(controller)) { 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 immediateFuture( AcceptedResultBuilder(session, controller) .setMediaButtonPreferences(listOf(seekBackButton, seekForwardButton)) .setAvailablePlayerCommands(playerCommands) .build() ) } // Default commands with default button preferences for all other controllers. return immediateFuture(AcceptedResultBuilder(session, controller).build()) }
Java
@Override public ListenableFuture<ConnectionResult> onConnectAsync( MediaSession session, MediaSession.ControllerInfo controller) { if (session.isMediaNotificationController(controller)) { 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 immediateFuture( new AcceptedResultBuilder(session, controller) .setMediaButtonPreferences(ImmutableList.of(seekBackButton, seekForwardButton)) .setAvailablePlayerCommands(playerCommands) .build()); } // Default commands with default button preferences for all other controllers. return immediateFuture(new AcceptedResultBuilder(session, controller).build()); }
السماح لـ Android Auto بإرسال أوامر مخصّصة
عند استخدام MediaLibraryService
ولدعم Android Auto من خلال التطبيق المتوافق مع الأجهزة الجوّالة، تتطلّب وحدة التحكّم في Android Auto
توفُّر الطلبات المناسبة، وإلا سترفض Media3
الطلبات المخصّصة الواردة من وحدة التحكّم هذه:
Kotlin
override fun onConnectAsync( session: MediaSession, controller: MediaSession.ControllerInfo, ): ListenableFuture<ConnectionResult> { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon().add(customCommand).build() if (session.isMediaNotificationController(controller)) { // ... See above. } else if (session.isAutoCompanionController(controller)) { // Available commands to accept incoming custom commands from Auto. return immediateFuture( AcceptedResultBuilder(session, controller) .setAvailableSessionCommands(sessionCommands) .build() ) } // Default commands for all other controllers. return immediateFuture(AcceptedResultBuilder(session, controller).build()) }
Java
@Override public ListenableFuture<ConnectionResult> onConnectAsync( MediaSession session, MediaSession.ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon().add(customCommand).build(); if (session.isMediaNotificationController(controller)) { // ... See above. } else if (session.isAutoCompanionController(controller)) { // Available commands to accept incoming custom commands from Auto. return immediateFuture( new AcceptedResultBuilder(session, controller) .setAvailableSessionCommands(sessionCommands) .build()); } // Default commands for all other controllers. return immediateFuture(new AcceptedResultBuilder(session, controller).build()); }
يحتوي التطبيق التجريبي للجلسة على وحدة سيارات، توضّح كيفية دعم نظام التشغيل Automotive الذي يتطلّب حزمة APK منفصلة.