Sering kali, media perlu diputar saat aplikasi tidak berada di latar depan. Misalnya, pemutar musik umumnya terus memutar musik saat pengguna mengunci perangkatnya atau menggunakan aplikasi lain. Library Media3 menyediakan serangkaian antarmuka yang memungkinkan Anda mendukung pemutaran di latar belakang.
Menggunakan MediaSessionService
Untuk mengaktifkan pemutaran di latar belakang, Anda harus menyertakan Player dan
MediaSession di dalam Service terpisah.
Hal ini memungkinkan perangkat untuk terus menyajikan media meskipun aplikasi Anda tidak berada di
latar depan.
MediaSessionService memungkinkan sesi
media berjalan secara terpisah dari aktivitas aplikasiSaat menghosting pemutar di dalam Service, Anda harus menggunakan MediaSessionService.
Untuk melakukannya, buat class yang memperluas MediaSessionService dan buat
sesi media Anda di dalamnya.
Dengan menggunakan MediaSessionService, klien eksternal seperti Asisten Google,
kontrol media sistem, tombol media di perangkat periferal, atau
perangkat pendamping seperti Wear OS dapat menemukan layanan Anda, terhubung ke layanan tersebut, dan
mengontrol pemutaran, semuanya tanpa mengakses aktivitas UI aplikasi Anda sama sekali. Bahkan,
beberapa aplikasi klien dapat terhubung ke MediaSessionService yang sama secara
bersamaan, dan setiap aplikasi memiliki MediaController-nya sendiri.
Mengimplementasikan siklus proses layanan
Anda perlu mengimplementasikan dua metode siklus proses layanan Anda:
onCreate()dipanggil saat pengontrol pertama akan terhubung dan layanan dibuat instance-nya dan dimulai. Ini adalah tempat terbaik untuk membangunPlayerdanMediaSession.onDestroy()dipanggil saat layanan dihentikan. Semua resource, termasuk pemutar dan sesi, harus dirilis.
Anda dapat mengganti onTaskRemoved(Intent) untuk menyesuaikan apa yang terjadi
saat pengguna menutup aplikasi dari tugas terbaru. Secara default, layanan
akan tetap berjalan jika pemutaran sedang berlangsung dan akan dihentikan jika tidak.
Kotlin
class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null // Create your Player and MediaSession in the onCreate lifecycle event override fun onCreate() { super.onCreate() val player = ExoPlayer.Builder(this).build() mediaSession = MediaSession.Builder(this, player).build() } // Remember to release the player and media session in onDestroy override fun onDestroy() { mediaSession?.run { player.release() release() mediaSession = null } super.onDestroy() } override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession }
Java
class PlaybackService extends MediaSessionService { private MediaSession mediaSession = null; // Create your Player and MediaSession in the onCreate lifecycle event @Override public void onCreate() { super.onCreate(); ExoPlayer player = new ExoPlayer.Builder(this).build(); mediaSession = new MediaSession.Builder(this, player).build(); } // Remember to release the player and media session in onDestroy @Override public void onDestroy() { mediaSession.getPlayer().release(); mediaSession.release(); mediaSession = null; super.onDestroy(); } @Override public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) { return mediaSession; } }
Sebagai alternatif untuk terus memutar di latar belakang, Anda dapat menghentikan layanan dalam kasus apa pun saat pengguna menutup aplikasi:
Kotlin
@OptIn(UnstableApi::class) override fun onTaskRemoved(rootIntent: Intent?) { pauseAllPlayersAndStopSelf() }
Java
@OptIn(markerClass = UnstableApi.class) @Override public void onTaskRemoved(@Nullable Intent rootIntent) { pauseAllPlayersAndStopSelf(); }
Untuk penerapan manual onTaskRemoved lainnya, Anda dapat menggunakan
isPlaybackOngoing() untuk memeriksa apakah pemutaran dianggap sedang berlangsung dan
layanan latar depan dimulai.
Memberikan akses ke sesi media
Ganti metode onGetSession() untuk memberi klien lain akses ke sesi media Anda yang dibuat saat layanan dibuat.
Kotlin
class PlaybackService : MediaSessionService() { // [...] lifecycle methods omitted override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession }
Java
class PlaybackService extends MediaSessionService { // [...] lifecycle methods omitted @Override public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) { return mediaSession; } }
Mendeklarasikan layanan dalam manifes
Aplikasi memerlukan izin FOREGROUND_SERVICE dan FOREGROUND_SERVICE_MEDIA_PLAYBACK
untuk menjalankan layanan latar depan pemutaran:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
Anda juga harus mendeklarasikan class Service dalam manifes dengan filter intent
MediaSessionService dan foregroundServiceType yang menyertakan
mediaPlayback.
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
Mengontrol pemutaran menggunakan MediaController
Dalam Activity atau Fragment yang berisi UI pemutar, Anda dapat membuat link
antara UI dan sesi media menggunakan MediaController. UI Anda menggunakan
pengontrol media untuk mengirim perintah dari UI ke pemutar dalam
sesi. Lihat panduan
Membuat MediaController
untuk mengetahui detail tentang cara membuat dan menggunakan MediaController.
Menangani perintah MediaController
MediaSession menerima perintah dari pengontrol melalui
MediaSession.Callback-nya. Melakukan inisialisasi MediaSession akan membuat
implementasi default MediaSession.Callback yang otomatis menangani semua
perintah yang dikirim MediaController ke pemutar Anda.
Notifikasi
MediaSessionService akan otomatis membuat MediaNotification untuk Anda yang
seharusnya berfungsi dalam sebagian besar kasus. Secara default, notifikasi yang dipublikasikan adalah
notifikasi MediaStyle yang terus diperbarui dengan informasi
terbaru dari sesi media Anda dan menampilkan kontrol pemutaran. MediaNotification
mengetahui sesi Anda dan dapat digunakan untuk mengontrol pemutaran
untuk aplikasi lain yang terhubung ke sesi yang sama.
Misalnya, aplikasi streaming musik yang menggunakan MediaSessionService akan membuat
MediaNotification yang menampilkan judul, artis, dan gambar album untuk
item media yang sedang diputar bersama dengan kontrol pemutaran berdasarkan konfigurasi
MediaSession Anda.
Metadata yang diperlukan dapat diberikan di media atau dideklarasikan sebagai bagian dari item media seperti dalam cuplikan berikut:
Kotlin
val mediaItem = MediaItem.Builder() .setMediaId("media-1") .setUri(mediaUri) .setMediaMetadata( MediaMetadata.Builder() .setArtist("David Bowie") .setTitle("Heroes") .setArtworkUri(artworkUri) .build() ) .build() mediaController.setMediaItem(mediaItem) mediaController.prepare() mediaController.play()
Java
MediaItem mediaItem = new MediaItem.Builder() .setMediaId("media-1") .setUri(mediaUri) .setMediaMetadata( new MediaMetadata.Builder() .setArtist("David Bowie") .setTitle("Heroes") .setArtworkUri(artworkUri) .build()) .build(); mediaController.setMediaItem(mediaItem); mediaController.prepare(); mediaController.play();
Siklus proses notifikasi
Notifikasi dibuat segera setelah Player memiliki instance MediaItem
dalam playlist-nya.
Semua update notifikasi terjadi secara otomatis berdasarkan status Player dan
MediaSession.
Notifikasi tidak dapat dihapus saat layanan latar depan berjalan. Untuk
segera menghapus notifikasi, Anda harus memanggil Player.release() atau menghapus
playlist menggunakan Player.clearMediaItems().
Jika pemutar dijeda, dihentikan, atau gagal selama lebih dari 10 menit tanpa interaksi pengguna lebih lanjut, layanan akan otomatis dialihkan dari status layanan latar depan sehingga dapat dihancurkan oleh sistem. Anda dapat menerapkan kelanjutan pemutaran untuk memungkinkan pengguna memulai ulang siklus proses layanan dan melanjutkan pemutaran di lain waktu.
Penyesuaian notifikasi
Metadata tentang item yang sedang diputar dapat disesuaikan dengan mengubah
MediaItem.MediaMetadata. Jika ingin memperbarui metadata item yang ada, Anda dapat menggunakan Player.replaceMediaItem untuk memperbarui metadata tanpa mengganggu pemutaran.
Anda juga dapat menyesuaikan beberapa tombol yang ditampilkan dalam notifikasi dengan menetapkan preferensi tombol media kustom untuk kontrol Media Android. Baca selengkapnya tentang menyesuaikan kontrol Media Android.
Untuk menyesuaikan notifikasi itu sendiri lebih lanjut, buat
MediaNotification.Provider
dengan DefaultMediaNotificationProvider.Builder
atau dengan membuat implementasi kustom antarmuka penyedia. Tambahkan penyedia
Anda ke MediaSessionService dengan
setMediaNotificationProvider.
Kelanjutan pemutaran
Setelah MediaSessionService dihentikan, dan bahkan setelah perangkat
dimulai ulang, Anda dapat menawarkan kelanjutan pemutaran agar pengguna dapat
memulai ulang layanan dan melanjutkan pemutaran dari bagian terakhir yang mereka tinggalkan. Secara default,
kelanjutan pemutaran dinonaktifkan. Artinya, pengguna tidak dapat melanjutkan pemutaran
saat layanan Anda tidak berjalan. Untuk mengaktifkan fitur ini, Anda harus mendeklarasikan
penerima tombol media dan menerapkan metode onPlaybackResumption.
Mendeklarasikan penerima tombol media Media3
Mulailah dengan mendeklarasikan MediaButtonReceiver dalam manifes Anda:
<receiver android:name="androidx.media3.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
Menerapkan callback kelanjutan pemutaran
Saat melanjutkan pemutaran diminta oleh perangkat Bluetooth atau
fitur melanjutkan UI Sistem Android,
metode callback onPlaybackResumption() akan
dipanggil.
Kotlin
override fun onPlaybackResumption( mediaSession: MediaSession, controller: MediaSession.ControllerInfo, isForPlayback: Boolean, ): ListenableFuture<MediaSession.MediaItemsWithStartPosition> { val settableFuture = SettableFuture.create<MediaSession.MediaItemsWithStartPosition>() settableFuture.addListener( { // Your app is responsible for storing the playlist, metadata (like title // and artwork) of the current item and the start position to use here. val resumptionPlaylist = restorePlaylist() settableFuture.set(resumptionPlaylist) }, MoreExecutors.directExecutor(), ) return settableFuture }
Java
@Override public ListenableFuture<MediaItemsWithStartPosition> onPlaybackResumption( MediaSession mediaSession, ControllerInfo controller, boolean isForPlayback) { SettableFuture<MediaItemsWithStartPosition> settableFuture = SettableFuture.create(); settableFuture.addListener( () -> { // Your app is responsible for storing the playlist, metadata (like title // and artwork) of the current item and the start position to use here. MediaItemsWithStartPosition resumptionPlaylist = restorePlaylist(); settableFuture.set(resumptionPlaylist); }, MoreExecutors.directExecutor()); return settableFuture; }
Jika Anda telah menyimpan parameter lain seperti kecepatan pemutaran, mode ulang, atau
mode acak, onPlaybackResumption() adalah tempat yang tepat untuk mengonfigurasi pemutar
dengan parameter ini sebelum Media3 menyiapkan pemutar dan memulai pemutaran saat
callback selesai.
Metode ini dipanggil selama waktu booting untuk membuat notifikasi pelanjutan UI Sistem Android setelah perangkat dimulai ulang dengan isForPlayback ditetapkan ke false. Untuk notifikasi lengkap, sebaiknya isi kolom MediaMetadata
seperti title dan artworkData atau artworkUri item saat ini dengan
nilai yang tersedia secara lokal, karena akses jaringan mungkin belum tersedia. Anda juga
dapat menambahkan MediaConstants.EXTRAS_KEY_COMPLETION_STATUS dan
MediaConstants.EXTRAS_KEY_COMPLETION_PERCENTAGE ke MediaMetadata.extras
untuk menunjukkan posisi pemutaran yang dilanjutkan.
Konfigurasi pengontrol lanjutan dan kompatibilitas mundur
Skenario yang umum adalah menggunakan MediaController di UI aplikasi untuk mengontrol
pemutaran dan menampilkan playlist. Pada saat yang sama, sesi ini diekspos
ke klien eksternal seperti kontrol media Android dan Asisten di perangkat seluler atau TV,
Wear OS untuk smartwatch, dan Android Auto di mobil. Aplikasi demo sesi Media3
adalah contoh aplikasi yang mengimplementasikan skenario tersebut.
Klien eksternal ini dapat menggunakan API seperti MediaControllerCompat dari library AndroidX lama atau android.media.session.MediaController dari platform Android. Media3 sepenuhnya kompatibel dengan versi lama library lama dan
menyediakan interoperabilitas dengan API platform Android.
Mengidentifikasi pengontrol tepercaya
Aplikasi apa pun dapat mencoba terhubung ke sesi atau library media Anda. Jika Anda ingin
membatasi akses ke pengontrol sistem, pengontrol dengan izin kontrol konten
media, dan aplikasi Anda sendiri, Anda dapat menggunakan ControllerInfo.isTrusted() untuk
pemeriksaan akses dasar. Atau, Anda dapat mengidentifikasi pengontrol yang lebih spesifik
seperti pengontrol notifikasi media atau pengontrol Android Auto seperti yang dijelaskan
di bagian berikut.
Menggunakan pengontrol notifikasi media
Penting untuk dipahami bahwa pengontrol lama dan platform ini memiliki
status yang sama dan visibilitas tidak dapat disesuaikan oleh pengontrol (misalnya
PlaybackState.getActions() dan PlaybackState.getCustomActions() yang tersedia).
Anda dapat menggunakan pengontrol notifikasi media untuk
mengonfigurasi status yang ditetapkan dalam sesi media platform untuk kompatibilitas dengan
pengontrol lama dan platform ini.
Misalnya, aplikasi dapat menyediakan implementasi
MediaSession.Callback.onConnect() untuk menetapkan perintah yang tersedia dan
preferensi tombol media khusus untuk sesi platform sebagai berikut:
Kotlin
override fun onConnectAsync( session: MediaSession, controller: MediaSession.ControllerInfo, ): ListenableFuture<ConnectionResult> { if (session.isMediaNotificationController(controller)) { val playerCommands = ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon() .remove(COMMAND_SEEK_TO_PREVIOUS) .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) .remove(COMMAND_SEEK_TO_NEXT) .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) .build() // Custom button preferences and commands to configure the platform session. return immediateFuture( AcceptedResultBuilder(session, controller) .setMediaButtonPreferences(listOf(seekBackButton, seekForwardButton)) .setAvailablePlayerCommands(playerCommands) .build() ) } // Default commands with default button preferences for all other controllers. return immediateFuture(AcceptedResultBuilder(session, controller).build()) }
Java
@Override public ListenableFuture<ConnectionResult> onConnectAsync( MediaSession session, MediaSession.ControllerInfo controller) { if (session.isMediaNotificationController(controller)) { Player.Commands playerCommands = ConnectionResult.DEFAULT_PLAYER_COMMANDS .buildUpon() .remove(COMMAND_SEEK_TO_PREVIOUS) .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) .remove(COMMAND_SEEK_TO_NEXT) .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) .build(); // Custom button preferences and commands to configure the platform session. return immediateFuture( new AcceptedResultBuilder(session, controller) .setMediaButtonPreferences(ImmutableList.of(seekBackButton, seekForwardButton)) .setAvailablePlayerCommands(playerCommands) .build()); } // Default commands with default button preferences for all other controllers. return immediateFuture(new AcceptedResultBuilder(session, controller).build()); }
Memberi Android Auto otorisasi untuk mengirim perintah kustom
Saat menggunakan MediaLibraryService
dan untuk mendukung Android Auto dengan aplikasi seluler, pengontrol Android Auto
memerlukan perintah yang tersedia dan sesuai. Jika tidak, Media3 akan menolak
perintah kustom yang masuk dari pengontrol tersebut:
Kotlin
override fun onConnectAsync( session: MediaSession, controller: MediaSession.ControllerInfo, ): ListenableFuture<ConnectionResult> { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon().add(customCommand).build() if (session.isMediaNotificationController(controller)) { // ... See above. } else if (session.isAutoCompanionController(controller)) { // Available commands to accept incoming custom commands from Auto. return immediateFuture( AcceptedResultBuilder(session, controller) .setAvailableSessionCommands(sessionCommands) .build() ) } // Default commands for all other controllers. return immediateFuture(AcceptedResultBuilder(session, controller).build()) }
Java
@Override public ListenableFuture<ConnectionResult> onConnectAsync( MediaSession session, MediaSession.ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon().add(customCommand).build(); if (session.isMediaNotificationController(controller)) { // ... See above. } else if (session.isAutoCompanionController(controller)) { // Available commands to accept incoming custom commands from Auto. return immediateFuture( new AcceptedResultBuilder(session, controller) .setAvailableSessionCommands(sessionCommands) .build()); } // Default commands for all other controllers. return immediateFuture(new AcceptedResultBuilder(session, controller).build()); }
Aplikasi demo sesi memiliki modul otomotif, yang menunjukkan dukungan untuk Automotive OS yang memerlukan APK terpisah.