Sterowanie multimediami

Opcje sterowania multimediami na Androidzie znajdują się w pobliżu Szybkich ustawień. Sesje z różnych aplikacji są ułożone w karuzeli, którą można przewijać. Karuzela zawiera listę sesji w tej kolejności:

  • Strumienie odtwarzane lokalnie na telefonie
  • strumienie zdalne, np. wykryte na urządzeniach zewnętrznych lub sesjach przesyłania;
  • poprzednie sesje, które można wznowić, w kolejności, w jakiej były ostatnio odtwarzane;

Od Androida 13 (API na poziomie 33) przyciski poleceń w opcjach sterowania multimediami są pobierane ze stanu Player, aby użytkownicy mieli dostęp do bogatego zestawu opcji sterowania multimediami w aplikacjach odtwarzających multimedia.

Dzięki temu możesz prezentować spójny zestaw opcji sterowania multimediami i zapewniać lepsze wrażenia z korzystania z nich na różnych urządzeniach.

Rysunek 1 pokazuje, jak wygląda to na telefonie i tablecie.

Opcje sterowania multimediami na telefonach i tabletach na przykładzie przykładowego utworu pokazującego, jak mogą wyglądać przyciski
Ilustracja 1. Opcje sterowania multimediami na telefonach i tabletach

System wyświetla maksymalnie 5 przycisków poleceń w zależności od stanu Player, jak opisano w tabeli poniżej. W trybie kompaktowym wyświetlane są tylko pierwsze 3 miejsca na działania. Jest to zgodne ze sposobem renderowania opcji sterowania multimediami na innych platformach Androida, takich jak Auto, Asystent i Wear OS.

Boks Kryteria Działanie
1 playWhenReady ma wartość false lub bieżący stan odtwarzania to STATE_ENDED. Odtwórz
playWhenReady ma wartość prawda, a bieżący stan odtwarzania to STATE_BUFFERING. Wskaźnik postępu wczytywania
playWhenReady ma wartość prawda, a bieżący stan odtwarzania to STATE_READY. Wstrzymaj
2 Ustawienia przycisków multimedialnych zawierają niestandardowy przycisk dla CommandButton.SLOT_BACK. Niestandardowy
Dostępne jest polecenie odtwarzacza COMMAND_SEEK_TO_PREVIOUS lub COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM. Wstecz
Nie jest dostępny ani przycisk niestandardowy, ani żadne z wymienionych poleceń. Pusta
3 Ustawienia przycisków multimedialnych zawierają niestandardowy przycisk dla CommandButton.SLOT_FORWARD Niestandardowy
Dostępne jest polecenie odtwarzacza COMMAND_SEEK_TO_NEXT lub COMMAND_SEEK_TO_NEXT_MEDIA_ITEM. Dalej
Nie jest dostępny ani przycisk niestandardowy, ani żadne z wymienionych poleceń. Pusta
4 Ustawienia przycisków multimedialnych zawierają przycisk niestandardowy dla aplikacji CommandButton.SLOT_OVERFLOW, który nie został jeszcze umieszczony. Niestandardowy
5 Ustawienia przycisków multimedialnych zawierają przycisk niestandardowy dla aplikacji CommandButton.SLOT_OVERFLOW, który nie został jeszcze umieszczony. Niestandardowy

Niestandardowe przyciski rozwijania menu są umieszczane w kolejności, w jakiej zostały dodane do preferencji przycisków multimedialnych.

Dostosowywanie przycisków poleceń

Aby dostosować opcje sterowania multimediami w systemie za pomocą Jetpack Media3, możesz odpowiednio ustawić preferencje przycisków multimedialnych sesji i dostępne polecenia kontrolerów:

  1. Utwórz MediaSession i zdefiniuj preferencje przycisków multimedialnych dla niestandardowych przycisków poleceń.

  2. W MediaSession.Callback.onConnect() autoryzuj kontrolery, definiując ich dostępne polecenia, w tym polecenia niestandardowe, w ConnectionResult.

  3. W pliku MediaSession.Callback.onCustomCommand() odpowiedz na polecenie niestandardowe wybrane przez użytkownika.

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 ListenableFuture onCustomCommand(
        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);
    }
  }
}

Aby dowiedzieć się więcej o konfigurowaniu MediaSession, aby klienci tacy jak system mogli łączyć się z aplikacją do multimediów, przeczytaj artykuł o przyznawaniu kontroli innym klientom.

