作者:郭孝星
微博:郭孝星的新浪微博
博客:http://blog.csdn.net/allenwells
Github:https://github.com/AllenWells
Android为播放音乐、闹铃、通知铃、来电声音、系统声音、打电话声音和DTMF频道都分别维护了一个隔离的音频流,这是我们能够控制不同音频的前提,这其中大多数的音频流都是被系统限制的,不能胡乱使用。
一 音频控制
默认情况下,按下音量控制键会调节当前被激活的音频流,如果我们的App没有播放任何声音,则会调节闹铃的声音。如果是一个游戏或音乐程序,则需要不管是否正在播放歌曲或者游戏目前是否发出声音,按硬件的音量键都会有相应的音量调节。
Android提供了setVolumeControlStream()的方法来直接控制指定的音频流,我们需要在鉴别出App使用了哪个音频流之后,在Activity或Fragment创建的时候就设置音量控制,这样保证App是否可见,音频功能都能正常工作,如下所示:
setVolumeControlStream(AudioManager.STREAM_MUSIC);
常见的媒体播放控制如下所示:
- play
- pause
- stop
- skip
- previous
当我们在软件或者硬件(耳麦线控等)进行上述操作时,系统都会广播一个ACTION_MEDIA_BUTTON的Intent,为了响应那些操作,需要在AndroidManifest.xml文件中注册一个BroadcastReceiver,如下所示:
<receiver android:name=".RemoteControlReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
Receiver需要判断这个广播是来自哪个按钮的操作,Intent在 EXTRA_KEY_EVENT 中包含了KEY的信息,同样KeyEvent类包含了一列 KEYCODE_MEDIA_ 的静态变量来表示不同的媒体按钮,如下所示:
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()) {
// Handle key press.
}
}
}
}
因为可能有多个程序都同样监听了哪些控制按钮,那么必须在代码中特意控制当前哪个Receiver会进行响应,就就是需要进行适时的注册监听和取消监听,如下所示:
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);
...
// Start listening for button presses
am.registerMediaButtonEventReceiver(RemoteControlReceiver);
...
// Stop listening for button presses
am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
二 音频焦点
当有多个音频播放音频时,它们的交互对于用户体验就显得很重要,为了防止多个App同时播放音频,Android使用焦点Audio Focus来控制音频播放,只有获得Audio Focus的App才能播放音频,App 播放音频的过程如下所示:
- 发出请求
- 接受请求
- 音频焦点锁定
2.1 请求获取音频焦点
在App播放音频之前。我们需要获取它将要使用的音频流的音频焦点。通过使用requsetAudioFocus()方法来获取想要的音频焦点,如果请求成功,该方法会返回AUDIOFOCUS_REQUEST_GRANTED。
请求焦点的时候,我们需要确定请求的是哪种焦点类型,如下所示:
- 短暂的焦点锁定:当期待播放一个短暂的音频的时候,例如,播放导航提示。
- 开启Ducking的短暂焦点锁定:当请求短暂音频焦点的时候,我们可以选择是否开启Ducking。
Ducking是一个特殊的机制使得允许音频间歇性的短暂播放。 通常情况下,一个好的App在失去音频焦点的时候它会立即保持安静。如果我们选择在请求短暂音频焦点的时候开启了Ducking,那意味着其它App可以继续播放,仅仅是在这一刻降低自己的音量,在短暂重新获取到音频焦点后恢复正常音量。
也就是说,不用理会这个请求短暂焦点的请求,这并不会导致目前在播放的音频受到牵制,比如在播放音乐的时候突然出现一个短暂的短信提示声音,这个时候仅仅是把播放歌曲的音量暂时调低,好让短信声能够让用户听到,之后立马恢复正常播放。
- 永久的焦点锁定:当计划播放可预期到的较长的音频的时候,例如播放音乐。
举例1
在播放音乐的时候请求永久的音频焦点,如下所示:
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);
...
// Request audio focus for playback
int result = am.requestAudioFocus(afChangeListener,
// Use the music stream.
AudioManager.STREAM_MUSIC,
// Request permanent focus.
AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
am.registerMediaButtonEventReceiver(RemoteControlReceiver);
// Start playback.
}
一旦结束了播放,我们需要确保调用abandonAudioFocus()方法。这样会通知系统说你不再需要获取焦点并且取消注册AudioManager.OnAudioFocusChangeListener的监听。在释放短暂音频焦点的情况下,这会允许任何被打断的App继续播放。
// Abandon audio focus when playback complete
am.abandonAudioFocus(afChangeListener);
举例2
开启Ducking的短暂焦点锁定,如下所示:
// Request audio focus for playback
int result = am.requestAudioFocus(afChangeListener,
// Use the music stream.
AudioManager.STREAM_MUSIC,
// Request permanent focus.
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// Start playback.
}
2.2 处理失去音频焦点
在音频焦点的监听器里面,当接受到描述焦点改变的事件时会触发onAudioFocusChange()回调方法,对于失去焦点有三种类型,如下所示:
- 短暂的失去焦点:通常在失去焦点的情况下,我们会暂停当前音频的播放或者降低音量,同时需要准备在重新获取焦点之后恢复播放。
- 开启Ducking的短暂的失去焦点:Ducking是一个特殊的机制使得允许音频间歇性的短暂播放。在Ducking的情况下,正常播放的歌曲会降低音量来凸显**这个短暂的音频声音,这样既让这个短暂的声音比较突出,又不至于打断正常的声音。
- 永久的失去焦点:假设另外一个程序开始播放音乐,那么我们的程序就应该有效的结束自己。常见的做法是:移除Button监听,允许新的音频播放器独占监听那些按钮时间,并且放弃自己的音频焦点,这种情况下,重新播放你的音频之前。我们需要确保用户重新点击了App的播放按钮。
举例1
短暂的失去焦点,如下所示:
OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {
public void onAudioFocusChange(int focusChange) {
if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT
// Pause playback
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Resume playback
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
am.abandonAudioFocus(afChangeListener);
// Stop playback
}
}
};
举例2
播放器在暂时失去焦点时降低音量,并在重新获得音频焦点之后恢复原来的音量,如下所示:
OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {
public void onAudioFocusChange(int focusChange) {
if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
// Lower the volume
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Raise it back to normal
}
}
};
三 音频设备
用户在播放音乐时有多个选择,可以使用内置的扬声器,有线耳机或者A2DP蓝牙耳机。
A2DP(Advanced Audio Distribution Profile)即蓝牙音频传输模型协定,A2DP是能够采用耳机内的芯片来堆栈数据,达到声音的高清晰度。有A2DP的耳机就是蓝牙立体声耳机。声音能达到44.1kHz,一般的耳机只能达到8kHz。如果手机支持蓝牙,只要装载A2DP协议,就能使用A2DP耳机了。
3.1 检测音频输出设备
选择音频输出设备会影响App的行为,可以使用AudioManager来查询某个音频是输出到扬声器,有线耳机还是蓝牙,如下所示:
if (isBluetoothA2dpOn()) {
// Adjust output for Bluetooth.
} else if (isSpeakerphoneOn()) {
// Adjust output for Speakerphone.
} else if (isWiredHeadsetOn()) {
// Adjust output for headsets
} else {
// If audio plays and noone can hear it, is it still playing?
}
2.2 处理音频设备的改变
当有线耳机被拔出或者蓝牙设备断开连接的时候,音频流会自动输出到内置的扬声器上。假设之前播放声音很大,这个时候突然转到扬声器播放会显得非常嘈杂。Android系统会在那种事件发生时会广播带有ACTION_AUDIO_BECOMING_NOISY的intent。无论何时播放音频去注册一个BroadcastReceiver来监听这个intent会是比较好的做法。
在音乐播放器下,用户通常希望发生那样事情的时候能够暂停当前歌曲的播放。在游戏里,通常会选择减低音量。
private class NoisyAudioStreamReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
// Pause the playback
}
}
}
private IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
private void startPlayback() {
registerReceiver(myNoisyAudioStreamReceiver(), intentFilter);
}
private void stopPlayback() {
unregisterReceiver(myNoisyAudioStreamReceiver);
}
版权声明:本文为博主原创文章,未经博主允许不得转载。