Uygulamanız, manifest dosyasında intent filtresiyle MediaBrowserService beyan etmelidir. Kendi hizmet adınızı seçebilirsiniz. Aşağıdaki örnekte, seçilen hizmet adı MediaPlaybackService'dır.
<service android:name=".MediaPlaybackService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
Medya oturumunu başlatma
Hizmet, onCreate() yaşam döngüsü geri çağırma yöntemini aldığında şu adımları uygulamalıdır:
- Medya oturumunu oluşturma ve başlatma
- Medya oturumu geri çağırmasını ayarlama
- Medya oturumu jetonunu ayarlama
Aşağıdaki onCreate() kodda bu adımlar gösterilmektedir:
Kotlin
private const val MY_MEDIA_ROOT_ID = "media_root_id" private const val MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id" class MediaPlaybackService : MediaBrowserServiceCompat() { private var mediaSession: MediaSessionCompat? = null private lateinit var stateBuilder: PlaybackStateCompat.Builder override fun onCreate() { super.onCreate() // Create a MediaSessionCompat mediaSession = MediaSessionCompat(baseContext, LOG_TAG).apply { // Enable callbacks from MediaButtons and TransportControls setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS ) // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player stateBuilder = PlaybackStateCompat.Builder() .setActions(PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PLAY_PAUSE ) setPlaybackState(stateBuilder.build()) // MySessionCallback() has methods that handle callbacks from a media controller setCallback(MySessionCallback()) // Set the session's token so that client activities can communicate with it. setSessionToken(sessionToken) } } }
Java
public class MediaPlaybackService extends MediaBrowserServiceCompat { private static final String MY_MEDIA_ROOT_ID = "media_root_id"; private static final String MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id"; private MediaSessionCompat mediaSession; private PlaybackStateCompat.Builder stateBuilder; @Override public void onCreate() { super.onCreate(); // Create a MediaSessionCompat mediaSession = new MediaSessionCompat(context, LOG_TAG); // Enable callbacks from MediaButtons and TransportControls mediaSession.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player stateBuilder = new PlaybackStateCompat.Builder() .setActions( PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PLAY_PAUSE); mediaSession.setPlaybackState(stateBuilder.build()); // MySessionCallback() has methods that handle callbacks from a media controller mediaSession.setCallback(new MySessionCallback()); // Set the session's token so that client activities can communicate with it. setSessionToken(mediaSession.getSessionToken()); } }
İstemci bağlantılarını yönetme
Bir MediaBrowserService, istemci bağlantılarını işleyen iki yönteme sahiptir:
onGetRoot(), hizmete erişimi denetler ve onLoadChildren(), istemcinin MediaBrowserService'nın içerik hiyerarşisinin menüsünü oluşturup görüntülemesine olanak tanır.
onGetRoot() ile istemci bağlantılarını kontrol etme
onGetRoot() yöntemi, içerik hiyerarşisinin kök düğümünü döndürür. Yöntem null değerini döndürürse bağlantı reddedilir.
İstemcilerin hizmetinize bağlanmasına ve medya içeriğine göz atmasına izin vermek için
onGetRoot(), içerik hiyerarşinizi temsil eden bir kök kimlik olan boş olmayan bir BrowserRoot döndürmelidir.
İstemcilerin göz atma işlemi yapmadan MediaSession'ınıza bağlanmasına izin vermek için onGetRoot()
yine de null olmayan bir BrowserRoot döndürmelidir ancak kök kimliği boş bir içerik hiyerarşisini temsil etmelidir.
onGetRoot() öğesinin tipik bir uygulaması şu şekilde görünebilir:
Kotlin
override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle? ): MediaBrowserServiceCompat.BrowserRoot { // (Optional) Control the level of access for the specified package name. // You'll need to write your own logic to do this. return if (allowBrowsing(clientPackageName, clientUid)) { // Returns a root ID that clients can use with onLoadChildren() to retrieve // the content hierarchy. MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null) } else { // Clients can connect, but this BrowserRoot is an empty hierarchy // so onLoadChildren returns nothing. This disables the ability to browse for content. MediaBrowserServiceCompat.BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null) } }
Java
@Override public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { // (Optional) Control the level of access for the specified package name. // You'll need to write your own logic to do this. if (allowBrowsing(clientPackageName, clientUid)) { // Returns a root ID that clients can use with onLoadChildren() to retrieve // the content hierarchy. return new BrowserRoot(MY_MEDIA_ROOT_ID, null); } else { // Clients can connect, but this BrowserRoot is an empty hierarchy // so onLoadChildren returns nothing. This disables the ability to browse for content. return new BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null); } }
Bazı durumlarda, MediaBrowserService cihazınıza kimlerin bağlanabileceğini kontrol etmek isteyebilirsiniz. Bunun bir yolu, hangi bağlantılara izin verildiğini belirten veya hangi bağlantıların yasaklanması gerektiğini sıralayan bir erişim kontrol listesi (EKL) kullanmaktır. Belirli bağlantılara izin veren bir ACL'nin nasıl uygulanacağına dair örnek için Universal Android Music Player örnek uygulamasındaki PackageValidator sınıfına bakın.
Hangi tür istemcinin sorgu yaptığına bağlı olarak farklı içerik hiyerarşileri sağlamayı düşünebilirsiniz. Android Auto, kullanıcıların ses uygulamalarıyla etkileşimini özellikle sınırlar. Daha fazla bilgi için Playing Audio for
Auto başlıklı makaleyi inceleyin. İstemci türünü belirlemek için bağlantı sırasında clientPackageName öğesine bakabilir ve istemciye bağlı olarak farklı bir BrowserRoot (veya varsa rootHints) döndürebilirsiniz.
onLoadChildren() ile içerik iletişimi
İstemci bağlandıktan sonra, MediaBrowserCompat.subscribe() işlevine tekrar tekrar çağrı yaparak içerik hiyerarşisinde gezinebilir ve kullanıcı arayüzünün yerel bir temsilini oluşturabilir. subscribe() yöntemi, geri aramayı hizmete gönderir. Hizmet, MediaBrowser.MediaItem nesnelerinin listesini döndürür.onLoadChildren()
Her MediaItem'ın, opak bir jeton olan benzersiz bir kimlik dizesi vardır. Bir istemci, alt menü açmak veya bir öğeyi oynatmak istediğinde kimliği iletir. Hizmetiniz, kimliği uygun menü düğümü veya içerik öğesiyle ilişkilendirmekten sorumludur.
onLoadChildren()'nın basit bir uygulaması şu şekilde görünebilir:
Kotlin
override fun onLoadChildren( parentMediaId: String, result: MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>> ) { // Browsing not allowed if (MY_EMPTY_MEDIA_ROOT_ID == parentMediaId) { result.sendResult(null) return } // Assume for example that the music catalog is already loaded/cached. val mediaItems = emptyList<MediaBrowserCompat.MediaItem>() // Check if this is the root menu: if (MY_MEDIA_ROOT_ID == parentMediaId) { // Build the MediaItem objects for the top level, // and put them in the mediaItems list... } else { // Examine the passed parentMediaId to see which submenu we're at, // and put the children of that menu in the mediaItems list... } result.sendResult(mediaItems) }
Java
@Override public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) { // Browsing not allowed if (TextUtils.equals(MY_EMPTY_MEDIA_ROOT_ID, parentMediaId)) { result.sendResult(null); return; } // Assume for example that the music catalog is already loaded/cached. List<MediaItem> mediaItems = new ArrayList<>(); // Check if this is the root menu: if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) { // Build the MediaItem objects for the top level, // and put them in the mediaItems list... } else { // Examine the passed parentMediaId to see which submenu we're at, // and put the children of that menu in the mediaItems list... } result.sendResult(mediaItems); }
Not: MediaItem MediaBrowserService tarafından sunulan nesneler simge bit eşlemleri içermemelidir. Her öğe için MediaDescription oluştururken setIconUri() numaralı telefonu arayarak bunun yerine Uri kullanın.
onLoadChildren() nasıl uygulanacağına dair bir örnek için Universal Android Music Player örnek uygulamasına bakın.
Medya tarayıcı hizmetinin yaşam döngüsü
Android hizmetinin davranışı, bir veya daha fazla istemciye başlatılıp başlatılmadığı ya da bağlanıp bağlanmadığına bağlıdır. Bir hizmet oluşturulduktan sonra başlatılabilir, bağlanabilir veya her ikisi de yapılabilir. Bu durumların hepsinde tam işlevseldir ve tasarlandığı işi yapabilir. Aradaki fark, hizmetin ne kadar süreyle var olacağıdır. Bağlı bir hizmet, bağlı tüm istemcileri bağlantıyı kaldırana kadar yok edilmez. Başlatılan bir hizmet, artık herhangi bir istemciye bağlı olmadığı varsayılarak açıkça durdurulabilir ve yok edilebilir.
Başka bir etkinlikte çalışan bir MediaBrowser, bir MediaBrowserService'ye bağlandığında etkinliği hizmete bağlar ve hizmeti bağlı (ancak başlatılmamış) hale getirir. Bu varsayılan davranış, MediaBrowserServiceCompat sınıfına yerleştirilmiştir.
Yalnızca bağlı olan (başlatılmamış) bir hizmet, tüm istemcileri bağlantıyı kaldırdığında yok edilir. Kullanıcı arayüzü etkinliğiniz bu noktada bağlantıyı keserse hizmet yok edilir. Henüz müzik çalmadıysanız bu bir sorun değildir. Ancak oynatma başladığında kullanıcı, uygulamalar arasında geçiş yaptıktan sonra da dinlemeye devam etmeyi bekler. Kullanıcı arayüzünün başka bir uygulamayla çalışacak şekilde bağlantısını kestiğinizde oynatıcıyı kaldırmak istemezsiniz.
Bu nedenle, startService() işlevini çağırarak hizmetin oynatmaya başladığında başlatıldığından emin olmanız gerekir. Başlatılan bir hizmet, bağlı olup olmadığına bakılmaksızın açıkça durdurulmalıdır. Bu, kontrol eden kullanıcı arayüzü etkinliği bağlantısı kaldırılsa bile oynatıcınızın çalışmaya devam etmesini sağlar.
Başlatılan bir hizmeti durdurmak için Context.stopService() veya stopSelf()'ı arayın. Sistem, hizmeti mümkün olan en kısa sürede durdurur ve yok eder. Ancak bir veya daha fazla istemci hizmete bağlı kalırsa hizmeti durdurma çağrısı, tüm istemcilerin bağlantısı kaldırılana kadar ertelenir.
MediaBrowserService yaşam döngüsü, oluşturulma şekli, kendisine bağlı istemci sayısı ve medya oturumu geri çağırmalarından aldığı çağrılar tarafından kontrol edilir. Özetlemek gerekirse:
- Hizmet, bir medya düğmesine yanıt olarak başlatıldığında veya bir etkinlik
MediaBrowserüzerinden bağlandıktan sonra hizmete bağlandığında oluşturulur. - Medya oturumu
onPlay()geri çağırması,startService()işlevini çağıran kodu içermelidir. Bu, hizmetin, kendisine bağlı tüm kullanıcı arayüzüMediaBrowseretkinlikleri bağlantısı kaldırıldığında bile başlatılmasını ve çalışmaya devam etmesini sağlar. onStop()geri çağırma işlevistopSelf()işlevini çağırmalıdır. Hizmet başlatılmışsa durdurulur. Ayrıca, hizmete bağlı etkinlik yoksa hizmet yok edilir. Aksi takdirde, tüm etkinlikleri bağlantıyı kaldırana kadar hizmet bağlı kalır. (Hizmet yok edilmeden önce sonraki birstartService()çağrısı alınırsa bekleyen durdurma işlemi iptal edilir.)
Aşağıdaki akış şeması, bir hizmetin yaşam döngüsünün nasıl yönetildiğini göstermektedir. Değişken sayacı, bağlı istemci sayısını izler:

Bir ön plan hizmeti ile MediaStyle bildirimlerini kullanma
Bir hizmet oynatılırken ön planda çalışmalıdır. Bu sayede sistem, hizmetin yararlı bir işlev gerçekleştirdiğini ve sistemin belleği azaldığında sonlandırılmaması gerektiğini anlar. Ön plan hizmeti, kullanıcıya bilgi vermek ve isteğe bağlı olarak kontrol etmesini sağlamak için bildirim göstermelidir. onPlay() geri çağırması, hizmeti ön plana getirmelidir. (Bunun "ön plan"ın özel bir anlamı olduğunu unutmayın. Android, işlem yönetimi amacıyla hizmeti ön planda kabul etse de kullanıcıya göre oynatıcı, ekranda başka bir uygulama "ön planda" görünürken arka planda oynatılıyor.)
Bir hizmet ön planda çalışırken ideal olarak bir veya daha fazla aktarım kontrolü içeren bir bildirim göstermelidir. Bildirim, oturumun meta verilerindeki faydalı bilgileri de içermelidir.
Oynatıcı oynatmaya başladığında bildirimi oluşturun ve gösterin. Bunu yapmanın en iyi yolu MediaSessionCompat.Callback.onPlay() yönteminin içidir.
Aşağıdaki örnekte, medya uygulamaları için tasarlanmış olan NotificationCompat.MediaStyle kullanılıyor. Bu örnekte, meta verileri ve aktarım kontrollerini gösteren bir bildirimin nasıl oluşturulacağı açıklanmaktadır. Kolaylık yöntemi
getController()
doğrudan medya oturumunuzdan medya kumandası oluşturmanıza olanak tanır.
Kotlin
// Given a media session and its context (usually the component containing the session) // Create a NotificationCompat.Builder // Get the session's metadata val controller = mediaSession.controller val mediaMetadata = controller.metadata val description = mediaMetadata.description val builder = NotificationCompat.Builder(context, channelId).apply { // Add the metadata for the currently playing track setContentTitle(description.title) setContentText(description.subtitle) setSubText(description.description) setLargeIcon(description.iconBitmap) // Enable launching the player by clicking the notification setContentIntent(controller.sessionActivity) // Stop the service when the notification is swiped away setDeleteIntent( MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_STOP ) ) // Make the transport controls visible on the lockscreen setVisibility(NotificationCompat.VISIBILITY_PUBLIC) // Add an app icon and set its accent color // Be careful about the color setSmallIcon(R.drawable.notification_icon) color = ContextCompat.getColor(context, R.color.primaryDark) // Add a pause button addAction( NotificationCompat.Action( R.drawable.pause, getString(R.string.pause), MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_PLAY_PAUSE ) ) ) // Take advantage of MediaStyle features setStyle(android.support.v4.media.app.NotificationCompat.MediaStyle() .setMediaSession(mediaSession.sessionToken) .setShowActionsInCompactView(0) // Add a cancel button .setShowCancelButton(true) .setCancelButtonIntent( MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_STOP ) ) ) } // Display the notification and place the service in the foreground startForeground(id, builder.build())
Java
// Given a media session and its context (usually the component containing the session) // Create a NotificationCompat.Builder // Get the session's metadata MediaControllerCompat controller = mediaSession.getController(); MediaMetadataCompat mediaMetadata = controller.getMetadata(); MediaDescriptionCompat description = mediaMetadata.getDescription(); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId); builder // Add the metadata for the currently playing track .setContentTitle(description.getTitle()) .setContentText(description.getSubtitle()) .setSubText(description.getDescription()) .setLargeIcon(description.getIconBitmap()) // Enable launching the player by clicking the notification .setContentIntent(controller.getSessionActivity()) // Stop the service when the notification is swiped away .setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP)) // Make the transport controls visible on the lockscreen .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) // Add an app icon and set its accent color // Be careful about the color .setSmallIcon(R.drawable.notification_icon) .setColor(ContextCompat.getColor(context, R.color.primaryDark)) // Add a pause button .addAction(new NotificationCompat.Action( R.drawable.pause, getString(R.string.pause), MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_PLAY_PAUSE))) // Take advantage of MediaStyle features .setStyle(new MediaStyle() .setMediaSession(mediaSession.getSessionToken()) .setShowActionsInCompactView(0) // Add a cancel button .setShowCancelButton(true) .setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP))); // Display the notification and place the service in the foreground startForeground(id, builder.build());
MediaStyle bildirimlerini kullanırken aşağıdaki NotificationCompat ayarlarının davranışına dikkat edin:
setContentIntent()kullandığınızda bildirim tıklandığında hizmetiniz otomatik olarak başlatılır. Bu, kullanışlı bir özelliktir.- Kilit ekranı gibi "güvenilmeyen" durumlarda bildirim içeriklerinin varsayılan görünürlüğü
VISIBILITY_PRIVATEolur. Kilit ekranında taşıma kontrollerini görmek istiyorsanızVISIBILITY_PUBLICseçeneğini kullanmanız gerekir. - Arka plan rengini ayarlarken dikkatli olun. Android 5.0 veya sonraki sürümlerdeki normal bir bildirimde renk yalnızca küçük uygulama simgesinin arka planına uygulanır. Ancak Android 7.0'dan önceki MediaStyle bildirimlerinde renk, bildirimin tamamının arka planı için kullanılır. Arka plan renginizi test edin. Gözleri yormayacak ve çok parlak ya da floresan renkler kullanmayın.
Bu ayarlar yalnızca NotificationCompat.MediaStyle kullanıldığında kullanılabilir:
- Bildirimi oturumunuzla ilişkilendirmek için
setMediaSession()simgesini kullanın. Bu sayede üçüncü taraf uygulamaları ve eşlik eden cihazlar oturuma erişip oturumu kontrol edebilir. - Bildirimin standart boyutlu contentView'inde gösterilecek en fazla 3 işlem eklemek için
setShowActionsInCompactView()simgesini kullanın. (Burada duraklatma düğmesi belirtilir.) - Android 5.0 (API düzeyi 21) ve sonraki sürümlerde, hizmet artık ön planda çalışmadığında oynatıcıyı durdurmak için bildirimi kaydırarak kapatabilirsiniz. Bu işlemi önceki sürümlerde yapamazsınız. Kullanıcıların Android 5.0'dan (API düzeyi 21) önce bildirimi kaldırmasına ve oynatmayı durdurmasına izin vermek için
setShowCancelButton(true)vesetCancelButtonIntent()'i çağırarak bildirimin sağ üst köşesine bir iptal düğmesi ekleyebilirsiniz.
Duraklat ve iptal düğmelerini eklediğinizde, oynatma işlemine eklemek için bir PendingIntent'e ihtiyacınız olur. MediaButtonReceiver.buildMediaButtonPendingIntent() yöntemi, PlaybackState işlemini PendingIntent'e dönüştürme işini yapar.
AVRCP medya taramasına izin verme
Android Auto gibi özel uygulamaların yanı sıra sistemin Bluetooth katmanı, kablosuz uzaktan katalog taramayı (AVRCP) kolaylaştırmak için MediaBrowserService cihazınızın istemcisi olarak da işlev görür.
Android 16 ve Android 17'de platform, Media3'ü kullanmayan uygulamaların göz atma için doğrulanacak bir intent filtresiyle belirli bir etkinliği kullanıma sunmasını zorunlu kılar.
AndroidManifest.xml uygulamanızdaki dışa aktarılan bir etkinliğe bu belirli intent filtresini ekleyin. Uygulamanızın yerel ses dosyaları için genel "Bununla aç" menülerinde görünmesini önlemek amacıyla CATEGORY_DEFAULT karakterinin kasıtlı olarak çıkarıldığını unutmayın:
<activity
android:name=".BluetoothValidationActivity"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay"
android:excludeFromRecents="true"
android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="content" />
<data android:host="media" />
<!-- Specific path check used by Bluetooth stack for validation -->
<data android:pathPrefix="/internal/audio/media/" />
<data android:mimeType="audio/*" />
</intent-filter>
</activity>