1.概述
在android app开发中,在一些音乐视频播放器中,在某些页面的时候,需要多个视频布局控件切换播放视频,这时发现会抛异常播放不了视频,经过问题排查发现这是由于音频焦点引起的冲突,所以会导致播放失败,接下来看下音频焦点的问题
2.音频焦点的问题相关分析
2.1 AudioManager相关api的分析讲解
在系统api中,AudioManager是在android.Media 包中,该类提供访问控制音量和钤声模式的操作
使用Context.getSystemService(Context.AUDIO_SERVICE)来得到这个类的一个实例
核心api的讲解如下
abandonAudioFocus(AudioManager.OnAudioFocusChangeListenerl)
放弃音频的焦点。
adjustStreamVolume(int streamType, int direction, int flags)
调整手机指定类型的声音。
adjustSuggestedStreamVolume(int,int suggestedStreamType intflags)
调整最相关的流的音量。或者给定的回退流。
adjustVolume(int,intflags)
调整最相关的数据流的音量。
getMode()
返回当前音频模式。
getParameters(String keys)
给音频硬件设置一个varaible数量的參数值
getRingerMode()
返回当前的铃声模式。
getRouting(int mode)
这种方法是不赞成的。不直接查询音频路由,使用isSpeakerphoneOn(), isBluetoothA2dpOn isBluetoothScoOn(),()和isWiredHeadsetOn()方法。
getStreamMaxVolume(int streamType)
返回特定流的最大音量指数。
getStreamVolume(int streamType)
返回特定数据流的当前音量索引。
getVibrateSetting(int vibrateType)
返回是否该用户的振动设置为振动类型。
isBluetoothA2dpOn()
检查是否A2DP蓝牙耳机音频路由是打开或关闭。
isBluetoothScoAvailableOffCall()
显示当前平台是否支持使用SCO的关闭调用用例。
isBluetoothScoOn()
检查通信是否使用蓝牙SCO。
isMicrophoneMute()
检查是否打开或关闭麦克风静音。
isMusicActive()
检查是否有音乐处于活跃状态。
isSpeakerphoneOn()
检查是否打开或关闭扬声器。
isWiredHeadsetOn()
检查是否插入耳机
loadSoundEffects()
载入声音效果。
playSoundEffect((int effectType, float volume)
播放声音效果(按键点击,盖开启/关闭...)
playSoundEffect(int effectType)
播放声音效果(按键点击,盖开启/关闭...)
registerMediaButtonEventReceiver(ComponentName eventReceiver)
注冊一个组件MEDIA_BUTTON意图的唯一接收机。
requestAudioFocus(AudioManager.OnAudioFocusChangeListener l,int streamType,int durationHint)
请求音频的焦点。
setBluetoothA2dpOn(booleanon)
这种方法是不赞成的。
不要使用。
setBluetoothScoOn(booleanon)
要求使用蓝牙SCO耳机进行通讯。
setMicrophoneMute(booleanon)
设置是否让麦克风静音。
setMode(int mode)
设置声音模式。
setParameters(String keyValuePairs)
设置一个音频硬件数量可变的參数值。
setRingerMode(int ringerMode)
设置手机电话铃声的模式。
setRouting(int mode, int routes, int mask)
这种方法是不赞成的。不设置音频路由直接使用setSpeakerphoneOn(), setBluetoothScoOn()方法。
setSpeakerphoneOn(boolean on)
设置扬声器打开或关闭。
setStreamMute(int streamType,booleanstate)
将手机的指定类型的声音调整为静音。
setStreamSolo(int streamType,boolean state)
独奏或来取消特定流。
setStreamVolume (int streamType, int index, int flags)
直接设置手机的指定类型的音量值。
setVibrateSetting (int vibrateType, int vibrateSetting)
设置设置时应该振动的振动类型。
setWiredHeadsetOn(boolean on)
这种方法是不赞成的。不要使用。
shouldVibrate(int vibrateType)
返回特定类型是否应该依据用户震动设置和当前的振铃器 mode。
startBluetoothSco()
启动蓝牙SCO音频连接。
stopBluetoothSco()
停止蓝牙SCO音频连接。
unloadSoundEffects()
卸载音效。
unregisterMediaButtonEventReceiver(ComponentName eventReceiver)
注销MEDIA_BUTTON意图的接收机。
音频焦点的管理
两个或两个以上的 Android 应用可同时向同一输出流播放音频。系统会将所有音频流混合在一起。虽然这是一项出色的技术,但却会给用户带来很大的困扰。为了避免所有音乐应用同时播放,Android 引入了“音频焦点”的概念。 一次只能有一个应用获得音频焦点。
当您的应用需要输出音频时,它需要请求获得音频焦点,获得焦点后,就可以播放声音了。不过,在您获得音频焦点后,您可能无法将其一直持有到播放完成。其他应用可以请求焦点,从而占有您持有的音频焦点。如果发生这种情况,您的应用应暂停播放或降低音量,以便于用户听到新的音频源。
行为标准
在即将开始播放之前调用 requestAudioFocus(),并验证调用是否返回 AUDIOFOCUS_REQUEST_GRANTED。
在其他应用获得音频焦点时,应该停止或者暂停播放,或者降低音量。
播放停止后应该放弃音频焦点
当想录音或者播放歌曲的时候,最好(非必须)先请求音频焦点,这个时候需要调用AudioManager.requestAudioFocus()方法,函数原型如下
AudioManager.requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint)
第一个参数用于监听焦点变化
第二个参数表明请求的音频焦点影响的是那种类型流,例如,如果我们录音,我们肯定是要影响Music这一类型的音频流,因此可以选择AudioManager.STREAM_MUSIC。当然还有许多类型
/** 通话相关 */
public static final int STREAM_VOICE_CALL = AudioSystem.STREAM_VOICE_CALL;
/** 系统声音 */
public static final int STREAM_SYSTEM = AudioSystem.STREAM_SYSTEM;
/** 铃声 */
public static final int STREAM_RING = AudioSystem.STREAM_RING;
/** 音乐相关 */
public static final int STREAM_MUSIC = AudioSystem.STREAM_MUSIC;
/** 闹钟相关 */
public static final int STREAM_ALARM = AudioSystem.STREAM_ALARM;
第三个参数用于表明音频焦点的持续时间,这个很关键,它也有许多种类型,下面一一列出。
AudioManager.AUDIOFOCUS_GAIN: API文档说请求的这类音频焦点持续时间是未知的,通常用来表示长时间获取焦点,可以用来播放音乐,录音等等。AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: 表明请求的音频焦点持续时间比较短,通常用来播放导航路线的声音,或者播放通知声音。AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: 这个也是表明请求的音频焦点持续时间比较短,但是在这段时间内,它希望其他应用以较低音量继续播放。例如,我们在使用导航的时候可以听音乐,当出现导航语音的时候,音乐音量会降低以便我们能听清楚导航的语音,当导航语音播放完毕后,音乐恢复音量,继续播放。AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: 这个也是表明音请求的音频焦点持续时间比较短,但是在这段时间内,不希望任何应用(包括系统应用)来做任何与音频相关的事情,就算是降低音量播放音乐也是不被希望的。例如当我们进行录音或者语音识别的时候,我们不希望其他的声音出现干扰。
AudioManager.requestAudioFocus()的返回值表明请求的结果AudioManager.AUDIOFOCUS_REQUEST_FAILED表明请求焦点失败,AudioManager.AUDIOFOCUS_REQUEST_GRANTED表明请求焦点成功。
当我们成功请求焦点后,就可以做一些与音频有关的事情,例如播放音乐,录音,或者语音识别。当完成这些工作后,我们必须调用AudioManager.abandonAudioFocus(onAudioFocusChangeListener l)释放音频焦点。
3. 两个视频同时播放音频冲突的解决方法
当在Activity页面需要在同一页面播放两个视频时 这时会由于音频冲突而导致播放失败,这时就需要在第二个视频播放之前放弃音频焦点 代码如下
private AudioManager mAudioMgr;
if (mAudioMgr == null) {
mAudioMgr = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
//放弃音频焦点 以免两个播放器同时播放会起冲突
mAudioMgr.abandonAudioFocus(mAudioFocusChangeListener);
//因为AudioManager.OnAudioFocusChangeListener在SDK8版本开始才有。
注册OnAudioFocusChangeListener 音频焦点变化的监听
AudioManager.AUDIOFOCUS_LOSS表示失去音频焦点
AudioManager.AUDIOFOCUS_GAIN 获取音频焦点
private AudioManager.OnAudioFocusChangeListener mAudioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
if(focusChange == AudioManager.AUDIOFOCUS_LOSS){
JLog.e("VideoPreViewDialog","失去音频焦点");
}else if(focusChange == AudioManager.AUDIOFOCUS_GAIN){
//获得焦点之后的操作
JLog.e("VideoPreViewDialog","获取音频焦点");
}
}
};