Android中关于AudioFocus你所该知道的知识

一:首先我们来了解下什么是AudioFocus:
AudioFocus是Android引入的一个Audio协调机制,当多方需要使用Audio资源时,可以通过AudioFocus机制来协调配合,提高用户的体验。

需要注意的一点是:该机制需要开发者主动去遵守,比如A应用没遵守该机制,则其它遵守了该机制的应用是完全没办法影响A应用的。

二:为什么要使用AudioFocus:
试想下后台在播放着音乐的时候你点开了某个视频,使得后台的音乐和视频的声音一起播放,毫无关联的声音一同播放会给用户带来极差的体验,此时我们就可以通过AudioFocus机制来解决这样的问题。现在市面上的网易云音乐、虾米音乐等主流音乐应用都有遵循该机制。

三:AudioFocus的使用:
以下事例所用到的源码请参考:源码
我们先来看下没有用AudioFocus的场景:

在Activity中定义了4个Button,第一第二个按钮分别用于开始和暂停手机中的第一首音乐,第三和第四个按钮分别用于开始和暂停手机中的第二首音乐。

protected Player mPlayerOne;

protected Player mPlayerTwo;

@Override
public void onClick(View view) {
    switch (view.getId()) {
        case R.id.button_start_one:
            mPlayerOne.startMedia();
            break;
        case R.id.button_pause_one:
            mPlayerOne.pauseMedia();
            break;
        case R.id.button_start_two:
            mPlayerTwo.startMedia();
            break;
        case R.id.button_pause_two:
            mPlayerTwo.pauseMedia();
            break;
        default:
            break;
    }
}
public class Player {


    private MediaPlayer mMediaPlayer;

    public void startMedia() {
        mMediaPlayer.start();
    }

    public void pauseMedia() {
        if (mMediaPlayer.isPlaying()) {
            mMediaPlayer.pause();
        }
    }

}

如果我们点击button_start_one播放第一首音乐之后,再点击button_start_two播放第二首音乐,会使得两首音乐同时播放,这样的用户体验非常不好的。

接下来我们就在这基础上加入AudioFocus机制,从而使音乐一和音乐二的播放能相互配合。

在加入之前,我们先来了解下AudioFocus机制的相关知识:

使用AudioFocus机制主要是通过android.media.AudioManager这个类来进行的。

  1. 申请音频焦点
    在这个类中通过

requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint)
1
方法请求获取焦点,如果获取成功,会返回int值AudioManager.AUDIOFOCUS_REQUEST_GRANTED,失败则返回AudioManager.AUDIOFOCUS_REQUEST_FAILED。

通过abandonAudioFocus(OnAudioFocusChangeListener l)方法放弃焦点。

由于音频焦点是唯一的,所以我们可以在需要播放音乐时去申请音频焦点,如果获取到了则播放,同时正在播放的音频在失去音频焦点时停止播放或者调低音量,从而达到音频播放间的相互协调。

接下来我们对requestAudioFocus方法的参数进行解析:

OnAudioFocusChangeListener

OnAudioFocusChangeListener是一个接口,在这个接口里面只有一个方法需要实现
public void onAudioFocusChange(int focusChange);该方法会在焦点状态变化的时候被调用。

参数focusChange代表变化后当前的状态,一共有以下四个值:

AUDIOFOCUS_GAIN
重新获取到音频焦点时触发的状态。

AUDIOFOCUS_LOSS
失去音频焦点时触发的状态,且该状态应该会长期保持,此时应当暂停音频并释放音频相关的资源。

AUDIOFOCUS_LOSS_TRANSIENT
失去音频焦点时触发的状态,但是该状态不会长时间保持,此时我们应该暂停音频,且当重新获取音频焦点的时候继续播放。

AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
失去音频焦点时触发的状态,在该状态的时候不需要暂停音频,但是我们应该降低音频的声音。

