APP中播放音频,最重要的一点就是要保证用户能够按预期的来控制音频的播放,还有就是保证多个APP不会同时播放音频。为了达到这两点要求,我们需要掌握如何控制音频的音量和播放,如何控制同一时刻只有有一个音频流,如何控制音频设备的输出等等。下面就从3个方面详细讲解。
用户能够控制APP播放音频的音量以及控制它的播放暂停快进后退等操作,这对提升APP体验来说是基本的。设想如果一个APP突然播放了声音,然后你不知声音从哪里来,没法停止播放,又没法调整音量,你肯定会很懊恼。
首先我们要先了解音频流的概念。Android系统为不同的场景设定了不同的音频流,比如音乐播放,闹钟铃声,通知声音,来电铃声,系统声音和DTMF铃声等都有独立的音频流。我们可以独立地空着各类音频流而不会影响其他音频流。就像我们在用酷狗播放音乐时,我们通过物理按键调整音量,只会影响音乐音频流的声音大小,而来电铃声的音量不会改变。系统内定的音频流如下所示:
虽然有这么多的音频流,但是在同一时刻,只会有一个音频流处于激活状态。默认情况下,按音量加减物理键时会调整当前激活状态的音频流的音量。系统默认激活的音频流是STREAM_RING
,也就是说如果你的APP没有播放任何音频,那么按音量加减物理键调整的就是来电铃声的音量。Android系统提供了一个方法setVolumnControlStream()
来指定当前要调节音量的音频流。如果我们有用到音量调整功能,我们需要在Activity或Fragment的onCreate回调中明显地设置目标音频流,这样能保证不管我们的APP是否可见,音量控制都是符合用户预期的。比如播放音乐,我们需要调用如下代码:
setVolumeControlStream(AudioManager.STREAM_MUSIC);
这样调用之后,不管Activity或Fragment是否可见,按音量加减键时都会调整音乐播放的音频流的音量。
注意:上面讲述的物理音量加减键的响应。如果是虚拟音量加减按钮,我们只需要监听按钮的点击操作,然后调用AudioManager
的adjustStreamVolume(int streamType, int direction, int flags)
来调整音量。
播放,暂停,停止,跳过,上一首或者下一首等物理按键在许多手持设备中都有,甚至一些头戴设备如耳机也会有这些物理按钮。当这些物理按钮被按下时,Android系统会发送一个广播,ACTION指定为ACTION_ MEDIA_BUTTON
。我们需要实现这样一个广播监听器来响应这些按钮操作。
<receiver android:name=".RemoteControlReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
在传入的Intent中会包含被按下的按钮的信息,通过EXTRA_KEY_EVENT来获取对应的值,可能的值包含如下内容:
如果需要响应播放按钮的按下事件,可以这样子来做:
public class RemoteControlReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
KeyEvent event = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (KeyEvent.KEYCODE_MEDIA_PLAY == event.getKeyCode()) {
// 处理播放按钮按下的操作
}
}
}
}
因为可能存在多个APP都会去监听这样的按钮事件,所以我们需要决定什么时候该去监听,什么时候不需要监听,以免做了不该有的响应操作,如果别的APP在播放音乐,如果你在监听物理暂停键,你可能就会暂停掉别人的APP的音乐的播放。可以使用AudioManager
类的方法,示例如下:
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);
...
// 开始监听
am.registerMediaButtonEventReceiver(RemoteControlReceiver);
...
// 停止监听
am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
通常我们需要在播放音频的界面变得不可见时停止对物理按钮操作的监听,但是,对于音乐播放类的APP,在其播放界面变得不可见时,可以通过物理按钮来控制音乐的播放,这是非常重要的。更好的方法是在APP获得或失去音频焦点(Audio Focus)的时候来开始或停止监听按钮事件。
考虑这样的一个场景,当有多个APP在同时播放音频时改怎么办呢?Android系统引入了音频焦点(Audio Focus)的概念,只有当APP持有音频焦点时才能播放声音。所以,我们APP在播放音频之前需要先请求音频焦点,同时还要处理在失去音频焦点或者重新获得音频焦点时的场景。
通过调用AudioManager
的requestAudioFocus
来请求焦点,该方法返回请求结果,AUDIOFOCUS_REQUEST_GRANTED
表示请求成功,AUDIOFOCUS_REQUEST_FAILED
表示请求失败。
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);
...
// 请求音频焦点
int result = am.requestAudioFocus(afChangeListener,
// 使用的音频流
AudioManager.STREAM_MUSIC,
// 表示永久获得.
AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
//如果请求成功,则开始监听物理按钮事件
am.registerMediaButtonEventReceiver(RemoteControlReceiver);
// 播放音频
}
获得焦点分为临时获得和永久获得两种,当我们只需要短暂地播放一下音频,比如播放按钮反馈音,我们指定类型为AUDIOFOCUS_GAIN_TRANSIENT
或者AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
或者AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
,否则指定类型为AUDIOFOCUS_GAIN
。
AUDIOFOCUS_GAIN_TRANSIENT
:表示其他APP如果有在播放声音,其他APP应该静音或者暂停播放
AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
:表示其他APP如果有在播放声音,它应该暂停播放
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
:表示其他APP如果有在播放声音,它可以不暂停播放而是调小音量。
当我们APP完成播放时,一定要记得调用abandonAudioFocus()
,用来通知系统APP不再需要焦点,同时解除 AudioManager.OnAudioFocusChangeListener
的监听。如果APP是临时获得焦点,那么这个时候允许其他APP继续播放音频。
如果在结束播放之前,有其他APP请求了音频焦点,这个时候传入的listener会被回调,我们需要在回调方法中做相应的处理。
其他APP请求焦点时,我们的APP就会失去焦点,失去焦点也分为临时失去和永久失去,对应于上面讲到的临时获得和永久获得。我们可以根据失去焦点的类型来做不同的处理。
假如是临时失去焦点,我们可以暂停播放音频,但是要继续监听焦点变化事件,这样可以在重新获得焦点后继续播放音频。如果是永久失去焦点,我们需要停止播放音频,停止监听焦点变化事件,停止监听物理按钮事件,同时还要调用abandonAudioFocus()
。这种情况下,我们通常需要用户手动来发起播放。
AudioManager.OnAudioFocusChangeListener afChangeListener =
new AudioManager.OnAudioFocusChangeListener() {
public void onAudioFocusChange(int focusChange) {
if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT) {
// 临时失去焦点,暂停播放音频
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// 重新获得焦点,继续播放音频
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
//永久失去焦点,取消监听
am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
am.abandonAudioFocus(afChangeListener);
// 停止播放音频
}else if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
//临时失去焦点,可以调小音量
// 调小音量
}
}
};
声音可能通过多种设备播放出来,例如可以通过有线耳机,可以通过蓝牙耳机,可以通过外放喇叭等等。我们可以通过AudioManager
中一些方法判断当前是用何种设备在播放声音。如下所示:
当有线耳机被拔出或者蓝牙耳机断开连接时,系统会自动将声音通过内置喇叭播放出来。如果播放的声音很大,这个时候通过外放播放出来声音会更大。通常我们希望此时应该暂停播放。
在这种情况下,系统会发出一个广播,我们可以通过监听该广播来做相应的操作。广播的ACTION有如下几种:
private class NoisyAudioStreamReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
// 声音太大,暂停播放
}else if(AudioManager.ACTION_HEADSET_PLUG.equals(intent.getAction())){
//耳机被插入或者拔出
}
}
}
以上就是音频播放相关的重点知识,里面没有涉及很多细节,查看AudioManager
的api可以了解这些细节,这里就不赘述了。