翻譯自Managing audio focushtml
兩個或更多的Android應用程序能夠同時播放音頻到相同的輸出流。系統把全部東西混合在一塊兒。雖然這在技術上是使人印象深入的,但對用戶來講倒是很是使人惱火的。爲了不全部音樂應用同時播放,Android引入了音頻聚焦的概念。只有一個應用程序能夠一次聚焦音頻。android
當您的應用程序須要輸出音頻時,它應該請求音頻焦點。當它有焦點時,它能夠播放聲音。然而,在你得到音頻焦點後,你可能沒法持有它直到你播放完。另外一個應用程序能夠請求焦點,它會搶佔你的音頻焦點。若是出現這種狀況,你的應用程序應該暫停播放或下降音量,讓用戶更容易聽到新的音頻源。bash
音頻聚焦是合做的。鼓勵應用程序遵照音頻聚焦指南,但該系統不執行這些規則。若是一個應用程序想在失去聲音焦點後繼續大聲播放,沒有什麼能阻止它。這是一種糟糕的體驗,用戶頗有可能會卸載出現這種糟糕體驗的應用程序。app
一個表現良好的音頻應用程序應該管理音頻焦點根據這些通常準則:異步
requestAudioFocus()
,並驗證調用是否返回AUDIOFOCUS_REQUEST_GRANTED。若是您按照咱們在本指南中描述的那樣設計應用程序,那麼應該在媒體會話的onPlay()
回調中調用requestAudioFocus()
。音頻焦點的處理方式因Android版本不一樣而異:ide
requestAudioFocus()
方法,它接受AudioFocusRequest參數。AudioFocusRequest包含關於音頻上下文和應用程序功能的信息。系統使用這些信息自動管理音頻焦點的獲取和丟失。從Android 8.0 (API級別26)開始,當您調用requestAudioFocus()時,必須提供AudioFocusRequest參數。若要釋放音頻焦點,請調用abandonAudioFocusRequest()方法,該方法還將AudioFocusRequest做爲其參數。當請求和放棄焦點時,應該使用相同的AudioFocusRequest實例。函數
要建立AudioFocusRequest,請使用AudioFocusRequest. builder。因爲焦點請求必須始終指定請求的類型,所以該類型包含在構造器的構造函數中。使用構建器的方法設置請求的其餘字段。oop
須要FocusGain
字段;全部其餘字段都是可選的。 解釋一下主要方法:post
每一個請求都須要這個字段。它的值與android8.0以前對requestaudiofococus()的調用中使用的durationHint相同:
AUDIOFOCUS_GAIN
,AUDIOFOCUS_GAIN_TRANSIENT
,AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
,或AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
。ui
audioattributes描述了系統應用程序的用例。當一個應用程序得到和失去了音頻的焦點時系統會查看他們。屬性取代了流類型的概念。在Android 8.0 (API級別26)及更高版本中,除了音量控制以外的任何操做都不支持流類型。在focus請求中使用與音頻播放器中相同的屬性(以下表所示)。
使用一個AudioAttributes.Builder首先指定屬性,而後使用此方法將屬性分配給請求。
若是沒有指定,AudioAttributes默認爲
AudioAttributes.USAGE_MEDIA
。
當另外一個應用程序請求使用
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
進行焦點處理時,具備焦點的應用程序一般不會收到onAudioFocusChange()回調,由於系統能夠本身執行這個操做。當你須要暫停播放而不是下降音量時,調用setWillPauseWhenDucked(true),建立並設置一個OnAudioFocusChangeListener,以下面的自動ducking所述。
當焦點被另外一個應用鎖定時,對音頻焦點的請求可能會失敗。這種方法容許延遲焦點增益:當焦點可用時,異步獲取焦點的能力。
注意,只有當你指定了一個AudioManager.OnAudioFocusChangeListener時,延遲的焦點增益(focus gain)纔有效。由於您的應用程序須要接收回調以便知道焦點已被容許獲取。
只有在請求中指定willPauseWhenDucked(true)或setAcceptsDelayedFocusGain(true)時,才須要OnAudioFocusChangeListener。
設置偵聽器有兩種方法:一種有處理程序參數,另外一種沒有處理程序參數。處理程序是偵聽器運行的線程。若是不指定處理程序,則使用與主Looper關聯的處理程序(handler)。
下面的例子展現瞭如何使用AudioFocusRequest.Bulder構建一個AudioFocusRequest還有請求和放棄音頻焦點:
mAudioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
mPlaybackAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_GAME)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(mPlaybackAttributes)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(afChangeListener, mHandler)
.build();
mMediaPlayer = new MediaPlayer();
final Object mFocusLock = new Object();
boolean mPlaybackDelayed = false;
boolean mPlaybackNowAuthorized = false;
// ...
int res = mAudioManager.requestAudioFocus(mFocusRequest);
synchronized(mFocusLock) {
if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
mPlaybackNowAuthorized = false;
} else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
mPlaybackNowAuthorized = true;
playbackNow();
} else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
mPlaybackDelayed = true;
mPlaybackNowAuthorized = false;
}
}
// ...
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
if (mPlaybackDelayed || mResumeOnFocusGain) {
synchronized(mFocusLock) {
mPlaybackDelayed = false;
mResumeOnFocusGain = false;
}
playbackNow();
}
break;
case AudioManager.AUDIOFOCUS_LOSS:
synchronized(mFocusLock) {
mResumeOnFocusGain = false;
mPlaybackDelayed = false;
}
pausePlayback();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
synchronized(mFocusLock) {
mResumeOnFocusGain = true;
mPlaybackDelayed = 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()
回調。
在音樂和視頻播放應用程序中,自動下降音量是能夠接受的行爲,但在播放語音內容(好比在有聲書應用程序中)時,它就沒用了。在這種狀況下,應用程序應該暫停。
若是你想讓你的應用程序在被要求ducking時暫停而不是減小它的音量,建立一個OnAudioFocusChangeListener
,使用一個onAudioFocusChange()
回調方法來實現所需的暫停/恢復行爲。調用setOnAudioFocusChangeListener()來註冊偵聽器,並調用setWillPauseWhenDucked(true)來告訴系統使用您的回調,而不是執行自動ducking操做。
有時系統不能批准音頻焦點請求,由於焦點被另外一個應用程序「鎖定」,好比在打電話時。在本例中,requestAudioFocus()
返回AUDIOFOCUS_REQUEST_FAILED
。當這種狀況發生時,您的應用程序不該該繼續進行音頻播放,由於它沒有得到焦點。
該方法名爲setAcceptsDelayedFocusGain(true),它容許應用程序異步處理焦點請求。設置此標誌後,鎖定焦點時發出的請求將返回AUDIOFOCUS_REQUEST_DELAYED
。當鎖定音頻焦點的條件再也不存在時,例如當電話結束時,系統會授予掛起的焦點請求,並調用onAudioFocusChange()來通知應用程序。
爲了處理延遲的焦點增益,您必須建立一個OnAudioFocusChangeListener,它使用一個onAudioFocusChange()回調方法來實現所需的行爲,並經過調用setOnAudioFocusChangeListener()來註冊偵聽器。
當你調用requestAudioFocus()時,你必須指定一個duration hint,這個duration hint可能會被另外一個當前保持焦點並播放的應用程序使用:
當你計劃在可預見的未來播放音頻時(例如,播放音樂時)請求永久的音頻焦點(AUDIOFOCUS_GAIN),而且您但願先前的音頻焦點持有者中止播放。
請求瞬態焦點(AUDIOFOCUS_GAIN_TRANSIENT),當您但願只在短期內播放音頻,而您但願以前的持有者暫停播放。
請求ducking瞬態焦點 (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK),以指示您但願只在短期內播放音頻,而且若是先前的焦點全部者「duck」(下降)其音頻輸出,仍能夠繼續播放着。兩個音頻輸出都混合到音頻流中。Ducking特別適用於間歇性使用音頻流的應用程序,好比聲音驅動方向。
requestAudioFocus()方法也須要一個AudioManager.OnAudioFocusChangeListener。這個偵聽器應該建立在你的媒體會話所處的相同活動或服務中。它實現了回調onAudioFocusChange(),當其餘應用程序得到或放棄音頻焦點時,您的應用程序會接收到這個回調。
如下代碼片斷請求流STREAM_MUSIC上的永久音頻焦點,並註冊一個OnAudioFocusChangeListener來處理音頻焦點中的後續更改。(在下面的對音頻焦點變化的響應部分討論了更改偵聽器。)
AudioManager mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
AudioManager.OnAudioFocusChangeListener afChangeListener;
...
// Request audio focus for playback
int result = am.requestAudioFocus(afChangeListener,
// Use the music stream.
AudioManager.STREAM_MUSIC,
// Request permanent focus.
AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// Start playback
}
複製代碼
當你完成播放時,調用abandonAudioFocus().
// Abandon audio focus when playback complete
mAudioManager.abandonAudioFocus(afChangeListener);
複製代碼
這將通知系統您再也不須要焦點,並註銷相關的OnAudioFocusChangeListener。若是您請求瞬態焦點,這將通知暫停或duck的應用程序可能繼續播放或恢復音量。
當一個應用程序得到音頻焦點時,它必須可以在另外一個應用程序請求本身的音頻焦點時釋放它。當發生這種狀況時,您的應用程序在AudioFocusChangeListener中接收到對onAudioFocusChange()方法的調用,該調用是在調用requestAudioFocus()時指定的。
傳遞給onAudioFocusChange()的focusChange參數指示正在發生的變化。它對應於應用程序獲取焦點所使用的持續時間提示。你的應用程序應該作出適當的響應。
若是焦點的變化是瞬態的(AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK或AUDIOFOCUS_LOSS_TRANSIENT),應用程序應該ducking下降音量(若是你不依賴自動ducking)或暫停播放,但以其餘方式保持相同的狀態。
在音頻焦點暫時消失期間,您應該繼續監視音頻焦點的變化,並準備在恢復焦點後恢復正常回放。當阻塞應用程序放棄焦點時,您將收到一個回調(AUDIOFOCUS_GAIN
)。此時,您能夠將音量恢復到正常水平或從新開始播放。
若是音頻焦點丟失是永久性的(AUDIOFOCUS_LOSS
),另外一個應用程序正在播放音頻。你的應用應該當即暫停播放,由於它永遠不會收到AUDIOFOCUS_GAIN
回調。要從新啓動回放,用戶必須採起顯式操做,好比在通知或應用程序UI中按下play傳輸控件。
下面的代碼片斷演示瞭如何實現OnAudioFocusChangeListener及其onAudioFocusChange()回調。請注意,使用一個Handler在永久失去音頻焦點時以延遲執行中止操做的回調。
private Handler mHandler = 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
mHandler.postDelayed(mDelayedStopRunnable,
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
}
}
};
複製代碼
handler使用一個以下所示的Runnable
private Runnable mDelayedStopRunnable = new Runnable() {
@Override
public void run() {
getMediaController().getTransportControls().stop();
}
};
複製代碼
若是用戶從新啓動播放,爲了確保延遲中止不會起做用,能夠調用mHandler.removeCallbacks(mDelayedStopRunnable)
來響應任何狀態更改。例如,在回調的onPlay()、onSkipToNext()中調用removeCallbacks()。在清理服務使用的資源時,還應該在服務的onDestroy()回調中調用此方法。