Medya denetimleri

Android'deki medya denetimleri Hızlı Ayarlar'ın yakınındadır. Şu kaynaktan gelen oturumlar: birden fazla uygulama kaydırılabilir bir bantta düzenlenir. Bant, oturumları listeler şu sırayla:

  • Telefonda yerel olarak oynatılan canlı yayınlar
  • Harici cihazlarda veya yayın oturumlarında algılananlar gibi uzak yayınlar
  • Son çalındıkları sırayla önceki devam ettirilebilir oturumlar

Kullanıcıların zengin bir içeriğe erişebilmesini sağlamak için Android 13'ten (API düzeyi 33) itibaren Medya oynatan uygulamalar için medya denetimleri grubu, medya denetimlerindeki işlem düğmeleri Player durumundan türetilir.

Böylece tutarlı medya denetimleri sunabilir ve daha gösterişli tüm cihazlarda medya kontrolü deneyimi.

Şekil 1'de bunun bir telefon ve tablet cihazında nasıl görüneceği gösterilmektedir. tıklayın.

Telefon ve tabletlerde nasıl göründükleri konusunda medya denetimleri
            düğmelerin nasıl görünebileceğini gösteren örnek bir parça örneği kullanarak
Şekil 1: Telefon ve tablet cihazlardaki medya kontrolleri

Sistem, Player durumuna göre en fazla beş işlem düğmesi gösterir. aşağıdaki tabloda açıklanmıştır. Kompakt modda, yalnızca ilk üç işlem yuva gösterilir. Bu, medya denetimlerinin diğer Auto, Asistan ve Wear OS gibi Android platformları.

Alan Ölçütler İşlem
1 playWhenReady yanlış veya geçerli oynatma eyalet STATE_ENDED. Oynat
playWhenReady doğru ve mevcut oynatma durumu STATE_BUFFERING. Yükleme döner simgesi
playWhenReady doğru ve mevcut oynatma durumu STATE_READY. Duraklat
2 Oynatıcı komutu COMMAND_SEEK_TO_PREVIOUS veya COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM kullanılabilir. Önceki
COMMAND_SEEK_TO_PREVIOUS veya COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM oynatıcı komutu kullanılamaz. Ayrıca henüz yerleştirilmeyen özel düzenden özel bir komut, alanı doldurmak için kullanılabilir. Özel
(henüz Media3 ile desteklenmemektedir) PlaybackState ekstraları, EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV anahtarı için true boole değeri içerir. Boş
3 Oynatıcı komutu COMMAND_SEEK_TO_NEXT veya COMMAND_SEEK_TO_NEXT_MEDIA_ITEM kullanılabilir. Sonraki
COMMAND_SEEK_TO_NEXT veya COMMAND_SEEK_TO_NEXT_MEDIA_ITEM oynatıcı komutu kullanılamaz. Ayrıca henüz yerleştirilmeyen özel düzenden özel bir komut, alanı doldurmak için kullanılabilir. Özel
(henüz Media3 ile desteklenmemektedir) PlaybackState ekstraları, EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT anahtarı için true boole değeri içerir. Boş
4 Alanı doldurmak için henüz yerleştirilmeyen özel düzenden özel bir komut kullanılabilir. Özel
5 Alanı doldurmak için henüz yerleştirilmeyen özel düzenden özel bir komut kullanılabilir. Özel

Özel komutlar özel düzen.

Komut düğmelerini özelleştir

Sistem medya denetimlerini Jetpack Media3 ile özelleştirmek için oturumun özel düzenini ve uygun şekilde kontrol edin: MediaSessionService uygularken:

  1. onCreate() bölgesinde MediaSession oluşturun ve özel düzeni tanımlamanızı sağlar ekleyebilirsiniz.

  2. MediaSession.Callback.onConnect() bölgesinde, Mevcut komutlarını tanımlayarak denetleyicileri yetkilendirin. Örneğin, özel komutlar ConnectionResult içinde.

  3. MediaSession.Callback.onCustomCommand() bölgesinde, kullanıcı tarafından seçilen özel komuta yanıt verme.

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()
        .setDisplayName("Save to favorites")
        .setIconResId(R.drawable.favorite_icon)
        .setSessionCommand(customCommandFavorites)
        .build()
    val player = ExoPlayer.Builder(this).build()
    // Build the session with a custom layout.
    mediaSession =
      MediaSession.Builder(this, player)
        .setCallback(MyCallback())
        .setCustomLayout(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)
      .setAvailablePlayerCommands(
        ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
          .remove(COMMAND_SEEK_TO_NEXT)
          .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
          .remove(COMMAND_SEEK_TO_PREVIOUS)
          .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
          .build()
      )
      .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()
            .setDisplayName("Save to favorites")
            .setIconResId(R.drawable.favorite_icon)
            .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())
            .setCustomLayout(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)
          .setAvailablePlayerCommands(
              ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
                .remove(COMMAND_SEEK_TO_NEXT)
                .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
                .remove(COMMAND_SEEK_TO_PREVIOUS)
                .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
                .build())
          .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);
    }
  }
}

