android汽车音频焦点方案,管理音频焦点 | Android 开发者 | Android Developers
兩個或兩個以上的 Android 應用可同時向同一輸出流播放音頻。系統會將所有音頻流混合在一起。雖然這是一項出色的技術,但卻會給用戶帶來很大的困擾。為了避免所有音樂應用同時播放,Android 引入了“音頻焦點”的概念。
一次只能有一個應用獲得音頻焦點。
當您的應用需要輸出音頻時,它需要請求獲得音頻焦點,獲得焦點后,就可以播放聲音了。不過,在您獲得音頻焦點后,您可能無法將其一直持有到播放完成。其他應用可以請求焦點,從而占有您持有的音頻焦點。如果發生這種情況,您的應用應暫停播放或降低音量,以便于用戶聽到新的音頻源。
音頻焦點采用合作模式。我們建議應用遵守音頻焦點準則,但系統不會強制執行這些準則。如果應用想要在失去音頻焦點后繼續大聲播放,系統無法阻止它。這是一種不好的體驗,用戶很可能會卸載具有這種不良行為的應用。
行為恰當的音頻應用應根據以下一般準則來管理音頻焦點:
在即將開始播放之前調用 requestAudioFocus(),并驗證調用是否返回 onPlay() 回調中調用 requestAudioFocus()。
在其他應用獲得音頻焦點時,停止或暫停播放,或降低音量。
播放停止后,放棄音頻焦點。
運行的 Android 版本不同,音頻焦點的處理方式也會不同:
對于以 Android 5.0(API 級別 21)及更高版本為目標平臺的應用,音頻應用應使用
面向 Android 8.0(API 級別 26)或更高版本的應用應使用 AudioFocusRequest 包含有關應用的音頻上下文和功能的信息。系統使用這些信息來自動管理音頻焦點的得到和失去。
Android 8.0 及更高版本中的音頻焦點
從 Android 8.0(API 級別 26)開始,當您調用 AudioFocusRequest 參數。要釋放音頻焦點,請調用 AudioFocusRequest 作為參數。在請求和放棄焦點時,應使用相同的 AudioFocusRequest 實例。
要創建
FocusGain 字段為必需字段;所有其他字段均為可選字段。
方法備注
每個請求中都必須包含此字段。此字段的值與 Android 8.0 之前的 requestAudioFocus() 調用中所使用的 durationHint 值相同:AUDIOFOCUS_GAIN、AUDIOFOCUS_GAIN_TRANSIENT、AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 或 AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE。
首先使用
如果未指定,則 AudioAttributes 默認為 AudioAttributes.USAGE_MEDIA。
當其他應用使用 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 請求焦點時,持有焦點的應用通常不會收到 自行降低音量。如果您需要暫停播放而不是降低音量,請調用 setWillPauseWhenDucked(true),然后創建并設置 OnAudioFocusChangeListener,具體如自動降低音量中所述。
當焦點被其他應用鎖定時,對音頻焦點的請求可能會失敗。此方法可實現延遲獲取焦點,即在焦點可用時異步獲取焦點。
請注意,要使“延遲獲取焦點”起作用,您還必須在音頻請求中指定
只有當您在請求中還指定了 willPauseWhenDucked(true) 或 setAcceptsDelayedFocusGain(true) 時,才需要 OnAudioFocusChangeListener。
有兩個方法可以設置監聽器:一個帶處理程序參數,一個不帶。處理程序是運行監聽器的線程。如果您未指定處理程序,則會使用與主
以下示例展示了如何使用 AudioFocusRequest.Builder 構建 AudioFocusRequest 來請求和放棄音頻焦點:
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;
}
}
}
自動降低音量
在 Android 8.0(API 級別 26)中,當其他應用使用 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 請求焦點時,系統可以在不調用應用的 onAudioFocusChange() 回調的情況下降低和恢復音量。
雖然自動降低音量的行為對于音樂和視頻播放應用來說是可接受的,但在播放語音內容時(例如在聽書應用中)就沒什么用處了。在這種情況下,應用應該暫停播放。
如果您希望應用在被要求降低音量時暫停播放,請創建包含 onAudioFocusChange() 回調方法的 OnAudioFocusChangeListener,該回調方法可以實現所需的暫停/恢復行為。
調用
延遲獲取焦點
在有些情況下,系統不能批準對音頻焦點的請求,因為焦點被其他應用“鎖定”了,例如在通話過程中。在這種情況下,requestAudioFocus() 會返回 AUDIOFOCUS_REQUEST_FAILED。在這種情況下,您的應用將不會播放音頻,因為它未獲得焦點。
方法 AUDIOFOCUS_REQUEST_DELAYED。當鎖定音頻焦點的情況不再存在時(例如當通話結束時),系統會批準待處理的焦點請求,并調用 onAudioFocusChange() 來通知您的應用。
為了處理“延遲獲取焦點”,您必須創建包含 onAudioFocusChange() 回調方法的 OnAudioFocusChangeListener,該回調方法會通過調用
Android 8.0 之前的音頻焦點
當您調用
如果您計劃在可預見的將來播放音頻(例如在播放音樂時),并且希望前一個持有音頻焦點的應用停止播放,則應該請求永久性的音頻焦點 (AUDIOFOCUS_GAIN)。
如果您只希望在短時間內播放音頻,并且希望前一個持有音頻焦點的應用暫停播放,則應該請求暫時性的焦點 (AUDIOFOCUS_GAIN_TRANSIENT)。
請求附帶“降低音量”(AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) 的暫時性焦點,表示您只希望在短時間內播放音頻,并允許前一個持有焦點的應用在降低其音頻輸出的情況下繼續播放。這兩個音頻輸出會混合到音頻流中。降低音量特別適合于間歇性使用音頻流的應用,例如有聲的行車路線。
requestAudioFocus() 方法同樣需要 onAudioFocusChange() 回調,您的應用會在其他應用獲取或放棄音頻焦點時收到該回調。
以下代碼段會請求對 STREAM_MUSIC 流的永久性音頻焦點,并注冊 OnAudioFocusChangeListener 來處理音頻焦點的后續更改。(有關更改監聽器的說明,請參閱響應音頻焦點更改。)
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
}
Kotlin
audioManager.abandonAudioFocus(afChangeListener)Java
// Abandon audio focus when playback complete
audioManager.abandonAudioFocus(afChangeListener);
這會通知系統您不再需要焦點,并注銷關聯的 OnAudioFocusChangeListener。如果您請求的是暫時性焦點,則會通知已暫停或降低音量的應用它可以繼續播放或恢復其音量。
響應音頻焦點更改
當應用獲得音頻焦點后,它必須能夠在其他應用為自己請求音頻焦點時釋放該焦點。出現這種情況時,您的應用會收到對 AudioFocusChangeListener 中的 requestAudioFocus() 時指定的。
傳遞給 onAudioFocusChange() 的 focusChange 參數表示所發生的更改類型。它對應于獲取焦點的應用所使用的持續時間提示。您的應用應該做出適當的響應。
暫時性失去焦點
如果焦點更改是暫時性的(AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 或 AUDIOFOCUS_LOSS_TRANSIENT),您的應用應該降低音量(如果您不依賴于自動降低音量)或暫停播放,否則保持相同的狀態。
在暫時性失去音頻焦點時,您應該繼續監控音頻焦點的變化,并準備好在重新獲得焦點后恢復正常播放。當搶占焦點的應用放棄焦點時,您會收到一個回調 (AUDIOFOCUS_GAIN)。此時,您可以將音量恢復到正常水平或重新開始播放。永久性失去焦點
如果是永久性失去音頻焦點 (AUDIOFOCUS_LOSS),則其他應用會播放音頻。您的應用應立即暫停播放,因為它不會收到 AUDIOFOCUS_GAIN 回調。要重新開始播放,用戶必須執行明確的操作,例如在通知或應用界面中按播放傳輸控件。
以下代碼段展示了如何實現 OnAudioFocusChangeListener 及其 onAudioFocusChange() 回調。請注意這里使用 Handler 延遲了對永久性失去音頻焦點的停止回調。
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
}
}
};
處理程序使用如下所示的 Runnable:
Kotlin
private var delayedStopRunnable = Runnable {
mediaController.transportControls.stop()
}Java
private Runnable delayedStopRunnable = new Runnable() {
@Override
public void run() {
getMediaController().getTransportControls().stop();
}
};
為了確保在用戶重新開始播放時不會觸發延遲停止,請調用 mHandler.removeCallbacks(mDelayedStopRunnable) 來響應任何狀態變化。例如,在回調的 onPlay()、onSkipToNext() 等中調用 removeCallbacks()。此外,在清理服務使用的資源時,您也應該在服務的 onDestroy() 回調中調用此方法。
總結
以上是生活随笔為你收集整理的android汽车音频焦点方案,管理音频焦点 | Android 开发者 | Android Developers的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java终结方法_Java终结任务:Ca
- 下一篇: 没有bug队——加贝——Python 5