تقع عناصر التحكّم في الوسائط على أجهزة Android بالقرب من "الإعدادات السريعة". يتم ترتيب الجلسات من تطبيقات متعدّدة في لوحة عرض دوّارة قابلة للتمرير. تعرض اللوحة الدوّارة الجلسات بهذا الترتيب:
- الفيديوهات التي يتم تشغيلها محليًا على الهاتف
- الفيديوهات عن بُعد، مثل تلك التي يتم رصدها على الأجهزة الخارجية أو جلسات البث
- الجلسات السابقة القابلة للاستئناف، بالترتيب الذي تم تشغيلها به آخر مرة
بدءًا من Android 13 (المستوى 33 من واجهة برمجة التطبيقات)، ولضمان وصول المستخدمين إلى مجموعة كبيرة من أدوات التحكّم في الوسائط للتطبيقات التي تشغّل الوسائط، يتم استخلاص أزرار الإجراءات في أدوات التحكّم في الوسائط من حالة Player.
بهذه الطريقة، يمكنك عرض مجموعة متّسقة من عناصر التحكّم في الوسائط وتجربة أكثر سلاسة للتحكّم في الوسائط على جميع الأجهزة.
يعرض الشكل 1 مثالاً على كيفية ظهور ذلك على جهاز هاتف وجهاز لوحي على التوالي.
يعرض النظام ما يصل إلى خمسة أزرار إجراءات استنادًا إلى حالة Player كما هو موضّح في الجدول التالي. في الوضع المصغّر، يتم عرض أول ثلاث شرائح للإجراءات فقط. يتوافق ذلك مع طريقة عرض عناصر التحكّم في الوسائط في منصات Android الأخرى، مثل Auto و"مساعد Google" وWear OS.
| الشريحة | المعايير | الإجراء |
|---|---|---|
| 1 |
playWhenReady هي false أو حالة التشغيل الحالية
للتشغيل هي STATE_ENDED.
|
تشغيل |
playWhenReady هي true وحالة التشغيل الحالية
هي STATE_BUFFERING.
|
مؤشر سريان عملية التحميل | |
playWhenReady هي true وحالة التشغيل الحالية
هي 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
`Player` في Media3.
يُرجى الرجوع إلى إضافة أوامر مخصّصة للحصول على إرشادات حول كيفية الردّ على أمر مخصّص.
إتاحة استئناف تشغيل الوسائط
يسمح استئناف تشغيل الوسائط للمستخدمين بإعادة تشغيل الجلسات السابقة من اللوحة الدوّارة بدون الحاجة إلى بدء التطبيق. وعند بدء التشغيل، يتفاعل المستخدم مع عناصر التحكّم في الوسائط بالطريقة المعتادة.
يمكن تفعيل ميزة استئناف التشغيل وإيقافها باستخدام تطبيق "الإعدادات"، ضمن الخيار الصوت > الوسائط. يمكن للمستخدم أيضًا الوصول إلى "الإعدادات" من خلال النقر على رمز الترس الذي يظهر بعد التمرير سريعًا على اللوحة الدوّارة الموسّعة.
تقدّم Media3 واجهات برمجة تطبيقات لتسهيل إتاحة استئناف تشغيل الوسائط. يُرجى الاطّلاع على مستندات استئناف التشغيل باستخدام Media3 للحصول على إرشادات حول تنفيذ هذه الميزة.
استخدام واجهات برمجة التطبيقات القديمة للوسائط
يوضّح هذا القسم كيفية التكامل مع عناصر التحكّم في الوسائط على مستوى النظام باستخدام واجهات برمجة تطبيقات MediaCompat القديمة.
يستردّ النظام المعلومات التالية من MediaMetadata في MediaSession ويعرضها عندما تكون متاحة:
METADATA_KEY_ALBUM_ART_URIMETADATA_KEY_TITLEMETADATA_KEY_DISPLAY_TITLEMETADATA_KEY_ARTISTMETADATA_KEY_DURATION(إذا لم يتم ضبط المدة، لن يظهر شريط التقدّم)
لضمان حصولك على إشعار صالح ودقيق للتحكّم في الوسائط، اضبط قيمة البيانات الوصفية METADATA_KEY_TITLE أو METADATA_KEY_DISPLAY_TITLE على عنوان الوسائط التي يتم تشغيلها حاليًا.
يعرض مشغّل الوسائط الوقت المنقضي للوسائط التي يتم تشغيلها حاليًا، بالإضافة إلى شريط البحث الذي يتم ربطه بـ PlaybackState في MediaSession.
يعرض مشغّل الوسائط مستوى التقدّم للوسائط التي يتم تشغيلها حاليًا، بالإضافة إلى شريط البحث الذي يتم ربطه بـ PlaybackState في MediaSession. يسمح شريط التقديم/الترجيع للمستخدمين بتغيير الموضع ويعرض الوقت المنقضي لملف وسائط. لتفعيل شريط التقديم/الترجيع، يجب تنفيذ PlaybackState.Builder#setActions وتضمين ACTION_SEEK_TO.
| الشريحة | الإجراء | المعايير |
|---|---|---|
| 1 | تشغيل |
الحالة الحالية لـ state هي إحدى الحالات التالية:
PlaybackState
|
| مؤشر سريان عملية التحميل |
الحالة الحالية لـ PlaybackState هي إحدى الحالات التالية:
|
|
| إيقاف مؤقت | الحالة الحالية لـ PlaybackState ليست أيًا من الحالات أعلاه. |
|
| 2 | السابق | PlaybackState تتضمّن إجراءات الخيار ACTION_SKIP_TO_PREVIOUS. |
| مخصص | PlaybackState الإجراءات لا تتضمّن ACTION_SKIP_TO_PREVIOUS، وتتضمّن PlaybackState الإجراءات المخصّصة إجراءً مخصّصًا لم يتم وضعه بعد. |
|
| فارغة | PlaybackState الإضافات تتضمّن قيمة منطقية true للمفتاح SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV. |
|
| 3 | التالي | PlaybackState تتضمّن إجراءات الخيار ACTION_SKIP_TO_NEXT. |
| مخصص | PlaybackState الإجراءات لا تتضمّن ACTION_SKIP_TO_NEXT، وتتضمّن PlaybackState الإجراءات المخصّصة إجراءً مخصّصًا لم يتم وضعه بعد. |
|
| فارغة | PlaybackState تتضمّن الإضافات قيمة منطقية 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
عندما ينقر المستخدم على زر، تستخدم واجهة مستخدم النظام
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 باستخدام اتصال من واجهة مستخدم النظام. يجب أن يسمح تطبيقك بهذه الاتصالات، وإلا لن يتمكّن من إتاحة استئناف التشغيل.
يمكن تحديد الاتصالات من واجهة مستخدم النظام والتحقّق منها باستخدام اسم الحزمة com.android.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();