管理音訊焦點

兩個以上的 Android 應用程式可以在同一個輸出串流中播放音訊 然後系統會結合所有元素雖然這是 這可能會讓使用者感到非常開心。為了避免每次 同時播放音樂應用程式時,Android 引進了音訊功能 重點:一次只能有一個應用程式保留音訊焦點。

如果應用程式需要輸出音訊,則應要求音訊焦點。採用時機 還可以播放音效但獲得音訊焦點後 一直到結束為止其他應用程式可以要求聚焦, 預先對音訊焦點執行保留操作。遇到這種情況時,您的應用程式應暫停執行 播放或調低音量,讓使用者能更輕鬆聽到新的音訊來源。

在 Android 12 (API 級別 31) 以下版本中,音訊焦點並非由系統管理。所以 應用程式開發人員也會遵守音訊焦點規範 就算失去裝置上的音訊焦點,應用程式還是會繼續大聲播放 搭載 Android 11 (API 級別 30) 以下版本,系統無法防止這個情況發生。 不過,這種應用程式行為會對使用者體驗造成負面影響,而且通常會導致 使用者解除安裝無法正常運作的應用程式。

設計完善的音訊應用程式應根據下列一般項目管理音訊焦點 規範:

  • 請在開始播放前立即呼叫 requestAudioFocus() 並驗證 呼叫傳回 AUDIOFOCUS_REQUEST_GRANTED。 撥打電話: requestAudioFocus()儲存在媒體工作階段的 onPlay() 回呼中。

  • 其他應用程式取得音訊焦點、停止/暫停播放或是鴨子 調低) 音量。

  • 播放停止時 (例如應用程式沒有可播放的內容時) 放棄音訊焦點如果使用者,您的應用程式就不必放棄音訊焦點 就會暫停播放,但稍後或許能繼續播放。

  • 使用 AudioAttributes 提供說明 應用程式播放的音訊類型以播放語音的應用程式為例 指定 CONTENT_TYPE_SPEECH

視搭載的 Android 版本而定,音訊焦點的處理方式會有所不同 正在執行:

Android 12 (API 級別 31) 以上版本
音訊焦點是由系統管理。系統會強制從 應用程式就會淡出。系統 功能在有人來電時將音訊靜音。
Android 8.0 (API 級別 26) 至 Android 11 (API 級別 30)
音訊焦點並非由系統管理,但包含進行的部分變更 。
Android 7.1 (API 級別 25) 以下版本
音訊焦點並非由系統管理,應用程式用來管理音訊焦點 requestAudioFocus()abandonAudioFocus()

Android 12 以上版本的音訊焦點

如果媒體或遊戲應用程式使用音訊焦點,不應在遺失後播放音訊 重點。在 Android 12 (API 級別 31) 以上版本中,系統會強制執行此設定 行為應用程式要求音訊焦點,而另一個應用程式有焦點 時,系統會強迫播放的應用程式淡出。將 淡出淡出,可在應用程式間移動時享有更流暢的轉場效果。

在符合下列條件時,這樣就會排除行為:

  1. 第一個目前播放的應用程式符合下列所有條件:

  2. 第二個應用程式使用 AudioManager.AUDIOFOCUS_GAIN 要求音訊焦點。

符合這些條件時,音訊系統就會淡出第一個應用程式。在 淡出時,系統會通知第一個應用程式。應用程式的 會保持靜音,直到應用程式再次要求音訊焦點。

現有的音訊焦點行為

你也應該瞭解其他需要切換音訊裝置的情況 重點。

自動降低背景音量

自動降低其他應用程式音量 (暫時降低其中一個應用程式的音訊音量), 您清楚知道,Android 8.0 (API 級別 26)

透過系統實作降低背景播放模式,您就不必在

當音訊通知擷取到焦點時,也會發生自動「降低背景音量」功能 或者使用正在播放的應用程式通知播放的開始時間會保持同步 結束運氣

系統會在符合下列條件時,自動降低背景脈搏:

  1. 第一個目前正在播放的應用程式,必須滿足以下所有條件:

  2. 第二個應用程式要求使用音訊焦點 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK

符合這些條件時,音訊系統會設法讓所有使用中的玩家 第二個應用程式則是焦點第二個應用程式棄用時 這讓他們專心處理第一個應用程式不會在失去焦點時收到通知。 也不必採取任何行動

請注意,使用者聆聽音訊時,系統不會自動降低降低音量 卻可能錯過某些程式。例如: 未提供行車路線的語音導引。

將來電時從目前的音訊靜音

