Dua atau lebih aplikasi Android dapat memutar audio ke aliran output yang sama secara bersamaan. Sistem akan memadukan semua audio tersebut. Meskipun secara teknis mengesankan, hal ini dapat memberikan pengalaman pengguna yang buruk. Agar beberapa aplikasi musik tidak diputar secara bersamaan, Android memperkenalkan gagasan fokus audio. Hanya satu aplikasi yang dapat menerima fokus audio pada satu waktu.
Saat perlu meng-output audio, aplikasi Anda harus meminta fokus audio. Setelah mendapatkan fokus, aplikasi Anda dapat memutar audio. Namun, setelah fokus audio diperoleh, Anda mungkin tidak dapat mempertahankannya sampai pemutaran audio selesai. Aplikasi lain dapat meminta fokus, dan hal tersebut akan mengakhiri fokus audio Anda saat ini. Jika itu terjadi, aplikasi Anda harus menjeda pemutaran atau menurunkan volumenya agar pengguna dapat mendengar sumber audio baru dengan lebih mudah.
Fokus audio bersifat kooperatif. Aplikasi dianjurkan untuk mematuhi pedoman fokus audio, tetapi sistem tidak memberlakukan aturan. Jika sebuah aplikasi ingin terus memutar audio dengan keras bahkan setelah kehilangan fokusnya, tidak ada yang dapat mencegahnya. Ini adalah pengalaman yang buruk dan sangat mungkin pengguna akan meng-uninstal aplikasi yang berperilaku buruk semacam ini.
Aplikasi audio yang berperilaku baik harus mengelola fokus audio sesuai dengan pedoman umum berikut:
- Panggil
requestAudioFocus()
tepat sebelum mulai memutar audio dan pastikan panggilan tersebut menampilkanAUDIOFOCUS_REQUEST_GRANTED
. Jika aplikasi Anda didesain seperti yang kami jelaskan dalam panduan ini, panggilan kerequestAudioFocus()
harus dilakukan dalam callbackonPlay()
sesi media Anda. - Saat aplikasi lain memperoleh fokus audio, hentikan atau jeda pemutaran, atau perkecil volume.
- Setelah pemutaran berhenti, tinggalkan fokus audio.
Fokus audio ditangani secara berbeda bergantung pada versi Android yang sedang berjalan:
- Mulai dari Android 2.2 (API level 8), aplikasi mengelola fokus audio dengan memanggil
requestAudioFocus()
danabandonAudioFocus()
. Aplikasi juga harus mendaftarkanAudioManager.OnAudioFocusChangeListener
ke kedua panggilan agar dapat menerima callback dan mengelola level audionya sendiri. - Jika menargetkan Android 5.0 (API level 21) dan yang lebih baru, aplikasi audio harus menggunakan
AudioAttributes
untuk mendeskripsikan jenis audio yang sedang diputar aplikasi Anda. Misalnya, aplikasi yang memutar ucapan harus menetapkanCONTENT_TYPE_SPEECH
. - Aplikasi yang menjalankan Android 8.0 (API level 26) atau yang lebih baru harus menggunakan metode
requestAudioFocus()
, yang menggunakan parameterAudioFocusRequest
.AudioFocusRequest
berisi informasi tentang konteks audio dan kemampuan aplikasi Anda. Sistem menggunakan informasi ini untuk mengelola perolehan dan kehilangan fokus audio secara otomatis.
Fokus audio pada Android versi 8.0 dan yang lebih baru
Mulai dari Android 8.0 (API level 26), ketika memanggil requestAudioFocus()
, Anda harus memberikan parameter AudioFocusRequest
.
Untuk melepas fokus audio, panggil metode abandonAudioFocusRequest()
yang juga menggunakan argumen AudioFocusRequest
. Instance AudioFocusRequest
yang sama harus digunakan saat meminta dan meninggalkan fokus.
Untuk membuat AudioFocusRequest
, gunakan AudioFocusRequest.Builder
. Karena permintaan fokus harus selalu menetapkan jenis permintaan, jenis tersebut disertakan dalam constructor untuk builder. Gunakan metode builder ini untuk menetapkan kolom lainnya dalam permintaan.
Kolom FocusGain
wajib diisi; semua kolom lainnya bersifat opsional.
Metode | Catatan |
---|---|
setFocusGain()
|
Kolom ini wajib diisi dalam setiap permintaan. Kolom ini menerima nilai yang sama dengan yang digunakan durationHint dalam panggilan ke requestAudioFocus() : AUDIOFOCUS_GAIN , AUDIOFOCUS_GAIN_TRANSIENT , AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK , atau AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE pada versi Android sebelum 8.0.
|
setAudioAttributes()
|
AudioAttributes mendeskripsikan kasus penggunaan untuk aplikasi Anda. Sistem akan memeriksanya saat aplikasi memperoleh dan kehilangan fokus audio. Atribut menggantikan konsep jenis aliran. Di Android 8.0 (API level 26) dan yang lebih baru, jenis aliran untuk operasi apa pun selain kontrol volume tidak digunakan lagi. Gunakan atribut yang sama dalam permintaan fokus dengan yang Anda gunakan di pemutar audio (seperti yang ditunjukkan pada contoh di bawah tabel ini).
Gunakan
Jika tidak ditentukan, |
setWillPauseWhenDucked()
|
Saat aplikasi lain meminta fokus dengan AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK , aplikasi yang mendapatkan fokus biasanya tidak menerima callback onAudioFocusChange() karena sistem dapat mengecilkan volume sendiri. Jika Anda perlu menjeda pemutaran, bukan mengecilkan volume, panggil setWillPauseWhenDucked(true) , lalu buat dan tetapkan OnAudioFocusChangeListener , seperti dijelaskan dalam pengecilan volume otomatis.
|
setAcceptsDelayedFocusGain()
|
Permintaan untuk fokus audio dapat gagal jika fokus dikunci oleh aplikasi lain. Metode ini mengaktifkan perolehan fokus tertunda: kemampuan untuk memperoleh fokus secara asinkron saat fokus mulai tersedia.
Perhatikan bahwa perolehan fokus tertunda hanya berfungsi jika Anda juga menentukan |
setOnAudioFocusChangeListener()
|
OnAudioFocusChangeListener hanya diperlukan jika Anda menentukan willPauseWhenDucked(true) atau setAcceptsDelayedFocusGain(true) dalam permintaan.
Ada dua metode untuk menetapkan pemroses: satu dengan dan satu tanpa argumen pengendali. Pengendali ini adalah thread tempat pemroses berjalan. Jika Anda tidak menentukan pengendali, maka yang digunakan adalah pengendali yang terkait dengan |
Contoh berikut menunjukkan cara menggunakan AudioFocusRequest.Builder
untuk membuat AudioFocusRequest
serta meminta dan meninggalkan fokus audio:
Kotlin
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run { setAudioAttributes(AudioAttributes.Builder().run { setUsage(AudioAttributes.USAGE_GAME) setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) build() }) setAcceptsDelayedFocusGain(true) setOnAudioFocusChangeListener(afChangeListener, handler) build() } mediaPlayer = MediaPlayer() val focusLock = Any() var playbackDelayed = false var playbackNowAuthorized = false // ... val res = audioManager.requestAudioFocus(focusRequest) synchronized(focusLock) { playbackNowAuthorized = when (res) { AudioManager.AUDIOFOCUS_REQUEST_FAILED -> false AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> { playbackNow() true } AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> { playbackDelayed = true false } else -> false } } // ... override fun onAudioFocusChange(focusChange: Int) { when (focusChange) { AudioManager.AUDIOFOCUS_GAIN -> if (playbackDelayed || resumeOnFocusGain) { synchronized(focusLock) { playbackDelayed = false resumeOnFocusGain = false } playbackNow() } AudioManager.AUDIOFOCUS_LOSS -> { synchronized(focusLock) { resumeOnFocusGain = false playbackDelayed = false } pausePlayback() } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> { synchronized(focusLock) { resumeOnFocusGain = true playbackDelayed = false } pausePlayback() } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> { // ... pausing or ducking depends on your app } } }
Java
audioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE); playbackAttributes = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_GAME) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build(); focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) .setAudioAttributes(playbackAttributes) .setAcceptsDelayedFocusGain(true) .setOnAudioFocusChangeListener(afChangeListener, handler) .build(); mediaPlayer = new MediaPlayer(); final Object focusLock = new Object(); boolean playbackDelayed = false; boolean playbackNowAuthorized = false; // ... int res = audioManager.requestAudioFocus(focusRequest); synchronized(focusLock) { if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) { playbackNowAuthorized = false; } else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { playbackNowAuthorized = true; playbackNow(); } else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) { playbackDelayed = true; playbackNowAuthorized = false; } } // ... @Override public void onAudioFocusChange(int focusChange) { switch (focusChange) { case AudioManager.AUDIOFOCUS_GAIN: if (playbackDelayed || resumeOnFocusGain) { synchronized(focusLock) { playbackDelayed = false; resumeOnFocusGain = false; } playbackNow(); } break; case AudioManager.AUDIOFOCUS_LOSS: synchronized(focusLock) { resumeOnFocusGain = false; playbackDelayed = false; } pausePlayback(); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: synchronized(focusLock) { resumeOnFocusGain = true; playbackDelayed = false; } pausePlayback(); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: // ... pausing or ducking depends on your app break; } } }
Pengecilan volume otomatis
Di Android 8.0 (API level 26), saat aplikasi lain meminta fokus dengan AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
, sistem dapat mengecilkan dan memulihkan volume tanpa memanggil callback onAudioFocusChange()
aplikasi.
Meskipun pengecilan volume otomatis merupakan perilaku yang dapat diterima untuk aplikasi pemutaran musik dan video, kapabilitas tersebut tidak berguna saat memutar konten lisan, seperti dalam aplikasi buku audio. Dalam kasus ini, aplikasi harus menjeda audio.
Jika Anda ingin aplikasi menjeda pemutaran, bukan mengurangi volumenya, saat diminta mengecilkan volume, buatlah OnAudioFocusChangeListener
dengan metode callback onAudioFocusChange()
yang menerapkan perilaku jeda/lanjutkan yang diinginkan.
Panggil setOnAudioFocusChangeListener()
untuk mendaftarkan pemroses, dan panggil setWillPauseWhenDucked(true)
untuk memberi tahu sistem agar menggunakan callback Anda, bukan menjalankan pengecilan volume otomatis.
Perolehan fokus tertunda
Terkadang, sistem tidak dapat memenuhi permintaan fokus audio karena fokus "dikunci" oleh aplikasi lain, misalnya selama panggilan telepon. Dalam hal ini, requestAudioFocus()
menampilkan AUDIOFOCUS_REQUEST_FAILED
. Jika ini terjadi, aplikasi Anda tidak boleh melanjutkan pemutaran audio karena tidak memperoleh fokus.
Metode setAcceptsDelayedFocusGain(true)
memungkinkan aplikasi Anda menangani permintaan fokus secara asinkron. Setelah tanda ini ditetapkan, permintaan yang dibuat saat fokus audio terkunci akan menampilkan AUDIOFOCUS_REQUEST_DELAYED
. Saat kondisi yang mengunci fokus audio tidak ada lagi, misalnya setelah panggilan telepon berakhir, sistem akan memenuhi permintaan fokus tertunda dan memanggil onAudioFocusChange()
untuk memberi tahu aplikasi Anda.
Untuk menangani pemerolehan fokus tertunda, Anda harus membuat OnAudioFocusChangeListener
dengan metode callback onAudioFocusChange()
yang menerapkan perilaku yang diinginkan dan mendaftarkan pemroses dengan memanggil setOnAudioFocusChangeListener()
.
Fokus audio pada Android sebelum versi 8.0
Saat Anda memanggil requestAudioFocus()
, Anda harus menentukan petunjuk durasi, yang dapat diikuti oleh aplikasi lain yang saat ini memegang fokus dan melakukan pemutaran:
- Mintalah fokus audio permanen (
AUDIOFOCUS_GAIN
) jika Anda ingin memutar audio untuk waktu yang dapat diprediksi (misalnya, saat memutar musik) dan Anda ingin pemegang fokus audio sebelumnya menghentikan pemutaran. - Mintalah fokus audio sementara (
AUDIOFOCUS_GAIN_TRANSIENT
) jika Anda ingin memutar audio hanya untuk waktu singkat dan Anda ingin pemegang fokus audio sebelumnya menjeda pemutaran. - Mintalah fokus sementara dengan pengecilan audio (
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
) jika Anda ingin memutar audio dalam waktu singkat dan membolehkan pemegang fokus audio sebelumnya melanjutkan pemutaran asalkan output audionya dikecilkan. Kedua output audio dicampur ke dalam aliran audio. Pengecilan volume sangat cocok untuk aplikasi yang menggunakan aliran audio secara terputus-putus, seperti untuk audio petunjuk arah mengemudi.
Metode requestAudioFocus()
juga memerlukan AudioManager.OnAudioFocusChangeListener
. Pemroses ini harus dibuat dalam aktivitas atau layanan yang sama dengan yang memiliki sesi media Anda. Pemroses ini menerapkan callback onAudioFocusChange()
yang diterima aplikasi Anda saat beberapa aplikasi lainnya memperoleh atau meninggalkan fokus audio.
Cuplikan berikut meminta fokus audio permanen pada aliran STREAM_MUSIC
dan mendaftarkan OnAudioFocusChangeListener
untuk menangani perubahan selanjutnya dalam fokus audio. (Pemroses perubahan dibahas dalam Merespons perubahan fokus audio.)
Kotlin
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager lateinit var afChangeListener AudioManager.OnAudioFocusChangeListener ... // Request audio focus for playback val result: Int = audioManager.requestAudioFocus( afChangeListener, // Use the music stream. AudioManager.STREAM_MUSIC, // Request permanent focus. AudioManager.AUDIOFOCUS_GAIN ) if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { // Start playback }
Java
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); AudioManager.OnAudioFocusChangeListener afChangeListener; ... // Request audio focus for playback int result = audioManager.requestAudioFocus(afChangeListener, // Use the music stream. AudioManager.STREAM_MUSIC, // Request permanent focus. AudioManager.AUDIOFOCUS_GAIN); if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { // Start playback }
Setelah pemutaran selesai, panggil abandonAudioFocus()
.
Kotlin
audioManager.abandonAudioFocus(afChangeListener)
Java
// Abandon audio focus when playback complete audioManager.abandonAudioFocus(afChangeListener);
Ini akan memberi tahu sistem bahwa Anda tidak lagi memerlukan fokus dan membatalkan pendaftaran OnAudioFocusChangeListener
yang terkait. Jika Anda telah meminta fokus sementara, aplikasi yang menjeda atau mengecilkan volumenya akan diberi tahu sehingga dapat melanjutkan pemutaran atau memulihkan volumenya.
Merespons perubahan fokus audio
Aplikasi yang mendapatkan fokus audio harus dapat melepaskannya jika aplikasi lain meminta fokus audio untuknya sendiri. Jika ini terjadi, aplikasi Anda akan menerima panggilan ke metode onAudioFocusChange()
dalam AudioFocusChangeListener
yang Anda tentukan saat aplikasi memanggil requestAudioFocus()
.
Parameter focusChange
yang diteruskan ke onAudioFocusChange()
menunjukkan jenis perubahan yang sedang terjadi. Parameter ini berkaitan dengan petunjuk durasi yang digunakan oleh aplikasi yang mendapatkan fokus. Aplikasi Anda harus merespons dengan tepat.
- Kehilangan fokus sementara
-
Jika perubahan fokus bersifat sementara (
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
atauAUDIOFOCUS_LOSS_TRANSIENT
), aplikasi Anda harus mengecilkan volumenya (jika Anda tidak mengandalkan pengecilan volume otomatis) atau menjeda pemutaran. Dalam kondisi sebaliknya, aplikasi Anda harus mempertahankan status yang sama.Selama kehilangan fokus audio sementara, aplikasi Anda harus terus memantau perubahan fokus audio dan bersiap melanjutkan pemutaran normal setelah memperoleh kembali fokus. Setelah aplikasi yang memegang fokus audio meninggalkan fokusnya, Anda akan menerima callback (
AUDIOFOCUS_GAIN
). Pada tahap ini, Anda dapat memulihkan volume ke level normal atau memulai ulang pemutaran. - Kehilangan fokus permanen
-
Jika kehilangan fokus audio bersifat permanen (
AUDIOFOCUS_LOSS
), aplikasi lain akan memutar audio. Aplikasi Anda harus segera menjeda pemutaran, karena tidak akan pernah menerima callbackAUDIOFOCUS_GAIN
. Untuk memulai ulang pemutaran, pengguna harus melakukan tindakan eksplisit, seperti menekan kontrol transport play di UI notifikasi atau aplikasi.
Cuplikan kode berikut menunjukkan cara menerapkan OnAudioFocusChangeListener
dan callback onAudioFocusChange()
-nya. Perhatikan penggunaan Handler
untuk menunda callback stop saat kehilangan fokus audio bersifat permanen.
Kotlin
private val handler = Handler() private val afChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange -> when (focusChange) { AudioManager.AUDIOFOCUS_LOSS -> { // Permanent loss of audio focus // Pause playback immediately mediaController.transportControls.pause() // Wait 30 seconds before stopping playback handler.postDelayed(delayedStopRunnable, TimeUnit.SECONDS.toMillis(30)) } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> { // Pause playback } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> { // Lower the volume, keep playing } AudioManager.AUDIOFOCUS_GAIN -> { // Your app has been granted audio focus again // Raise volume to normal, restart playback if necessary } } }
Java
private Handler handler = new Handler(); AudioManager.OnAudioFocusChangeListener afChangeListener = new AudioManager.OnAudioFocusChangeListener() { public void onAudioFocusChange(int focusChange) { if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { // Permanent loss of audio focus // Pause playback immediately mediaController.getTransportControls().pause(); // Wait 30 seconds before stopping playback handler.postDelayed(delayedStopRunnable, TimeUnit.SECONDS.toMillis(30)); } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) { // Pause playback } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { // Lower the volume, keep playing } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { // Your app has been granted audio focus again // Raise volume to normal, restart playback if necessary } } };
Pengendali menggunakan Runnable
yang terlihat seperti ini:
Kotlin
private var delayedStopRunnable = Runnable { mediaController.transportControls.stop() }
Java
private Runnable delayedStopRunnable = new Runnable() { @Override public void run() { getMediaController().getTransportControls().stop(); } };
Untuk memastikan stop tertunda tidak menjadi aktif jika pengguna memulai ulang pemutaran, panggil mHandler.removeCallbacks(mDelayedStopRunnable)
sebagai respons terhadap perubahan status apa pun. Misalnya, panggil removeCallbacks()
di onPlay()
, onSkipToNext()
, dll. untuk Callback Anda. Sebaiknya Anda juga memanggil metode ini di callback onDestroy()
layanan Anda saat membersihkan resource yang digunakan oleh layanan Anda.