Die Mediensteuerung in Android befindet sich in der Nähe der Schnelleinstellungen. Sitzungen aus mehreren Apps werden in einem wischbaren Karussell angeordnet. Im Karussell werden die Sessions in dieser Reihenfolge aufgeführt:
- Streams, die lokal auf dem Smartphone abgespielt werden
- Remotestreams, z. B. solche, die auf externen Geräten oder in Cast-Sitzungen erkannt werden
- Vorherige fortsetzbare Sitzungen in der Reihenfolge, in der sie zuletzt gespielt wurden
Ab Android 13 (API-Level 33) werden Aktionsschaltflächen in Mediensteuerelementen aus dem Player
-Status abgeleitet, damit Nutzer auf eine Vielzahl von Mediensteuerelementen für Apps zugreifen können, die Medien wiedergeben.
So können Sie auf allen Geräten einheitliche Media-Steuerelemente und eine optimierte Media-Steuerung anbieten.
Abbildung 1 zeigt ein Beispiel dafür, wie das auf einem Smartphone und einem Tablet aussieht.
Das System zeigt je nach Player
-Status bis zu fünf Aktionsschaltflächen an, wie in der folgenden Tabelle beschrieben. Im kompakten Modus werden nur die ersten drei Aktionsfelder angezeigt. Dies entspricht der Darstellung von Mediensteuerelementen auf anderen Android-Plattformen wie Auto, Assistant und Wear OS.
Stellen | Kriterien | Aktion |
---|---|---|
1 |
playWhenReady ist „false“ oder der aktuelle
Wiedergabestatus ist STATE_ENDED .
|
Wiedergeben |
playWhenReady ist „true“ und der aktuelle
Wiedergabestatus ist STATE_BUFFERING .
|
Rotierendes Ladesymbol | |
playWhenReady ist „true“ und der aktuelle
Wiedergabestatus ist STATE_READY .
|
Pausieren | |
2 |
Die Einstellungen für die Mediataste enthalten eine benutzerdefinierte Taste für CommandButton.SLOT_BACK .
|
Benutzerdefiniert |
Player-Befehl
COMMAND_SEEK_TO_PREVIOUS oder
COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM ist verfügbar.
|
Zurück | |
Weder eine benutzerdefinierte Schaltfläche noch einer der aufgeführten Befehle ist verfügbar. | Leer | |
3 |
Die Einstellungen für die Medientaste enthalten eine benutzerdefinierte Taste für CommandButton.SLOT_FORWARD .
|
Benutzerdefiniert |
Player-Befehl
COMMAND_SEEK_TO_NEXT oder
COMMAND_SEEK_TO_NEXT_MEDIA_ITEM ist verfügbar.
|
Weiter | |
Weder eine benutzerdefinierte Schaltfläche noch einer der aufgeführten Befehle ist verfügbar. | Leer | |
4 |
Die Einstellungen für die Media-Schaltfläche enthalten eine benutzerdefinierte Schaltfläche für CommandButton.SLOT_OVERFLOW , die noch nicht platziert wurde.
|
Benutzerdefiniert |
5 |
Die Einstellungen für die Media-Schaltfläche enthalten eine benutzerdefinierte Schaltfläche für CommandButton.SLOT_OVERFLOW , die noch nicht platziert wurde.
|
Benutzerdefiniert |
Benutzerdefinierte Überlaufschaltflächen werden in der Reihenfolge platziert, in der sie den Einstellungen für Media-Schaltflächen hinzugefügt wurden.
Befehlsschaltflächen anpassen
Wenn Sie die System-Media-Steuerelemente mit Jetpack Media3 anpassen möchten, können Sie die Einstellungen für die Media-Schaltfläche der Sitzung und die verfügbaren Befehle der Controller entsprechend festlegen:
Erstellen Sie ein
MediaSession
und definieren Sie die Einstellungen für die Media-Schaltfläche für benutzerdefinierte Befehlsschaltflächen.Autorisieren Sie Controller in
MediaSession.Callback.onConnect()
, indem Sie ihre verfügbaren Befehle, einschließlich benutzerdefinierter Befehle, inConnectionResult
definieren.Reagiere in
MediaSession.Callback.onCustomCommand()
auf die Auswahl des benutzerdefinierten Befehls durch den Nutzer.
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); } } }
Weitere Informationen zum Konfigurieren von MediaSession
, damit Clients wie das System eine Verbindung zu Ihrer Media-App herstellen können, finden Sie unter Anderen Clients die Steuerung ermöglichen.
Wenn Sie mit Jetpack Media3 ein MediaSession
implementieren, wird Ihr PlaybackState
automatisch mit dem Media Player auf dem neuesten Stand gehalten. Wenn Sie eine MediaSessionService
implementieren, veröffentlicht die Bibliothek automatisch eine MediaStyle
Benachrichtigung für Sie und hält sie auf dem neuesten Stand.
Auf Aktionsschaltflächen reagieren
Wenn ein Nutzer in den System-Media-Steuerelementen auf eine Aktionsschaltfläche tippt, sendet das MediaController
des Systems einen Wiedergabebefehl an dein MediaSession
. MediaSession
leitet diese Befehle dann an den Player weiter. Befehle, die in der Media3-Schnittstelle Player
definiert sind, werden automatisch von der Mediensitzung verarbeitet.
Eine Anleitung dazu, wie Sie auf einen benutzerdefinierten Befehl reagieren, finden Sie unter Benutzerdefinierte Befehle hinzufügen.
Fortsetzung der Medienwiedergabe unterstützen
Mit der Medienwiedergabe können Nutzer vorherige Sitzungen über das Karussell fortsetzen, ohne die App starten zu müssen. Wenn die Wiedergabe beginnt, interagiert der Nutzer wie gewohnt mit den Mediensteuerelementen.
Die Funktion zum Fortsetzen der Wiedergabe kann in den Einstellungen unter Ton > Medien aktiviert und deaktiviert werden. Der Nutzer kann auch auf die Einstellungen zugreifen, indem er auf das Zahnradsymbol tippt, das nach dem Wischen im maximierten Karussell angezeigt wird.
Media3 bietet APIs, die die Unterstützung der Medienwiederaufnahme erleichtern. Weitere Informationen zur Implementierung dieses Features finden Sie in der Dokumentation zur Wiederaufnahme der Wiedergabe mit Media3.
Legacy-Media-APIs verwenden
In diesem Abschnitt wird erläutert, wie du die MediaCompat-APIs verwenden kannst, um die System-Media-Steuerelemente zu integrieren.
Das System ruft die folgenden Informationen aus der MediaMetadata
des MediaSession
ab und zeigt sie an, sofern sie verfügbar sind:
METADATA_KEY_ALBUM_ART_URI
METADATA_KEY_TITLE
METADATA_KEY_DISPLAY_TITLE
METADATA_KEY_ARTIST
METADATA_KEY_DURATION
(Wenn die Dauer nicht festgelegt ist, wird auf der Suchleiste kein Fortschritt angezeigt.)
Damit du eine gültige und korrekte Benachrichtigung zur Mediensteuerung erhältst, musst du den Wert der Metadaten METADATA_KEY_TITLE
oder METADATA_KEY_DISPLAY_TITLE
auf den Titel der aktuell wiedergegebenen Medien festlegen.
Im Mediaplayer wird die verstrichene Zeit für die aktuell wiedergegebenen Medien zusammen mit einer Suchleiste angezeigt, die der MediaSession
PlaybackState
zugeordnet ist.
Der Mediaplayer zeigt den Fortschritt der aktuell wiedergegebenen Medien sowie eine Suchleiste an, die der MediaSession
PlaybackState
zugeordnet ist. Über die Suchleiste können Nutzer die Position ändern. Außerdem wird die verstrichene Zeit für das Media-Element angezeigt. Damit die Suchleiste aktiviert wird, müssen Sie PlaybackState.Builder#setActions
implementieren und ACTION_SEEK_TO
einfügen.
Stellen | Aktion | Kriterien |
---|---|---|
1 | Wiedergeben |
Der aktuelle Status von PlaybackState ist einer der folgenden:
|
Rotierendes Ladesymbol |
Der aktuelle Status von PlaybackState ist einer der folgenden:
|
|
Pausieren | Der aktuelle Status von PlaybackState ist keiner der oben genannten. |
|
2 | Zurück | PlaybackState actions umfasst ACTION_SKIP_TO_PREVIOUS . |
Benutzerdefiniert | PlaybackState -Aktionen enthalten kein ACTION_SKIP_TO_PREVIOUS und PlaybackState -benutzerdefinierte Aktionen enthalten eine benutzerdefinierte Aktion, die noch nicht platziert wurde. |
|
Leer | PlaybackState extras enthält einen booleschen Wert true für den Schlüssel SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV . |
|
3 | Weiter | PlaybackState actions umfasst ACTION_SKIP_TO_NEXT . |
Benutzerdefiniert | PlaybackState -Aktionen enthalten kein ACTION_SKIP_TO_NEXT und PlaybackState -benutzerdefinierte Aktionen enthalten eine benutzerdefinierte Aktion, die noch nicht platziert wurde. |
|
Leer | PlaybackState extras enthält einen booleschen Wert true für den Schlüssel SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT . |
|
4 | Benutzerdefiniert | PlaybackState Benutzerdefinierte Aktionen enthalten eine benutzerdefinierte Aktion, die noch nicht platziert wurde. |
5 | Benutzerdefiniert | PlaybackState Benutzerdefinierte Aktionen enthalten eine benutzerdefinierte Aktion, die noch nicht platziert wurde. |
Standardaktionen hinzufügen
Die folgenden Codebeispiele veranschaulichen, wie Sie PlaybackState
-Standard- und benutzerdefinierte Aktionen hinzufügen.
Für die Wiedergabe, Pause, vorherige und nächste Aktion legen Sie diese Aktionen in der PlaybackState
für die Mediensitzung fest.
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);
Wenn Sie keine Schaltflächen in den vorherigen oder nächsten Slots wünschen, fügen Sie ACTION_SKIP_TO_PREVIOUS
oder ACTION_SKIP_TO_NEXT
nicht hinzu, sondern stattdessen Extras zur Sitzung:
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);
Benutzerdefinierte Aktionen hinzufügen
Für andere Aktionen, die in der Mediensteuerung angezeigt werden sollen, kannst du ein PlaybackStateCompat.CustomAction
erstellen und es stattdessen dem PlaybackState
hinzufügen. Diese Aktionen werden in der Reihenfolge angezeigt, in der sie hinzugefügt wurden.
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);
Auf PlaybackState-Aktionen reagieren
Wenn ein Nutzer auf eine Schaltfläche tippt, verwendet SystemUI MediaController.TransportControls
, um einen Befehl an MediaSession
zurückzusenden. Sie müssen einen Callback registrieren, der auf diese Ereignisse reagieren kann.
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); } } };
Fortsetzung der Medienwiedergabe
Damit Ihre Player-App im Bereich für Schnelleinstellungen angezeigt wird, müssen Sie eine MediaStyle
-Benachrichtigung mit einem gültigen MediaSession
-Token erstellen.
Verwenden Sie NotificationBuilder.setContentTitle()
, um den Titel für die MediaStyle-Benachrichtigung anzuzeigen.
Verwenden Sie NotificationBuilder.setSmallIcon()
, um das Markensymbol für den Mediaplayer anzuzeigen.
Damit die Wiedergabe fortgesetzt werden kann, müssen Apps MediaBrowserService
und MediaSession
implementieren. Ihr MediaSession
muss den onPlay()
-Callback implementieren.
MediaBrowserService
-Implementierung
Nach dem Start des Geräts sucht das System nach den fünf zuletzt verwendeten Media-Apps und bietet Steuerelemente, mit denen die Wiedergabe in jeder App neu gestartet werden kann.
Das System versucht, über eine Verbindung von SystemUI Kontakt zu Ihrem MediaBrowserService
aufzunehmen. Ihre App muss solche Verbindungen zulassen, da sonst die Wiedergabe nicht fortgesetzt werden kann.
Verbindungen von SystemUI können anhand des Paketnamens com.android.systemui
und der Signatur identifiziert und überprüft werden. Die SystemUI ist mit der Plattformsignatur signiert. Ein Beispiel dafür, wie die Plattformsignatur geprüft werden kann, findest du in der UAMP-App.
Damit die Wiedergabe fortgesetzt werden kann, muss dein MediaBrowserService
diese Verhaltensweisen implementieren:
onGetRoot()
muss schnell einen nicht leeren Stamm zurückgeben. Andere komplexe Logik sollte inonLoadChildren()
verarbeitet werden.Wenn
onLoadChildren()
für die Media-ID des Stammverzeichnisses aufgerufen wird, muss das Ergebnis ein untergeordnetes Element vom Typ FLAG_PLAYABLE enthalten.MediaBrowserService
sollte das zuletzt wiedergegebene Media-Element zurückgeben, wenn eine Anfrage vom Typ EXTRA_RECENT empfangen wird. Der zurückgegebene Wert sollte ein tatsächliches Media-Element und keine generische Funktion sein.MediaBrowserService
muss eine geeignete MediaDescription mit einem nicht leeren title und subtitle enthalten. Außerdem sollte ein Symbol-URI oder eine Symbol-Bitmap festgelegt werden.
Die folgenden Codebeispiele veranschaulichen, wie onGetRoot()
implementiert wird.
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); }
Verhalten vor Android 13
Aus Gründen der Abwärtskompatibilität bietet die System-UI weiterhin ein alternatives Layout, in dem Benachrichtigungsaktionen für Apps verwendet werden, die nicht für Android 13 aktualisiert wurden oder keine PlaybackState
-Informationen enthalten. Die Aktionsschaltflächen werden aus der Liste Notification.Action
abgeleitet, die der Benachrichtigung MediaStyle
angehängt ist. Das System zeigt bis zu fünf Aktionen in der Reihenfolge an, in der sie hinzugefügt wurden. Im kompakten Modus werden bis zu drei Schaltflächen angezeigt, die durch die in setShowActionsInCompactView()
übergebenen Werte bestimmt werden.
Benutzerdefinierte Aktionen werden in der Reihenfolge platziert, in der sie dem PlaybackState
hinzugefügt wurden.
Das folgende Codebeispiel zeigt, wie Sie der MediaStyle-Benachrichtigung Aktionen hinzufügen :
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();