Gdy zaimplementujesz MediaSession za pomocą Jetpack Media3, PlaybackState będzie automatycznie aktualizowany zgodnie z odtwarzaczem multimediów. Podobnie, gdy implementujesz MediaSessionService, biblioteka automatycznie publikuje powiadomienie MediaStyle i na bieżąco je aktualizuje.

Przyciski poleceń

Gdy użytkownik kliknie przycisk polecenia w systemowych opcjach sterowania multimediami, MediaController systemu wysyła polecenie odtwarzania do MediaSession. MediaSession przekazuje te polecenia do odtwarzacza. Polecenia zdefiniowane w interfejsie Player Media3 są automatycznie obsługiwane przez sesję multimedialną.

Wskazówki dotyczące reagowania na polecenia niestandardowe znajdziesz w artykule Dodawanie poleceń niestandardowych.

Obsługa wznawiania multimediów

Wznowienie odtwarzania multimediów umożliwia użytkownikom ponowne uruchamianie poprzednich sesji z karuzeli bez konieczności uruchamiania aplikacji. Po rozpoczęciu odtwarzania użytkownik może korzystać z opcji sterowania multimediami w zwykły sposób.

Funkcję wznawiania odtwarzania można włączać i wyłączać w aplikacji Ustawienia, w sekcji Dźwięk > Multimedia. Użytkownik może też uzyskać dostęp do ustawień, klikając ikonę koła zębatego, która pojawia się po przesunięciu palcem po rozwiniętej karuzeli.

Media3 udostępnia interfejsy API, które ułatwiają obsługę wznawiania odtwarzania multimediów. Wskazówki dotyczące implementacji tej funkcji znajdziesz w dokumentacji Wznawianie odtwarzania za pomocą Media3.

Korzystanie ze starszych interfejsów API multimediów

W tej sekcji wyjaśniamy, jak zintegrować aplikację z systemowymi opcjami sterowania multimediami za pomocą starszych interfejsów MediaCompat API.

System pobiera z MediaMetadata MediaSession te informacje i wyświetla je, gdy są dostępne:

  • METADATA_KEY_ALBUM_ART_URI
  • METADATA_KEY_TITLE
  • METADATA_KEY_DISPLAY_TITLE
  • METADATA_KEY_ARTIST
  • METADATA_KEY_DURATION (Jeśli czas trwania nie jest ustawiony, pasek przewijania nie pokazuje postępu)

Aby mieć pewność, że powiadomienie o sterowaniu multimediami jest prawidłowe i dokładne, ustaw wartość metadanych METADATA_KEY_TITLE lub METADATA_KEY_DISPLAY_TITLE na tytuł aktualnie odtwarzanych multimediów.

Odtwarzacz multimediów pokazuje czas, który upłynął od rozpoczęcia odtwarzania, oraz pasek przewijania, który jest mapowany na MediaSession PlaybackState.

Odtwarzacz multimediów pokazuje postęp odtwarzania bieżących multimediów oraz pasek przewijania, który jest mapowany na MediaSession PlaybackState. Pasek przewijania umożliwia użytkownikom zmianę pozycji i wyświetla czas, który upłynął od rozpoczęcia odtwarzania elementu multimedialnego. Aby włączyć pasek przewijania, musisz zaimplementować PlaybackState.Builder#setActions i uwzględnić ACTION_SEEK_TO.

Boks Działanie Kryteria
1 Odtwórz Obecny stan zasobu PlaybackState to jeden z tych:
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
Wskaźnik postępu wczytywania Bieżący stan zasobu PlaybackState to jedna z tych wartości:
  • STATE_CONNECTING
  • STATE_BUFFERING
Wstrzymaj Bieżący stan zasobu PlaybackState nie jest żadnym z powyższych.
2 Wstecz PlaybackState działania obejmują ACTION_SKIP_TO_PREVIOUS.
Niestandardowy PlaybackState działania nie obejmują ACTION_SKIP_TO_PREVIOUS, a PlaybackState działania niestandardowe obejmują działanie niestandardowe, które nie zostało jeszcze umieszczone.
Pusta Dodatki PlaybackState zawierają wartość logiczną true dla klucza SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV.
3 Dalej PlaybackState działania obejmują ACTION_SKIP_TO_NEXT.
Niestandardowy PlaybackState działania nie obejmują ACTION_SKIP_TO_NEXT, a PlaybackState działania niestandardowe obejmują działanie niestandardowe, które nie zostało jeszcze umieszczone.
Pusta Dodatki PlaybackState zawierają wartość logiczną true dla klucza SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT.
4 Niestandardowy PlaybackState działania niestandardowe zawierają działanie niestandardowe, które nie zostało jeszcze umieszczone.
5 Niestandardowy PlaybackState działania niestandardowe zawierają działanie niestandardowe, które nie zostało jeszcze umieszczone.

