在开始播放音频之前,App必须先获取需要处理的音频流的音频焦点,通过requestAudioFocus()方法,成功则返回AUDIOFOCUS_REQUEST_GRANTED常量,失败则返回AUDIOFOCUS_REQUEST_FAILED常量
短暂的音频焦点用于处理播放短时间的音频(例如汽车导航仪的提示)。如果您想长时间播放音频(例如播放音乐),那么就需要请求长期的音频焦点。
获得音频焦点的请求应该在马上就要播放音频前发出,比如在用户按下播放键或下一关游戏的背景音乐就要开始时发出焦点请求,接着再播放音乐。
下面是获得长期焦点的相关代码:
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);
...
//请求播放的音频焦点
int result = am.requestAudioFocus(afChangeListener,
// 指定所使用的音频流
AudioManager.STREAM_MUSIC,
// 请求长时间的音频焦点
AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
// 开始播放
}
当完成音频播放后,请一定记得调用abandonAudioFocus()方法,这会通知系统您的App不再需要音频焦点,并移除相关OnAudioFocusChangeListener的注册。如果释放的是短暂音调焦点,那么被打断的音频会被继续播放。
// 当播放结束,您需要释放音频焦点
am.abandonAudioFocus(afChangeListener);
当请求短暂音频焦点时,您可以添加有一额外的选择——是否使用“浮动声音(英文为“ducking”,这里是指降低原音频流播放的音量,并使获得短暂音频焦点的音频流音量较大,而不去停止原来音频流的播放)”方式。
通常来说,一个好的音频播放App会在失去音频焦点时立即停止播放。但如果在请求短暂音频焦点时使用“浮动声音”方式,可以允许先前的App以较低的音量继续播放,在重新获得音频焦点后再以原来的音量播放。
// 请求播放的音频焦点
int result = am.requestAudioFocus(afChangeListener,
// 指定所使用的音频流
AudioManager.STREAM_MUSIC,
// 请求短暂焦点
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// 开始播放
}
浮动音频非常适合间断播放音频的App,例如导航仪的提示
注册的监听器可以判断是否获得到了长期或短暂(可以选择浮动音频方式)的音频焦点。
App请求并得到音频焦点后,当其他App请求得到焦点时,先前的App就会失去焦点
onAudioFocusChange(int)回调函数
失去音频焦点的事件类型与请求焦点的类型相对应——失去长期焦点(AUDIOFOCUS_LOSS)、短暂焦点(AUDIOFOCUS_LOSS_TRANSIENT)和浮动音频方式的短暂焦点(AUDIOFOCUS_LOSS_TRANSIENT)。
一般情况下,
App在失去短暂音频焦点时,应该停止播放并记录下播放状态。但仍然需要监听音频焦点的变化,当重新获得音频焦点时,需要在从先前暂停的地方继续播放。
如果失去的是长期音频焦点,那就是说其他App需要使用当前音频流,而您的App需要尽快结束。实际上,意味着您的App需要停止播放,移除媒体键监听器并释放音频焦点——这将允许新的音频播放器独占地持有音频焦点等
OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {
public void onAudioFocusChange(int focusChange) {
if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT
// 暂停播放
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// 恢复播放
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
am.abandonAudioFocus(afChangeListener);
// 停止播放
}
}
};
如果失去的是短暂且允许使用浮动播放(duck)的音频焦点,相比暂停播放,更好的做法应该是使用“浮动播放”的方式。
浮动播放会将您正使用的音频流输出音量降低,这会使其他App的短暂音频更容易听到,如此一来您的App就不用被完全打断了。
下面的代码会使App在暂时失去焦点时降低媒体播放器的音量,并在重新获得音频焦点时恢复到原来的音量大小。
大多数设备有一个内置扬声器,有线耳机的耳机插孔,不少还配备了蓝牙连接,支持A2DP的音频。
if (isBluetoothA2dpOn()) {
// Adjust output for Bluetooth.
} else if (isSpeakerphoneOn()) {
// Adjust output for Speakerphone.
} else if (isWiredHeadsetOn()) {
// Adjust output for headsets
} else {
// If audio plays and noone can hear it, is it still playing?
}
当耳机被拔掉,或蓝牙设备断开连接,音频流将会自动重新路由到内置的扬声器。如果你正用很高的音量听歌,那将会是一个嘈杂的“惊喜”。
幸运的是,当这一切发生的时候,系统会广播ACTION_AUDIO_BECOMING_NOISY意图。当你正播放音频时,注册BroadcastReceiver,并监听这个意图是很好的做法。在音乐播放器案例中,用户通常期望要暂停播放,而在游戏中,你可以选择大大地降低音量。
private class NoisyAudioStreamReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
// Pause the playback
}
}
}
private IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
private void startPlayback() {
registerReceiver(myNoisyAudioStreamReceiver(), intentFilter);
}
private void stopPlayback() {
unregisterReceiver(myNoisyAudioStreamReceiver);
}