Android'deki medya kontrolleri, Hızlı Ayarlar'ın yanında bulunur. Birden fazla uygulamadaki oturumlar, kaydırılabilir bir bantta düzenlenir. Rulo, oturumları şu sırayla listeler:
- Telefonda yerel olarak oynatılan yayınlar
- Harici cihazlarda veya yayın oturumlarında algılananlar gibi uzaktan akışlar
- Önceki devam ettirilebilir oturumlar, son oynatıldıkları sırayla gösterilir.
Android 13'ten (API düzeyi 33) itibaren, kullanıcıların medya oynatan uygulamalar için zengin bir medya kontrolü grubuna erişebilmesi amacıyla medya kontrollerindeki işlem düğmeleri Player durumundan türetilir.
Bu sayede, cihazlar arasında tutarlı bir medya kontrolleri grubu ve daha gelişmiş bir medya kontrolü deneyimi sunabilirsiniz.
Şekil 1'de, bunun bir telefon ve tablet cihazda nasıl göründüğüne dair bir örnek verilmiştir.
Sistem, aşağıdaki tabloda açıklandığı gibi Player durumuna göre en fazla beş işlem düğmesi gösterir. Kompakt modda yalnızca ilk üç işlem alanı gösterilir. Bu, medya kontrollerinin Auto, Asistan ve Wear OS gibi diğer Android platformlarında oluşturulma şekliyle uyumludur.
| Alan | Ölçütler | İşlem |
|---|---|---|
| 1 |
playWhenReady yanlış veya mevcut
oynatma durumu STATE_ENDED.
|
Oynat |
playWhenReady doğru ve mevcut
oynatma durumu STATE_BUFFERING ise.
|
Yükleme döner simgesi | |
playWhenReady doğru ve mevcut
oynatma durumu STATE_READY.
|
Duraklat | |
| 2 |
Medya düğmesi tercihleri, CommandButton.SLOT_BACK için özel bir düğme içeriyor.
|
Özel |
Oynatıcı komutu
COMMAND_SEEK_TO_PREVIOUS veya
COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM kullanılabilir.
|
Önceki | |
| Özel düğme veya listelenen komutlardan biri kullanılamıyor. | Boş | |
| 3 |
Medya düğmesi tercihleri, CommandButton.SLOT_FORWARD için özel bir düğme içeriyor.
|
Özel |
Oynatıcı komutu
COMMAND_SEEK_TO_NEXT veya
COMMAND_SEEK_TO_NEXT_MEDIA_ITEM kullanılabilir.
|
Sonraki | |
| Özel düğme veya listelenen komutlardan biri kullanılamıyor. | Boş | |
| 4 |
Medya düğmesi tercihleri, CommandButton.SLOT_OVERFLOW için henüz yerleştirilmemiş özel bir düğme içeriyor.
|
Özel |
| 5 |
Medya düğmesi tercihleri, CommandButton.SLOT_OVERFLOW için henüz yerleştirilmemiş özel bir düğme içeriyor.
|
Özel |
Özel taşma düğmeleri, medya düğmesi tercihlerine eklendikleri sıraya göre yerleştirilir.
Komut düğmelerini özelleştirme
Sistem medya kontrollerini Jetpack Media3 ile özelleştirmek için oturumun medya düğmesi tercihlerini ve kumandaların kullanılabilir komutlarını buna göre ayarlayabilirsiniz:
MediaSessionoluşturun ve özel komut düğmeleri için medya düğmesi tercihlerini tanımlayın.MediaSession.Callback.onConnect()'da,ConnectionResultiçinde özel komutlar da dahil olmak üzere kullanılabilir komutlarını tanımlayarak denetleyicileri yetkilendirin.MediaSession.Callback.onCustomCommand()içinde, kullanıcı tarafından seçilen özel komuta yanıt verin.
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); } } }
Sistem gibi istemcilerin medya uygulamanıza bağlanabilmesi için MediaSession uygulamanızı yapılandırma hakkında daha fazla bilgi edinmek istiyorsanız Diğer istemcilere kontrol izni verme başlıklı makaleyi inceleyin.
Jetpack Media3 ile bir MediaSession uyguladığınızda PlaybackState, medya oynatıcıyla otomatik olarak güncel tutulur. Benzer şekilde, bir MediaSessionService uyguladığınızda kitaplık sizin için otomatik olarak bir MediaStyle bildirimi yayınlar ve güncel tutar.
İşlem düğmelerine yanıt verme
Kullanıcı, sistem medya kontrollerindeki bir işlem düğmesine dokunduğunda sistemin MediaController, MediaSession uygulamanıza bir oynatma komutu gönderir. MediaSession, bu komutları oynatıcıya iletir. Media3'ün Player arayüzünde tanımlanan komutlar, medya oturumu tarafından otomatik olarak işlenir.
Özel komutlara nasıl yanıt verileceğiyle ilgili bilgi edinmek için Özel komut ekleme bölümünü inceleyin.
Medya devam ettirmeyi destekleme
Medyaya devam etme özelliği, kullanıcıların uygulamayı başlatmak zorunda kalmadan önceki oturumları banttan yeniden başlatmasına olanak tanır. Oynatma başladığında kullanıcı, medya kontrolleriyle her zamanki gibi etkileşimde bulunur.
Oynatmaya devam etme özelliği, Ayarlar uygulamasındaki Ses > Medya seçeneklerinden açılıp kapatılabilir. Kullanıcı, genişletilmiş bantta kaydırma yaptıktan sonra görünen dişli simgesine dokunarak da Ayarlar'a erişebilir.
Media3, medya devam ettirme özelliğini desteklemeyi kolaylaştıran API'ler sunar. Bu özelliğin uygulanmasıyla ilgili talimatlar için Media3 ile oynatmayı devam ettirme dokümanını inceleyin.
Eski medya API'lerini kullanma
Bu bölümde, eski MediaCompat API'lerini kullanarak sistem medya kontrolleriyle nasıl entegrasyon yapılacağı açıklanmaktadır.
Sistem, MediaSession adlı kullanıcının MediaMetadata hesabından aşağıdaki bilgileri alır ve mevcut olduğunda gösterir:
METADATA_KEY_ALBUM_ART_URIMETADATA_KEY_TITLEMETADATA_KEY_DISPLAY_TITLEMETADATA_KEY_ARTISTMETADATA_KEY_DURATION(Süre ayarlanmazsa arama çubuğu ilerleme durumunu göstermez)
Geçerli ve doğru bir medya kontrolü bildirimi almak için METADATA_KEY_TITLE veya METADATA_KEY_DISPLAY_TITLE meta verilerinin değerini, oynatılmakta olan medyanın başlığı olarak ayarlayın.
Medya oynatıcı, o anda oynatılan medyanın geçen süresini ve MediaSession
PlaybackState ile eşlenen bir arama çubuğunu gösterir.
Medya oynatıcı, o anda oynatılan medyanın ilerleme durumunu ve MediaSession PlaybackState ile eşlenen bir arama çubuğunu gösterir. Arama çubuğu, kullanıcıların konumu değiştirmesine olanak tanır ve medya öğesi için geçen süreyi gösterir. Arama çubuğunun etkinleştirilmesi için PlaybackState.Builder#setActions uygulamanız ve ACTION_SEEK_TO eklemeniz gerekir.
| Alan | İşlem | Ölçütler |
|---|---|---|
| 1 | Oynat |
PlaybackState öğesinin mevcut durumu aşağıdakilerden biridir:
|
| Yükleme döner simgesi |
PlaybackState için geçerli durum aşağıdakilerden biridir:
|
|
| Duraklat | PlaybackState öğesinin mevcut durumu yukarıdakilerden hiçbiri değil. |
|
| 2 | Önceki | PlaybackState işlem ACTION_SKIP_TO_PREVIOUS içeriyor. |
| Özel | PlaybackState işlem ACTION_SKIP_TO_PREVIOUS içermiyor ve PlaybackState özel işlem henüz yerleştirilmemiş bir özel işlem içeriyor. |
|
| Boş | PlaybackState extras, 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şlem ACTION_SKIP_TO_NEXT içermiyor ve PlaybackState özel işlem henüz yerleştirilmemiş bir özel işlem içeriyor. |
|
| Boş | PlaybackState extras, SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT anahtarı için true boole değeri içerir. |
|
| 4 | Özel | PlaybackState özel işlem, henüz yerleştirilmemiş bir özel işlem içeriyor. |
| 5 | Özel | PlaybackState özel işlem, henüz yerleştirilmemiş bir özel işlem içeriyor. |
Standart işlemler ekleme
Aşağıdaki kod örneklerinde, standart ve özel işlemlerin nasıl ekleneceği PlaybackState gösterilmektedir.
Oynatma, duraklatma, önceki ve sonraki işlemler için bu işlemleri medya oturumunun PlaybackState bölümünde ayarlayın.
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 düğme olmasını istemiyorsanız ACTION_SKIP_TO_PREVIOUS veya ACTION_SKIP_TO_NEXT eklemeyin ve bunun yerine oturuma ekstralar ekleyin:
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 ekleme
Medya kontrollerinde göstermek istediğiniz diğer işlemler için PlaybackStateCompat.CustomAction oluşturup PlaybackState öğesine ekleyebilirsiniz. Bu işlemler, eklendikleri sırayla gösterilir.
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 işlemlerine yanıt verme
Kullanıcı bir düğmeye dokunduğunda SystemUI, MediaSession'e geri komut göndermek için MediaController.TransportControls kullanır. Bu etkinliklere doğru şekilde yanıt verebilecek bir geri çağırma kaydetmeniz gerekir.
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ünmesi için geçerli bir MediaSession jetonu olan bir MediaStyle bildirimi oluşturmanız gerekir.
MediaStyle bildiriminin başlığını göstermek için NotificationBuilder.setContentTitle() kullanın.
Medya oynatıcıda marka simgesini göstermek için NotificationBuilder.setSmallIcon() kullanın.
Oynatmaya devam etme özelliğini desteklemek için uygulamaların MediaBrowserService ve MediaSession uygulaması gerekir. MediaSession, onPlay() geri çağırma işlevini uygulamalıdır.
MediaBrowserService uygulaması
Cihaz başlatıldıktan sonra sistem, en son kullanılan beş medya uygulamasını arar ve her uygulamadan oynatmayı yeniden başlatmak için kullanılabilecek kontroller sağlar.
Sistem, SystemUI'dan bir bağlantıyla MediaBrowserService ile iletişim kurmaya çalışır. Uygulamanız bu tür bağlantılara izin vermelidir. Aksi takdirde oynatmanın devam ettirilmesini destekleyemez.
SystemUI'dan gelen bağlantılar, paket adı com.android.systemui ve imza kullanılarak tanımlanıp doğrulanabilir. SystemUI, platform imzasıyla imzalanır. Platform imzasına karşı kontrolün nasıl yapılacağına dair bir örneği UAMP uygulamasında bulabilirsiniz.
Oynatmaya devam etme özelliğini desteklemek için MediaBrowserService uygulamanızın şu davranışları uygulaması gerekir:
onGetRoot(), null olmayan bir kökü hızlı bir şekilde döndürmelidir. Diğer karmaşık mantıklaronLoadChildren()içinde işlenmelidir.Kök medya kimliğinde
onLoadChildren()çağrıldığında sonuçta bir FLAG_PLAYABLE alt öğesi bulunmalıdır.MediaBrowserService, EXTRA_RECENT sorgusu aldığında en son çalınan medya öğesini döndürmelidir. Döndürülen değer, genel bir işlev yerine gerçek bir medya öğesi olmalıdır.MediaBrowserService, boş olmayan bir title ve subtitle içeren uygun bir MediaDescription sağlamalıdır. Ayrıca bir simge URI'si veya simge bit eşlemi ayarlamalıdır.
Aşağıdaki kod örneklerinde onGetRoot() öğesinin nasıl uygulanacağı gösterilmektedir.
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 Öncesi Davranış
Geriye dönük uyumluluk için Sistem Kullanıcı Arayüzü, Android 13'ü hedefleyecek şekilde güncellenmeyen veya PlaybackState bilgilerini içermeyen uygulamalar için bildirim işlemlerini kullanan alternatif bir düzen sağlamaya devam eder. İşlem düğmeleri, MediaStyle bildirimine eklenen Notification.Action listesinden türetilir. Sistem, eklendikleri sırayla en fazla beş işlem gösterir. Kompakt modda, setShowActionsInCompactView() öğesine iletilen değerlere göre belirlenen en fazla üç düğme gösterilir.
Özel işlemler, PlaybackState uygulamasına eklendikleri sıraya göre yerleştirilir.
Aşağıdaki kod örneğinde, MediaStyle bildirimine nasıl işlem ekleneceği gösterilmektedir :
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();