OnAudioFocusChangeListener还有一个非常重要的作用,就是它的实例作为每一个申请音频焦点的“单位”。通过查看源码可以发现每次申请音频焦点时,都会把这次申请存进一个HashMap<String, OnAudioFocusChangeListener> 当中,并通过调用String getIdForAudioFocusListener(OnAudioFocusChangeListener l) 方法获取每个OnAudioFocusChangeListener 对应的key,接下来获取音频焦点等操作都是基于这个key的。而放弃音频焦点时则会将对应的OnAudioFocusChangeListener 从HashMap<String, OnAudioFocusChangeListener>中移除,同时基于该接口的key来实现放弃音频焦点的操作。

streamType
streamType大家应该都很熟悉,就不多介绍了,需要注意的一点是不同的streamType之间的音频焦点是不会相互影响的。

durationHint
durationHint用来表示获取焦点的时长,同时通知其它获取了音频焦点的OnAudioFocusChangeListener该如何相互配合,有以下几个值:

AUDIOFOCUS_GAIN
代表此次申请的音频焦点需要长时间持有,原本获取了音频焦点的OnAudioFocusChangeListener 接口将会回调onAudioFocusChange(int focusChange) 方法,传入的参数为AUDIOFOCUS_LOSS。

AUDIOFOCUS_GAIN_TRANSIENT
代表此次申请的音频焦点只需短暂持有,原本获取了音频焦点的OnAudioFocusChangeListener 接口将会回调onAudioFocusChange(int focusChange) 方法,传入的参数为AUDIOFOCUS_LOSS_TRANSIENT。按照官方注释:在接收到事件通知等情景时可使用该durationHint。

AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
代表此次申请的音频焦点只需短暂持有,原本获取了音频焦点的OnAudioFocusChangeListener 接口将会回调onAudioFocusChange(int focusChange) 方法,传入的参数为AUDIOFOCUS_LOSS_TRANSIENT。按照官方注释:在需要录音或语音识别等情景时可使用该durationHint。

AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
代表此次申请不需要暂停其它申请的音频播放,但是需要其降低音量。原本获取了音频焦点的OnAudioFocusChangeListener 接口将会回调onAudioFocusChange(int focusChange) 方法,传入的参数为AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK。

  1. 释放音频焦点
    通过public int abandonAudioFocus(OnAudioFocusChangeListener l) 方法释放音频焦点,参数只有一个OnAudioFocusChangeListener。

在讲解requestAudioFocus 方法的时候就已经说过音频焦点的机制中是以OnAudioFocusChangeListener 为单位的,因此在abandonAudioFocus 方法中的参数需要与requestAudioFocus 方法对应起来,才能成功释放。

  1. 进行修改
    在简单了解了AudioFocus的基本知识之后,我们就可以来动手修改了。

先是修改Activity的代码:

@Override
public void onClick(View view) {
    switch (view.getId()) {
        case R.id.button_start_one:
            mPlayerOne.startMediaWithAudioFocus();
            break;
        case R.id.button_pause_one:
            mPlayerOne.pauseMediaWithAudioFocus();
            break;
        case R.id.button_start_two:
            mPlayerTwo.startMediaWithAudioFocus();
            break;
        case R.id.button_pause_two:
            mPlayerTwo.pauseMediaWithAudioFocus();
            break;
        default:
            break;
    }
}

然后修改Play.java

public class Player {


    private MediaPlayer mMediaPlayer;

    private AudioManager mAudioManager; 

    private MyAudioFocusChangeListener mFocusChangeListener; 

    ......

    public void startMediaWithAudioFocus() {
        if (mMediaPlayer == null) {
            initPlayer();
        }
        int result = mAudioManager.requestAudioFocus(mFocusChangeListener, AudioManager.STREAM_MUSIC,
                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
        if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            mMediaPlayer.start();
        }
    }

    public void pauseMediaWithAudioFocus() {
        mAudioManager.abandonAudioFocus(mFocusChangeListener);
        if (mMediaPlayer.isPlaying()) {
            mMediaPlayer.pause();
        }
    } 

   


