android音視頻指南-管理音頻焦點

翻譯自Managing audio focushtml

兩個或更多的Android應用程序能夠同時播放音頻到相同的輸出流。系統把全部東西混合在一塊兒。雖然這在技術上是使人印象深入的,但對用戶來講倒是很是使人惱火的。爲了不全部音樂應用同時播放,Android引入了音頻聚焦的概念。只有一個應用程序能夠一次聚焦音頻。android

當您的應用程序須要輸出音頻時,它應該請求音頻焦點。當它有焦點時,它能夠播放聲音。然而,在你得到音頻焦點後,你可能沒法持有它直到你播放完。另外一個應用程序能夠請求焦點,它會搶佔你的音頻焦點。若是出現這種狀況,你的應用程序應該暫停播放或下降音量,讓用戶更容易聽到新的音頻源。bash

音頻聚焦是合做的。鼓勵應用程序遵照音頻聚焦指南,但該系統不執行這些規則。若是一個應用程序想在失去聲音焦點後繼續大聲播放,沒有什麼能阻止它。這是一種糟糕的體驗,用戶頗有可能會卸載出現這種糟糕體驗的應用程序。app

一個表現良好的音頻應用程序應該管理音頻焦點根據這些通常準則:異步

  • 在開始播放以前當即調用requestAudioFocus(),並驗證調用是否返回AUDIOFOCUS_REQUEST_GRANTED。若是您按照咱們在本指南中描述的那樣設計應用程序,那麼應該在媒體會話的onPlay()回調中調用requestAudioFocus()
  • 當另外一個應用程序得到音頻焦點時,中止或暫停播放,或下降音量。
  • 當播放中止,放棄音頻焦點。

音頻焦點的處理方式因Android版本不一樣而異:ide

  • 從Android 2.2 (API level 8)開始,應用程序經過調用requestAudioFocus()abandonAudioFocus()來管理音頻焦點。應用程序還必須註冊一個 AudioManager.OnAudioFocusChangeListener,以接收回調和管理本身的音頻級別。
  • 對於Android 5.0 (API level 21)或更高版本的應用程序,音頻應用程序應該使用AudioAttributes來描述你的應用程序正在播放的音頻類型。例如,播放語音的應用程序應該指定CONTENT_TYPE_SPEECH
  • 運行Android 8.0 (API級別26)或更高版本的應用程序應該使用requestAudioFocus()方法,它接受AudioFocusRequest參數。AudioFocusRequest包含關於音頻上下文和應用程序功能的信息。系統使用這些信息自動管理音頻焦點的獲取和丟失。

在Android 8.0和更高版本上的音頻聚焦

從Android 8.0 (API級別26)開始,當您調用requestAudioFocus()時,必須提供AudioFocusRequest參數。若要釋放音頻焦點,請調用abandonAudioFocusRequest()方法,該方法還將AudioFocusRequest做爲其參數。當請求和放棄焦點時,應該使用相同的AudioFocusRequest實例。函數

要建立AudioFocusRequest,請使用AudioFocusRequest. builder。因爲焦點請求必須始終指定請求的類型,所以該類型包含在構造器的構造函數中。使用構建器的方法設置請求的其餘字段。oop

須要FocusGain字段;全部其餘字段都是可選的。 解釋一下主要方法:post

每一個請求都須要這個字段。它的值與android8.0以前對requestaudiofococus()的調用中使用的durationHint相同:AUDIOFOCUS_GAIN, AUDIOFOCUS_GAIN_TRANSIENT, AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,或AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVEui

audioattributes描述了系統應用程序的用例。當一個應用程序得到和失去了音頻的焦點時系統會查看他們。屬性取代了流類型的概念。在Android 8.0 (API級別26)及更高版本中,除了音量控制以外的任何操做都不支持流類型。在focus請求中使用與音頻播放器中相同的屬性(以下表所示)。

使用一個AudioAttributes.Builder首先指定屬性,而後使用此方法將屬性分配給請求。

若是沒有指定,AudioAttributes默認爲AudioAttributes.USAGE_MEDIA

當另外一個應用程序請求使用AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK進行焦點處理時,具備焦點的應用程序一般不會收到onAudioFocusChange()回調,由於系統能夠本身執行這個操做。當你須要暫停播放而不是下降音量時,調用setWillPauseWhenDucked(true),建立並設置一個OnAudioFocusChangeListener,以下面的自動ducking所述。

當焦點被另外一個應用鎖定時,對音頻焦點的請求可能會失敗。這種方法容許延遲焦點增益:當焦點可用時,異步獲取焦點的能力。

