(1)使用硬件控制应用的声音,在Activity的生命周期内进行设置,只需一次并尽早调用,可以在Activity的OnCreate()方法中设置。
setVolumeControlStream(AudioManager.STREAM_MUSIC);
(2)使用播放按钮控制应用的声音,无论什么时候点击这些按钮,都会广播一个action为ACTION_MEDIA_BUTTON的Intent。
定义一个广播接收者就可以接收到广播,并执行相应的操作。
<receiverandroid:name=".RemoteControlReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON"/>
</intent-filter>
</receiver>
public classRemoteControlReceiver extends BroadcastReceiver {
@Override
public void onReceive(Contextcontext, 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.
}
}
}
}
AudioManager am =mContext.getSystemService(Context.AUDIO_SERVICE);
...
// Start listening forbutton presses
am.registerMediaButtonEventReceiver(RemoteControlReceiver);
...
// Stop listening forbutton presses
am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
注意:media button event receiver 并不像以往在Activity中注册和Activity不可见或销毁时反注册的情形,应该视应用在什么时候获取audio资源和释放audio资源时进行注册和反注册。
(3)管理Audio资源的焦点
使用requestAudioFocus获取声音资源焦点
获取成功返回AUDIOFOCUS_REQUEST_GRANTED
AudioManager am =mContext.getSystemService(Context.AUDIO_SERVICE);
...
// Request audio focusfor 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.
}
注意:当完成playback操作之后记得要
//Abandon audio focus when playback complete
am.abandonAudioFocus(afChangeListener);
告诉系统你不再需要焦点和取消与AudioManager.OnAudioFocusChangeListener关联。好让其它应用继续完成playback操作。
获取Audio焦点,其它的应用声音降低直到焦点返回?
// Request audio focusfor 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.
}
AUDIOFOCUS_GAIN
指示申请得到的AudioFocus不知道会持续多久,一般是长期占有;
AUDIOFOCUS_GAIN_TRANSIENT
指示要申请的AudioFocus是暂时性的,会很快用完释放的;
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
不但说要申请的AudioFocus是暂时性的,还指示当前正在使用AudioFocus的可以继续播放,只是要“duck”一下(降低音量)。
返回值,可能是:
AUDIOFOCUS_REQUEST_GRANTED:申请成功;
AUDIOFOCUS_REQUEST_FAILED:申请失败。
(4)处理焦点丢失,添加监听器
AudioManager.OnAudioFocusChangeListener是申请成功之后监听AudioFocus使用情况的Listener,后续如果有别的程序要竞争AudioFocus,都是通过这个Listener的onAudioFocusChange()方法来通知这个AudioFocus的使用者的。
OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {
public void onAudioFocusChange(intfocusChange) {
if(focusChange == AUDIOFOCUS_LOSS_TRANSIENT )
// Pause playback
} elseif (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Resume playback
} elseif (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
am.abandonAudioFocus(afChangeListener);
// Stop playback
}
}
};
Duck模式:降低其它应用的声音
OnAudioFocusChangeListenerafChangeListener = new OnAudioFocusChangeListener() {
public void onAudioFocusChange(intfocusChange) {
if(focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
// Lower the volume
} elseif (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Raise it back to normal
}
}
};
AUDIOFOCUS_GAIN:获得了Audio Focus;
AUDIOFOCUS_LOSS: 失去了AudioFocus,并将会持续很长的时间。这里因为可能会停掉很长时间,所以不仅仅要停止Audio的播放,最好直接释放掉Media资源。而因为停止播放Audio的时间会很长,如果程序因为这个原因而失去AudioFocus,最好不要让它再次自动获得AudioFocus而继续播放,不然突然冒出来的声音会让用户感觉莫名其妙,感受很不好。这里直接放弃AudioFocus,当然也不用再侦听远程播放控制【如下面代码的处理】。要再次播放,除非用户再在界面上点击开始播放,才重新初始化Media,进行播放。
AUDIOFOCUS_LOSS_TRANSIENT:暂时失去AudioFocus,并会很快再次获得。必须停止Audio的播放,但是因为可能会很快再次获得AudioFocus,这里可以不释放Media资源;
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:暂时失去AudioFocus,但是可以继续播放,不过要降低音量。
(5)典型的应用AudioFocus的场景
下面的时序图描述了AudioFocus被抢占与再次获取的典型场景:
Audio Focus被抢占与再次获取的时序图
注意:为了描述简单,此图中除了两个竞争AudioFocus的App之外,只用AudioManager表征了Android的AudioFocus机制中内部参与的对象,实际AudioManager只是外部的表象,内部参与的对象很多,回调函数也并非简单的直接由AudioManager调用,其中还包含了复杂的IPC机制。
图中:
AudioFocusClient通过requestAudioFocus()获取AudioFocus,在获得AudioFocus之后,开始播放Audio[Step#1 ~ #2];
其它程序(OtherApp)也通过requestAudioFocus()获取AudioFocus [Step#3]
AudioFocusClient失去了Audio Focus,在onAudioFocusChanged()中,根据focusChange【focusChange的值与OtherApp申请时的durationHint相反,即focusChange = -1*durationHint】的值,做第二节中所描述的处理[Step#4];
其它程序(Other App)获取AudioFocus之后,开始播放Audio[Step#5];
其它程序(OtherApp)使用Audio之后,通过abandonAudioFocus()归还AudioFocus [Step#6];
AudioFocusClient重新获得了Audio Focus,可做进一步的处理 [Step#7]
小结
AudioFocus机制要参与各方充分理解并统一遵照施行,有没有遵照者或者实现有误的程序存在就可能打破这一机制,带来糟糕的用户体验。在保证Built-in程序没问题的前提下,如果进入AndroidMarket之前的程序都严格执行了AudioFocus相关的测试,应该也没问题。
使用Audio的程序要做到:
使用前,用requestAudioFocus()申请AudioFocus,并根据应用的实际选取恰当的durationHint值;
正确的在AudioManager.OnAudioFocusChangeListener中响应AudioFocus失去和重新获取事件;
Audio使用结束,用
abandonAudioFocus()
归还AudioFocus。
(6)AudioManager的一般使用
来自 <http://blog.youkuaiyun.com/winson_jason/article/details/8126856>
AudioManager除了能在AudioFocus中使用外,最主要的是它能控制声音和铃声
这里只讲述几个比较常用到的方法:
adjustVolume(intdirection, int flags) ——用来控制手机音量大小,当传入的第一个参数为 AudioManager.ADJUST_LOWER时,可将音量调小一个单位,传入AudioManager.ADJUST_RAISE 时,则可以将音量调大一个单位。
adjustStreamVolume(intstreamType, int direction, int flags)——(以步长)调节手机音量大小
参数1:声音类型,可取为STREAM_VOICE_CALL(通话)、STREAM_SYSTEM(系统声音)、STREAM_RING(铃声)、STREAM_MUSIC(音乐)、STREAM_ALARM(闹铃声)
参数2:调整音量的方向,可取ADJUST_LOWER(降低)、ADJUST_RAISE(升高)、ADJUST_SAME
参数3:可选的标志位
setStreamVolume(intstreamType, int index, int flags)——直接设置音量大小
getMode() ——返回当前音频模式,如NORMAL(普通), RINGTONE(铃声), or IN_CALL(通话)
setMode()——设置声音模式,可取值NORMAL(普通),RINGTONE(铃声), or IN_CALL(通话)
getRingerMode()——返回当前的铃声模式。如RINGER_MODE_NORMAL(普通)、RINGER_MODE_SILENT(静音)、RINGER_MODE_VIBRATE(震动)
setRingerMode(intringerMode) ——改变铃声模式
getStreamVolume(intstreamType) ——取得当前手机的音量,最大值为7,最小值为0,当为0时,手机自动将模式调整为“震动模式”。
getStreamMaxVolume(intstreamType)——获得当前手机最大铃声。
setStreamMute(intstreamType, boolean state)
//音量控制,初始化定义
AudioManagermAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
//最大音量
int maxVolume =mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
//当前音量
int currentVolume =mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
直接控制音量的多少:
以一步步长控制音量的增减,并弹出系统默认音量控制条:
//降低音量,调出系统音量控制
if(flag ==0){
mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC
,AudioManager.ADJUST_LOWER
,AudioManager.FX_FOCUS_NAVIGATION_UP);
}else if(flag ==1){
//增加音量,调出系统音量控制
mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC
,AudioManager.ADJUST_RAISE
,AudioManager.FX_FOCUS_NAVIGATION_UP);
}
常用方法:
androidaudioManager获取音量:
先获取AudioManager实例,
AudioManagermAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
//通话音量
int max =mAudioManager.getStreamMaxVolume( AudioManager.STREAM_VOICE_CALL );
int current =mAudioManager.getStreamVolume( AudioManager.STREAM_VOICE_CALL );
//系统音量
int max =mAudioManager.getStreamMaxVolume( AudioManager.STREAM_SYSTEM );
current =mAudioManager.getStreamVolume( AudioManager.STREAM_SYSTEM );
//铃声音量
max =mAudioManager.getStreamMaxVolume( AudioManager.STREAM_RING );
current =mAudioManager.getStreamVolume( AudioManager.STREAM_RING );
//音乐音量
max =mAudioManager.getStreamMaxVolume( AudioManager.STREAM_MUSIC );
current =mAudioManager.getStreamVolume( AudioManager.STREAM_MUSIC );
//提示声音音量
max =mAudioManager.getStreamMaxVolume( AudioManager.STREAM_ALARM );
current =mAudioManager.getStreamVolume( AudioManager.STREAM_ALARM );
(7)处理Audio硬件设备
查找正在使用的声音设备
if(isBluetoothA2dpOn()) {
// Adjust output forBluetooth.
} else if(isSpeakerphoneOn()) {
// Adjust output forSpeakerphone.
} else if(isWiredHeadsetOn()) {
// Adjust output for headsets
} else {
// If audio plays and noone can hearit, is it still playing?
}
当耳机设备或蓝牙设备断开连接之后,Audio设备自动切换到喇叭,会发送ACTION_AUDIO_BECOMING_NOISY 广播,注册一个广播接收者用于接收该 广播就可以控制声音播放
private classNoisyAudioStreamReceiver extends BroadcastReceiver {
@Override
public void onReceive(Contextcontext, Intent intent) {
if(AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
// Pause the playback
}
}
}
private IntentFilterintentFilter = newIntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
private voidstartPlayback() {
registerReceiver(myNoisyAudioStreamReceiver(), intentFilter);
}
private voidstopPlayback() {
unregisterReceiver(myNoisyAudioStreamReceiver);
}