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


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

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

若是您不妥善處理好音頻聚焦,您的用戶可能受到下圖所示的困擾。
前端


如今您已經知道音頻聚焦的重要性,讓咱們經過一些步驟來讓您的應用程序正確處理音頻焦點。

開始代碼示例以前,先看看下圖,它展現了實現步驟:android

步驟一 :請求音頻焦點

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

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

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

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 接口。代碼以下:bash

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 的來響應狀態改變。app

如下代碼展現了 OnAudioFocusChangeListener 接口的實現,它處理了與 Google Assistant 應用協同工做的時候,音頻焦點的各類狀態的變化。ide

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前端後端產品設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索