今天写了一个关于播放audio的简单demo,发现程序无法抢占其他音频,具体表现在当有其他播放器在播放audio的时候,如果demo播放audio的话,那么会有两个audio同时播放;而且其他播放器也无法抢占本程序的音频,具体表现在当demo播放audio时,如果有其他播放器播放audio的话,也会出现audio同时播放的情况。随即上网查资料,发现大部分资料关于AudioFocus的写法还停留在Android 8.0 前。所以我翻阅了万能的官方文档(文末传送门),开此一贴记录一下。

AudioFocus

首先获取AudioManager实例

以后请求音频焦点时需要在AudioManager中注册

AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);

使用AudioAttributes描述音频类型、用途

自从Android 5.0 开始,要想使用音频焦点,必须使用AudioAttributes来描述你的Audio App播放的是什么种类的音频。比如播放的是演讲类音频,那么类型就为CONTENT_TYPE_SPEECH。由于我的测试demo播放的是一般的音乐,所以这里种类为CONTENT_TYPE_MUSIC。
这里还可以设置音频的用途,比如USAGE_MEDIA就表示音频用于媒体文件播放,可以是音乐、电影音轨等等。

AudioAttributes audioAttributes = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_GAME)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build();

构造AudioFocusRequest对象,并重写onAudioFocusChange方法。

AudioFocusRequest包含了一些有关音频上下文以及(程序获取音频焦点的)能力的信息。系统会使用这些信息来自动控制音频的焦点的丢失与获取。
因为音频焦点的请求必须指出这个request的请求类型,所以这里使用AudioFocusRequest.Builder()创建audioFocusRequest对象,并传入类型参数AUDIOFOCUS_GAIN表示这个request是用于请求获取音频焦点的。
接下来就调用各种设置参数的API来完成构造了,下面说几个重要的:
setAudioAttributes():描述了App的使用场景,直接传入上面构造好的参数即可。
setWillPauseWhenDucked():Duck的意思就是“降低音量”。例如当手机正在播放音乐时有短暂的音频(如短信)来了,这时音乐的声音就会降低(但不会暂停)以此凸显出短信提示音,这是由系统自己自动处理的,默认不会回调onAudioFocusChange方法(下面会说)。如果你想在此时暂停音乐的播放,那么就传入true并重写onAudioFocusChange方法,添加暂停逻辑。
setAcceptsDelayedFocusGain():有时音频焦点会被一些程序上锁。例如你在打电话时,是不可能有其他的音频能成功抢占电话的音频焦点的,因为电话给音频焦点上锁了。此时若有其他软件请求音频焦点的话,requestAudioFocus()就会返回AUDIOFOCUS_REQUEST_FAILED。所以Android就提供了一个“延时接收”音频焦点的机制,可以让程序处理延时焦点的获取。若传入true,则此时requestAudioFocus()返回AUDIOFOCUS_REQUEST_DELAYED,一旦音频焦点被释放,就可以自动取得音频焦点了。

audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
                .setAudioAttributes(audioAttributes)
                .setOnAudioFocusChangeListener(new AudioManager.OnAudioFocusChangeListener() {
                    @Override
                    public void onAudioFocusChange(int focusChange) {
                        switch (focusChange){
                            case AudioManager.AUDIOFOCUS_GAIN:
                                if(!mediaPlayer.isPlaying()){
                                    mediaPlayer.start();
                                }
                                break;
                            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
                                break;
                            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
                                break;
                            case AudioManager.AUDIOFOCUS_LOSS:
                                if(mediaPlayer.isPlaying()){
                                    mediaPlayer.pause();
                                }
                                break;
                            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                                if(mediaPlayer.isPlaying()){
                                    mediaPlayer.pause();
                                }
                                break;
                            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                                break;
                            default:
                        }
                    }
                }).build();

setOnAudioFocusChangeListener():实现音频焦点变化监听的方法,接收AudioManager.OnAudioFocusChangeListener参数,并重写onAudioFocusChange()方法。
该方法传入一个focusChange参数,下面稍作解释:
这里先说明一下,音频焦点生效的前提是两个播放音频的APP都要实现音频焦点。举个例子,此时有一播放器正在播放音频,然后这时本程序开始播放音频,那么本程序就会去请求获取音频焦点,此时若音频焦点未上锁的话,请求成功,回调方法传入AUDIOFOCUS_GAIN参数,另一个播放器也会回调方法并传入AUDIOFOCUS_LOSS参数,使其失去音频焦点。所以,必须两个播放器都实现音频焦点才行。不过现在的音乐播放器肯定都实现了这一功能也就不用操心了。
AUDIOFOCUS_LOSS:当本程序正在播放音频时有另一播放器请求获得音频焦点播放音频,那么就会回调该方法并传入此参数
AUDIOFOCUS_LOSS_TRANSIENT:当另一个播放器请求“短暂”获得音频焦点,传入此参数
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:当另一播放器请求“短暂”获得音频焦点且不用完全暂停可以让对方降低音量时传入此参数
AUDIOFOCUS_GAIN:当其他播放器正在播放音频时,本程序请求获得音频焦点播放音频,传入此参数
AUDIOFOCUS_GAIN_TRANSIENT:本程序请求“短暂”获取音频焦点时传入此参数
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:本程序请求“短暂”音频焦点且可能会将低对方音量时传入此参数
想必各位也发现了,这六个参数其实是互相对应的。

最后在AudioManager中请求音频焦点:

audioManager.requestAudioFocus(audioFocusRequest);

在程序结束时,别忘了释放音频焦点:

注意,这里传入的参数audioFocusRequest与之前申请音频焦点时使用的是同一个对象。

audioManager.abandonAudioFocusRequest(audioFocusRequest);

参考资料:
Android官方文档:https://developer.android.google.cn/guide/topics/media-apps/audio-focus#java