两个或更多 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 8.0(API 级别 26)开始引入。
- Android 7.1(API 级别 25)及更低版本
- 音频焦点不由系统管理,应用可使用以下规则管理音频焦点:
requestAudioFocus()
和abandonAudioFocus()
。
Android 12 及更高版本中的音频焦点
使用音频焦点的媒体或游戏应用在丢失后不应播放音频 关注度。在 Android 12(API 级别 31)及更高版本中,系统会强制执行此 行为如果某个应用请求音频焦点,而另一个应用具有焦点,并且 系统会强制让正在播放的应用淡出。添加 淡出功能可在应用之间提供更顺畅的过渡。
当满足以下条件时,就会出现这种淡出行为:
当前正在播放的第一个应用符合以下所有条件:
- 该应用具有
AudioAttributes.USAGE_MEDIA
或AudioAttributes.USAGE_GAME
用法属性。 - 应用已成功通过
AudioManager.AUDIOFOCUS_GAIN
请求音频焦点。 - 应用未在播放内容类型为
AudioAttributes.CONTENT_TYPE_SPEECH
的音频。
- 该应用具有
第二个应用使用
AudioManager.AUDIOFOCUS_GAIN
请求音频焦点。
当满足这些条件时,音频系统会淡出第一个应用。在 淡出结束时,系统会通知第一个应用焦点丢失。应用的 播放器会保持静音,直到应用再次请求音频焦点。
现有的音频焦点行为
您还应注意下面这些涉及切换音频焦点的其他情况。
自动降低音量
自动降低音量(暂时降低某个应用的音量,以便 另一个可清楚听到)是在 Android 8.0(API 级别 26)中引入的。
通过让系统实现降低音量功能,您就不必实现降低音量功能 。
当音频通知从正在播放音频的应用夺取焦点时,也会出现自动降低音量的行为。通知播放的开头与降低音量坡道的结尾同步。
当满足以下条件时,就会出现自动降低音量的行为:
当前正在播放的第一个应用符合以下所有条件:
- 该应用成功请求了具有任意类型的焦点的音频焦点 增益。
- 应用未在播放内容类型为 1 的音频
AudioAttributes.CONTENT_TYPE_SPEECH
。 - 应用未设置
AudioFocusRequest.Builder.setWillPauseWhenDucked(true)
。
第二个应用使用
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
。
当这些条件满足时,音频系统会降低所有正在播放的播放器 第一个应用,而第二个应用获得焦点。第二个应用放弃时 就能减轻这类负担第一个应用在失去焦点时不会收到通知, 因此无需进行任何操作
请注意,当用户收听音频时,系统不会执行自动降低音量功能 语音内容,因为用户可能会错过某些节目。例如: 行车路线的语音导航不会闪避。
来电时将当前音频静音
某些应用会无法正常运行,并会继续在通话期间播放音频。 这种情况会迫使用户查找违规应用并将其静音或退出, 以便听取他们的来电为防止出现这种情况,系统可能会将其他设备的音频 应用。当出现以下情况时,系统会调用该功能 接到来电,且应用满足以下条件:
- 该应用具有
AudioAttributes.USAGE_MEDIA
或AudioAttributes.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() 的调用中使用的 durationHint :
AUDIOFOCUS_GAIN 、AUDIOFOCUS_GAIN_TRANSIENT 、
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 或 AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 。
|
setAudioAttributes()
|
AudioAttributes 描述了应用的用例。通过
系统会在应用获得和失去音频焦点时查看它们。属性
会取代音频流类型的概念在 Android 8.0(API 级别 26)及更高版本中,
已废弃音量控件之外的任何其他操作的流类型。使用
焦点请求中包含您在音频播放器中使用的相同属性(如
如此表下面的示例所示)。
使用
如果未指定,则 |
setWillPauseWhenDucked()
|
当另一个应用使用
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK ,获得焦点的应用不会
通常会收到
onAudioFocusChange()
回调,因为系统可以执行
自动闪避。如果您需要暂停播放
降低音量,调用 setWillPauseWhenDucked(true) 并创建并设置
OnAudioFocusChangeListener ,如自动
降低其他应用音量。
|
setAcceptsDelayedFocusGain()
|
当焦点被其他应用锁定时,对音频焦点的请求可能会失败。
此方法可实现延迟焦点获取,即:
在焦点可用时异步获取焦点
请注意,只有在同时指定了
|
setOnAudioFocusChangeListener()
|
仅当您也同时指定 OnAudioFocusChangeListener 时,才需要指定
willPauseWhenDucked(true) 或 setAcceptsDelayedFocusGain(true) 。
设置监听器的方法有两种:一种是带
处理程序参数。处理程序是运行监听器的线程。如果您
则不指定处理程序,即与主
|
以下示例展示了如何使用 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
) 表示您要播放音频 并且之前的焦点所有者可以保留 CANNOT TRANSLATE(降低)其音频输出。两个音频输出混合 流式传输到音频流中降低音量功能特别适合使用 音频流时断时续,例如有声音的行车路线。
requestAudioFocus()
方法还需要 AudioManager.OnAudioFocusChangeListener
。此监听器应为
是在媒体会话所属的同一 activity 或服务中创建的。它
实现 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()
传递给 onAudioFocusChange()
的 focusChange
参数指示种类
所发生的变化它对应于
获取焦点的应用所使用的时长提示。您的应用应
并作出适当回应。
- 暂时性失去焦点
-
如果焦点变化是瞬态的(
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
或AUDIOFOCUS_LOSS_TRANSIENT
),您的应用应闪避(如果您不依赖 或暂停播放,但 否则会保持相同的状态在暂时性失去音频焦点期间,您应继续监控变化 处于音频焦点,并准备好在重新获得 关注度。当屏蔽应用放弃焦点时,您会收到一个回调 (
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()
回调。