管理音频焦点
注意: 如果您使用 ExoPlayer,请考虑让 ExoPlayer 通过为 handleAudioFocus 参数调用 setAudioAttributes 并使用 true 来自动管理音频焦点。启用此行为后,您的应用不应包含任何用于请求或响应音频焦点更改的代码。
两个或两个以上的 Android 应用可同时向同一输出流播放音频,并且系统会将所有音频混合在一起。虽然这是一项出色的技术,但却会给用户带来很大的困扰。为了避免所有音乐应用同时播放,Android 引入了“音频焦点”的概念。一次只能有一个应用获得音频焦点。
当您的应用需要输出音频时,它需要请求获得音频焦点,获得焦点后,就可以播放声音了。不过,在您获得音频焦点后,您可能无法将其一直持有到播放完成。其他应用可以请求焦点,从而占有您持有的音频焦点。如果发生这种情况,您的应用应暂停播放或降低音量,以便于用户听到新的音频源。
在 Android 12(API 级别 31)之前,音频焦点不受系统管理。因此,虽然我们鼓励应用开发者遵守音频焦点指南,但如果应用在搭载 Android 11(API 级别 30)或更低版本的设备上失去音频焦点后仍继续大声播放,系统也无法阻止。不过,这种应用行为会导致糟糕的用户体验,并经常导致用户卸载行为不当的应用。
设计良好的音频应用应根据以下一般准则来管理音频焦点:
a.在即将开始播放之前调用 requestAudioFocus(),并验证调用是否返回 AUDIOFOCUS_REQUEST_GRANTED。在媒体会话的 onPlay() 回调中调用 requestAudioFocus()。
b. 在其他应用获得音频焦点时,停止或暂停播放,或降低音量。
c.当播放停止时(例如,当应用没有剩余内容可播放时),放弃音频焦点。如果用户暂停播放但稍后可能会继续播放,您的应用不必放弃音频焦点。
d.使用 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 15(API 级别 35)或更高版本为目标平台,则除非是顶部应用或正在运行前台服务,否则无法请求音频焦点。此要求与现有的音频播放要求类似。如果应用在不满足这些要求的情况下请求音频焦点,该方法会返回 AUDIOFOCUS_REQUEST_FAILED。
使用音频焦点的媒体或游戏应用在失去焦点后不应播放音频。在 Android 12(API 级别 31)及更高版本中,系统会强制执行此行为。当一个应用在另一个应用具有焦点且正在播放音频的同时请求音频焦点时,系统会强制正在播放音频的应用淡出。随着淡出功能的添加,在从一个应用到另一个应用时,可以实现更流畅的过渡。
当满足以下条件时,就会出现这种淡出行为:
1 当前正在播放音频的第一个应用必须满足以下所有条件:
应用具有 AudioAttributes.USAGE_MEDIA 或 AudioAttributes.USAGE_GAME 用途属性。
应用使用 AudioManager.AUDIOFOCUS_GAIN 成功请求了音频焦点。
应用未在播放内容类型为 AudioAttributes.CONTENT_TYPE_SPEECH 的音频。
2 第二个应用使用 AudioManager.AUDIOFOCUS_GAIN 请求音频焦点。
满足这些条件时,音频系统会淡出第一个应用。在淡出结束时,系统会通知第一个应用它已失去焦点。该应用的播放器将会保持静音,直到该应用再次请求音频焦点。
现有的音频焦点行为
您还应注意下面这些涉及切换音频焦点的其他情况。
自动降低音量
自动降低音量功能是在 Android 8.0(API 级别 26)中引入的,该功能是指暂时降低一个应用的音量,以便能够清楚地听到另一个应用的声音。
通过让系统实现降低音量功能,您就不必在应用中实现降低音量功能。
当音频通知从正在播放音频的应用夺取焦点时,也会出现自动降低音量的行为。通知播放的开头与降低音量坡道的结尾同步。
当满足以下条件时,就会出现自动降低音量的行为:
1 当前正在播放音频的第一个应用必须满足以下所有条件:
应用已成功请求音频焦点(任何类型的焦点增益)。
应用未在播放内容类型为 AudioAttributes.CONTENT_TYPE_SPEECH 的音频。
应用未设置 AudioFocusRequest.Builder.setWillPauseWhenDucked(true)。
2 第二个应用使用 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 请求音频焦点。
满足这些条件时,音频系统会降低第一个应用的所有活跃播放器的音量,同时第二个应用获得焦点。当第二个应用放弃焦点时,系统会为这些播放器取消降低音量。第一个应用在失去焦点时不会收到通知,因此它无需执行任何操作。
请注意,在用户收听语音内容时不会执行自动降低音量的操作,因为用户可能会错过部分节目。例如,驾驶路线的语音导航不会被调低音量。
将来电时正在播放的音频设为静音
某些应用的行为不当,在通话过程中继续播放音频。 这样会迫使用户找到并静音或退出违规应用,以便听到通话的声音。为防止出现这种情况,系统可以在有来电时将其他应用的音频静音。当系统收到来电且应用满足以下条件时,就会调用此功能:
1 应用具有 AudioAttributes.USAGE_MEDIA 或 AudioAttributes.USAGE_GAME 用途属性。
2 应用已成功请求音频焦点(任何焦点增益)并且正在播放音频。
如果某个应用在通话过程中继续播放,其播放会静音,直到通话结束。不过,如果某个应用在通话过程中开始播放,该播放器不会静音,因为系统认为用户是有意开始播放的。
Android 8.0 至 Android 11 中的音频焦点
从 Android 8.0(API 级别 26)开始,当您调用 requestAudioFocus() 时,必须提供 AudioFocusRequest 参数。AudioFocusRequest 包含有关应用的音频上下文和功能的信息。系统使用这些信息来自动管理音频焦点的得到和失去。如需释放音频焦点,请调用 abandonAudioFocusRequest() 方法,该方法也接受 AudioFocusRequest 作为参数。在请求和放弃焦点时,请使用相同的 AudioFocusRequest 实例。
如需创建 AudioFocusRequest,请使用 AudioFocusRequest.Builder。由于焦点请求始终必须指定请求的类型,因此此类型会包含在构建器的构造函数中。使用构建器的方法来设置请求的其他字段。
FocusGain 字段为必需字段;所有其他字段均为可选字段。

