Aplikasi Anda harus mendeklarasikan MediaBrowserService
dengan filter intent dalam manifesnya. Anda dapat memilih nama layanan Anda sendiri; dalam contoh berikut, nama layanan yang digunakan adalah "MediaPlaybackService".
<service android:name=".MediaPlaybackService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
Catatan: Penerapan MediaBrowserService
yang direkomendasikan
adalah MediaBrowserServiceCompat
.
yang didefinisikan dalam
support library media-compat.
Di sepanjang halaman ini, istilah "MediaBrowserService" mengacu pada instance
dari MediaBrowserServiceCompat
.
Menginisialisasi sesi media
Saat menerima metode callback siklus proses onCreate()
, layanan harus melakukan langkah-langkah berikut:
- Membuat dan menginisialisasi sesi media
- Menetapkan callback sesi media
- Menetapkan token sesi media
Kode onCreate()
di bawah ini menunjukkan langkah-langkah tersebut:
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()); } }
Mengelola koneksi klien
MediaBrowserService
memiliki dua metode yang menangani koneksi klien:
Kontrol onGetRoot()
akses ke layanan, dan
onLoadChildren()
memberikan kemampuan bagi klien untuk membuat dan menampilkan menu hierarki konten MediaBrowserService
.
Mengontrol koneksi klien dengan onGetRoot()
Metode onGetRoot()
menampilkan node root hierarki konten. Jika
mengembalikan null, koneksi akan ditolak.
Agar klien dapat terhubung ke layanan Anda dan menjelajahi konten medianya, onGetRoot() harus mengembalikan BrowserRoot non-null yang merupakan ID root yang merepresentasikan hierarki konten.
Untuk mengizinkan klien terhubung ke MediaSession Anda tanpa melakukan penjelajahan, onGetRoot() tetap harus mengembalikan BrowserRoot bukan null, tetapi ID root harus mewakili hierarki konten yang kosong.
Implementasi standar onGetRoot()
mungkin terlihat seperti ini:
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); } }
Dalam beberapa kasus, Anda mungkin
ingin mengontrol siapa saja yang dapat terhubung
ke MediaBrowserService
Anda. Salah satu caranya adalah dengan menggunakan
daftar kontrol akses (ACL)
yang menentukan koneksi mana yang diperbolehkan, atau
koneksi mana yang harus dilarang. Contoh cara mengimplementasikan ACL
yang memungkinkan koneksi tertentu, lihat
PackageValidator
di Universal Android Music Player
aplikasi contoh.
Anda harus mempertimbangkan untuk menyediakan hierarki
konten yang berbeda tergantung pada
jenis klien apa yang membuat kueri. Secara khusus, Android Auto membatasi cara
pengguna berinteraksi dengan aplikasi audio. Untuk informasi selengkapnya, lihat Memutar Audio untuk
Otomatis. Anda
dapat melihat clientPackageName
pada waktu koneksi untuk menentukan klien
jenis, dan menampilkan BrowserRoot
yang berbeda bergantung pada klien (atau rootHints
jika ada).
Mengomunikasikan konten dengan onLoadChildren()
Setelah tersambung, klien dapat menjelajahi hierarki konten dengan melakukan panggilan berulang ke MediaBrowserCompat.subscribe()
untuk membuat representasi lokal dari UI. Metode subscribe()
mengirim callback onLoadChildren()
ke layanan, yang akan menampilkan daftar objek MediaBrowser.MediaItem
.
Setiap MediaItem memiliki string ID unik, yang merupakan token buram. Saat klien ingin membuka submenu atau memutar item, ID tersebut akan diteruskan. Layanan Anda bertanggung jawab untuk mengaitkan ID ini dengan node menu atau item konten yang sesuai.
Implementasi sederhana dari onLoadChildren()
mungkin terlihat seperti ini:
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); }
Catatan: Objek MediaItem
yang dikirimkan oleh MediaBrowserService
tidak boleh berisi bitmap ikon. Gunakan Uri
sebagai gantinya dengan memanggil
setIconUri()
saat Anda membangun MediaDescription
untuk setiap item.
Untuk cara mengimplementasikan onLoadChildren()
, lihat contoh aplikasi Universal Android Music Player.
Siklus hidup layanan browser media
Perilaku layanan Android bergantung pada apakah layanan tersebut dimulai atau terikat ke satu atau beberapa klien. Setelah dibuat, layanan dapat berstatus dimulai, terikat, atau keduanya. Dalam semua status ini, layanan berfungsi sepenuhnya dan dapat menjalankan tugas yang ditetapkan padanya. Perbedaannya terletak pada berapa lama layanan itu akan bertahan. Layanan terikat tidak akan dimusnahkan hingga semua klien terikatnya terbebas. Layanan yang dimulai dapat dihentikan dan dimusnahkan secara eksplisit (dengan asumsi layanan tersebut tidak terikat lagi dengan klien mana pun).
Ketika MediaBrowser
yang berjalan di aktivitas lain terhubung ke MediaBrowserService
, aktivitas akan diikat ke layanan, sehingga layanan menjadi terikat (tetapi tidak dimulai). Perilaku default ini tertanam dalam class MediaBrowserServiceCompat
.
Layanan yang hanya terikat (dan tidak dimulai) dimusnahkan setelah semua kliennya terbebas. Jika aktivitas UI Anda terputus pada tahap ini, layanan akan dimusnahkan. Hal ini tidak menjadi masalah jika Anda belum memutar musik apa pun. Namun, jika pemutaran sudah dimulai, pengguna mungkin berharap untuk terus mendengarkan musik meskipun mereka telah beralih aplikasi. Anda tidak boleh memusnahkan pemutar ketika membebaskan UI agar berfungsi dengan aplikasi lain.
Untuk alasan ini, Anda perlu memastikan bahwa
layanan dimulai saat layanan itu dimulai
untuk diputar dengan memanggil startService()
. J
layanan yang dimulai harus dihentikan secara eksplisit, terlepas dari apakah layanan tersebut terikat atau tidak. Ini
memastikan pemutar Anda terus berperforma baik meskipun UI pengontrol
aktivitas pelepasan ikatan.
Untuk menghentikan layanan yang dimulai, panggil Context.stopService()
atau stopSelf()
. Sistem akan menghentikan dan memusnahkan layanan sesegera mungkin. Namun, jika satu atau beberapa klien masih terikat ke layanan, panggilan untuk menghentikan layanan akan ditunda sampai semua kliennya terbebas.
Siklus proses MediaBrowserService
dikendalikan berdasarkan cara pembuatannya, jumlah klien yang terikat padanya, dan panggilan yang diterimanya dari callback sesi media. Ringkasnya:
- Layanan dibuat saat dimulai sebagai respons terhadap tombol media atau ketika sebuah aktivitas menjadi terikat padanya (setelah tersambung melalui
MediaBrowser
-nya). - Callback
onPlay()
sesi media harus menyertakan kode yang memanggilstartService()
. Hal ini memastikan bahwa layanan dimulai dan terus berjalan meskipun semua aktivitasMediaBrowser
UI yang terikat padanya telah terbebas. - Callback
onStop()
harus memanggilstopSelf()
. Jika layanan dimulai, panggilan ini akan menghentikannya. Selain itu, layanan akan dimusnahkan jika tidak ada aktivitas yang terikat padanya. Jika sebaliknya, layanan akan tetap terikat hingga semua aktivitasnya terbebas. (Jika panggilanstartService()
berikutnya diterima sebelum layanan dimusnahkan, penghentian yang tertunda akan dibatalkan.)
Diagram alir berikut menunjukkan cara mengelola siklus proses layanan. Penghitung variabel melacak jumlah klien terikat:
Menggunakan notifikasi MediaStyle dengan layanan latar depan
Saat dijalankan, layanan seharusnya berjalan di latar depan. Dengan begitu sistem akan mengetahui bahwa layanan tersebut menjalankan fungsi yang berguna dan tidak akan diakhiri jika sistem hampir kehabisan memori. Layanan latar depan harus menampilkan notifikasi agar pengguna mengetahuinya dan dapat mengendalikannya, jika ingin. Callback onPlay()
harus menempatkan layanan di latar depan. (Perhatikan bahwa ini adalah makna khusus dari "latar depan". Bagi Android, layanan dianggap berjalan di latar depan untuk tujuan pengelolaan proses. Sebaliknya, bagi pengguna, layanan dianggap memutar media di latar belakang sementara beberapa aplikasi lainnya tampak di "latar depan" pada layar.)
Saat berjalan di latar depan, layanan harus menampilkan notifikasi, yang idealnya berisi satu atau beberapa kontrol transport. Notifikasi ini juga harus menyertakan informasi berguna dari metadata sesi.
Buat dan tampilkan notifikasi saat pemutar mulai memutar media. Tempat terbaik untuk melakukannya adalah di dalam metode MediaSessionCompat.Callback.onPlay()
.
Contoh di bawah ini menggunakan
NotificationCompat.MediaStyle
,
yang didesain untuk aplikasi media. Contoh ini menunjukkan cara membuat notifikasi yang menampilkan kontrol transport dan metadata. Metode praktis
getController()
memungkinkan Anda membuat pengontrol media langsung dari sesi media.
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());
Saat menggunakan notifikasi MediaStyle, perhatikan perilaku Setelan NotificationCompat:
- Jika Anda menggunakan
setContentIntent()
, layanan Anda akan otomatis dimulai saat notifikasi diklik, sebuah fitur praktis. - Dalam situasi "tidak tepercaya" situasi
seperti layar kunci, visibilitas default untuk konten notifikasi adalah
VISIBILITY_PRIVATE
. Anda mungkin ingin melihat kontrol transport di layar kunci, jadiVISIBILITY_PUBLIC
adalah cara yang tepat. - Berhati-hatilah saat menyetel warna latar belakang. Dalam notifikasi biasa di Android versi 5.0 atau yang lebih baru, warna hanya diterapkan ke latar belakang ikon aplikasi kecil. Namun untuk notifikasi MediaStyle sebelum Android 7.0, warna digunakan untuk seluruh latar belakang notifikasi. Uji warna latar belakang Anda. Mulai lembut untuk mata dan hindari warna yang sangat terang atau bercahaya.
Setelan ini hanya tersedia jika Anda menggunakan NotificationCompat.MediaStyle:
- Gunakan
setMediaSession()
mengaitkan notifikasi dengan sesi Anda. Tindakan ini mengizinkan aplikasi pihak ketiga dan perangkat pendamping untuk mengakses dan mengontrol sesi. - Gunakan
setShowActionsInCompactView()
untuk menambahkan hingga 3 tindakan yang akan ditampilkan contentView berukuran standar notifikasi. (Di sini tombol jeda adalah lain.) - Di Android 5.0 (API level 21) dan yang lebih baru, Anda bisa menggeser notifikasi untuk menghentikan
setelah layanan tidak lagi berjalan di latar depan. Anda tidak bisa melakukan
hal ini pada versi-versi sebelumnya. Untuk mengizinkan pengguna menghapus notifikasi dan menghentikan pemutaran
sebelum Android 5.0 (API level 21), Anda bisa menambahkan tombol batal di sudut kanan atas
notifikasi dengan memanggil
setShowCancelButton(true)
dansetCancelButtonIntent()
.
Saat menambahkan tombol jeda dan batal, Anda memerlukan PendingIntent untuk dilampirkan
ke tindakan pemutaran. Metode MediaButtonReceiver.buildMediaButtonPendingIntent()
melakukan tugas konversi
tindakan PlaybackState menjadi PendingIntent.