手记

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

处理音频焦点问题主要是处理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

未完成内容

源码简单分析

在不同机型中进行测试

5人推荐
随时随地看视频
慕课网APP

热门评论

感谢你的文章,解决了我的问题,如果选择手动管理音频焦点,应该是开始播放前调用requestAudioFocus()方法,其他音频才会暂停

查看全部评论