Uygulamanızın, MediaBrowserService
özelliğini manifest dosyasında intent filtresiyle beyan etmesi gerekir. Kendi hizmet adınızı seçebilirsiniz. Aşağıdaki örnekte bu ad "MediaPlaybackService"tir.
<service android:name=".MediaPlaybackService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
Not: Önerilen MediaBrowserService
uygulaması: MediaBrowserServiceCompat
.
Bu ad, media-compat destek kitaplığında
tanımlanır.
Bu sayfada "MediaTarayıcıService" terimi, MediaBrowserServiceCompat
öğesinin bir örneğini belirtir.
Medya oturumunu başlatın
Hizmet, onCreate()
yaşam döngüsü geri çağırma yöntemini aldığında şu adımları uygulamalıdır:
- Medya oturumunu oluşturun ve başlatın
- Medya oturumu geri çağırmasını ayarla
- Medya oturumu jetonunu ayarlama
Aşağıdaki onCreate()
kodu, şu adımları gösterir:
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
MediaBrowserService
, istemci bağlantılarını yöneten iki yönteme sahiptir:
onGetRoot()
hizmete erişimi kontrol eder,
onLoadChildren()
istemcinin MediaBrowserService
içerik hiyerarşisinin bir menüsünü oluşturup görüntülemesini sağlar.
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 kök kimliği olan, boş olmayan bir TarayıcıRoot döndürmelidir.
İstemcilerin göz atmadan MediaSession'a bağlanmasına izin vermek için onGetRoot() yine de boş olmayan bir ScannerRoot döndürmelidir, ancak kök kimliği boş bir içerik hiyerarşisini temsil etmelidir.
Tipik bir onGetRoot()
uygulaması aşağıdaki gibi 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 bir erişim kontrol listesi (EKL) kullanmaktır ya da alternatif olarak hangi bağlantıların yasaklanması gerektiğini sıralar. Belirli bağlantılara izin veren bir EKL'nin nasıl uygulanacağına ilişkin bir örnek için Universal Android Music Player örnek uygulamasındaki PackageValidator sınıfına bakın.
Sorguyu yapan istemcinin türüne bağlı olarak farklı içerik hiyerarşileri sağlamanız gerekir. Özellikle, Android Auto, kullanıcıların
sesli uygulamalarla etkileşimde bulunma şeklini sınırlandırır. Daha fazla bilgi için Otomatik için Ses Oynatma bölümüne bakın. İstemci türünü belirlemek için bağlantı zamanında clientPackageName
öğesine bakabilir ve istemciye bağlı olarak farklı bir BrowserRoot
(veya varsa rootHints
) döndürebilirsiniz.
İçerikleri onLoadChildren()
ile iletişim kurma
İstemci bağlandıktan sonra, kullanıcı arayüzünün yerel bir temsilini oluşturmak için MediaBrowserCompat.subscribe()
öğesine tekrarlanan çağrılar yaparak içerik hiyerarşisinde gezinebilir. subscribe()
yöntemi, onLoadChildren()
geri çağırmasını hizmete gönderir ve bu işlem, MediaBrowser.MediaItem
nesnelerinin bir listesini döndürür.
Her MediaItem, opak bir jeton olan benzersiz bir kimlik dizesine sahiptir. Bir müşteri bir alt menü açmak veya bir öğeyi oynatmak istediğinde kimliği aktarır. Kimliği uygun menü düğümü veya içerik öğesiyle ilişkilendirmekten hizmetiniz sorumludur.
Basit bir onLoadChildren()
uygulaması aşağıdaki gibi 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: MediaTarayıcı Hizmeti tarafından yayınlanan MediaItem
nesneleri simge bit eşlemleri içermemelidir. Her bir öğe için MediaDescription
oluştururken setIconUri()
yöntemini çağırarak bunun yerine bir Uri
kullanın.
onLoadChildren()
uygulamasının nasıl uygulanacağına ilişkin bir örnek için Universal Android Music Player örnek uygulamasına bakın.
Medya tarayıcısı hizmet yaşam döngüsü
Bir Android hizmetinin davranışı, hizmetin bir veya daha fazla istemciye başlatılmasına veya bağlı olmasına bağlıdır. Hizmet oluşturulduktan sonra başlatılabilir, bağlanabilir veya her ikisi birden olabilir. Bu durumların tümünde, cihaz tam olarak işlevseldir ve tasarlandığı işi gerçekleştirebilir. Aradaki fark, hizmetin ne kadar süreyle var olacağıdır. Bir bağlı hizmet, tüm bağlı istemcilerinin bağlantısı kaldırılana kadar kaldırılmaz. Başlatılan bir hizmet açıkça durdurulabilir ve kaldırılabilir (artık herhangi bir istemciye bağlı olmadığı varsayılır).
Başka bir etkinlikte çalışan MediaBrowser
bir MediaBrowserService
hizmetine bağlandığında etkinliği hizmete bağlar. Böylece hizmeti bağlı hale getirir (ancak başlatılmamış olur). Bu varsayılan davranış MediaBrowserServiceCompat
sınıfında yerleşik olarak bulunur.
Yalnızca bağlı olan (ve başlatılmayan) bir hizmet, tüm istemcilerinin bağlantısını kaldırdığınızda kaldırılır. Kullanıcı arayüzü etkinliğiniz bu noktada kesilirse hizmet kaldırılır. Henüz hiç müzik çalmadıysanız bu bir sorun değildir. Bununla birlikte, oynatma başladığında kullanıcı büyük olasılıkla uygulama değiştirdikten sonra da dinlemeye devam etmeyi bekler. Başka bir uygulamayla çalışmak için kullanıcı arayüzünün bağlantısını kaldırdığınızda oynatıcıyı yok etmek istemezsiniz.
Bu nedenle, çalmaya başladığında startService()
yöntemini çağırarak hizmetin başlatıldığından emin olmanız gerekir. Başlatılan bir hizmet, bağlı olup olmamasına bakılmaksızın açıkça durdurulmalıdır. Bu, kontrol eden kullanıcı arayüzü etkinliğinin bağlantısı kesilse bile oynatıcınızın çalışmaya devam etmesini sağlar.
Başlatılan bir hizmeti durdurmak için Context.stopService()
veya stopSelf()
numaralı telefonu arayın. Sistem en kısa sürede hizmeti durdurur ve kaldırır. Ancak hâlâ hizmete bağlı bir veya daha fazla istemci varsa hizmeti durdurma çağrısı, tüm istemcilerin bağlantısı kesilene kadar ertelenir.
MediaBrowserService
yaşam döngüsü; oluşturulma yöntemi, ona 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 ona bağlandığında (
MediaBrowser
üzerinden bağlandıktan sonra) oluşturulur. - Medya oturumu
onPlay()
geri çağırması,startService()
kodunu içeren kodu içermelidir. Bu, hizmete bağlı tüm UIMediaBrowser
etkinliklerinin bağlantısı kaldırılsa bile hizmetin başlatılmasını ve çalışmaya devam etmesini sağlar. onStop()
geri çağırmasıylastopSelf()
çağrılır. Hizmet başlatıldıysa durdurulur. Ayrıca, herhangi bir etkinlik yoksa hizmet imha edilir. Aksi takdirde, tüm etkinliklerinin bağlantısı kaldırılana kadar hizmet bağlı kalır. (Hizmet kaldırılmadan önce birstartService()
araması gelirse beklemedeki durdurma iptal edilir.)
Aşağıdaki akış şemasında, bir hizmetin yaşam döngüsünün nasıl yönetildiği gösterilmektedir. Değişken sayacı, bağlı istemcilerin sayısını izler:
Bir ön plan hizmetiyle MediaStyle bildirimlerini kullanma
Bir hizmet oynatılırken ön planda çalışıyor olmalıdır. Bu, sistemin, hizmetin yararlı bir işlev gerçekleştirdiğini ve sistemde bellek yetersizse sonlandırılmaması gerektiğini bilmesini sağlar. Ön plan hizmetinin, kullanıcı hakkında bilgi sahibi olması ve isteğe bağlı olarak kontrol edebilmesi için bir bildirim görüntülemesi gerekir. onPlay()
geri çağırması, hizmeti ön plana getirir. (Bunun "ön plan"ın özel bir anlamı olduğunu unutmayın. Android, işlem yönetimi amacıyla bu hizmeti ön planda kabul eder. Ancak kullanıcı için oynatıcı arka planda oyun oynarken ekranda "ön planda" başka bir uygulama görünür.)
Bir hizmet ön planda çalıştığında, tercihen bir veya daha fazla aktarım denetimiyle birlikte bir bildirim görüntülemelidir. Bildirimde, oturumun meta verilerinden faydalı bilgiler de yer almalıdır.
Oynatıcı oynamaya başladığında bildirimi oluşturun ve görüntüleyin. Bunu yapmanın en iyi yolu MediaSessionCompat.Callback.onPlay()
yöntemini kullanmaktır.
Aşağıdaki örnekte, medya uygulamaları için tasarlanmış NotificationCompat.MediaStyle
kullanılmaktadır. Meta verileri ve aktarım kontrollerini gösteren bir bildirimin nasıl oluşturulacağı gösterilmektedir. Kolaylık yöntemi getController()
, doğrudan medya oturumunuzdan medya denetleyicisi 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 bu NotificationCompat ayarlarının davranışına dikkat edin:
setContentIntent()
hizmetini kullandığınızda bildirim tıklandığında hizmetiniz otomatik olarak başlar. Bu, kullanışlı bir özelliktir.- Kilit ekranı gibi "güvenilmeyen" bir durumda, bildirim içeriklerinin varsayılan görünürlüğü
VISIBILITY_PRIVATE
şeklindedir. Büyük olasılıkla taşıma kontrollerini kilit ekranında görmek istersiniz, o yüzden en iyi yolVISIBILITY_PUBLIC
. - Arka plan rengini belirlerken dikkatli olun. Android 5.0 veya sonraki sürümlerdeki sıradan bir bildirimde, renk yalnızca küçük uygulama simgesinin arka planına uygulanır. Ancak Android 7.0'dan önceki MediaStyle bildirimlerinde renk, bildirim arka planının tamamı için kullanılır. Arka plan renginizi test edin. Gözlerinizi nazikçe uygulamayın ve aşırı parlak veya floresan renklerden kaçının.
Bu ayarlar yalnızca NotificationCompat.MediaStyle kullanırken kullanılabilir:
- Bildirimi oturumunuzla ilişkilendirmek için
setMediaSession()
öğesini kullanın. Bu işlem, üçüncü taraf uygulamalarının ve tamamlayıcı cihazların oturuma erişip kontrol etmesini sağlar. - Bildirimin standart boyutlu ContentView öğesinde gösterilmek üzere en fazla 3 işlem eklemek için
setShowActionsInCompactView()
öğesini kullanın. (Burada duraklat düğmesi belirtilmiştir.) - 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 hızlıca kaydırabilirsiniz. Önceki sürümlerde bunu
yapamazsınız. Android 5.0 (API düzeyi 21) sürümünden önce kullanıcıların bildirimi kaldırmasına ve oynatmayı durdurmasına izin vermek için
setShowCancelButton(true)
vesetCancelButtonIntent()
çağrılarını yaparak 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.