Android 音视焦点管理

最近在做项目的时候发现做的视频通话功能在有音乐播放的时候进入通话中或播放铃声的时候会出现声音同时叠加播放的情况,也就是音乐在进入播放铃声或这通话的时候并没有正常暂停,于是就研究了一下Android上音频焦点的管理部分,现在来总结一下。

其实在很多种场景都是这样的,比如平日我们用手机在使用某家的音乐播放器听音乐,然后突然想起要看个电影或电视剧什么的就会打开像爱奇艺或腾讯视频等视频软件,你会发现在你开始观看某个视频的时候之前你听的音乐就会暂停,这就是音频焦点管理的功劳。

Google有一片文档专门来介绍了音频焦点控制的做法。连接如下

https://developer.android.com/guide/topics/media-apps/audio-focus

可以参考一下这篇文章,应该会有很大帮助,对于不同版本的Android系统会有不太一样的处理方式,分界线就在于Android 8.0,Android 8.0(包含)以后需要通过AudioFocusRequest来实现了,具体大家可以参照Google的上面这篇文档来理解。

无论是什么Android版本如果做音频焦点的管理自然都需要AudioManager来处理,我们先获取一下这个管理类。

private AudioManager mAudioManager;

mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);

在使用之前一定要判断mAudioManager是否为空,否则会造成控制住奔溃哦。

Android 8.0以前

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

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

然后这种方式并不可用,在播放音乐同时做铃声播放会出现铃声和音乐同时播放的现象,通过各种实验发现像如下的写法才可用。

int requestFocusResult = mAudioManager.requestAudioFocus( 
    mAudioFocusChangeListener,
    AudioManager.STREAM_MUSIC,
    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);

通过对比防线是 AudioManager.AUDIOFOCUS_GAIN 和 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT这点区别,AudioManager.AUDIOFOCUS_GAIN_TRANSIENT是指临时获取音频焦点可能这种方式生效。

Android 8.0以后

Android8.0中也使用了requestAudioFocus()来请求音频焦点,但不一样的是释放API有所改变,使用abandonAudioFocusRequest来做音频焦点的释放,这个API需要传入和申请焦点的时候创建的同一个AudioFocusRequest的实例。可以看一下AudioFocusRequest的创建方式和使用方式。

AudioFocusRequest 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()方法。

处理音频焦点变化回调

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;
            }
        }
    };

一个完整音频焦点管理类

/**
 * 用AudioManager获取音频焦点避免音视频声音重叠问题
 */
public class AudioFocusManager {

    private static final String TAG = "AudioFocusManager";

    private Context mContext;
    private AudioManager mAudioManager;
    private AudioManager.OnAudioFocusChangeListener mAudioFocusChangeListener;
    private AudioFocusRequest mAudioFocusRequest;

    public AudioFocusManager(Context context) {
        mContext = context;
    }

    /**
     * 请求音频焦点 设置监听
     */
    public int requestAudioFocus(final AudioListener audioListener) {
        if (mAudioManager == null) {
            mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        }
        if (mAudioFocusChangeListener == null) {
            mAudioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {//监听器
                @Override
                public void onAudioFocusChange(int focusChange) {
                    Log.d(TAG, "onAudioFocusChange: " + focusChange);
                    switch (focusChange) {
                        case AudioManager.AUDIOFOCUS_GAIN:
                            Log.d(TAG, "onAudioFocusChange: AUDIOFOCUS_GAIN");
                            audioListener.play();
                            break;
                        case AudioManager.AUDIOFOCUS_LOSS:
                            Log.d(TAG, "onAudioFocusChange: AUDIOFOCUS_LOSS");
                            audioListener.pause();
                            break;
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                            Log.d(TAG, "onAudioFocusChange: AUDIOFOCUS_LOSS_TRANSIENT");
                            audioListener.pause();
                            break;
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                            // ... pausing or ducking depends on your app
                            Log.d(TAG, "onAudioFocusChange: AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK");
                            audioListener.pause();
                            break;
                    }
                }
            };
        }

        int requestFocusResult = 0;
        if (mAudioManager != null) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
                requestFocusResult = mAudioManager.requestAudioFocus(mAudioFocusChangeListener,
                        AudioManager.STREAM_MUSIC,
                        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
                Log.d(TAG, "requestAudioFocus: SDK_INT < 26 =" + requestFocusResult);
            } else {
                if (mAudioFocusRequest == null) {
                    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);
                Log.d(TAG, "requestAudioFocus: SDK_INT >= 26 =" + requestFocusResult);
            }
        }
        return requestFocusResult;
    }

    /**
     * 暂停、播放完成或退到后台释放音频焦点
     * 应该先请求音频焦点
     */
    public int releaseAudioFocus() {
        if (mAudioManager == null) {
            mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        }

        int abandonFocusResult = 0;
        if (mAudioManager != null && mAudioFocusChangeListener != null) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
                abandonFocusResult = mAudioManager.abandonAudioFocus(mAudioFocusChangeListener);
                Log.d(TAG, "releaseAudioFocus: SDK_INT < 26 =" + abandonFocusResult);
            } else {
                if (mAudioFocusRequest != null) {
                    abandonFocusResult = mAudioManager.abandonAudioFocusRequest(mAudioFocusRequest);
                    Log.d(TAG, "releaseAudioFocus: SDK_INT >= 26 =" + abandonFocusResult);
                }
            }
        }
        return abandonFocusResult;
    }

    public interface AudioListener {
        void play();

        void pause();
    }
}

参考博客:https://haohaozaici.github.io/2018/04/24/Android%20Sound%20overlap/index.html

说点什么

avatar
  Subscribe  
提醒