Android播放音訊之按鈕控制
控制音量和播放
良好的使用者體驗是可預測的。 如果您的應用程式播放媒體,您的使用者必須使用裝置,藍芽耳機或耳機的硬體或軟體音量控制來控制應用程式的音量。
類似地,在適當和可用的情況下,播放,停止,暫停,跳過和先前的媒體回放鍵應當對於由應用使用的音訊流執行它們各自的動作。
標識要使用的音訊流
建立可預測的音訊體驗的第一步是瞭解您的應用將使用哪個音訊流。
Android維護一個單獨的音訊流,用於播放音樂,鬧鐘,通知,來電鈴聲,系統聲音,通話音量和DTMF鈴聲。 這主要是為了允許使用者獨立地控制每個流的音量。
大多數這些流僅限於系統事件,因此除非您的應用程式是替換鬧鐘,否則您幾乎肯定會使用STREAM_MUSIC流播放您的音訊。
使用硬體音量鍵控制音量
預設情況下,按音量控制可修改Activity音訊流的音量。如果您的應用程式目前沒有播放任何內容,按下音量鍵即可調整鈴聲音量。
如果你有一個遊戲或音樂應用程式,那麼當用戶點選音量鍵,他們想要控制遊戲或音樂的音量,即使他們當前在歌曲之間或沒有音樂在當前遊戲位置。
你可能會試著嘗試監聽音量鍵按下並修改音訊流的音量。不要慌, Android提供了方便的setVolumeControlStream()方法來將音量鍵降到您指定的音訊流。
確定應用程式將使用的音訊流後,應將其設定為音訊流目標。您應該在應用程式的生命週期中提前進行此呼叫 - 因為您只需在Activity生命週期中呼叫一次,通常應在onCreate()方法(控制您的媒體的Activity或Fragment)中呼叫它。這確保了只要應用程式可見,音量控制功能就像使用者期望的那樣。
setVolumeControlStream(AudioManager.STREAM_MUSIC);
從這一點開始,只要目標Activity或Fragment可見,按裝置上的音量鍵就會影響您指定的音訊流(在本例中為“音樂”)。
使用硬體播放控制鍵控制音訊播放
媒體播放按鈕,如播放,暫停,停止,跳過和上一個在某些手機和許多連線或無線耳機可用。每當使用者按下這些硬體鍵之一時,系統使用ACTION_MEDIA_BUTTON操作廣播一個Intent。
要響應媒體按鈕點選,您需要在您的清單中註冊一個BroadcastReceiver,監聽此操作廣播,如下所示。
<receiver android:name=".RemoteControlReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
接收器實現本身需要提取哪個鍵被按下來引起廣播。 Intent在EXTRA_KEY_EVENT鍵下包括此鍵,而KeyEvent類包括表示每個可能的媒體按鈕的列表KEYCODE_MEDIA_ *靜態常量,例如KEYCODE_MEDIA_PLAY_PAUSE和KEYCODE_MEDIA_NEXT。
以下程式碼片段顯示如何提取按下的媒體按鈕,並相應地影響媒體播放。
public class RemoteControlReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
KeyEvent event = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (KeyEvent.KEYCODE_MEDIA_PLAY == event.getKeyCode()) {
// Handle key press.
}
}
}
}
因為多個應用程式可能想要監聽媒體按鈕按下,所以您還必須以程式設計方式控制應用程式何時應該接收媒體按鈕按下事件。
以下程式碼可在您的應用程式中使用AudioManager註冊和登出媒體按鈕事件接收器。註冊後,您的廣播接收器是所有媒體按鈕廣播的專屬接收器。
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);
//...
// Start listening for button presses
am.registerMediaButtonEventReceiver(RemoteControlReceiver);
//...
// Stop listening for button presses
am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
通常,當應用程式變為不活動或不可見時(例如在onStop()回撥期間),應取消註冊大多數接收者。然而,對於媒體播放應用程式來說並不簡單,事實上,當應用程式不可見且無法通過螢幕上的使用者介面控制時,響應媒體播放按鈕是最重要的。
更好的方法是在應用程式獲取並丟失音訊焦點時註冊和取消註冊媒體按鈕事件接收器。這將在下面詳細介紹。
管理音訊焦點
有多個應用程式可能播放音訊,重要的是要考慮他們應該如何互動。為了避免每個音樂應用程式同時播放,Android使用音訊焦點來控制音訊播放 - 只有擁有音訊焦點的應用程式才能播放音訊。
在您的應用程式開始播放音訊之前,應該請求並接收音訊焦點。同樣,它應該知道如何監聽音訊焦點的丟失,並在發生這種情況時適當地做出反應。
請求音訊焦點
在您的應用開始播放任何音訊之前,它應該保留將使用的流的音訊焦點。這是通過呼叫requestAudioFocus()完成的,如果您的請求成功,它返回AUDIOFOCUS_REQUEST_GRANTED。
您必須指定您使用的流,以及是否需要暫時或永久的音訊焦點。當您希望只在短時間內播放音訊時請求暫時聚焦(例如在播放導航指示時)。當您計劃在可預見的未來播放音訊時請求永久音訊聚焦(例如,播放音樂時)。
以下程式碼片段請求音樂音訊流的永久音訊焦點。您應該在開始播放之前立即請求音訊焦點,例如當用戶按下播放或下一個遊戲關卡的背景音樂開始時。
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);
...
// 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) {
am.registerMediaButtonEventReceiver(RemoteControlReceiver);
// Start playback.
}
一旦你完成播放,一定要呼叫abandonAudioFocus()。這會通知系統您不再需要焦點和登出相關聯的AudioManager.OnAudioFocusChangeListener。在放棄瞬態焦點的情況下,這允許任何中斷的應用程式繼續播放。
// Abandon audio focus when playback complete
am.abandonAudioFocus(afChangeListener);
當請求瞬態音訊焦點時,您有一個附加選項:是否要啟用“低音”。通常,當良好的音訊應用程式失去音訊焦點時,它立即使其播放靜音。通過請求一個允許迴避的暫時音訊焦點,你告訴其他音訊應用程式它們可以接受他們繼續播放,只要他們降低音量,直到焦點回到他們。
// Request audio focus for playback
int result = am.requestAudioFocus(afChangeListener,
// Use the music stream.
AudioManager.STREAM_MUSIC,
// Request permanent focus.
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// Start playback.
}
低音尤其適用於間歇性使用音訊流的應用程式,例如用於可聽見的駕駛方向。
每當另一個應用程式請求音訊焦點如上所述,其永久和瞬態(有或沒有支援低音)之間的選擇音訊焦點由您在請求焦點時註冊的偵聽器接收。
處理音訊焦點的丟失
如果您的應用程式可以請求音訊焦點,那麼當其他應用程式請求焦點時,它會反過來失去焦點。您的應用程式如何響應音訊焦點的丟失取決於丟失的方式。
您請求音訊焦點時註冊的音訊焦點更改偵聽器的onAudioFocusChange()回撥方法接收到描述焦點更改事件的引數。具體來說,可能的聚焦丟失事件映象來自前一部分的聚焦請求型別 - 永久丟失,瞬時丟失和允許具有迴避的瞬態。
一般來說,暫時的(臨時的)音訊焦點的丟失應導致您的應用程式使它的音訊流靜音,否則保持相同的狀態。您應該繼續監視音訊焦點的變化,並準備在恢復焦點後暫停播放。
如果音訊焦點丟失是永久性的,假設另一個應用程式現在正用於聽音訊,您的應用程式應該有效地結束自己。實際上,這意味著停止播放,刪除媒體按鈕偵聽器 - 允許新的音訊播放器專門處理這些事件 - 並放棄您的音訊焦點。此時,您需要在恢復播放音訊之前需要執行使用者操作(在應用程式中按下播放)。
在下面的程式碼片段中,如果音訊丟失是暫時的,我們暫停播放我們的媒體播放器物件,並在我們恢復焦點後重新開始播放。如果丟失是永久性的,它將取消註冊我們的媒體按鈕事件接收器,並停止監視音訊焦點更改。
AudioManager.OnAudioFocusChangeListener afChangeListener =
new AudioManager.OnAudioFocusChangeListener() {
public void onAudioFocusChange(int focusChange) {
if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT) {
// Pause playback
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Resume playback
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
am.abandonAudioFocus(afChangeListener);
// Stop playback
}
}
};
在瞬時丟失音訊焦點的情況下,允許duck(迴避),而不是暫停回放。
如何Duck
低音是降低音訊流輸出音量的過程,使來自另一個應用程式的瞬態音訊更容易聽到,而不會完全中斷您自己的應用程式的音訊。
在以下程式碼段中,當我們暫時失去焦點時,降低媒體播放器物件上的音量,然後在我們重新獲得焦點時將其返回到上一級。
OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {
public void onAudioFocusChange(int focusChange) {
if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
// Lower the volume
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Raise it back to normal
}
}
};
丟失音訊焦點是最重要的廣播,但不是唯一的。 系統廣播多個Intent,以提醒您使用者的音訊體驗的變化。
處理音訊輸出硬體
當用戶從他們的Android裝置享受音訊時,使用者有很多選擇。大多數裝置都有內建揚聲器,有線耳機的耳機插孔,許多裝置還具有藍芽連線和支援A2DP音訊。
檢查正在使用的硬體
您的應用程式的行為可能會受到輸出路由到的硬體的影響。
您可以查詢AudioManager以確定音訊當前是否路由到裝置揚聲器,有線耳機或連線的藍芽裝置,如以下程式碼段所示:
if (isBluetoothA2dpOn()) {
// Adjust output for Bluetooth.
} else if (isSpeakerphoneOn()) {
// Adjust output for Speakerphone.
} else if (isWiredHeadsetOn()) {
// Adjust output for headsets
} else {
// If audio plays and noone can hear it, is it still playing?
}
處理音訊輸出硬體的更改
當耳機拔掉或藍芽裝置斷開連線時,音訊流自動重新路由到內建揚聲器。如果你聽到你的音樂高達我的音量,這可能是一個嘈雜的驚喜。
幸運的是,當發生這種情況時,系統廣播ACTION_AUDIO_BECOMING_NOISY Intent。這是一個好的習慣,註冊一個BroadcastReceiver監聽這個Intent,無論你在播放音訊。在音樂播放器的情況下,使用者通常期望暫停播放,而對於遊戲,可以選擇顯著降低音量。
private class NoisyAudioStreamReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
// Pause the playback
}
}
}
private IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
private void startPlayback() {
registerReceiver(myNoisyAudioStreamReceiver(), intentFilter);
}
private void stopPlayback() {
unregisterReceiver(myNoisyAudioStreamReceiver);
}