    class MyAudioFocusChangeListener implements AudioManager.OnAudioFocusChangeListener {
    
            private int mPreviousState;
    
            private boolean mShouldStart = true;
    
            @Override
            public void onAudioFocusChange(int focusChange) {
                handlerAudioFocus(focusChange);
                mPreviousState = focusChange;
            }
    
            private void handlerAudioFocus(int focusChange) {
                switch (focusChange) {
                    case AudioManager.AUDIOFOCUS_GAIN:
                        handlerAudioFocusGain();
                        break;
                    case AudioManager.AUDIOFOCUS_LOSS:
                        mMediaPlayer.release();
                        mMediaPlayer = null;
                        break;
                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                        mMediaPlayer.pause();
                        break;
                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                        mMediaPlayer.setVolume(0.5f,0.5f);
                        break;
                }
            }
    
            private void handlerAudioFocusGain() {
                switch (mPreviousState) {
                    case AudioManager.AUDIOFOCUS_LOSS:
                        initPlayer();
                        mShouldStart = false;
                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                        if (mShouldStart) {
                            mMediaPlayer.start();
                        } else {
                            mShouldStart = true;
                        }
                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                        mMediaPlayer.setVolume(1,1);
                        break;
                    default:
                }
            }
        }
    }

在开始播放音乐之前,我们先是申请了音频焦点,durationHint为AUDIOFOCUS_GAIN_TRANSIENT,并且在成功获取到音频焦点后播放音乐。

在暂停音乐的时候我们会去释放音频焦点,通知原先的音频焦点申请者可以继续播放音乐。

接下来我们看下MyAudioFocusChangeListener 里面的代码,在onAudioFocusChange回调的时候我们通过handlerAudioFocus 方法来进行处理。

AudioManager.AUDIOFOCUS_GAIN

当focusChange为AudioManager.AUDIOFOCUS_GAIN时,我们会调用handlerAudioFocusGain() 方法来处理,在这个方法中会根据上一次的音频焦点状态来做恢复操作,比如上次的状态为AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK ,则把音量调回去正常的大小,上次的状态为AudioManager.AUDIOFOCUS_LOSS_TRANSIENT ,则重新播放音频,上次的状态为AudioManager.AUDIOFOCUS_LOSS 则重新初始化音乐资源。

细心的朋友应该会发现handlerAudioFocusGain()方法的每个case之间并没有用break跳出,这样做的原因是Loss状态中,资源释放度高的状态能够在资源释放度低的状态被触发后接着触发。举个栗子:当前有一个A应用正在播放音乐,这时候一个B应用请求了音频焦点使得当前接收到了AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 从而导致A应用音量调低,过了一会B应用通过同一个AudioManager.OnAudioFocusChangeListener 又请求了音频焦点,使得当前音乐接收到了AudioManager.AUDIOFOCUS_LOSS_TRANSIENT 并暂停了音乐播放。过了一段时间后A应用释放了音频焦点,此时B应用会回调对应的AudioManager.OnAudioFocusChangeListener 的onAudioFocusChange方法,此时记录的上一个状态为AudioManager.AUDIOFOCUS_LOSS_TRANSIENT。所以如果我们在每个case中都添加了break语句,则在上面所述的栗子中B应用重新播放音乐的时候被调低声音的音乐将无法被恢复。

AudioManager.AUDIOFOCUS_LOSS
当focusChange为AudioManager.AUDIOFOCUS_LOSS 时,我们将音乐资源进行了释放。

AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
当focusChange为AudioManager.AUDIOFOCUS_LOSS_TRANSIENT 时,我们使音乐暂停。

AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
当focusChange为AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 时,我们调低了音乐的音量。

本篇博客到此结束,如果有错漏欢迎提出,谢谢。
————————————————
版权声明:本文为CSDN博主「mikechenmj」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_37077539/article/details/61202750

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值