一:首先我们来了解下什么是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)
方法请求获取焦点,如果获取成功,会返回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。
2. 释放音频焦点
通过public int abandonAudioFocus(OnAudioFocusChangeListener l)
方法释放音频焦点,参数只有一个OnAudioFocusChangeListener
。
在讲解requestAudioFocus
方法的时候就已经说过音频焦点的机制中是以OnAudioFocusChangeListener
为单位的,因此在abandonAudioFocus
方法中的参数需要与requestAudioFocus
方法对应起来,才能成功释放。
3. 进行修改
在简单了解了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
时,我们调低了音乐的音量。
本篇博客到此结束,如果有错漏欢迎提出,谢谢。