音视频播放在手机日常使用中非常频繁,当我们听着音乐刷着微博时,看到有趣的视频点击开始播放,音乐自动暂停,退出视频时音乐又自动恢复了播放(如果没有自动恢复的话,比如QQ音乐就会提示是否设置中断后继续播放)。
这一系列流畅自然的操作不是理所应当的吗~当然如果处理不好的话就会出现声音重叠,音频焦点长期被占用的问题,下面我们来看看具体的功能逻辑。
音频焦点相关的讲解参考以下文章
Managing Audio Focus
不同的APP可以同时播放音频,系统会将他们混合在一起,但为了避免同时播放,Android提供了audio focus机制来合理使用音频播放资源。同一时间只能有一个APP获取音频焦点,当需要播放音频时,应该立即请求音频焦点,同样的,在你的APP获取到音频焦点后,其他APP也可以抢占音频焦点,这时你的APP就需要暂停播放或降低声音。音频焦点是合作类型的,APP拥有完全自主的控制权,系统无法阻止,但应用应该遵守音频焦点的指导规则。
好的音频APP在播放时应该遵守以下规则:
- 开始播放后立即调用
requestAudioFocus()
方法,并验证返回值为AUDIOFOCUS_REQUEST_GRANTED
- 当其他app占用音频焦点时,暂停或停止播放,或者降低声音
- 当播放停止时,放弃音频焦点
在不同的Android版本下,需要使用不同的方法来处理音频焦点:
- API level 8以后,使用
requestAudioFocus()
和abandonAudioFocus()
方法,并注册AudioManager.OnAudioFocusChangeListener
接收回调。 - API level 21以后,需要使用
AudioAttributes
来描述播放音频的类型。 - API level 26以后,需要使用
AudioFocusRequest
参数,它携带了音频的context和相关功能,系统会根据这些自动管理音频焦点。
处理音频焦点问题主要是处理onAudioFocusChange
方法:
private AudioManager.OnAudioFocusChangeListener mAudioFocusChange = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange){
case AudioManager.AUDIOFOCUS_GAIN:
//当其他应用申请焦点之后又释放焦点会触发此回调
//可重新播放音乐
Log.d(TAG, "AUDIOFOCUS_GAIN");
start();
break;
case AudioManager.AUDIOFOCUS_LOSS:
//长时间丢失焦点,当其他应用申请的焦点为AUDIOFOCUS_GAIN时,
//会触发此回调事件,例如播放QQ音乐,网易云音乐等
//通常需要暂停音乐播放,若没有暂停播放就会出现和其他音乐同时输出声音
Log.d(TAG, "AUDIOFOCUS_LOSS");
stop();
//释放焦点,该方法可根据需要来决定是否调用
//若焦点释放掉之后,将不会再自动获得
mAudioManager.abandonAudioFocus(mAudioFocusChange);
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
//短暂性丢失焦点,当其他应用申请AUDIOFOCUS_GAIN_TRANSIENT或AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE时,
//会触发此回调事件,例如播放短视频,拨打电话等。
//通常需要暂停音乐播放
stop();
Log.d(TAG, "AUDIOFOCUS_LOSS_TRANSIENT");
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
//短暂性丢失焦点并作降音处理
Log.d(TAG, "AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK");
break;
}
}
};
Android8.0以前
参考官方文档获取焦点的方法如下:
// Request audio focus for playback
int result = am.requestAudioFocus(afChangeListener,
// Use the music stream.
AudioManager.STREAM_MUSIC,
// Request permanent focus.
AudioManager.AUDIOFOCUS_GAIN);
但在听音乐的时候播放音频或视频,依旧出现了重叠的声音,实际上有效的做法是这样的
//下面两个常量参数试过很多 都无效,最终反编译了其他app才搞定,汗~
int requestFocusResult = mAudioManager.requestAudioFocus(
mAudioFocusChangeListener,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
具体参考文章Android MediaPlayer音频焦点问题,抢占声道
最后在对应的播放状态下获取和释放音频焦点。
Android8.0之后
在Android8.0中一样使用了requestAudioFocus()
来请求音频焦点,不一样的是,使用abandonAudioFocusRequest()
释放音频焦点,请求和释放都需要传入同一个AudioFocusRequest
实例。使用AudioFocusRequest.Builder
来创建,具体实现代码参考官方文档Audio focus in Android 8.0 and later。
mAudioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
.build())
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(mAudioFocusChangeListener)
.build();
//请求音频焦点
requestFocusResult = mAudioManager.requestAudioFocus(mAudioFocusRequest);
//释放音频焦点
abandonFocusResult = mAudioManager.abandonAudioFocusRequest(mAudioFocusRequest);
注意事项:
- Android8.0中其他APP使用
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
参数获取焦点时,将不会回调本APP的onAudioFocusChange()
方法。 - 焦点延迟获取,当焦点被其他APP“锁”住时,
requestAudioFocus()
会返回AUDIOFOCUS_REQUEST_FAILED
,比如正在打电话时,焦点就会被锁住。如果使用了setAcceptsDelayedFocusGain(true)
方法,请求将会返回AUDIOFOCUS_REQUEST_DELAYED
,在锁解除后,系统会继续处理未完成的焦点请求,并回调onAudioFocusChange()
方法。
最后
音频相关的API常常让人疑惑,明明已经根据注释的描述和官方文档中的方法实现了,却达不到预期的效果。还有就是随着Android更新的推广越来越快,高版本API也在随时发生变化,在的适配时需要注意这些细节,以及进行完善的测试。
工具类参见AudioFocusManager
未完成内容
源码简单分析
在不同机型中进行测试
热门评论
感谢你的文章,解决了我的问题,如果选择手动管理音频焦点,应该是开始播放前调用
requestAudioFocus()
方法,其他音频才会暂停