注意,只有當你指定了一個AudioManager.OnAudioFocusChangeListener時,延遲的焦點增益(focus gain)纔有效。由於您的應用程序須要接收回調以便知道焦點已被容許獲取。

只有在請求中指定willPauseWhenDucked(true)或setAcceptsDelayedFocusGain(true)時,才須要OnAudioFocusChangeListener。

設置偵聽器有兩種方法:一種有處理程序參數,另外一種沒有處理程序參數。處理程序是偵聽器運行的線程。若是不指定處理程序,則使用與主Looper關聯的處理程序(handler)。

下面的例子展現瞭如何使用AudioFocusRequest.Bulder構建一個AudioFocusRequest還有請求和放棄音頻焦點:

mAudioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
mPlaybackAttributes = new AudioAttributes.Builder()
        .setUsage(AudioAttributes.USAGE_GAME)
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .build();
mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
        .setAudioAttributes(mPlaybackAttributes)
        .setAcceptsDelayedFocusGain(true)
        .setOnAudioFocusChangeListener(afChangeListener, mHandler)
        .build();
mMediaPlayer = new MediaPlayer();
final Object mFocusLock = new Object();

boolean mPlaybackDelayed = false;
boolean mPlaybackNowAuthorized = false;

// ...
int res = mAudioManager.requestAudioFocus(mFocusRequest);
synchronized(mFocusLock) {
    if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
        mPlaybackNowAuthorized = false;
    } else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
        mPlaybackNowAuthorized = true;
        playbackNow();
    } else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
       mPlaybackDelayed = true;
       mPlaybackNowAuthorized = false;
    }
}

// ...
@Override
public void onAudioFocusChange(int focusChange) {
    switch (focusChange) {
        case AudioManager.AUDIOFOCUS_GAIN:
            if (mPlaybackDelayed || mResumeOnFocusGain) {
                synchronized(mFocusLock) {
                    mPlaybackDelayed = false;
                    mResumeOnFocusGain = false;
                }
                playbackNow();
            }
            break;
        case AudioManager.AUDIOFOCUS_LOSS:
            synchronized(mFocusLock) {
                mResumeOnFocusGain = false;
                mPlaybackDelayed = false;
            }
            pausePlayback();
            break;
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
            synchronized(mFocusLock) {
                mResumeOnFocusGain = true;
                mPlaybackDelayed = false;
            }
            pausePlayback();
            break;
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
            // ... pausing or ducking depends on your app
            break;
        }
    }
}
複製代碼

自動ducking

在Android 8.0 (API級別26)中,當另外一個應用程序使用AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK請求焦點時,系統能夠下降音量並恢復音頻播放,而無需調用應用程序的onAudioFocusChange()回調。

在音樂和視頻播放應用程序中,自動下降音量是能夠接受的行爲,但在播放語音內容(好比在有聲書應用程序中)時,它就沒用了。在這種狀況下,應用程序應該暫停。

若是你想讓你的應用程序在被要求ducking時暫停而不是減小它的音量,建立一個OnAudioFocusChangeListener,使用一個onAudioFocusChange()回調方法來實現所需的暫停/恢復行爲。調用setOnAudioFocusChangeListener()來註冊偵聽器,並調用setWillPauseWhenDucked(true)來告訴系統使用您的回調,而不是執行自動ducking操做。

延遲得到焦點

有時系統不能批准音頻焦點請求,由於焦點被另外一個應用程序「鎖定」,好比在打電話時。在本例中,requestAudioFocus()返回AUDIOFOCUS_REQUEST_FAILED。當這種狀況發生時,您的應用程序不該該繼續進行音頻播放,由於它沒有得到焦點。

該方法名爲setAcceptsDelayedFocusGain(true),它容許應用程序異步處理焦點請求。設置此標誌後,鎖定焦點時發出的請求將返回AUDIOFOCUS_REQUEST_DELAYED。當鎖定音頻焦點的條件再也不存在時,例如當電話結束時,系統會授予掛起的焦點請求,並調用onAudioFocusChange()來通知應用程序。

爲了處理延遲的焦點增益,您必須建立一個OnAudioFocusChangeListener,它使用一個onAudioFocusChange()回調方法來實現所需的行爲,並經過調用setOnAudioFocusChangeListener()來註冊偵聽器。

android 8.0以前版本 音頻聚焦

