「播放器」Android音频焦点

音视频播放在手机日常使用中非常频繁,当我们听着音乐刷着微博时,看到有趣的视频点击开始播放,音乐自动暂停,退出视频时音乐又自动恢复了播放(如果没有自动恢复的话,比如QQ音乐就会提示是否设置中断后继续播放)。

中断后继续播放

这一系列流畅自然的操作不是理所应当的吗~当然如果处理不好的话就会出现声音重叠,音频焦点长期被占用的问题,下面我们来看看具体的功能逻辑。

音频焦点相关的讲解参考以下文章

Android音频焦点详解
Managing Audio Focus

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和相关功能,系统会根据这些自动管理音频焦点。

    Android8.0以前

    参考官方文档获取焦点的方法如下:

1
2
3
4
5
6
// Request audio focus for playback
int result = am.requestAudioFocus(afChangeListener,
// Use the music stream.
AudioManager.STREAM_MUSIC,
// Request permanent focus.
AudioManager.AUDIOFOCUS_GAIN);

但在听音乐的时候播放音频或视频,依旧出现了重叠的声音,实际上有效的做法是这样的

1
2
3
4
5
//下面两个常量参数试过很多 都无效,最终反编译了其他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

1
2
3
4
5
6
7
8
9
10
11
12
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()方法。

处理音频焦点变化

了解相应的请求、释放方法后,还需要进一步处理音频焦点变化问题,也就是优化跟其他APP或者是系统APP合作的过程。主要是处理onAudioFocusChange方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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;
}
}
};

最后

音频相关的API常常让人疑惑,明明已经根据注释的描述和官方文档中的方法实现了,却达不到预期的效果。还有就是随着Android更新的推送速度越来越快,更新普及率也水涨船高,高版本API随时在发生变化,适配时需要注意这些细节,以及进行完善的测试。

工具类参见AudioFocusManager

未完成内容

源码简单分析

在不同机型中进行测试