AudioFocus是Android引入的一个Audio协调机制,当多方需要使用Audio资源时,可以通过AudioFocus机制来协调配合,提高用户的体验。
需要注意的一点是:该机制需要开发者主动去遵守,比如A应用没遵守该机制,则其它遵守了该机制的应用是完全没办法影响A应用的。
试想下后台在播放着音乐的时候你点开了某个视频,使得后台的音乐和视频的声音一起播放,毫无关联的声音一同播放会给用户带来极差的体验,此时我们就可以通过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这个类来进行的。
在这个类中通过
requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint)方法请求获取焦点,如果获取成功,会返回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。
通过public int abandonAudioFocus(OnAudioFocusChangeListener l) 方法释放音频焦点,参数只有一个OnAudioFocusChangeListener。
在讲解requestAudioFocus 方法的时候就已经说过音频焦点的机制中是以OnAudioFocusChangeListener 为单位的,因此在abandonAudioFocus 方法中的参数需要与requestAudioFocus 方法对应起来,才能成功释放。
在简单了解了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 时,我们调低了音乐的音量。
本篇博客到此结束,如果有错漏欢迎提出,谢谢。
