Sterowanie multimediami

Elementy sterujące multimediami na Androidzie znajdują się w pobliżu Szybkich ustawień. Sesje z wielu aplikacji są ułożone w karuzeli, którą można przesuwać. Karuzela zawiera sesje w tej kolejności:

  • Strumienie odtwarzane lokalnie na telefonie
  • strumienie zdalne, np. wykryte na urządzeniach zewnętrznych lub w sesjach przesyłania;
  • Poprzednie sesje z możliwością wznowienia w kolejności, w jakiej były ostatnio odtwarzane

Od Androida 13 (poziom API 33) przyciski działań w elementach sterujących odtwarzaniem multimediów są wyodrębniane ze stanu Player, aby zapewnić użytkownikom dostęp do bogatego zestawu elementów sterujących odtwarzaniem multimediów w aplikacjach odtwarzających multimedia.

Dzięki temu możesz wyświetlać spójny zestaw elementów sterujących multimediami i zapewnić bardziej dopracowane sterowanie multimediami na różnych urządzeniach.

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

sterowanie multimediami na telefonach i tabletach, na przykładzie przykładowego utworu pokazującego, jak mogą wyglądać przyciski;
Rysunek 1. Sterowanie multimediami na telefonach i tabletach

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

Boks Kryteria Działanie
1 playWhenReady – wartość jest fałszywa lub bieżący stan odtwarzania to STATE_ENDED. Odtwórz
playWhenReady jest prawdziwe, a obecny stan odtwarzania to STATE_BUFFERING. Wskaźnik postępu wczytywania
playWhenReady jest prawdziwe, a obecny stan odtwarzania to STATE_READY. Wstrzymaj
2 Preferencje przycisków multimediów zawierają przycisk niestandardowy dla CommandButton.SLOT_BACK Możliwość
Dostępne są polecenia 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ń. Puste
3 Ustawienia przycisku multimediów zawierają przycisk niestandardowy dla CommandButton.SLOT_FORWARD Możliwość
Dostępne są polecenia 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ń. Puste
4 ustawieniach przycisku multimediów znajduje się niestandardowy przycisk dla CommandButton.SLOT_OVERFLOW, który nie został jeszcze umieszczony. Możliwość
5 ustawieniach przycisku multimediów znajduje się niestandardowy przycisk dla CommandButton.SLOT_OVERFLOW, który nie został jeszcze umieszczony. Możliwość

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

Dostosowywanie przycisków poleceń

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

  1. Utwórz MediaSessionokreśl preferencje przycisku multimediów dla przycisków poleceń niestandardowych.

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

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

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

Więcej informacji o konfigurowaniu MediaSession, aby klienci, np. system, mogli łączyć się z Twoją aplikacją multimedialną, znajdziesz w artykule Udzielanie kontroli innym klientom.

Gdy w Jetpack Media3 zaimplementujesz MediaSession, PlaybackState będzie automatycznie aktualizowany w odtwarzaczu multimediów. Podobnie, gdy zaimplementujesz MediaSessionService, biblioteka automatycznie opublikuje MediaStyle powiadomienie i będzie je aktualizować.

Odpowiadanie na przyciski działań

Gdy użytkownik kliknie przycisk działania w systemowych elementach sterujących multimediami, systemMediaController wyśle polecenie odtwarzania do Twojej MediaSession. MediaSession następnie przekazuje te polecenia do odtwarzacza. Polecenia zdefiniowane w interfejsie Player Media3 są automatycznie obsługiwane przez sesję multimedialną.

Więcej informacji o tym, jak odpowiadać na polecenia niestandardowe, znajdziesz w artykule Dodawanie poleceń niestandardowych.

Obsługa wznowienia multimediów

Wznawianie odtwarzania multimediów umożliwia użytkownikom ponowne uruchomienie poprzednich sesji z poziomu karuzeli bez konieczności uruchamiania aplikacji. Po rozpoczęciu odtwarzania użytkownik wchodzi w interakcję z elementami sterującymi odtwarzaniem multimediów 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ż otworzyć Ustawienia, klikając ikonę koła zębatego, która pojawia się po przesunięciu palcem po rozwiniętym karuzeli.

Media3 udostępnia interfejsy API, które ułatwiają obsługę wznawiania odtwarzania multimediów. Więcej informacji o wdrażaniu tej funkcji znajdziesz w dokumentacji dotyczącej wznawiania odtwarzania za pomocą Media3.

Korzystanie ze starszych interfejsów API multimediów

W tej sekcji opisujemy, jak zintegrować się z systemowymi elementami sterującymi multimediami za pomocą starszych interfejsów MediaCompat API.

