تتوفّر عناصر التحكّم في الوسائط على أجهزة Android بالقرب من "الإعدادات السريعة". يتم ترتيب الجلسات من تطبيقات متعدّدة في لوحة عرض دوّارة يمكن التمرير سريعًا بينها. تعرض لوحة العرض الدوّارة الجلسات بالترتيب التالي:
- عمليات البث التي يتم تشغيلها محليًا على الهاتف
- عمليات البث عن بُعد، مثل تلك التي يتم رصدها على الأجهزة الخارجية أو جلسات البث
- الجلسات السابقة التي يمكن استئنافها، بالترتيب الذي تم تشغيلها به آخر مرة
بدءًا من Android 13 (المستوى 33 لواجهة برمجة التطبيقات)، ولضمان إمكانية وصول المستخدمين إلى مجموعة كبيرة من عناصر التحكّم في الوسائط للتطبيقات التي تشغّل الوسائط، يتم استخلاص أزرار الإجراءات في عناصر التحكّم في الوسائط من حالة Player
.
بهذه الطريقة، يمكنك عرض مجموعة متسقة من عناصر التحكّم في الوسائط وتوفير تجربة أكثر سلاسة في استخدام عناصر التحكّم في الوسائط على جميع الأجهزة.
يوضّح الشكل 1 مثالاً على كيفية ظهور ذلك على هاتف وجهاز لوحي، على التوالي.
يعرض النظام ما يصل إلى خمسة أزرار إجراء استنادًا إلى حالة Player
كما هو موضّح في الجدول التالي. في الوضع المضغوط، يتم عرض أول ثلاث خانات للإجراءات فقط. يتوافق ذلك مع طريقة عرض عناصر التحكّم في الوسائط على منصات Android الأخرى، مثل Auto و"مساعد Google" وWear OS.
الشريحة | المعايير | الإجراء |
---|---|---|
1 |
playWhenReady غير صحيح أو أنّ
حالة التشغيل الحالية هي STATE_ENDED .
|
تشغيل |
playWhenReady صحيحة وحالة
التشغيل الحالية هي STATE_BUFFERING .
|
مؤشر سريان التحميل | |
playWhenReady صحيحة وحالة
التشغيل الحالية هي STATE_READY .
|
إيقاف مؤقت | |
2 |
تحتوي إعدادات زر الوسائط على زر مخصّص لتطبيق CommandButton.SLOT_BACK
|
قرض مخصص |
يتوفّر أمر اللاعب
COMMAND_SEEK_TO_PREVIOUS أو
COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM .
|
السابق | |
لا يتوفّر زر مخصّص ولا أحد الأوامر المُدرَجة. | فارغ | |
3 |
تحتوي إعدادات أزرار الوسائط على زر مخصّص لتطبيق CommandButton.SLOT_FORWARD
|
قرض مخصص |
يتوفّر أمر اللاعب
COMMAND_SEEK_TO_NEXT أو
COMMAND_SEEK_TO_NEXT_MEDIA_ITEM .
|
التالي | |
لا يتوفّر زر مخصّص ولا أحد الأوامر المُدرَجة. | فارغ | |
4 |
تحتوي إعدادات زر الوسائط على زر مخصّص لتطبيق CommandButton.SLOT_OVERFLOW لم يتم وضعه بعد.
|
قرض مخصص |
5 |
تحتوي إعدادات زر الوسائط على زر مخصّص لتطبيق CommandButton.SLOT_OVERFLOW لم يتم وضعه بعد.
|
قرض مخصص |
يتم ترتيب أزرار القائمة الإضافية المخصّصة حسب ترتيب إضافتها إلى إعدادات أزرار الوسائط.
تخصيص أزرار الأوامر
لتخصيص عناصر التحكّم في الوسائط على مستوى النظام باستخدام Jetpack Media3، يمكنك ضبط إعدادات أزرار الوسائط الخاصة بالجلسة والأوامر المتاحة لوحدات التحكّم على النحو التالي:
أنشئ
MediaSession
وحدِّد إعدادات زر الوسائط لأزرار الأوامر المخصّصة.في
MediaSession.Callback.onConnect()
، يمكنك منح الأذونات لعناصر التحكّم من خلال تحديد الأوامر المتاحة لها، بما في ذلك الأوامر المخصّصة، فيConnectionResult
.في
MediaSession.Callback.onCustomCommand()
، استجب للأمر المخصّص الذي اختاره المستخدم.
Kotlin
class PlaybackService : MediaSessionService() { private val customCommandFavorites = SessionCommand(ACTION_FAVORITES, Bundle.EMPTY) private var mediaSession: MediaSession? = null override fun onCreate() { super.onCreate() val favoriteButton = CommandButton.Builder(CommandButton.ICON_HEART_UNFILLED) .setDisplayName("Save to favorites") .setSessionCommand(customCommandFavorites) .build() val player = ExoPlayer.Builder(this).build() // Build the session with a custom layout. mediaSession = MediaSession.Builder(this, player) .setCallback(MyCallback()) .setMediaButtonPreferences(ImmutableList.of(favoriteButton)) .build() } private inner class MyCallback : MediaSession.Callback { override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): ConnectionResult { // Set available player and session commands. return AcceptedResultBuilder(session) .setAvailableSessionCommands( ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(customCommandFavorites) .build() ) .build() } override fun onCustomCommand( session: MediaSession, controller: MediaSession.ControllerInfo, customCommand: SessionCommand, args: Bundle ): ListenableFuture{ if (customCommand.customAction == ACTION_FAVORITES) { // Do custom logic here saveToFavorites(session.player.currentMediaItem) return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) } return super.onCustomCommand(session, controller, customCommand, args) } } }
Java
public class PlaybackService extends MediaSessionService { private static final SessionCommand CUSTOM_COMMAND_FAVORITES = new SessionCommand("ACTION_FAVORITES", Bundle.EMPTY); @Nullable private MediaSession mediaSession; public void onCreate() { super.onCreate(); CommandButton favoriteButton = new CommandButton.Builder(CommandButton.ICON_HEART_UNFILLED) .setDisplayName("Save to favorites") .setSessionCommand(CUSTOM_COMMAND_FAVORITES) .build(); Player player = new ExoPlayer.Builder(this).build(); // Build the session with a custom layout. mediaSession = new MediaSession.Builder(this, player) .setCallback(new MyCallback()) .setMediaButtonPreferences(ImmutableList.of(favoriteButton)) .build(); } private static class MyCallback implements MediaSession.Callback { @Override public ConnectionResult onConnect( MediaSession session, MediaSession.ControllerInfo controller) { // Set available player and session commands. return new AcceptedResultBuilder(session) .setAvailableSessionCommands( ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(CUSTOM_COMMAND_FAVORITES) .build()) .build(); } public ListenableFutureonCustomCommand( MediaSession session, MediaSession.ControllerInfo controller, SessionCommand customCommand, Bundle args) { if (customCommand.customAction.equals(CUSTOM_COMMAND_FAVORITES.customAction)) { // Do custom logic here saveToFavorites(session.getPlayer().getCurrentMediaItem()); return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS)); } return MediaSession.Callback.super.onCustomCommand( session, controller, customCommand, args); } } }
لمزيد من المعلومات حول كيفية ضبط MediaSession
كي تتمكّن الأجهزة الأخرى، مثل النظام، من الاتصال بتطبيق الوسائط، يُرجى الاطّلاع على منح إذن التحكّم لأجهزة أخرى.
باستخدام Jetpack Media3، عند تنفيذ MediaSession
، يتم تلقائيًا إبقاء PlaybackState
محدّثًا مع مشغّل الوسائط. وبالمثل، عند تنفيذ MediaSessionService
، تنشر المكتبة تلقائيًا MediaStyle
إشعارًا نيابةً عنك وتحافظ على تحديثه.
الردّ على أزرار الإجراءات
عندما ينقر المستخدم على زر إجراء في عناصر التحكّم في الوسائط بالنظام، يرسل النظام
MediaController
أمر تشغيل إلى MediaSession
. بعد ذلك، يفوّض
MediaSession
هذه الأوامر إلى اللاعب. تتعامل جلسة الوسائط تلقائيًا مع الأوامر المحدّدة في واجهة Player
الخاصة بمكتبة Media3.
راجِع مقالة إضافة أوامر مخصّصة للحصول على إرشادات حول كيفية الردّ على أمر مخصّص.
إتاحة استئناف تشغيل الوسائط
تتيح ميزة "استئناف تشغيل الوسائط" للمستخدمين إعادة تشغيل الجلسات السابقة من لوحة العرض الدوّارة بدون الحاجة إلى فتح التطبيق. وعند بدء التشغيل، يتفاعل المستخدم مع عناصر التحكّم في الوسائط بالطريقة المعتادة.
يمكن تفعيل ميزة استئناف التشغيل وإيقافها باستخدام تطبيق "الإعدادات"، ضمن خيارات الصوت > الوسائط. يمكن للمستخدم أيضًا الوصول إلى "الإعدادات" من خلال النقر على رمز الترس الذي يظهر بعد التمرير سريعًا على لوحة العرض الدوّارة الموسّعة.
توفّر Media3 واجهات برمجة تطبيقات لتسهيل إمكانية استئناف تشغيل الوسائط. راجِع مستند استئناف التشغيل باستخدام Media3 للحصول على إرشادات حول تنفيذ هذه الميزة.
استخدام واجهات برمجة التطبيقات القديمة للوسائط
يوضّح هذا القسم كيفية الدمج مع عناصر التحكّم في الوسائط بالنظام باستخدام واجهات برمجة التطبيقات القديمة MediaCompat.
يسترد النظام المعلومات التالية من MediaSession
MediaMetadata
ويعرضها عند توفّرها:
METADATA_KEY_ALBUM_ART_URI
METADATA_KEY_TITLE
METADATA_KEY_DISPLAY_TITLE
METADATA_KEY_ARTIST
METADATA_KEY_DURATION
(إذا لم يتم ضبط المدة، لن يظهر شريط التقدّم)
لضمان ظهور إشعار صالح ودقيق لعناصر التحكّم في الوسائط، اضبط قيمة البيانات الوصفية METADATA_KEY_TITLE
أو METADATA_KEY_DISPLAY_TITLE
على عنوان الوسائط التي يتم تشغيلها حاليًا.
يعرض مشغّل الوسائط الوقت المنقضي للوسائط التي يتم تشغيلها حاليًا، بالإضافة إلى شريط البحث الذي يتم ربطه بمفتاح MediaSession
PlaybackState
.
يعرض مشغّل الوسائط مستوى تقدّم الوسائط التي يتم تشغيلها حاليًا، بالإضافة إلى شريط البحث الذي يتم ربطه بالمفتاحين MediaSession
PlaybackState
. يتيح شريط التقديم/الترجيع للمستخدمين تغيير الموضع ويعرض الوقت المنقضي لعنصر الوسائط. لكي يتم تفعيل شريط التقدّم، يجب تنفيذ PlaybackState.Builder#setActions
وتضمين ACTION_SEEK_TO
.
الشريحة | الإجراء | المعايير |
---|---|---|
1 | تشغيل |
الحالة الحالية PlaybackState هي إحدى الحالات التالية:
|
مؤشر سريان التحميل |
تكون حالة PlaybackState الحالية إحدى الحالات التالية:
|
|
إيقاف مؤقت | حالة PlaybackState الحالية هي لا شيء مما سبق. |
|
2 | السابق | تتضمّن PlaybackState الإجراءات ACTION_SKIP_TO_PREVIOUS . |
قرض مخصص | لا تتضمّن PlaybackState الإجراءات ACTION_SKIP_TO_PREVIOUS ، وتتضمّن PlaybackState الإجراءات المخصّصة إجراءً مخصّصًا لم يتم وضعه بعد. |
|
فارغ | تتضمّن PlaybackState extras قيمة منطقية true للمفتاح SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV . |
|
3 | التالي | تتضمّن PlaybackState الإجراءات ACTION_SKIP_TO_NEXT . |
قرض مخصص | لا تتضمّن PlaybackState الإجراءات ACTION_SKIP_TO_NEXT ، وتتضمّن PlaybackState الإجراءات المخصّصة إجراءً مخصّصًا لم يتم وضعه بعد. |
|
فارغ | تتضمّن PlaybackState extras قيمة منطقية true للمفتاح SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT . |
|
4 | قرض مخصص | تتضمّن PlaybackState الإجراءات المخصّصة إجراءً مخصّصًا لم يتم وضعه بعد. |
5 | قرض مخصص | تتضمّن PlaybackState الإجراءات المخصّصة إجراءً مخصّصًا لم يتم وضعه بعد. |
إضافة إجراءات عادية
توضّح أمثلة الرموز البرمجية التالية كيفية إضافة إجراءات PlaybackState
عادية ومخصّصة.
بالنسبة إلى التشغيل والإيقاف المؤقت والملف السابق والملف التالي، اضبط هذه الإجراءات في PlaybackState
لجلسة الوسائط.
Kotlin
val session = MediaSessionCompat(context, TAG) val playbackStateBuilder = PlaybackStateCompat.Builder() val style = NotificationCompat.MediaStyle() // For this example, the media is currently paused: val state = PlaybackStateCompat.STATE_PAUSED val position = 0L val playbackSpeed = 1f playbackStateBuilder.setState(state, position, playbackSpeed) // And the user can play, skip to next or previous, and seek val stateActions = PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PLAY_PAUSE or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or PlaybackStateCompat.ACTION_SKIP_TO_NEXT or PlaybackStateCompat.ACTION_SEEK_TO // adding the seek action enables seeking with the seekbar playbackStateBuilder.setActions(stateActions) // ... do more setup here ... session.setPlaybackState(playbackStateBuilder.build()) style.setMediaSession(session.sessionToken) notificationBuilder.setStyle(style)
Java
MediaSessionCompat session = new MediaSessionCompat(context, TAG); PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder(); NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle(); // For this example, the media is currently paused: int state = PlaybackStateCompat.STATE_PAUSED; long position = 0L; float playbackSpeed = 1f; playbackStateBuilder.setState(state, position, playbackSpeed); // And the user can play, skip to next or previous, and seek long stateActions = PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS | PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SEEK_TO; // adding this enables the seekbar thumb playbackStateBuilder.setActions(stateActions); // ... do more setup here ... session.setPlaybackState(playbackStateBuilder.build()); style.setMediaSession(session.getSessionToken()); notificationBuilder.setStyle(style);
إذا كنت لا تريد أي أزرار في الخانتَين السابقة أو التالية، لا تُضِف
ACTION_SKIP_TO_PREVIOUS
أو ACTION_SKIP_TO_NEXT
، بل أضِف عناصر إضافية إلى الجلسة:
Kotlin
session.setExtras(Bundle().apply { putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true) putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true) })
Java
Bundle extras = new Bundle(); extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true); extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true); session.setExtras(extras);
إضافة إجراءات مخصّصة
بالنسبة إلى الإجراءات الأخرى التي تريد عرضها في عناصر التحكّم في الوسائط، يمكنك إنشاء
PlaybackStateCompat.CustomAction
وإضافتها إلى PlaybackState
بدلاً من ذلك. تظهر هذه الإجراءات بالترتيب الذي تمت إضافتها به.
Kotlin
val customAction = PlaybackStateCompat.CustomAction.Builder( "com.example.MY_CUSTOM_ACTION", // action ID "Custom Action", // title - used as content description for the button R.drawable.ic_custom_action ).build() playbackStateBuilder.addCustomAction(customAction)
Java
PlaybackStateCompat.CustomAction customAction = new PlaybackStateCompat.CustomAction.Builder( "com.example.MY_CUSTOM_ACTION", // action ID "Custom Action", // title - used as content description for the button R.drawable.ic_custom_action ).build(); playbackStateBuilder.addCustomAction(customAction);
الردّ على إجراءات PlaybackState
عندما ينقر المستخدم على زر، تستخدم SystemUI
MediaController.TransportControls
لإرسال أمر إلى MediaSession
. عليك تسجيل دالة ردّ نداء يمكنها الاستجابة بشكل صحيح لهذه الأحداث.
Kotlin
val callback = object: MediaSession.Callback() { override fun onPlay() { // start playback } override fun onPause() { // pause playback } override fun onSkipToPrevious() { // skip to previous } override fun onSkipToNext() { // skip to next } override fun onSeekTo(pos: Long) { // jump to position in track } override fun onCustomAction(action: String, extras: Bundle?) { when (action) { CUSTOM_ACTION_1 -> doCustomAction1(extras) CUSTOM_ACTION_2 -> doCustomAction2(extras) else -> { Log.w(TAG, "Unknown custom action $action") } } } } session.setCallback(callback)
Java
MediaSession.Callback callback = new MediaSession.Callback() { @Override public void onPlay() { // start playback } @Override public void onPause() { // pause playback } @Override public void onSkipToPrevious() { // skip to previous } @Override public void onSkipToNext() { // skip to next } @Override public void onSeekTo(long pos) { // jump to position in track } @Override public void onCustomAction(String action, Bundle extras) { if (action.equals(CUSTOM_ACTION_1)) { doCustomAction1(extras); } else if (action.equals(CUSTOM_ACTION_2)) { doCustomAction2(extras); } else { Log.w(TAG, "Unknown custom action " + action); } } };
استئناف تشغيل الوسائط
لكي يظهر تطبيق مشغّل الوسائط في منطقة إعدادات الإعدادات السريعة، يجب إنشاء إشعار MediaStyle
يتضمّن رمزًا مميزًا صالحًا MediaSession
.
لعرض عنوان إشعار MediaStyle، استخدِم
NotificationBuilder.setContentTitle()
.
لعرض رمز العلامة التجارية لمشغّل الوسائط، استخدِم
NotificationBuilder.setSmallIcon()
.
لإتاحة استئناف التشغيل، يجب أن تنفّذ التطبيقات MediaBrowserService
وMediaSession
. يجب أن تنفّذ MediaSession
وظيفة onPlay()
.
تنفيذ MediaBrowserService
بعد تشغيل الجهاز، يبحث النظام عن آخر خمسة تطبيقات وسائط تم استخدامها، ويوفر عناصر تحكّم يمكن استخدامها لإعادة تشغيل المحتوى من كل تطبيق.
يحاول النظام التواصل مع MediaBrowserService
من خلال اتصال من SystemUI. يجب أن يسمح تطبيقك بهذه الاتصالات، وإلا لن يتمكّن من إتاحة استئناف التشغيل.
يمكن تحديد الاتصالات من SystemUI والتحقّق منها باستخدام اسم الحزمة
com.android.systemui
والتوقيع. يتم توقيع SystemUI باستخدام توقيع النظام الأساسي. يمكنك الاطّلاع على مثال حول كيفية التحقّق من توقيع المنصة في تطبيق UAMP.
لكي يتيح تطبيقك MediaBrowserService
إمكانية استئناف التشغيل، يجب أن يتضمّن السلوكيات التالية:
يجب أن تعرض الدالة
onGetRoot()
جذرًا غير فارغ بسرعة. يجب التعامل مع أي منطق معقّد آخر فيonLoadChildren()
عند استدعاء
onLoadChildren()
على معرّف الوسائط الجذر، يجب أن تتضمّن النتيجة عنصرًا فرعيًا FLAG_PLAYABLE.يجب أن تعرض
MediaBrowserService
أحدث عنصر وسائط تم تشغيله عند تلقّي طلب بحث EXTRA_RECENT. يجب أن تكون القيمة المعروضة عنصر وسائط فعليًا وليس دالة عامة.يجب أن يوفّر العنصر
MediaBrowserService
MediaDescription مناسبًا يتضمّن عنوانًا وعنوانًا فرعيًا غير فارغَين. يجب أيضًا ضبط معرّف الموارد المنتظم (URI) الخاص بالرمز أو صورة نقطية للرمز.
توضّح أمثلة الرموز البرمجية التالية كيفية تنفيذ onGetRoot()
.
Kotlin
override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle? ): BrowserRoot? { ... // Verify that the specified package is SystemUI. You'll need to write your // own logic to do this. if (isSystem(clientPackageName, clientUid)) { rootHints?.let { if (it.getBoolean(BrowserRoot.EXTRA_RECENT)) { // Return a tree with a single playable media item for resumption. val extras = Bundle().apply { putBoolean(BrowserRoot.EXTRA_RECENT, true) } return BrowserRoot(MY_RECENTS_ROOT_ID, extras) } } // You can return your normal tree if the EXTRA_RECENT flag is not present. return BrowserRoot(MY_MEDIA_ROOT_ID, null) } // Return an empty tree to disallow browsing. return BrowserRoot(MY_EMPTY_ROOT_ID, null)
Java
@Override public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { ... // Verify that the specified package is SystemUI. You'll need to write your // own logic to do this. if (isSystem(clientPackageName, clientUid)) { if (rootHints != null) { if (rootHints.getBoolean(BrowserRoot.EXTRA_RECENT)) { // Return a tree with a single playable media item for resumption. Bundle extras = new Bundle(); extras.putBoolean(BrowserRoot.EXTRA_RECENT, true); return new BrowserRoot(MY_RECENTS_ROOT_ID, extras); } } // You can return your normal tree if the EXTRA_RECENT flag is not present. return new BrowserRoot(MY_MEDIA_ROOT_ID, null); } // Return an empty tree to disallow browsing. return new BrowserRoot(MY_EMPTY_ROOT_ID, null); }
السلوك في الإصدارات السابقة لإصدار Android 13
لضمان التوافق مع الإصدارات القديمة، تواصل واجهة مستخدم النظام توفير تصميم بديل
يستخدم إجراءات الإشعارات للتطبيقات التي لا يتم تعديلها لتستهدف الإصدار Android 13،
أو التي لا تتضمّن معلومات PlaybackState
. يتم استخلاص أزرار الإجراءات من قائمة Notification.Action
المرفقة بالإشعار MediaStyle
. يعرض النظام ما يصل إلى خمسة إجراءات بالترتيب الذي تمت إضافتها به. في الوضع المضغوط، يتم عرض ما يصل إلى ثلاثة أزرار، ويتم تحديدها حسب القيم التي تم تمريرها إلى
setShowActionsInCompactView()
.
يتم ترتيب الإجراءات المخصّصة حسب ترتيب إضافتها إلى PlaybackState
.
يوضّح مثال الرمز البرمجي التالي كيفية إضافة إجراءات إلى إشعار MediaStyle :
Kotlin
import androidx.core.app.NotificationCompat import androidx.media3.session.MediaStyleNotificationHelper var notification = NotificationCompat.Builder(context, CHANNEL_ID) // Show controls on lock screen even when user hides sensitive content. .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setSmallIcon(R.drawable.ic_stat_player) // Add media control buttons that invoke intents in your media service .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0 .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1 .addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2 // Apply the media style template .setStyle(MediaStyleNotificationHelper.MediaStyle(mediaSession) .setShowActionsInCompactView(1 /* #1: pause button */)) .setContentTitle("Wonderful music") .setContentText("My Awesome Band") .setLargeIcon(albumArtBitmap) .build()
Java
import androidx.core.app.NotificationCompat; import androidx.media3.session.MediaStyleNotificationHelper; NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL_ID) // Show controls on lock screen even when user hides sensitive content. .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setSmallIcon(R.drawable.ic_stat_player) // Add media control buttons that invoke intents in your media service .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0 .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1 .addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2 // Apply the media style template .setStyle(new MediaStyleNotificationHelper.MediaStyle(mediaSession) .setShowActionsInCompactView(1 /* #1: pause button */)) .setContentTitle("Wonderful music") .setContentText("My Awesome Band") .setLargeIcon(albumArtBitmap) .build();