Dodawanie działań standardowych

Poniższe przykłady kodu pokazują, jak dodać standardowe i niestandardowe działania PlaybackState.

Aby odtwarzać, wstrzymywać, przechodzić do poprzedniego i następnego utworu, ustaw te działania w PlaybackState sesji multimedialnej.

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);

Jeśli nie chcesz mieć żadnych przycisków w poprzednich lub następnych miejscach, nie dodawaj ACTION_SKIP_TO_PREVIOUS ani ACTION_SKIP_TO_NEXT, ale dodaj dodatki do sesji:

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);

Dodawanie działań niestandardowych

W przypadku innych działań, które chcesz wyświetlać w opcjach sterowania multimediami, możesz utworzyć PlaybackStateCompat.CustomAction i dodać go do PlaybackState. Działania są wyświetlane w kolejności, w jakiej zostały dodane.

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);

Odpowiadanie na działania PlaybackState

Gdy użytkownik kliknie przycisk, SystemUI używa MediaController.TransportControls, aby wysłać polecenie z powrotem do MediaSession. Musisz zarejestrować wywołanie zwrotne, które może prawidłowo reagować na te zdarzenia.

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);
        }
    }
};

Wznawianie odtwarzania multimediów

Aby aplikacja odtwarzacza pojawiła się w obszarze szybkich ustawień, musisz utworzyć powiadomienie MediaStyle z prawidłowym tokenem MediaSession.

Aby wyświetlić tytuł powiadomienia MediaStyle, użyj NotificationBuilder.setContentTitle().

Aby wyświetlić ikonę marki w odtwarzaczu multimediów, użyj NotificationBuilder.setSmallIcon().

Aby obsługiwać wznawianie odtwarzania, aplikacje muszą implementować MediaBrowserService i MediaSession. Obiekt MediaSession musi implementować wywołanie zwrotne onPlay().

Implementacja usługi MediaBrowserService

Po uruchomieniu urządzenia system wyszukuje 5 ostatnio używanych aplikacji do multimediów i udostępnia elementy sterujące, których można użyć do ponownego rozpoczęcia odtwarzania w każdej aplikacji.

System próbuje skontaktować się z urządzeniem MediaBrowserService, nawiązując połączenie z SystemUI. Aplikacja musi zezwalać na takie połączenia, w przeciwnym razie nie będzie mogła obsługiwać wznawiania odtwarzania.

Połączenia z interfejsu systemu można identyfikować i weryfikować za pomocą nazwy pakietu com.android.systemui i podpisu. Interfejs systemu jest podpisany podpisem platformy. Przykład sprawdzania podpisu platformy znajdziesz w aplikacji UAMP.

Aby obsługiwać wznawianie odtwarzania, MediaBrowserService musi implementować te zachowania:

  • onGetRoot() musi szybko zwrócić element główny o wartości innej niż null. Inne złożone operacje logiczne powinny być obsługiwane w onLoadChildren()

  • Gdy metoda onLoadChildren() jest wywoływana w głównym identyfikatorze multimediów, wynik musi zawierać element podrzędny FLAG_PLAYABLE.

  • MediaBrowserService powinna zwracać ostatnio odtwarzany element multimedialny, gdy otrzyma zapytanie EXTRA_RECENT. Zwrócona wartość powinna być rzeczywistym elementem multimedialnym, a nie ogólną funkcją.

  • MediaBrowserService musi podać odpowiedni obiekt MediaDescription z niepustymi polami title i subtitle. Powinien też ustawić identyfikator URI ikony lub bitmapę ikony.

Poniższe przykłady kodu pokazują, jak zaimplementować 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);
}

Działanie na urządzeniach z Androidem w wersji starszej niż 13

W celu zachowania zgodności wstecznej interfejs systemu nadal udostępnia alternatywny układ, który używa działań związanych z powiadomieniami w przypadku aplikacji, które nie zostały zaktualizowane do Androida 13 lub nie zawierają informacji PlaybackState. Przyciski poleceń pochodzą z listy Notification.Action dołączonej do powiadomienia MediaStyle. System wyświetla maksymalnie 5 działań w kolejności, w jakiej zostały dodane. W trybie kompaktowym wyświetlane są maksymalnie 3 przyciski, których liczba zależy od wartości przekazanych do setShowActionsInCompactView().

Działania niestandardowe są umieszczane w kolejności, w jakiej zostały dodane do PlaybackState.

Poniższy przykładowy kod pokazuje, jak dodać działania do powiadomienia 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();