管理音頻播放(摘自Android官方培訓課程中文版(v0.9.5))
如果我們的應用能夠播放音頻,那麼讓使用者能夠以自己預期的方式控制音頻是很重要的。為了保證良好的使用者體驗,我們應該讓應用能夠管理當前的音頻焦點,因為這樣才能確保多個應用不會在同一時刻一起播放音頻。
在學習本系列課程中,我們將會建立可以對音量按鈕進行響應的應用,該應用會在播放音訊時候請求擷取音頻焦點,並且在當前音頻焦點被系統或其他應用所改變的時候,做出正確的響應。
Lessons
控制音量與音頻播放(Controlling Your App’s Volume and Playback)
學習如何確保使用者能通過硬體或軟體音量控制器調節應用的音量(通常這些控制器上還具有播放、停止、暫停、跳過以及回放等功能按鍵)。
管理音頻焦點(Managing Audio Focus)
由於可能會有多個應用具有播放音訊功能,考慮他們如何互動非常重要。為了防止多個音樂應用同時播放音頻,Android使用音頻焦點(Audio Focus)來控制音訊播放。在這節課中可以學習如何請求音頻焦點,監聽音頻焦點的丟失,以及在這種情況發生時應該如何做出響應。
相容音訊輸出裝置(Dealing with Audio Output Hardware)
音頻有多種輸出裝置,在這節課中可以學習如何找出播放音訊裝置,以及處理播放時耳機被拔出的情況。
控制音量與音頻播放
編寫:kesenhoo- 原文:http://developer.android.com/training/managing-audio/volume-playback.html
良好的使用者體驗應該是可預期且可控的。如果我們的應用可以播放音頻,那麼顯然我們需要做到能夠通過硬體按鈕,軟體按鈕,藍芽耳麥等來控制音量。 同樣地,我們需要能夠對應用的音頻流進行播放(Play),停止(Stop),暫停(Pause),跳過(Skip),以及回放(Previous)等動作,並且並確保其正確性。
鑒別使用的是哪個音頻流(Identify Which Audio Stream to Use)
為了建立一個良好的音頻體驗,我們首先需要知道應用會使用到哪些音頻流。Android為播放音樂,鬧鈴,通知鈴,來電聲音,系統聲音,打電話聲音與撥號聲音分別維護了一個獨立的音頻流。這樣做的主要目的是讓使用者能夠單獨地控制不同的種類的音頻。上述音頻種類中,大多數都是被系統限制。例如,除非你的應用需要做替換鬧鐘的鈴聲的操作,不然的話你只能通過STREAM_MUSIC來播放你的音頻。
使用硬體音量鍵來控制應用的音量(Use Hardware Volume Keys to Control Your App’s Audio Volume)
預設情況下,按下音量修飾鍵會調節當前被啟用的音頻流,如果我們的應用當前沒有播放任何聲音,那麼按下音量鍵會調節響鈴的音量。對於遊戲或者音樂播放器而言,即使是在歌曲之間無聲音的狀態,或是當前遊戲處於無聲的狀態,使用者按下音量鍵的操作通常都意味著他們希望調節遊戲或者音樂的音量。你可能希望通過監聽音量鍵被按下的事件,來調節音頻流的音量。其實我們不必這樣做。Android提供了setVolumeControlStream()方法來直接控制指定的音頻流。在鑒別出應用會使用哪個音頻流之後,我們需要在應用生命週期的早期階段調用該方法,因為該方法只需要在Activity整個生命週期中調用一次,通常,我們可以在負責控制多媒體的Activity或者Fragment的onCreate()方法中調用它。這樣能確保不管應用當前是否可見,音頻控制的功能都能符合使用者的預期。
setVolumeControlStream(AudioManager.STREAM_MUSIC);
自此之後,不管目標Activity或Fragment是否可見,按下裝置的音量鍵都能夠影響我們指定的音頻流(在這個例子中,音頻流是"music")。
使用硬體的播放控制按鍵來控制應用的音頻播放(Use Hardware Playback Control Keys to Control Your App’s Audio Playback)
許多線控或者無線耳機都會有許多媒體播放控制按鈕,例如:播放,停止,暫停,跳過,以及回放等。無論使用者按下裝置上任意一個控制按鈕,系統都會廣播一個帶有ACTION_MEDIA_BUTTON的Intent。為了正確地響應這些操作,需要在Manifest檔案中註冊一個針對於該Action的BroadcastReceiver,如下所示:
在Receiver的實現中,需要判斷這個廣播來自於哪一個按鈕,Intent通過EXTRA_KEY_EVENT這一Key包含了該資訊,另外,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來為我們的應用註冊監聽與取消監聽媒體按鈕事件,當Receiver被註冊上時,它將是唯一一個能夠響應媒體按鈕廣播的Receiver。
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);...// Start listening for button pressesam.registerMediaButtonEventReceiver(RemoteControlReceiver);...// Stop listening for button pressesam.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
通常,應用需要在他們失去焦點或者不可見的時候(比如在onStop()方法裡面)取消註冊監聽。但是對於媒體播放應用來說並沒有那麼簡單,實際上,在應用不可見(不能通過可見的UI控制項進行控制)的時候,仍然能夠響應媒體播放按鈕事件是極其重要的。為了實現這一點,有一個更好的方法,我們可以在程式擷取與失去音頻焦點的時候註冊與取消對音頻按鈕事件的監聽。這個內容會在後面的課程中詳細講解。
管理音頻焦點
編寫:kesenhoo- 原文:http://developer.android.com/training/managing-audio/audio-focus.html
由於可能會有多個應用可以播放音頻,所以我們應當考慮一下他們應該如何互動。為了防止多個音樂播放應用同時播放音頻,Android使用音頻焦點(Audio Focus)來控制音訊播放——即只有擷取到音頻焦點的應用才能夠播放音頻。
在我們的應用開始播放音頻之前,它需要先請求音頻焦點,然後再擷取到音頻焦點。另外,它還需要知道如何監聽失去音頻焦點的事件並對此做出合適的響應。
請求擷取音頻焦點(Request the Audio Focus)
在我們的應用開始播放音頻之前,它需要擷取將要使用的音頻流的音頻焦點。通過使用requestAudioFocus()方法可以擷取我們希望得到的音頻流焦點。如果請求成功,該方法會返回AUDIOFOCUS_REQUEST_GRANTED。
另外我們必須指定正在使用的音頻流,而且需要確定所請求的音頻焦點是短暫的(Transient)還是永久的(Permanent)。
短暫的焦點鎖定:當計劃播放一個短暫的音頻時使用(比如播放導航指示)。 永久的焦點鎖定:當計劃播放一個較長但時間長度可預期的音頻時使用(比如播放音樂)。
下面的程式碼片段是一個在播放音樂時請求永久音頻焦點的例子,我們必須在開始播放之前立即請求音頻焦點,比如在使用者點擊播放或者遊戲中下一關的背景音樂開始前。
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);...// Request audio focus for playbackint 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);
當請求短暫音頻焦點的時候,我們可以選擇是否開啟“Ducking”。通常情況下,一個應用在失去音頻焦點時會立即關閉它的播放聲音。如果我們選擇在請求短暫音頻焦點的時候開啟了Ducking,那意味著其它應用可以繼續播放,僅僅是在這一刻降低自己的音量,直到重新擷取到音頻焦點後恢複正常音量(譯註:也就是說,不用理會這個短暫焦點的請求,這並不會打斷目前現正播放的音頻。比如在播放音樂的時候突然出現一個短暫的簡訊通知聲音,此時僅僅是把歌曲的音量暫時調低,使得使用者能夠聽到簡訊通知聲,在此之後便立馬恢複正常播放)。
// Request audio focus for playbackint 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.}
Ducking對於那些間歇性使用音頻焦點的應用來說特別合適,比如語音導航。
如果有另一個應用像上述那樣請求音頻焦點,它所請求的永久音頻焦點或者短暫音頻焦點(支援Ducking或不支援Ducking),都會被你在請求擷取音頻焦點時所註冊的監聽器接收到。
處理失去音頻焦點(Handle the Loss of Audio Focus)
如果應用A請求擷取了音頻焦點,那麼在應用B請求擷取音頻焦點的時候,A擷取到的焦點就會失去。如何響應失去焦時間點事件,取決於失去焦點的方式。
在音頻焦點的監聽器裡面,當接受到描述焦點改變的事件時會觸發onAudioFocusChange()回調方法。如之前提到的,擷取焦點有三種類型,我們同樣會有三種失去焦點的類型:永久失去,短暫失去,允許Ducking的短暫失去。
失去短暫焦點:通常在失去短暫焦點的情況下,我們會暫停當前音訊播放或者降低音量,同時需要準備在重新擷取到焦點之後恢複播放。
失去永久焦點:假設另外一個應用開始播放音樂,那麼我們的應用就應該有效地將自己停止。在實際情境當中,這意味著停止播放,移除媒體按鈕監聽,允許新的音頻播放器可以唯一地監聽那些按鈕事件,並且放棄自己的音頻焦點。此時,如果想要恢複自己的音頻播放,我們需要等待某種特定使用者行為發生(例如按下了我們應用當中的播放按鈕)。
在下面的程式碼片段當中,如果焦點的失去是短暫型的,我們將音頻播放對象暫停,並在重新擷取到焦點後進行恢複。如果是永久型的焦點失去事件,那麼我們的媒體按鈕監聽器會被登出,並且不再監聽音頻焦點的改變。
OnAudioFocusChangeListener afChangeListener = new 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 } }};
在上面失去短暫焦點的例子中,如果允許Ducking,那麼除了暫停當前的播放之外,我們還可以選擇使用“Ducking”。
Duck!
在使用Ducking時,正常播放的歌曲會降低音量來凸顯這個短暫的音頻聲音,這樣既讓這個短暫的聲音比較突出,又不至於打斷正常的聲音。
下面的程式碼片段讓我們的播放器在暫時失去音頻焦點時降低音量,並在重新獲得音頻焦點之後恢複原來音量。
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來向你告知使用者在使用音頻過程當中的各種變化。下節課會示範如何監聽這些廣播並提升使用者的整體體驗。
相容音訊輸出裝置
編寫:kesenhoo- 原文:http://developer.android.com/training/managing-audio/audio-output.html
當使用者想要通過Android裝置欣賞音樂的時候,他可以有多種選擇,大多數裝置擁有內建的擴音器,有線耳機,也有其它很多裝置支援藍芽串連,有些甚至還支援A2DP藍芽音頻傳輸模型協定。(譯註:A2DP全名是Advanced Audio Distribution Profile 藍芽音頻傳輸模型協定! A2DP是能夠採用耳機內的晶片來堆棧資料,達到聲音的高清晰度。有A2DP的耳機就是藍芽立體聲耳機。聲音能達到44.1kHz,一般的耳機只能達到8kHz。如果手機支援藍芽,只要裝載A2DP協議,就能使用A2DP耳機了。還有消費者看到技術參數提到藍芽V1.0 V1.1 V1.2 V2.0 - 這些是指藍芽的技術版本,是指通過藍芽傳輸的速度,他們是否支援A2DP具體要看藍芽產品製造商是否使用這個技術。來自百度百科)
檢測目前正在使用的硬體裝置(Check What Hardware is Being Used)
使用不同的硬體播放聲音會影響到應用的行為。可以使用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?}
處理音訊輸出裝置的改變(Handle Changes in the Audio Output Hardware)
當有線耳機被拔出或者藍牙裝置中斷連線的時候,音頻流會自動輸出到內建的擴音器上。假設播放聲音很大,這個時候突然轉到擴音器播放會顯得非常嘈雜。
幸運的是,系統會在這種情況下廣播帶有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);}