توجد أدوات التحكّم في الوسائط في Android بالقرب من "الإعدادات السريعة". يتم ترتيب الجلسات من تطبيقات متعددة في لوحة عرض دوّارة قابلة للتمرير السريع. تعرض لوحة العرض الدوّارة الجلسات بالترتيب التالي:
- أحداث البث التي يتم تشغيلها محليًا على الهاتف
- عمليات البث عن بُعد، مثل تلك التي يتم رصدها على الأجهزة الخارجية أو جلسات البث
- الجلسات السابقة القابلة للاستئناف، حسب ترتيب آخر تشغيل لها
بدءًا من الإصدار 13 من نظام التشغيل Android (المستوى 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.
يسترد النظام المعلومات التالية من MediaMetadata في "MediaSession"، ويعرضها عندما تكون متاحة:
METADATA_KEY_ALBUM_ART_URIMETADATA_KEY_TITLEMETADATA_KEY_DISPLAY_TITLEMETADATA_KEY_ARTISTMETADATA_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 include a true boolean value for key SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV. |
|
| 3 | التالي | PlaybackState إجراء يتضمّن ACTION_SKIP_TO_NEXT. |
| مخصص | لا يتضمّن الإجراءان (PlaybackState) "ACTION_SKIP_TO_NEXT"، ويتضمّن الإجراءان المخصّصان (PlaybackState) إجراءً مخصّصًا لم يتم وضعه بعد. |
|
| فارغة | PlaybackState extras include a true boolean value for key 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();