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