如果你的APP播放音频,你必须能够控制音频的播放。为了保证用户体验,你的APP必须要能够获取音频焦点,这样就能保证多个APP不会在同一时刻播放音频。
1、确认使用的音频流
Android为playing music, alarms, notifications, the incoming call ringer, system sounds, in-call volume, and DTMF tones提供单独的音频流。这些流的绝大部分被限制为系统事件,所以除非你的app是闹铃的一个替代品,不然在大多数情况下使用STREAM_MUSIC流播放音频。
2、使用设备的音量按键来控制APP的音频音量
默认情况下,按音量控制键会修改当前活跃的音频流的音量。如果你的app当前没有播放任何东西,按音量键会调整铃声的音量。
如果你已经是在运行一个游戏或者音乐APP,此时通过音量键就可以来控制游戏或者音乐的音量。
你可能会尝试监听音量键按下事件然后来修改你的音频流的音量。其实不需要这样做,Android提供手动的
setVoumeControlStream方法来让音量控制键来控制你制定的音频流的音量。
setVolumeControlStream(AudioManager.STREAM_MUSIC);
应该在activity或者fragment的oncreate方法里面来设置音量流的目标音频流。
3、使用媒体播放按钮来控制APP的音频播放
在一些手机以及许多有线或者无线的耳机上面都有媒体播放控制按钮,比如日常用的耳机上面就有播放、暂停、上一曲、下一曲等按钮。当用户按下这些按钮的时候,Android系统就会广播一个带有 ACTION_MEDIA_BUTTON
首先是在Manifest文件里面注册广播接收器:
<receiver android:name=".RemoteControlReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
RemoteControlReceiver的具体如下:
package com.easyliu.player.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.KeyEvent;
//监听耳机线控广播
public class RemoteControlReceiver extends BroadcastReceiver {
public static final String TAG = RemoteControlReceiver.class
.getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
// 获得Action
String intentAction = intent.getAction();
// 获得keyevent
KeyEvent event = (KeyEvent) intent
.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
Log.i(TAG,
"Action ---->" + intentAction + " KeyEvent----->"
+ event.toString());
// 按下 / 松开 按钮
int keyAction = event.getAction();
if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)
&& (keyAction == KeyEvent.ACTION_DOWN)) {
int keycode = event.getKeyCode();
// 获得事件的时间
long downtime = event.getEventTime();
// 获取按键码 keyCode
StringBuilder sb = new StringBuilder();
// 这些都是可能的按键码 , 打印出来用户按下的键
if (KeyEvent.KEYCODE_MEDIA_NEXT == keycode) {
sb.append("KEYCODE_MEDIA_NEXT");
}
if (KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE == keycode) {
sb.append("KEYCODE_MEDIA_PLAY_PAUSE");
}
if (KeyEvent.KEYCODE_HEADSETHOOK == keycode) {
sb.append("KEYCODE_HEADSETHOOK");
}
if (KeyEvent.KEYCODE_MEDIA_PREVIOUS == keycode) {
sb.append("KEYCODE_MEDIA_PREVIOUS");
}
if (KeyEvent.KEYCODE_MEDIA_STOP == keycode) {
sb.append("KEYCODE_MEDIA_STOP");
}
// 输出点击的按键码
Log.i(TAG, sb.toString());
}
}
}
因为许多APP可能会监听media button的播放行为,所以你必须通过编程来控制你的APP什么时候来监听media button按下事件。
一般都按如下形式:
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);
...
// Start listening for button presses
am.registerMediaButtonEventReceiver(RemoteControlReceiver);
...
// Stop listening for button presses
am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
在一般情况下,APP应该在他们变得不活跃或者不可见的时候解注册大部分的接收器。然而,对于媒体播放app来说却不是这么简单。事实上,当你的应用不可见的时候对media buttons的响应式最重要的。所以不能通过UI界面来控制这个media button event接收器。
一个更好的方法是当应用或者或者失去音频焦点的时候来注册和解注册media button event receiver。
由于有多个APP都可能播放音频,所以就得考虑他们之间的互动。为了避免每个音乐APP都在相同的时刻播放音乐,Android使用音频焦点来折中音频回放——只有获得音频焦点的app才能播放音频。
在APP开始播放音频之前它应该请求并且获得音频焦点。同样,它应该知道怎样监听音频焦点的失去然后进行合适的响应。
4、请求音频焦点
在APP开始播放音频之前,它应该先获得audio focus。当只需要播放很短时间的音频可以请求短暂的焦点(比如播放导航指令),当你需要播放比较长时间的音频可以请求永久音频焦点(比如播放音乐)。
获得音频焦点的代码如下:
<span style="font-size:18px;">am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
// Audio是否获得焦点
private boolean isAudioGetFocus() {
int result = am.requestAudioFocus(afChangeListener,
// Use the music stream. 使用music stream
AudioManager.STREAM_MUSIC,
// Request permanent focus. 获得永久焦点
AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// Start playback.
return true;
} else {
return false;
}</span>
一旦完成了音频播放,记得调用:
am.abandonAudioFocus(afChangeListener);
注意:如果只是放弃短暂的焦点,这个允许之前被打断的APP继续播放,也就是获得焦点。
当请求短暂的音频焦点的时候有一个额外的选项:是否使能“ducking”。正常情况下,当一个表现好的app失去音频焦点的时候它会立即静音。通过允许“ducking"的方式来请求短暂的音频焦点将会告诉别的app:他们可以继续保持播放,只是降低他们播放的音量直到他们重新获得焦点。
<span style="font-size:18px;">private boolean isAudioGetFocus() {
int result = am.requestAudioFocus(afChangeListener,
// Use the music stream. 使用music stream
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// Start playback.
return true;
} else {
return false;
}
}
}</span>
"ducking"非常适合使用间歇性音频流的APP,比如播放听得见的行驶方向,这样可以保证别的APP正常播放音频,只是希望他们降低音量而已。
5、失去音频焦点的处理
如果你的APP获得了音频焦点,正在播放音乐。那么当打开QQ音乐播放的音乐的时候,QQ音乐就会获得焦点,酷狗就会失去焦点,那么久需要对这个进行响应,比如暂停播放或者停止播放音乐。
通常来说,一个短暂性的失去音频焦点应该导致你的APP静音,但是保持别的状态不变,你应该继续监测音频状态改变并且一旦重新获得焦点就得准备恢复播放进度重新播放。而永久性的失去音频焦点就应该停止音频播放,移除media button listener,且丢弃你的音频焦点。
如果在"ducking"允许的情况下短暂性的失去焦点,不是停止播放,可以采取别的措施,一般是降低音量。
下面是OnAudioFocusChangeListener的代码:
// 焦点改变监听
OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {
public void onAudioFocusChange(int focusChange) {
// 永久获得焦点
if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Raise it back to normal 恢复到正常音量
// 注册MediaButton
am.registerMediaButtonEventReceiver(mComponentName);
startPlay();// 开始播放
// 短暂性的获得焦点,允许ducking
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) {
// 短暂性的获得焦点
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT) {
// 永久性失去焦点
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
// 取消注册MediaButton
am.unregisterMediaButtonEventReceiver(mComponentName);
// 取消监听
am.abandonAudioFocus(afChangeListener);
stopPlay();// 停止播放
// 短暂的丢失焦点,允许ducking
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
// Lower the volume 降低音量
am.adjustStreamVolume(AudioManager.STREAM_MUSIC,
AudioManager.ADJUST_LOWER, AudioManager.FLAG_PLAY_SOUND);
}
// 短暂的丢失焦点
else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
pausePlay(); // 暂停播放
} else {
}
}
};
6、检测音频输出正在使用的硬件
用户可以选择使用耳机、扬声器等设备来收听音频。可以通过查询AudioManager来决定当前的audio被连接到了哪个设备上面,如下代码所示:
@SuppressWarnings("deprecation")
public static void getAudioRoute(AudioManager am) {
if (am.isBluetoothA2dpOn()) {
am.setBluetoothA2dpOn(true);
} else if (am.isSpeakerphoneOn()) {
am.setSpeakerphoneOn(true);
} else if (am.isWiredHeadsetOn()) {
am.setWiredHeadsetOn(true);
}
}
7、处理音频输出硬件发生改变
当耳机被拔出或者蓝牙设备断开连接,音频流自动自动转到内置的扬声器。如果你正在以很大的音量听歌的话,可能会有噪音。
当音频输出硬件发生改变的时候,系统会广播ACTION_AUDIO_BECOMING_NOISY意图。所以我们应该在播放音频的时候注册接收这个intent的广播。
代码如下所示:
private void startPlay() {
if (isAudioGetFocus()) {
// 注册噪声监听广播
registerNoisyAudioStreamReceiver();
if (mediaPlayer != null) {
isPlaying = true;
mediaPlayer.start();
if (timer != null) {
// 取消定时器
timer.cancel();
}
// 开始定时器
timer = new Timer();
timerTask();
}
}
}
// 停止音乐
private void stopPlay() {
unregisterReceiver(myNoisyAudioStreamReceiver);
if (mediaPlayer != null) {
isPlaying = false;
mediaPlayer.stop();
if (timer != null) {
// 取消定时器
timer.cancel();
}
}
}
// 噪声监听和耳机广播
private class NoisyAudioStreamReceiver extends BroadcastReceiver {
@SuppressWarnings("deprecation")
@Override
public void onReceive(Context context, Intent intent) {
// 监听拔出和插入耳机
if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) {
int state = intent.getIntExtra("state", -1);
switch (state) {
case 0:
// 拔出耳机
pausePlay();
Log.d(TAG, "state:" + state);
break;
case 1:
// 插耳机自动播放
Log.d(TAG, "state:" + state);
startPlay();
break;
default:
Log.d(TAG, "未知状态");
break;
}
}
// 监听硬件改变
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent
.getAction())) {
// Pause the playback
// 接收到噪声之后暂停播放
pausePlay();
}
}
}
以上代码还单独监听了耳机的拔出和插入事件,插入耳机的时候继续播放,酷狗音乐就有这个功能。
来自本地Android SDK:training/managing-audio/index.html,因为Android官网访问不了!