MediaSession yapılandırması hakkında daha fazla bilgi edinmek için uygulamanıza bağlanabildiğini görmek için Diğer istemcilere denetim izni verin.

Jetpack Media3 ile bir MediaSession uyguladığınızda, PlaybackState medya oynatıcıyla otomatik olarak güncel tutulur. Aynı şekilde MediaSessionService uygulanırsa kitaplık otomatik olarak bir MediaStyle bildirim güncel tutar.

İşlem düğmelerine yanıt verme

Bir kullanıcı, sistem medya denetimlerindeki bir işlem düğmesine dokunduğunda sistem MediaController, MediaSession cihazınıza bir oynatma komutu gönderir. İlgili içeriği oluşturmak için kullanılan MediaSession, ardından bu komutları oyuncuya delege eder. Komutlar Media3'ün Player bölümünde tanımlanmıştır arayüz, medya öğeleri tarafından otomatik olarak işlenir. kabul edilir.

Özel komut ekleme bölümünü inceleyin. inceleyebilirsiniz.

Android 13 Öncesi Davranış

Sistem kullanıcı arayüzü, geriye dönük uyumluluk için alternatif bir düzen sağlamaya devam eder. Android 13'ü hedefleyecek şekilde güncellenmeyen uygulamalar için bildirim işlemleri kullanan veya PlaybackState bilgilerini içermez. İşlem düğmeleri MediaStyle öğesine ekli Notification.Action listesinden türetilmiştir bildirimi görürsünüz. Sistem en çok beş işlemi gerçekleştirildikleri sırada görüntüler eklendi. Kompakt modundayken setShowActionsInCompactView() değerine geçirilen değerlerdir.

Özel işlemler PlaybackState

Aşağıdaki kod örneğinde, MediaStyle'a işlem eklemek için bildirim :

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)
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent)
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent)
        .setStyle(new MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build();

Medyayı devam ettirmeyi destekle

Medyayı devam ettirme, kullanıcıların önceki oturumları banttan yeniden başlatmasına olanak tanır ve uygulamayı başlatmak zorunda kalmadan. Oynatma başladığında kullanıcı normal şekilde değiştirebilirsiniz.

Oynatmayı devam ettirme özelliği, Ayarlar uygulaması kullanılarak açılıp kapatılabilir. Ses > Medya seçenekleri. Kullanıcı Ayarlar'a şu şekilde de erişebilir: Genişletilmiş bantta hızlıca kaydırdıktan sonra görünen dişli simgesine dokunun.

Media3, medyaya devam ettirmeyi desteklemeyi kolaylaştıran API'ler sunar. Bkz. Media3 ile oynatmayı devam ettirme dokümanlarına göz atabilirsiniz.

Eski medya API'lerini kullanma

Bu bölümde, eski MediaCompat API'leri.

Sistem, Google Etiket Yöneticisi'nden aşağıdaki bilgileri MediaSession adlı çocuğun MediaMetadata parametresidir ve mümkün olduğunda gösterir:

  • METADATA_KEY_ALBUM_ART_URI
  • METADATA_KEY_TITLE
  • METADATA_KEY_DISPLAY_TITLE
  • METADATA_KEY_ARTIST
  • METADATA_KEY_DURATION (Süre ayarlanmamışsa arama çubuğu durmaz ilerleme durumunu göster)

Geçerli ve doğru bir medya kontrolü bildiriminiz olduğundan emin olmak için METADATA_KEY_TITLE veya METADATA_KEY_DISPLAY_TITLE değerini ayarlayın oynatılmakta olan medyanın başlığına eşlemesini sağlar.

Medya oynatıcı, oynatılan oynatma için geçen süreyi gösterir MediaSession ile eşlenen arama çubuğuyla birlikte medya PlaybackState.

Medya oynatıcı, oynatılan medyanın ilerleme durumunu MediaSession PlaybackState ile eşlenen bir arama çubuğu. Arama çubuğu Kullanıcıların konumu değiştirmesine olanak tanır ve medya içeriği için geçen süreyi görüntüler öğe. Arama çubuğunun etkinleştirilmesi için PlaybackState.Builder#setActions ve ACTION_SEEK_TO dahil edin.

Alan İşlem Ölçütler
1 Oynat PlaybackState bilgisinin mevcut eyaleti aşağıdakilerden biridir:
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
Yükleme döner simgesi PlaybackState bilgisinin mevcut eyaleti aşağıdakilerden biridir:
  • STATE_CONNECTING
  • STATE_BUFFERING
Duraklat PlaybackState bölgesinin mevcut eyaleti yukarıdakilerin hiçbirinden farklıdır.
2 Önceki PlaybackState işlem ACTION_SKIP_TO_PREVIOUS içeriyor.
Özel PlaybackState işlemleri ACTION_SKIP_TO_PREVIOUS içermiyor ve PlaybackState özel işlemi henüz uygulanmamış bir özel işlem içeriyor.
Boş PlaybackState ekstralar, SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV anahtarı için true boole değeri içerir.
3 Sonraki PlaybackState işlem ACTION_SKIP_TO_NEXT içeriyor.
Özel PlaybackState işlemleri ACTION_SKIP_TO_NEXT içermiyor ve PlaybackState özel işlemi henüz uygulanmamış bir özel işlem içeriyor.
Boş PlaybackState ekstralar, SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT anahtarı için true boole değeri içerir.
4 Özel PlaybackState özel işlemi henüz yerleştirilmemiş bir özel işlem içeriyor.
5 Özel PlaybackState özel işlemi henüz yerleştirilmemiş bir özel işlem içeriyor.

Standart işlemler ekle

Aşağıdaki kod örneklerinde PlaybackState standardının ve reklam öğelerinin özel işlemler.

Oynatma, duraklatma, önceki ve sonraki için bu işlemleri medya oturumu için 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);