部分應用程式無法正常運作,且無法在通話時繼續播放音訊。 這種情況下,使用者必須在以下位置找到違規的應用程式,並予以靜音或關閉 才能聆聽其通話內容。為避免這種情況,系統可以將其他音訊靜音 應用程式。如果發生特定情況,系統會叫用這項功能 應用程式必須符合以下條件:

  • 應用程式具有 AudioAttributes.USAGE_MEDIAAudioAttributes.USAGE_GAME 使用屬性。
  • 應用程式已成功要求音訊焦點 (任何焦點增加) 並正在播放 音訊。

如果應用程式在通話期間持續播放,則在 通話結束。但如果應用程式在通話期間開始執行,該播放器就不會出現 避免使用者刻意開始播放。

Android 8.0 至 Android 11 版本的音訊焦點

從 Android 8.0 (API 級別 26) 開始,當您呼叫 requestAudioFocus()敬上 您必須提供 AudioFocusRequest 參數AudioFocusRequest 包含應用程式音訊情境和功能的相關資訊。 系統會使用這項資訊來管理音訊焦點的增減 。如要釋出音訊焦點,請呼叫 abandonAudioFocusRequest()敬上 該函式也會使用 AudioFocusRequest 做為引數使用相同的 AudioFocusRequest 例項 (前提是您要求和捨棄焦點)。

如要建立AudioFocusRequest,請使用 AudioFocusRequest.Builder。由於焦點要求 一律指定要求類型,建構函式中會包含該類型 提供給建構工具使用使用建構工具的方法設定 請求。

FocusGain 為必填欄位。其他都是選填欄位

方法附註
setFocusGain() 所有要求都必須填寫此欄位。它的值會與 Android 8.0 之前呼叫 requestAudioFocus() 中使用的 durationHintAUDIOFOCUS_GAINAUDIOFOCUS_GAIN_TRANSIENTAUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCKAUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
setAudioAttributes() AudioAttributes 說明應用程式的用途。 當應用程式獲得及失去音訊焦點時,系統就會監控這些音訊屬性 取代串流類型的概念。在 Android 8.0 (API 級別 26) 以上版本中, 除了音量控制以外,任何作業的串流類型已淘汰。使用 您在音訊播放器中使用的焦點要求 如下表所示)。

使用 AudioAttributes.Builder 即可指定 然後使用這個方法將屬性指派給 請求。

如未指定,AudioAttributes 會預設為 AudioAttributes.USAGE_MEDIA

setWillPauseWhenDucked() 其他應用程式要求聚焦時 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,即未聚焦的應用程式 通常會收到 onAudioFocusChange() 回呼,因為系統可以 自駕當你需要暫停播放時 呼叫 setWillPauseWhenDucked(true),然後建立並設定 OnAudioFocusChangeListener,如自動 降低背景音量
setAcceptsDelayedFocusGain() 當其他應用程式鎖定焦點時,要求音訊焦點的要求可能會失敗。 這個方法可啟用延遲焦點增益:能力 以非同步方式獲取可以用的重點

請注意,只有當您指定 AudioManager.OnAudioFocusChangeListener ,因為應用程式必須 才能接收回呼,以知道已授予焦點。

setOnAudioFocusChangeListener() 只有在您同時指定 OnAudioFocusChangeListener 時,才需要 willPauseWhenDucked(true)setAcceptsDelayedFocusGain(true)

設定事件監聽器的方法有兩種:一種是有 處理常式引數。處理常式是執行事件監聽器的執行緒。如果發生以下情況: 不指定處理常式,也就是與主要應用程式相關聯的處理常式 使用的是 Looper

以下範例說明如何使用 AudioFocusRequest.Builder 建構 使用 AudioFocusRequest 並要求並放棄音訊焦點:

Kotlin

// initializing variables for audio focus and playback management
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run {
    setAudioAttributes(AudioAttributes.Builder().run {
        setUsage(AudioAttributes.USAGE_GAME)
        setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        build()
    })
    setAcceptsDelayedFocusGain(true)
    setOnAudioFocusChangeListener(afChangeListener, handler)
    build()
}
val focusLock = Any()

var playbackDelayed = false
var playbackNowAuthorized = false

// requesting audio focus and processing the response
val res = audioManager.requestAudioFocus(focusRequest)
synchronized(focusLock) {
    playbackNowAuthorized = when (res) {
        AudioManager.AUDIOFOCUS_REQUEST_FAILED -> false
        AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> {
            playbackNow()
            true
        }
        AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> {
            playbackDelayed = true
            false
        }
        else -> false
    }
}

// implementing OnAudioFocusChangeListener to react to focus changes
override fun onAudioFocusChange(focusChange: Int) {
    when (focusChange) {
        AudioManager.AUDIOFOCUS_GAIN ->
            if (playbackDelayed || resumeOnFocusGain) {
                synchronized(focusLock) {
                    playbackDelayed = false
                    resumeOnFocusGain = false
                }
                playbackNow()
            }
        AudioManager.AUDIOFOCUS_LOSS -> {
            synchronized(focusLock) {
                resumeOnFocusGain = false
                playbackDelayed = false
            }
            pausePlayback()
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
            synchronized(focusLock) {
                // only resume if playback is being interrupted
                resumeOnFocusGain = isPlaying()
                playbackDelayed = false
            }
            pausePlayback()
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
            // ... pausing or ducking depends on your app
        }
    }
}