System pobiera z MediaSessionMediaMetadata 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 odtwarzanego multimedium.

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

Odtwarzacz multimediów pokazuje postęp odtwarzania bieżących treści oraz pasek przewijania, który jest powiązany z przyciskiem MediaSession PlaybackState. Pasek przewijania umożliwia użytkownikom zmianę pozycji i wyświetla czas, który upłynął od początku elementu multimedialnego. Aby włączyć pasek przewijania, musisz wdrożyć PlaybackState.Builder#setActions i uwzględnić ACTION_SEEK_TO.

Boks Działanie Kryteria
1 Odtwórz Obecny stan PlaybackState może być jednym z tych stanów:
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
Wskaźnik postępu wczytywania Obecny stan PlaybackState jest jednym z tych stanów:
  • STATE_CONNECTING
  • STATE_BUFFERING
Wstrzymaj Obecny stan PlaybackState nie jest żadnym z powyższych.
2 Wstecz PlaybackState działania obejmują ACTION_SKIP_TO_PREVIOUS.
Możliwość PlaybackState Działania nie zawierają elementu ACTION_SKIP_TO_PREVIOUS, a PlaybackState działania niestandardowe zawierają działanie niestandardowe, które nie zostało jeszcze umieszczone.
Puste PlaybackState dodatki zawierają true wartość logiczną dla klucza SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV.
3 Dalej PlaybackState działania obejmują ACTION_SKIP_TO_NEXT.
Możliwość PlaybackState Działania nie zawierają elementu ACTION_SKIP_TO_NEXT, a PlaybackState działania niestandardowe zawierają działanie niestandardowe, które nie zostało jeszcze umieszczone.
Puste PlaybackState dodatki zawierają true wartość logiczną dla klucza SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT.
4 Możliwość PlaybackState działania niestandardowe obejmują działanie niestandardowe, które nie zostało jeszcze umieszczone;
5 Możliwość PlaybackState działania niestandardowe obejmują działanie niestandardowe, które nie zostało jeszcze umieszczone;

Dodawanie standardowych działań

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

W przypadku odtwarzania, wstrzymywania, poprzedniego i następnego ustaw te działania w PlaybackState dla 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, aby w poprzednim lub następnym slocie były przyciski, nie dodawaj elementów ACTION_SKIP_TO_PREVIOUS ani ACTION_SKIP_TO_NEXT, tylko dodaj dodatkowe elementy 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 elementach sterujących multimediami, możesz utworzyć PlaybackStateCompat.CustomAction i dodać je do PlaybackState. Te 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żyje MediaController.TransportControls do wysłania polecenia z powrotem do MediaSession. Musisz zarejestrować wywołanie zwrotne, które będzie 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);
        }
    }
};

Wznowienie multimediów

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

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

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

Aby obsługiwać wznawianie odtwarzania, aplikacje muszą implementować MediaBrowserServiceMediaSession. Twój MediaSession musi implementować wywołanie zwrotne onPlay().

Implementacja usługi MediaBrowserService

Po uruchomieniu urządzenia system wyszukuje 5 ostatnio używanych aplikacji multimedialnych i udostępnia elementy sterujące, za pomocą których można ponownie uruchomić odtwarzanie w każdej z nich.

System próbuje skontaktować się z Twoim MediaBrowserService za pomocą połączenia z SystemUI. Aplikacja musi zezwalać na takie połączenia, w przeciwnym razie nie będzie obsługiwać wznawiania odtwarzania.

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

Aby obsługiwać wznawianie odtwarzania, MediaBrowserService musi wykonywać te działania:

  • onGetRoot() musi szybko zwrócić niepusty węzeł główny. Inna złożona logika powinna być obsługiwana w onLoadChildren()

  • Gdy funkcja onLoadChildren() jest wywoływana w przypadku głównego identyfikatora multimediów, wynik musi zawierać element podrzędny FLAG_PLAYABLE.

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

  • MediaBrowserService musi podać odpowiedni element MediaDescription z niepustym elementem titlesubtitle. Powinien też ustawić identyfikator URI ikony lub mapę bitową ikony.

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

Zachowanie przed Androidem 13

Aby zachować zgodność wsteczną, interfejs systemu nadal udostępnia alternatywny układ, który wykorzystuje działania powiadomień w przypadku aplikacji, które nie zostały zaktualizowane pod kątem Androida 13 lub nie zawierają informacji PlaybackState. Przyciski poleceń są tworzone na podstawie 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, w zależności od wartości przekazywanych do elementu setShowActionsInCompactView().

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

Poniższy przykład kodu 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();