理解音訊焦點 (第 3/3 部分):三個步驟實現音訊聚焦

Android_開發者發表於2017-11-23


本系列文章旨在讓您深入理解音訊焦點的含義,使用方法和其對使用者體驗的重要性。本篇文章是該系列的第一部分,該系列三篇文章包含了:

  1. 最常見的音訊焦點用例和成為一個優秀的媒體事業人員的重要性
  2. 其它一些能體現音訊焦點對應用體驗的重要性的用例
  3. 在您的應用中實現音訊焦點的三個步驟 (此篇文章)

如果您不妥善處理好音訊聚焦,您的使用者可能受到下圖所示的困擾。


現在您已經知道音訊聚焦的重要性,讓我們通過一些步驟來讓您的應用程式正確處理音訊焦點。

開始程式碼示例之前,先看看下圖,它展示了實現步驟:

步驟一 :請求音訊焦點

獲取音訊焦點的第一個步驟是先向系統發出申請焦點的訊息。注意這只是發出請求,並非直接獲取。為了申請到音訊聚焦,您必須向系統描述好您的意圖。介紹四個常見音訊焦點型別:

  • AUDIOFOCUS_GAIN的使用場景:應用需要聚焦音訊的時長會根據使用者的使用時長改變,屬於不確定期限。例如:多媒體播放或者播客等應用。
  • AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK的使用場景:應用只需短暫的音訊聚焦,來播放一些提示類語音訊息,或錄製一段語音。例如:鬧鈴,導航等應用。
  • AUDIOFOCUS_GAIN_TRANSIENT的使用場景:應用只需短暫的音訊聚焦,但包含了不同響應情況,例如:電話、QQ、微信等通話應用。
  • AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 的使用場景:同樣您的應用只是需要短暫的音訊聚焦。未知時長,但不允許被其它應用擷取音訊焦點。例如:錄音軟體。

在 Android O 或者更新的版本上您必須使用 builder 來例項化一個 AudioFocusRequest 類。(在 builder 中必須指明請求的音訊焦點型別)

AudioManager mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
AudioAttributes mAudioAttributes =
       new AudioAttributes.Builder()
               .setUsage(AudioAttributes.USAGE_MEDIA)
               .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
               .build();
AudioFocusRequest mAudioFocusRequest =
       new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
               .setAudioAttributes(mAudioAttributes)
               .setAcceptsDelayedFocusGain(true)
               .setOnAudioFocusChangeListener(...) // Need to implement listener
               .build();
int focusRequest = mAudioManager.requestAudioFocus(mAudioFocusRequest);
switch (focusRequest) {
   case AudioManager.AUDIOFOCUS_REQUEST_FAILED:
       // 不允許播放
   case AudioManager.AUDIOFOCUS_REQUEST_GRANTED:
       // 開始播放
}複製程式碼

音訊焦點型別要點:

  1. AudioManager.AUDIOFOCUS_GAIN:請求長時間音訊聚焦。如果只是臨時需要音訊焦點可以選用這幾個:AUDIOFOCUS_GAIN_TRANSIENTAUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
  2. 您必須通過 setOnAudioFocusChangeListener() 方法來實現 AudioManager.OnAudioFocusChangeListener 介面。用來響應音訊焦點狀態的變化,如被其它應用擷取了音訊焦點,或者其它應用釋放焦點,都會在這裡回撥。
  3. 呼叫 AudioManager 的 requestAudioFocus(…) 方法,需要用到例項化好的 AudioFocusRequest。 請求結果以一個 int 變數返回:AUDIOFOCUS_REQUEST_GRANTED 表示獲得授權, AUDIOFOCUS_REQUEST_FAILED 表示被系統拒絕。

在 Android N 及其更早的版本中,不需要用到 AudioFocusRequest,只需實現 AudioManager.OnAudioFocusChangeListener 介面。程式碼如下:

AudioManager mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
int focusRequest = mAudioManager.requestAudioFocus(
..., // Need to implement listener
       AudioManager.STREAM_MUSIC,
       AudioManager.AUDIOFOCUS_GAIN);
switch (focusRequest) {
   case AudioManager.AUDIOFOCUS_REQUEST_FAILED:
       // don't start playback
   case AudioManager.AUDIOFOCUS_REQUEST_GRANTED:
       // actually start playback
}複製程式碼

上述皆為音訊焦點的申請,接下來我們將介紹 AudioManager.OnAudioFocusChangeListener 如何實現,以此來響應音訊焦點的狀態。

步驟二 :響應音訊焦點的狀態改變

一旦獲得音訊聚焦,您的應用要馬上做出響應,因為它的狀態可能在任何時間發生改變(丟失或重新獲取),您可以實現 OnAudioFocusChangeListener 的來響應狀態改變。

以下程式碼展示了 OnAudioFocusChangeListener 介面的實現,它處理了與 Google Assistant 應用協同工作的時候,音訊焦點的各種狀態的變化。

private final class AudioFocusHelper
        implements AudioManager.OnAudioFocusChangeListener {
private void abandonAudioFocus() {
        mAudioManager.abandonAudioFocus(this);
    }
@Override
    public void onAudioFocusChange(int focusChange) {
        switch (focusChange) {
            case AudioManager.AUDIOFOCUS_GAIN:
                if (mPlayOnAudioFocus && !isPlaying()) {
                    play();
                } else if (isPlaying()) {
                    setVolume(MEDIA_VOLUME_DEFAULT);
                }
                mPlayOnAudioFocus = false;
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                setVolume(MEDIA_VOLUME_DUCK);
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                if (isPlaying()) {
                    mPlayOnAudioFocus = true;
                    pause();
                }
                break;
            case AudioManager.AUDIOFOCUS_LOSS:
                mAudioManager.abandonAudioFocus(this);
                mPlayOnAudioFocus = false;
                stop();
                break;
        }
    }
}複製程式碼

