翻译自Managing audio focushtml
两个或更多的Android应用程序能够同时播放音频到相同的输出流。系统把全部东西混合在一块儿。虽然这在技术上是使人印象深入的,但对用户来讲倒是很是使人恼火的。为了不全部音乐应用同时播放,Android引入了音频聚焦的概念。只有一个应用程序能够一次聚焦音频。android
当您的应用程序须要输出音频时,它应该请求音频焦点。当它有焦点时,它能够播放声音。然而,在你得到音频焦点后,你可能没法持有它直到你播放完。另外一个应用程序能够请求焦点,它会抢占你的音频焦点。若是出现这种状况,你的应用程序应该暂停播放或下降音量,让用户更容易听到新的音频源。bash
音频聚焦是合做的。鼓励应用程序遵照音频聚焦指南,但该系统不执行这些规则。若是一个应用程序想在失去声音焦点后继续大声播放,没有什么能阻止它。这是一种糟糕的体验,用户颇有可能会卸载出现这种糟糕体验的应用程序。app
一个表现良好的音频应用程序应该管理音频焦点根据这些通常准则:异步
requestAudioFocus()
,并验证调用是否返回AUDIOFOCUS_REQUEST_GRANTED。若是您按照咱们在本指南中描述的那样设计应用程序,那么应该在媒体会话的onPlay()
回调中调用requestAudioFocus()
。音频焦点的处理方式因Android版本不一样而异:ide
requestAudioFocus()
方法,它接受AudioFocusRequest参数。AudioFocusRequest包含关于音频上下文和应用程序功能的信息。系统使用这些信息自动管理音频焦点的获取和丢失。从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_EXCLUSIVE
。ui
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;
}
}
}
复制代码
在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()来注册侦听器。
当你调用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()回调中调用此方法。