當你調用requestAudioFocus()時,你必須指定一個duration hint,這個duration hint可能會被另外一個當前保持焦點並播放的應用程序使用:

  • 當你計劃在可預見的未來播放音頻時(例如,播放音樂時)請求永久的音頻焦點(AUDIOFOCUS_GAIN),而且您但願先前的音頻焦點持有者中止播放。

  • 請求瞬態焦點(AUDIOFOCUS_GAIN_TRANSIENT),當您但願只在短期內播放音頻,而您但願以前的持有者暫停播放。

  • 請求ducking瞬態焦點 (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK),以指示您但願只在短期內播放音頻,而且若是先前的焦點全部者「duck」(下降)其音頻輸出,仍能夠繼續播放着。兩個音頻輸出都混合到音頻流中。Ducking特別適用於間歇性使用音頻流的應用程序,好比聲音驅動方向。

requestAudioFocus()方法也須要一個AudioManager.OnAudioFocusChangeListener。這個偵聽器應該建立在你的媒體會話所處的相同活動或服務中。它實現了回調onAudioFocusChange(),當其餘應用程序得到或放棄音頻焦點時,您的應用程序會接收到這個回調。

如下代碼片斷請求流STREAM_MUSIC上的永久音頻焦點,並註冊一個OnAudioFocusChangeListener來處理音頻焦點中的後續更改。(在下面的對音頻焦點變化的響應部分討論了更改偵聽器。)

AudioManager mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
AudioManager.OnAudioFocusChangeListener afChangeListener;

...
// 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) {
    // Start playback
}
複製代碼

當你完成播放時,調用abandonAudioFocus().

// Abandon audio focus when playback complete
mAudioManager.abandonAudioFocus(afChangeListener);
複製代碼

這將通知系統您再也不須要焦點,並註銷相關的OnAudioFocusChangeListener。若是您請求瞬態焦點,這將通知暫停或duck的應用程序可能繼續播放或恢復音量。

對音頻焦點變化的響應

當一個應用程序得到音頻焦點時,它必須可以在另外一個應用程序請求本身的音頻焦點時釋放它。當發生這種狀況時,您的應用程序在AudioFocusChangeListener中接收到對onAudioFocusChange()方法的調用,該調用是在調用requestAudioFocus()時指定的。

傳遞給onAudioFocusChange()的focusChange參數指示正在發生的變化。它對應於應用程序獲取焦點所使用的持續時間提示。你的應用程序應該作出適當的響應。

瞬態失焦

若是焦點的變化是瞬態的(AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK或AUDIOFOCUS_LOSS_TRANSIENT),應用程序應該ducking下降音量(若是你不依賴自動ducking)或暫停播放,但以其餘方式保持相同的狀態。

在音頻焦點暫時消失期間,您應該繼續監視音頻焦點的變化,並準備在恢復焦點後恢復正常回放。當阻塞應用程序放棄焦點時,您將收到一個回調(AUDIOFOCUS_GAIN)。此時,您能夠將音量恢復到正常水平或從新開始播放。

永久失去焦點

若是音頻焦點丟失是永久性的(AUDIOFOCUS_LOSS),另外一個應用程序正在播放音頻。你的應用應該當即暫停播放,由於它永遠不會收到AUDIOFOCUS_GAIN回調。要從新啓動回放,用戶必須採起顯式操做,好比在通知或應用程序UI中按下play傳輸控件。

下面的代碼片斷演示瞭如何實現OnAudioFocusChangeListener及其onAudioFocusChange()回調。請注意,使用一個Handler在永久失去音頻焦點時以延遲執行中止操做的回調。

private Handler mHandler = new Handler();
AudioManager.OnAudioFocusChangeListener afChangeListener =
  new AudioManager.OnAudioFocusChangeListener() {
    public void onAudioFocusChange(int focusChange) {
      if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
        // Permanent loss of audio focus
        // Pause playback immediately
        mediaController.getTransportControls().pause();
        // Wait 30 seconds before stopping playback
        mHandler.postDelayed(mDelayedStopRunnable,
          TimeUnit.SECONDS.toMillis(30));
      }
      else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
        // Pause playback
      } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
        // Lower the volume, keep playing
      } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
        // Your app has been granted audio focus again
        // Raise volume to normal, restart playback if necessary
      }
    }
  };
複製代碼

handler使用一個以下所示的Runnable

private Runnable mDelayedStopRunnable = new Runnable() {
    @Override
    public void run() {
        getMediaController().getTransportControls().stop();
    }
};
複製代碼

若是用戶從新啓動播放,爲了確保延遲中止不會起做用,能夠調用mHandler.removeCallbacks(mDelayedStopRunnable)來響應任何狀態更改。例如,在回調的onPlay()、onSkipToNext()中調用removeCallbacks()。在清理服務使用的資源時,還應該在服務的onDestroy()回調中調用此方法。

相關文章
相關標籤/搜索