關於暫停播放,應用程式的行為應該是不同的。如果使用者主動暫停播放時,您的應用應釋放音訊焦點。如果是為了響應音訊焦點的暫時丟失而暫停播放,則不應釋放音訊焦點。 這裡有一些用例來說明這一點。

分析上面介面mPlayOnAudioFocus 的場景,您的音訊應用正在後臺播放音樂:

  1. 使用者點選播放,您的應用向系統申請音訊聚焦,假如系統授權了。
  2. 現在使用者長按 HOME 鍵啟動 Google Assistant。Google Assistant 會向系統申請一個短暫的音訊聚焦。
  3. 一旦系統授權給 Google Assistant,您的 OnAudioFocusChangeListener 介面會收到 AUDIOFOCUS_LOSS_TRANSIENT 事件回撥。您在這個回撥裡處理暫停音樂播放。
  4. 當 Google Assistant 使用結束,您的 OnAudioFocusChangeListener 會收到 AUDIOFOCUS_GAIN 事件回撥。 在這裡您可以處理是否讓音樂恢復播放。

以下程式碼展示如何釋放音訊焦點:

public final void pause() {
   if (!mPlayOnAudioFocus) {
       mAudioFocusHelper.abandonAudioFocus();
   }
  onPause();
}複製程式碼

您可以看到釋放焦點是在使用者暫停播放的時候,而非其它應用請求焦點 AUDIOFOCUS_GAIN_TRANSIENT 導致他們釋放焦點。

應對焦點丟失

選擇在 OnAudioFocusChangeListener 中暫停還是降低音量,取決於您應用的互動方式。在 Android O上,會自動的幫您降低音量,所以您可以忽略 OnAudioFocusChangeListener 介面的 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 事件。

在 Android O 以下的版本,您需要自己用程式碼實現,具體實現方式如上面程式碼所示。

延遲聚焦

Android O 介紹了延遲聚焦這個概念,您可以在申請音訊聚焦的時候來響應 AUDIOFOCUS_REQUEST_DELAYED 這個結果,如下所示:

public void requestPlayback() {
    int audioFocus = mAudioManager.requestAudioFocus(mAudioFocusRequest);
    switch (audioFocus) {
        case AudioManager.AUDIOFOCUS_REQUEST_FAILED:
            ...
        case AudioManager.AUDIOFOCUS_REQUEST_GRANTED:
            ...
        case AudioManager.AUDIOFOCUS_REQUEST_DELAYED:
            mAudioFocusPlaybackDelayed = true;
    }
}複製程式碼

在您 OnAudioFocusChangeListener 的實現,您需要檢查 mAudioFocusPlaybackDelayed 這個變數,當您響應 AUDIOFOCUS_GAIN 音訊聚焦的時候, 如下所示:

private void onAudioFocusChange(int focusChange) {
   switch (focusChange) {
       case AudioManager.AUDIOFOCUS_GAIN:
           logToUI("Audio Focus: Gained");
           if (mAudioFocusPlaybackDelayed || mAudioFocusResumeOnFocusGained) {
               mAudioFocusPlaybackDelayed = false;
               mAudioFocusResumeOnFocusGained = false;
               start();
           }
           break;
       case AudioManager.AUDIOFOCUS_LOSS:
           mAudioFocusResumeOnFocusGained = false;
           mAudioFocusPlaybackDelayed = false;
           stop();
           break;
       case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
           mAudioFocusResumeOnFocusGained = true;
           mAudioFocusPlaybackDelayed = false;
           pause();
           break;
       case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
           pause();
           break;
   }
}複製程式碼

步驟三 :釋放音訊焦點

播放完音訊,記得使用 AudioManager.abandonAudioFocus(…) 來釋放掉音訊焦點。在前面的步驟中,我們遇到了一個應用暫停播放應該釋放音訊焦點的情況,但是這個應用依舊保留了音訊焦點。

程式碼示例

幾個您可以在您應用使用的案例

GitHub gist 上有三個類關於音訊焦點的使用,這可能對您的程式碼有幫助。

  • AudioFocusRequestCompat:使用這個類來描述您的音訊焦點型別
  • AudioFocusHelper:這個類幫助您處理音訊焦點,您可以把它加入您的程式碼,但是必須確保在您的播放 service 中使用 AudioFocusAwarePlayer 這個介面。
  • AudioFocusAwarePlayer:這個介面應該在 service 中實現,來管理您的播放元件(MediaPlayer或者ExoPlayer),它可以確保 AudioFocusHelper 正常工作。

完整的程式碼示例

android-MediaBrowserService 完整展示了音訊焦點的處理,使用 MediaPlayer 來播放音樂,同時使用了 MediaSession

PlayerAdapter展示了音訊聚焦的最佳實踐,請注意 pause()onAudioFocusChange(int) 方法的實現。

測試您的程式碼

一旦您在應用中實現了音訊焦點的處理,您可以使用安卓媒體控制工具來測試您的應用對音訊聚焦的真實反映,具體使用方法請查閱 GitHub/Android Media Controller.

Android多媒體開發資源


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章