Java

// initializing variables for audio focus and playback management
audioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
playbackAttributes = new AudioAttributes.Builder()
        .setUsage(AudioAttributes.USAGE_GAME)
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .build();
focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
        .setAudioAttributes(playbackAttributes)
        .setAcceptsDelayedFocusGain(true)
        .setOnAudioFocusChangeListener(afChangeListener, handler)
        .build();
final Object focusLock = new Object();

boolean playbackDelayed = false;
boolean playbackNowAuthorized = false;

// requesting audio focus and processing the response
int res = audioManager.requestAudioFocus(focusRequest);
synchronized(focusLock) {
    if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
        playbackNowAuthorized = false;
    } else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
        playbackNowAuthorized = true;
        playbackNow();
    } else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
        playbackDelayed = true;
        playbackNowAuthorized = false;
    }
}

// implementing OnAudioFocusChangeListener to react to focus changes
@Override
public void onAudioFocusChange(int focusChange) {
    switch (focusChange) {
        case AudioManager.AUDIOFOCUS_GAIN:
            if (playbackDelayed || resumeOnFocusGain) {
                synchronized(focusLock) {
                    playbackDelayed = false;
                    resumeOnFocusGain = false;
                }
                playbackNow();
            }
            break;
        case AudioManager.AUDIOFOCUS_LOSS:
            synchronized(focusLock) {
                resumeOnFocusGain = false;
                playbackDelayed = false;
            }
            pausePlayback();
            break;
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
            synchronized(focusLock) {
                // only resume if playback is being interrupted
                resumeOnFocusGain = isPlaying();
                playbackDelayed = false;
            }
            pausePlayback();
            break;
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
            // ... pausing or ducking depends on your app
            break;
        }
    }
}

自動降低背景音量

在 Android 8.0 (API 級別 26) 中,另一個應用程式要求聚焦 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 系統可折疊及還原音量 而不必叫用應用程式的 onAudioFocusChange() 回呼。

雖然自動「降低其他應用程式音量」是支援播放音樂及影片播放的行為, 但如果要播放語音內容 (例如 有聲書應用程式。在這種情況下,應用程式應改為暫停。

如要讓應用程式在系統詢問你的鴨子情況時暫停,而非降低音量,請建立 OnAudioFocusChangeListener,並加上 可實作所需的暫停/繼續行為的 onAudioFocusChange() 回呼方法。 呼叫 setOnAudioFocusChangeListener() 註冊事件監聽器,並呼叫 setWillPauseWhenDucked(true)。 指示系統使用您的回呼,而非執行自動降低的鎖定。

延後對焦

系統有時無法核准音訊焦點要求,這是因為 「已鎖定」由其他應用程式使用,例如在通話期間。在本例中 requestAudioFocus() 會傳回 AUDIOFOCUS_REQUEST_FAILED。發生這種情況時 您的應用程式並未獲得音訊,因此不應繼續播放音訊 重點。

可讓應用程式處理焦點要求的方法 setAcceptsDelayedFocusGain(true) 以非同步方式載入物件設定此標記後,系統會在焦點鎖定時提出要求 會傳回 AUDIOFOCUS_REQUEST_DELAYED。鎖定音訊的條件 焦點隨即消失,如通話結束時 會授予待處理的焦點要求,並呼叫 onAudioFocusChange() 以通知 應用程式。

如要處理焦點的延遲增益,您必須建立 OnAudioFocusChangeListener 搭配 onAudioFocusChange() 回呼方法, 會實作所需行為,並呼叫 setOnAudioFocusChangeListener()

Android 7.1 以下版本的音訊焦點

撥打電話時 requestAudioFocus()敬上 您必須指定時間長度提示, 表示最近擁有焦點和遊戲的其他應用程式會接受這些要求:

  • 計劃播放音訊時,要求永久音訊焦點 (AUDIOFOCUS_GAIN) 呈現出預測的未來 (例如播放音樂時) 來停止播放
  • 預期播放時要求暫時焦點 (AUDIOFOCUS_GAIN_TRANSIENT) 短暫播放音訊,且你預期前一位持有人暫停 資訊。
  • 使用降低背景音量功能,要求暫時聚焦 (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK),表示您預計播放音訊 且先前的焦點擁有者可以 會播放「鴨子」(調低) 輸出的音訊。這兩種音訊輸出方式皆混用 插入音訊串流如果應用程式會使用 音訊串流間歇性播放,例如用於聽清楚行車路線。

requestAudioFocus() 方法也需要 AudioManager.OnAudioFocusChangeListener。這個事件監聽器應 您在擁有媒體工作階段的相同活動或服務中建立的活動。這項服務 實作應用程式接收到的回呼 onAudioFocusChange() 可能有其他應用程式取得或放棄音訊焦點

下列程式碼片段要求串流的永久音訊焦點 STREAM_MUSIC 並註冊 OnAudioFocusChangeListener 來處理 之後的音訊焦點變更(如需變更監聽器,請參閱 回應音訊焦點變更)。

Kotlin

audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
lateinit var afChangeListener AudioManager.OnAudioFocusChangeListener

...
// Request audio focus for playback
val result: Int = audioManager.requestAudioFocus(
        afChangeListener,
        // Use the music stream.
        AudioManager.STREAM_MUSIC,
        // Request permanent focus.
        AudioManager.AUDIOFOCUS_GAIN
)

if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    // Start playback
}

