天天看點

EasyPlayer聲音自己主動停止、恢複,一鍵靜音等功能

我們在開發播放器時,可能會須要靜音或者減少音量的功能。比方說某款音樂播放器,當在背景播放時,假設此時有另外的系統通知聲音發出。可能播放器會把音量減少,系統聲音結束後,再調高;假設有來電了,播放器可能會把音樂暫停,等通話結束後再繼續播放。還有,比方說我們在某個場合放個視訊,不料音量非常大。會引來非常多目光(非常尴尬),這時候可能我們須要一鍵靜音的功能。那這些功能我們應該怎樣實作呢?

Android播放聲音的類為AudioTrack,播放器會先把音頻流demux出來,再decode。之後,把音頻PCM資料通過AudioTrack類write到音頻裝置中,進而通過話筒或者揚聲器發出聲音。

為了友善地實作聲音控制。我們須要從應用的最上層進行操作(由于底層可能已經被抽象成庫了),也就是要從AudioTrack來入手。讓我們看看AudioTrack的一些API吧。

int getPlayState ()
Returns the playback state of the AudioTrack instance.
擷取目前的播放狀态。這個接口會傳回PLAYSTATE_STOPPED、PLAYSTATE_PAUSED、PLAYSTATE_PLAYING
三種狀态,分别表示未播放、暫停中、正在播放           
void pause ()
Pauses the playback of the audio data. Data that has not been played back will not be discarded. Subsequent calls to play() will play this data back. See flush() to discard this data.
暫停播放音頻資料。           

已經在緩沖區中的未播放資料将不會被丢棄,在下次play的時候繼續播放。調用flush則會丢棄緩沖資料。

void play ()
Starts playing an AudioTrack.
開始播放           
int setStereoVolume (float leftGain, 
                float rightGain)

Sets the specified left and right output gain values on the AudioTrack.
設定左右聲道的音量增益。
           

有了這幾個API。足以滿足我們的需求。實作起來就非常easy了。

首先我們做一鍵靜音功能。

我們能夠做個切換的button。這個button初始狀态是要顯示目前的播放狀态:正在播放音頻或未在播放音頻。

播放狀态能夠調用getPlayState ()來擷取到;然後button按下後,再依據播放狀态進行播放或暫停。

代碼例如以下:

mAudioEnable = mAudioTrack!=null && mAudioTrack.getPlayState()==PLAYSTATE_PLAYING;

public void setAudioEnable(boolean enable) {
     mAudioEnable = enable;
     AudioTrack at = mAudioTrack;
     if (at != null) {
         synchronized (at) {
             if (!enable) {
                 at.pause();
                 at.flush();
             } else {
                 at.flush();
                 at.play();
             }
         }
     }
 }           

注意這裡在pause之後。play之前都調用了flush接口。這樣能夠確定在由暫停到播放切換時,不會把暫停時未播放的“舊資料”播放出來。

接下來我們實作音頻資源被其他程序占用(失去焦點)時,自己主動減少聲音或者停止聲音;在音頻資源又被釋放(又一次擷取到焦點)時再恢複播放的功能。

我們須要通過AudioManager來推斷目前音頻資源的狀态。而且在音頻焦點更改時得到回調。其關鍵API接口有:

int requestAudioFocus (AudioManager.OnAudioFocusChangeListener l, 
                int streamType, 
                int durationHint)
Request audio focus. Send a request to obtain the audio focus
請求擷取音頻焦點。           

第一個參數為音頻焦點更改時的回調; 第二個參數為音頻類型,在我們調節音量時能夠看到有若幹種音量,就相應的這裡的streamType。這裡我們基本用MUSIC,表示“媒體”。 第三個參數表示擷取焦點的“時長”,有例如以下幾種情況: AUDIOFOCUS_GAIN_TRANSIENT 表示僅僅為暫時擷取焦點。比方播放導航語音、通知聲音等,屬于時間非常短暫的情況。 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 表示為DUCK模式,表示當擷取焦點後,同意先前擷取過焦點的程式在減少輸出音量的前提下繼續播放。

AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 痛第一種情況相似,僅僅是不同意系統再播放其他聲音。通常應用在語音備忘、語音識别等情況; AUDIOFOCUS_GAIN 表示要擷取焦點的時長未知。比方播放音樂等等。 當擷取到焦點時,函數放回AUDIOFOCUS_REQUEST_GRANTED,當擷取失敗時,傳回AUDIOFOCUS_REQUEST_FAILED

// 擷取AudioManager執行個體
final AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
AudioManager.OnAudioFocusChangeListener l = new AudioManager.OnAudioFocusChangeListener() {
    @Override
    public void onAudioFocusChange(int focusChange) {
        if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {// 焦點擷取到了,那繼續播放,并恢複音量。
            AudioTrack audioTrack = mAudioTrack;
            if (audioTrack != null) {
                audioTrack.setStereoVolume(1.0f, 1.0f);
                if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) {
                    audioTrack.flush();
                    audioTrack.play();
                }
            }
        } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {// 焦點丢失了,暫停播放。
             AudioTrack audioTrack = mAudioTrack;
            if (audioTrack != null) {
                if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
                    audioTrack.pause();
                }
            }
        } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { // 焦點丢失了。可是同意在減少音量的前提下繼續播放,那麼減少聲音。
            AudioTrack audioTrack = mAudioTrack;
            if (audioTrack != null) {
                audioTrack.setStereoVolume(0.5f, 0.5f);
            }
        }
    }
};
// 由于這裡要獲得的焦點無法預知時長,是以用AUDIOFOCUS_GAIN模式。
int requestCode = am.requestAudioFocus(l, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
if (requestCode == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
   // 成功擷取到了焦點。那啟動播放
   AudioTrack audioTrack = mAudioTrack;
    if (audioTrack != null) {
        audioTrack.setStereoVolume(1.0f, 1.0f);
        if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) {
            audioTrack.flush();
            audioTrack.play();
        }
    }
}else{  // 沒有擷取到音頻焦點。那不播放聲音
    AudioTrack audioTrack = mAudioTrack;
    if (audioTrack != null) {
        if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
            audioTrack.pause();
        }
    }
}