Önceki veya sonraki alanlarda herhangi bir düğme istemiyorsanız eklemek için ACTION_SKIP_TO_PREVIOUS veya ACTION_SKIP_TO_NEXT olup yerine ekstralar ekleyebilirsiniz: oturum:

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

Özel işlem ekleyin

Medya denetimlerinde gösterilmesini istediğiniz diğer işlemler için PlaybackStateCompat.CustomAction bunun yerine bunu PlaybackState öğesine ekleyin. Bu işlemler, bir sıralamayı görürsünüz.

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

Oynatma Durumu işlemlerine yanıt verme

Kullanıcı bir düğmeye dokunduğunda SystemUI MediaController.TransportControls MediaSession öğesine bir komut gönderin. Geri çağırma kaydetmeniz gerekiyor yanıt verebilen bir grup insandır.

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

Medyayı Devam Ettirme

Oynatıcı uygulamanızın Hızlı Ayarlar alanında görünmesini sağlamak için geçerli bir MediaSession jetonuyla MediaStyle bildirimi oluşturmanız gerekir.

MediaStyle bildiriminin başlığını görüntülemek için NotificationBuilder.setContentTitle()

Medya oynatıcıda marka simgesini görüntülemek için şunu kullanın: NotificationBuilder.setSmallIcon()

Oynatmayı devam ettirmeyi desteklemek için uygulamaların bir MediaBrowserService uygulaması gerekir ve MediaSession. MediaSession uygulamanız, onPlay() geri çağırmasını uygulamalıdır.

MediaBrowserService uygulama

Cihaz başlatıldıktan sonra, sistem en son kullanılan beş medyayı arar ve her uygulamadan oynatmayı yeniden başlatmak için kullanılabilecek kontroller sağlar.

Sistem, şu kaynaktan bir bağlantıyla MediaBrowserService ile iletişim kurmaya çalışır: Sistem Arayüzü. Uygulamanız bu tür bağlantılara izin vermelidir, aksi takdirde desteklenmiyor oynatmaya devam etmesini sağlar.

SystemUI'den gelen bağlantılar, paket adı kullanılarak tanımlanıp doğrulanabilir com.android.systemui ve imza. SystemUI, platformla imzalanır imzası var. Platform imzasını karşılaştırarak kontrol etme UAMP uygulamasında bulabilirsiniz.

Oynatmayı devam ettirmeyi desteklemek için MediaBrowserService cihazınız şu davranışları uygulayabilirsiniz:

  • onGetRoot(), hızlı bir şekilde null olmayan bir kök döndürmelidir. Diğer karmaşık mantık onLoadChildren() içinde ele alınacak

  • Zaman onLoadChildren(), kök medya kimliğinde çağrılıyor. Sonuç, bir FLAG_PLAYABLE alt öğesi olarak görünür.

  • MediaBrowserService, şu durumda en son oynatılan medya öğesini döndürmelidir: CANNOT TRANSLATE EKSTRA_SON emin olun. Döndürülen değer, genel değil, gerçek bir medya öğesi olmalıdır işlevini kullanın.

  • MediaBrowserService, uygun bir Boş olmayan bir MediaDescription title ve altyazı. Ayrıca, simge URI'si veya simge bit eşlemi kullanır.

Aşağıdaki kod örnekleri, onGetRoot() uygulamasının nasıl uygulanacağını gösterir.

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