以下示例展示了如何使用 AudioFocusRequest.Builder 构建 AudioFocusRequest 来请求和放弃音频焦点:
// 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() 回调的情况下降低和恢复音量。
虽然自动降低音量的行为对于音乐和视频播放应用来说是可接受的,但在播放语音内容时(例如在听书应用中)就没什么用处了。在这种情况下,应用应该暂停播放。
如果您希望应用在被要求降低音量时暂停播放,请创建包含 onAudioFocusChange() 回调方法的 OnAudioFocusChangeListener,该回调方法可以实现所需的暂停/恢复行为。 调用 setOnAudioFocusChangeListener() 来注册监听器,然后调用 setWillPauseWhenDucked(true) 告诉系统使用您的回调,而不是执行自动降低音量。
延迟获取焦点
在有些情况下,系统不能批准对音频焦点的请求,因为焦点被其他应用“锁定”了,例如在通话过程中。在这种情况下,requestAudioFocus() 会返回 AUDIOFOCUS_REQUEST_FAILED。在这种情况下,您的应用将不会播放音频,因为它未获得焦点。
方法 setAcceptsDelayedFocusGain(true) 可让您的应用异步处理焦点请求。设置此标记后,在焦点锁定时发出的请求会返回 AUDIOFOCUS_REQUEST_DELAYED。当锁定音频焦点的情况不再存在时(例如当通话结束时),系统会批准待处理的焦点请求,并调用 onAudioFocusChange() 来通知您的应用。
为了处理“延迟获取焦点”,您必须创建包含 onAudioFocusChange() 回调方法的 OnAudioFocusChangeListener,该回调方法会通过调用 setOnAudioFocusChangeListener() 来实现所需行为并注册监听器。
Android 7.1 及更低版本中的音频焦点
当您调用 requestAudioFocus() 时,您必须指定持续时间提示,当前持有焦点并正在播放的其他应用可能会遵循该提示:
a 如果您计划在可预见的将来播放音频(例如在播放音乐时),并且希望前一个持有音频焦点的应用停止播放,则应该请求永久性的音频焦点 (AUDIOFOCUS_GAIN)。
b 如果您只希望在短时间内播放音频,并且希望前一个持有音频焦点的应用暂停播放,则应该请求暂时性的焦点 (AUDIOFOCUS_GAIN_TRANSIENT)。
c 请求附带“降低音量”(AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) 的暂时性焦点,表示您只希望在短时间内播放音频,并允许前一个持有焦点的应用在降低其音频输出的情况下继续播放。这两个音频输出会混合到音频流中。降低音量特别适合于间歇性使用音频流的应用,例如有声的行车路线。
requestAudioFocus() 方法还需要 AudioManager.OnAudioFocusChangeListener。此监听器应在媒体会话所在的 activity 或服务中创建。它会实现 onAudioFocusChange() 回调,您的应用会在其他应用获取或放弃音频焦点时收到该回调。
以下代码段会请求对 STREAM_MUSIC 流的永久性音频焦点,并注册 OnAudioFocusChangeListener 来处理音频焦点的后续更改。(有关更改监听器的说明,请参阅响应音频焦点更改。)
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()。
// Abandon audio focus when playback complete
audioManager.abandonAudioFocus(afChangeListener);
这会通知系统您不再需要焦点,并注销关联的 OnAudioFocusChangeListener。如果您请求的是暂时性焦点,则会通知已暂停或降低音量的应用它可以继续播放或恢复其音量。
响应音频焦点更改
当应用获得音频焦点后,它必须能够在其他应用为自己请求音频焦点时释放该焦点。出现这种情况时,您的应用会收到对 AudioFocusChangeListener 中 onAudioFocusChange() 方法的调用,该方法是您在应用调用 requestAudioFocus() 时指定的。
传递给 onAudioFocusChange() 的 focusChange 参数表示所发生的更改类型。它对应于获取焦点的应用所使用的持续时间提示。您的应用应该做出适当的响应。
暂时性失去焦点
如果焦点更改是暂时性的(AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 或 AUDIOFOCUS_LOSS_TRANSIENT),您的应用应该降低音量(如果您不依赖于自动降低音量)或暂停播放,否则保持相同的状态。
在暂时性失去音频焦点时,您应该继续监控音频焦点的变化,并准备好在重新获得焦点后恢复正常播放。当抢占焦点的应用放弃焦点时,您会收到一个回调 (AUDIOFOCUS_GAIN)。此时,您可以将音量恢复到正常水平或重新开始播放。
永久性失去焦点
如果是永久性失去音频焦点 (AUDIOFOCUS_LOSS),则其他应用会播放音频。您的应用应立即暂停播放,因为它不会收到 AUDIOFOCUS_GAIN 回调。要重新开始播放,用户必须执行明确的操作,例如在通知或应用界面中按播放传输控件。
以下代码段展示了如何实现 OnAudioFocusChangeListener 及其 onAudioFocusChange() 回调。请注意这里使用 Handler 延迟了对永久性失去音频焦点的停止回调。
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:
private Runnable delayedStopRunnable = new Runnable() {
@Override
public void run() {
getMediaController().getTransportControls().stop();
}
};
为了确保在用户重新开始播放时不会触发延迟停止,请调用 mHandler.removeCallbacks(mDelayedStopRunnable) 来响应任何状态变化。例如,在回调的 onPlay()、onSkipToNext() 等中调用 removeCallbacks()。此外,在清理服务使用的资源时,您也应该在服务的 onDestroy() 回调中调用此方法。
https://developer.android.google.cn/media/optimize/audio-focus?hl=zh-cn
1952

被折叠的 条评论
为什么被折叠?



