Android'deki medya kontrolleri, Hızlı Ayarlar'ın yakınında bulunur. Birden fazla uygulamadaki oturumlar, kaydırılabilir bir ruloda düzenlenir. Döngü, oturumları şu sırayla listeler:
- Telefonda yerel olarak oynatılan yayınlar
- Harici cihazlarda veya yayın oturumlarında algılananlar gibi uzaktan yayınlar
- Önceki devam ettirilebilir oturumlar (en son oynatıldıkları sırayla)
Android 13'ten (API düzeyi 33) itibaren, kullanıcıların medya oynatan uygulamalar için zengin bir medya kontrolü grubuna erişebilmesini sağlamak amacıyla medya kontrollerindeki işlem düğmeleri Player
durumundan türetilir.
Bu sayede, cihazlar arasında tutarlı bir medya denetimi grubu ve daha iyi bir medya denetimi deneyimi sunabilirsiniz.
Şekil 1'de, bunun sırasıyla telefonda ve tablette nasıl göründüğüne dair bir örnek gösterilmektedir.
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 yuvası gösterilir. Bu, Auto, Asistan ve Wear OS gibi diğer Android platformlarında medya kontrollerinin oluşturulma şekliyle uyumludur.
Alan | Ölçütler | İşlem |
---|---|---|
1 |
playWhenReady yanlış veya mevcut
oynatma durumu STATE_ENDED .
|
Oynat |
playWhenReady doğruysa ve mevcut
oynatma durumu STATE_BUFFERING ise.
|
Yükleme döner simgesi | |
playWhenReady doğruysa ve mevcut
oynatma durumu STATE_READY ise.
|
Duraklat | |
2 |
Medya
düğmesi tercihleri, CommandButton.SLOT_BACK için özel bir düğme içerir.
|
Özel |
Oynatıcı komutu
COMMAND_SEEK_TO_PREVIOUS veya
COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM kullanılabilir.
|
Önceki | |
Ne özel düğme ne de listelenen komutlardan biri kullanılabiliyor. | Boş | |
3 |
Medya düğmesi
tercihlerinde CommandButton.SLOT_FORWARD için özel bir düğme bulunur.
|
Özel |
Oynatıcı komutu
COMMAND_SEEK_TO_NEXT veya
COMMAND_SEEK_TO_NEXT_MEDIA_ITEM kullanılabilir.
|
Sonraki | |
Ne özel düğme ne de listelenen komutlardan biri kullanılabiliyor. | Boş | |
4 |
Medya düğmesi
tercihlerinde henüz yerleştirilmemiş bir CommandButton.SLOT_OVERFLOW için özel düğme bulunur.
|
Özel |
5 |
Medya düğmesi
tercihlerinde henüz yerleştirilmemiş bir CommandButton.SLOT_OVERFLOW için özel düğme bulunur.
|
Özel |
Özel taşma düğmeleri, medya düğmesi tercihlerine eklendikleri sırayla yerleştirilir.
Komut düğmelerini özelleştirme
Sistem medya kontrollerini Jetpack Media3 ile özelleştirmek için oturumun medya düğmesi tercihlerini ve kontrol cihazlarının kullanılabilir komutlarını buna göre ayarlayabilirsiniz:
MediaSession
oluşturun ve özel komut düğmeleri için medya düğmesi tercihlerini tanımlayın.MediaSession.Callback.onConnect()
içinde,ConnectionResult
bölümünde özel komutlar da dahil olmak üzere kullanılabilir komutlarını tanımlayarak kumandaları yetkilendirin.MediaSession.Callback.onCustomCommand()
içinde, kullanıcı tarafından seçilen özel komuta yanıt ver.
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); } } }
MediaSession
'nızı, sistem gibi istemcilerin medya uygulamanıza bağlanabileceği şekilde yapılandırma hakkında daha fazla bilgi edinmek için Diğer istemcilere kontrol izni verme başlıklı makaleyi inceleyin.
Jetpack Media3 ile 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
bildirim yayınlar ve güncel tutar.
İşlem düğmelerine yanıt verme
Kullanıcı, sistem medya kontrollerindeki bir işlem düğmesine dokunduğunda sistem, MediaController
, MediaSession
öğenize bir oynatma komutu gönderir. MediaSession
bu komutları oyuncuya devreder. Media3'ün Player
arayüzünde tanımlanan komutlar, medya oturumu tarafından otomatik olarak işlenir.
Özel komutlara nasıl yanıt vereceğinizle ilgili bilgi için Özel komut ekleme başlıklı makaleyi inceleyin.
Medyayı devam ettirme özelliğini destekleme
Medya oynatmaya 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 normal şekilde etkileşime girer.
Oynatmaya devam etme özelliği, Ayarlar uygulamasında Ses > Medya seçenekleri altında etkinleştirilebilir veya devre dışı bırakılabilir. Kullanıcı, genişletilmiş bantta kaydırdıktan sonra görünen dişli simgesine dokunarak da Ayarlar'a erişebilir.
Media3, medya oynatmaya devam etme özelliğini desteklemeyi kolaylaştıran API'ler sunar. Bu özelliği uygulama hakkında bilgi edinmek için Media3 ile oynatmaya devam etme dokümanına bakın.
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
'nın MediaMetadata
bölümünden aşağıdaki bilgileri alır ve kullanılabilir olduğunda görüntüler:
METADATA_KEY_ALBUM_ART_URI
METADATA_KEY_TITLE
METADATA_KEY_DISPLAY_TITLE
METADATA_KEY_ARTIST
METADATA_KEY_DURATION
(Süre ayarlanmamışsa arama çubuğunda ilerleme gösterilmez)
Geçerli ve doğru bir medya kontrolü bildirimi almak için METADATA_KEY_TITLE
veya METADATA_KEY_DISPLAY_TITLE
meta verilerinin değerini, şu anda oynatılan medyanın başlığı olarak ayarlayın.
Medya oynatıcıda, şu anda oynatılan medyanın geçen süresi ve MediaSession
PlaybackState
ile eşlenmiş bir arama çubuğu gösterilir.
Medya oynatıcı, şu anda oynatılan medyanın ilerleme durumunu ve MediaSession
PlaybackState
tuşuna eşlenmiş bir arama çubuğunu gösterir. Arama çubuğu, kullanıcıların konumu değiştirmesine olanak tanır ve medya öğesinin geçen süresini gösterir. Arama çubuğunun etkinleştirilmesi için PlaybackState.Builder#setActions
işlevini uygulamanız ve ACTION_SEEK_TO
işlevini eklemeniz gerekir.
Alan | İşlem | Ölçütler |
---|---|---|
1 | Oynat |
PlaybackState öğesinin mevcut durumu aşağıdakilerden biridir:
|
Yükleme döner simgesi |
PlaybackState öğesinin mevcut durumu aşağıdakilerden biridir:
|
|
Duraklat | PlaybackState 'ın mevcut durumu yukarıdakilerin hiçbiri değil. |
|
2 | Önceki | PlaybackState işlemler ACTION_SKIP_TO_PREVIOUS içerir. |
Özel | PlaybackState işlemleri ACTION_SKIP_TO_PREVIOUS içermiyor ve PlaybackState özel işlemleri henüz yerleştirilmemiş bir özel işlem içeriyor. |
|
Boş | PlaybackState extras, SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV anahtarı için bir true boole değeri içerir. |
|
3 | Sonraki | PlaybackState işlemler ACTION_SKIP_TO_NEXT içerir. |
Özel | PlaybackState işlemleri ACTION_SKIP_TO_NEXT içermiyor ve PlaybackState özel işlemleri henüz yerleştirilmemiş bir özel işlem içeriyor. |
|
Boş | PlaybackState extras, SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT anahtarı için bir true boole değeri içerir. |
|
4 | Özel | PlaybackState Özel işlemler, henüz yerleştirilmemiş bir özel işlem içeriyor. |
5 | Özel | PlaybackState Özel işlemler, henüz yerleştirilmemiş bir özel işlem içeriyor. |
Standart işlemler ekleme
Aşağıdaki kod örneklerinde, PlaybackState
standart ve özel işlemlerin nasıl ekleneceği gösterilmektedir.
Oynatma, duraklatma, önceki ve sonraki 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 yuvalarda düğme istemiyorsanız ACTION_SKIP_TO_PREVIOUS
veya ACTION_SKIP_TO_NEXT
eklemeyin. 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şlemler ekleme
Medya kontrollerinde göstermek istediğiniz diğer işlemler için bir
PlaybackStateCompat.CustomAction
oluşturabilir ve bunu PlaybackState
'ye 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, MediaController.TransportControls
kullanarak MediaSession
'e bir komut gönderir. Bu etkinliklere uygun ş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 içeren 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.
Uygulamaların, oynatmaya devam etme özelliğini desteklemek için MediaBrowserService
ve MediaSession
yöntemlerini uygulaması gerekir. MediaSession
, onPlay()
geri aramasını uygulamalıdır.
MediaBrowserService
uygulaması
Cihaz başlatıldıktan sonra sistem, en son kullanılan beş medya uygulamasını arar ve her uygulamada oynatmayı yeniden başlatmak için kullanılabilecek kontrolleri sağlar.
Sistem, SystemUI'den gelen bir bağlantıyla MediaBrowserService
ile iletişime geçmeye çalışır. Uygulamanız bu tür bağlantılara izin vermelidir. Aksi takdirde oynatmaya devam etme özelliği desteklenemez.
SystemUI'den gelen bağlantılar, paket adı com.android.systemui
ve imza kullanılarak tanımlanabilir ve doğrulanabilir. SystemUI, platform imzasıyla imzalanır. Platform imzasına karşı nasıl kontrol yapılacağına dair bir örneği UAMP uygulamasında bulabilirsiniz.
Oynatmaya devam etme özelliğinin desteklenmesi için MediaBrowserService
şu davranışları uygulamalıdır:
onGetRoot()
, kısa süre içinde null olmayan bir kök döndürmelidir. Diğer karmaşık mantıklaronLoadChildren()
içinde işlenmelidir.Kök medya kimliğinde
onLoadChildren()
çağrıldığında sonuç, FLAG_PLAYABLE alt öğesini içermelidir.MediaBrowserService
, EXTRA_RECENT sorgusu aldığında en son oynatılan medya öğesini döndürmelidir. Döndürülen değer, genel işlev yerine gerçek bir medya öğesi olmalıdır.MediaBrowserService
, boş olmayan bir title ve subtitle ile uygun bir MediaDescription sağlamalıdır. Ayrıca, bir simge URI'si veya bir simge bit eşlemi de ayarlamalıdır.
Aşağıdaki kod örneklerinde onGetRoot()
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ış
Sistem kullanıcı arayüzü, geriye dönük uyumluluk için Android 13'ü hedefleyecek şekilde güncellenmeyen veya PlaybackState
bilgisi içermeyen uygulamalarda bildirim işlemlerini kullanan alternatif bir düzen sağlamaya devam eder. İşlem düğmeleri, MediaStyle
bildirimine eklenen Notification.Action
listesinden alınır. Sistem, en fazla beş işlemi eklendikleri sırayla gösterir. Kompakt modda, setShowActionsInCompactView()
öğesine aktarılan değerlere göre en fazla üç düğme gösterilir.
Özel işlemler, PlaybackState
'ya eklendikleri sırayla 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();