Java

AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
AudioManager.OnAudioFocusChangeListener afChangeListener;

...
// Request audio focus for playback
int result = audioManager.requestAudioFocus(afChangeListener,
                             // Use the music stream.
                             AudioManager.STREAM_MUSIC,
                             // Request permanent focus.
                             AudioManager.AUDIOFOCUS_GAIN);

if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    // Start playback
}

播放完畢後,請呼叫 abandonAudioFocus()

Kotlin

audioManager.abandonAudioFocus(afChangeListener)

Java

// Abandon audio focus when playback complete
audioManager.abandonAudioFocus(afChangeListener);

這會通知系統您不再需要焦點,並取消註冊 相關聯的 OnAudioFocusChangeListener。如果你要求暫時聚焦 系統會通知已暫停或降低的應用程式可以繼續播放或 還原其磁碟區。

回應音訊焦點變更

當應用程式取得音訊焦點時,必須要能在其他應用程式中釋放 要求音訊焦點發生這種情況時 會收到對 onAudioFocusChange()敬上 AudioFocusChangeListener 中的方法 您在應用程式呼叫 requestAudioFocus() 時指定的 Cookie。

傳遞至 onAudioFocusChange()focusChange 參數會指出所需種類 發生了各種變動對應於 至應用程式取得焦點的應用程式所使用的時間長度提示。您的應用程式應 並提供適當的回應

短暫失去焦點
焦點變更是暫時性的 (AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKAUDIOFOCUS_LOSS_TRANSIENT),您的應用程式應 Duck (如果不是的話) 設為自動降低) 或暫停播放,但 否則就會維持相同的狀態

暫時失去音訊焦點時,你應該持續監控變化 並準備好在重新訂閱時,恢復正常播放 重點。當封鎖的應用程式放棄聚焦時,您會收到回呼 (AUDIOFOCUS_GAIN)。這時你可以將音量還原至正常音量 或重新開始播放。

永久失去焦點
如果音訊焦點喪失永久性 (AUDIOFOCUS_LOSS),則其他應用程式就會 播放音訊。應用程式應該會立即暫停播放, 接收 AUDIOFOCUS_GAIN 回呼。如要重新開始播放,請由使用者操作 必須採取明確行動,例如按下播放傳輸控制項 通知或應用程式使用者介面顯示

下列程式碼片段說明如何導入 OnAudioFocusChangeListener 及其 onAudioFocusChange() 回呼。請注意 使用 Handler,在音訊永久遺失時延遲停止回呼 重點。

Kotlin

private val handler = Handler()
private val afChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
    when (focusChange) {
        AudioManager.AUDIOFOCUS_LOSS -> {
            // Permanent loss of audio focus
            // Pause playback immediately
            mediaController.transportControls.pause()
            // Wait 30 seconds before stopping playback
            handler.postDelayed(delayedStopRunnable, TimeUnit.SECONDS.toMillis(30))
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
            // Pause playback
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
            // Lower the volume, keep playing
        }
        AudioManager.AUDIOFOCUS_GAIN -> {
            // Your app has been granted audio focus again
            // Raise volume to normal, restart playback if necessary
        }
    }
}

Java

private Handler handler = 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
        handler.postDelayed(delayedStopRunnable,
          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
      }
    }
  };

處理常式使用 Runnable,如下所示:

Kotlin

private var delayedStopRunnable = Runnable {
    mediaController.transportControls.stop()
}

Java

private Runnable delayedStopRunnable = new Runnable() {
    @Override
    public void run() {
        getMediaController().getTransportControls().stop();
    }
};

為確保使用者重新啟動播放時不會觸發延遲停止,請呼叫 mHandler.removeCallbacks(mDelayedStopRunnable)以回應任何狀態 並輸入變更內容例如,在回呼的 onPlay() 中呼叫 removeCallbacks()onSkipToNext() 等。您也應在服務的 清理服務使用的資源時的 onDestroy() 回呼。