
全栈工程师开发手册 (作者:栾鹏)
安卓教程全解
如今连接移动设备的耳机上基本都有按键,来控制音频的播放,暂停,下一首,上一首,或电话的拨打,视频的播放等功能。而外置媒体按键的按下,安卓系统接收到这个信号以后会向系统所有app发送一个媒体按键的广播事件。app注册接收按键事件来进行相应的操作。而且接收这一事件的广播接收器必须在manifest中注册
另外,安卓只允许当前时刻只有一个app能获取音频焦点。所以可以控制当有别的播放器打开后,自己的app就可以会失去焦点,通过节点事件调整自己app的播放功能。
由于外接媒体按键的广播接收器只能在maifest中注册,所以只能通过先用自定义接收器接收这一事件,再向本地app发送一个代表这一事件的间接事件。目标窗口接收间接事件,再进行相应的处理。
首先要在manifest中注册媒体按键的广播接收器
设备带有播放、停止、暂停的按键,按下、弹起时系统会广播一个带有ACTION_MEDIA_BUTTON动作的intent
<receiver android:name="com.lp.app.media.MediaControlReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON"/>
</intent-filter>
</receiver>
定义广播接收器,接收媒体按键广播事件,并向本地app广播间接事件。
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
//设备按键广播接收器,接收到intent后,判断是否是设备按键广播,然后广播给音频播放activity。因为这个监听器只能在mainfest中注册
public class MediaControlReceiver extends BroadcastReceiver {
public static final String ACTION_MEDIA_BUTTON = "com.lp.app.ACTION_MEDIA_BUTTON";
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
Intent internalIntent = new Intent(ACTION_MEDIA_BUTTON);
internalIntent.putExtras(intent.getExtras()); //将数据也传递给目标窗口,播放、暂停、声音调大、声音调低。按下弹起都会发起广播
context.sendBroadcast(internalIntent);
Log.v("媒体按键广播接收器", "接收到外接媒体按键"+internalIntent.getAction()+"发送内部广播给本app");
}
}
}
目标activty先进行基本的音频播放配置。准备接收间接媒体按键事件
private MediaPlayer mediaPlayer;
//配置音频播放
private void configureAudio() {
try {
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource("/sdcard/test.mp3");
mediaPlayer.prepare();
} catch (IllegalArgumentException e) {
Log.d("媒体按键广播接收窗口", "Illegal Argument Exception: " + e.getMessage());
} catch (SecurityException e) {
Log.d("媒体按键广播接收窗口", "Security Exception: " + e.getMessage());
} catch (IllegalStateException e) {
Log.d("媒体按键广播接收窗口", "Illegal State Exception: " + e.getMessage());
} catch (IOException e) {
Log.d("媒体按键广播接收窗口", "IO Exception: " + e.getMessage());
}
}
目标activity中接收间接广播事件。同时可以注册耳机拔出事件的广播接收器
private ActivityMediaControlReceiver activityMediaControlReceiver; //间接广播事件
@Override
protected void onResume() {
super.onResume();
//媒体按键事件的广播接收器,在mainfest中注册,接收器接收到事件后统一在app内广播一个新的事件表征媒体按键的事件,所有接收此事件的窗口再去处理。
AudioManager am =(AudioManager)getSystemService(Context.AUDIO_SERVICE); //获取声量管理服务
ComponentName component = new ComponentName(this, MediaControlReceiver.class);
am.registerMediaButtonEventReceiver(component); //将接收处理程序注册为媒体按键按下动作的唯一处理程序。这样屏蔽多个app的同时处理。不过不一定有权限
// 注册一个本地intent receiver,用于接收在manifest文件中注册的receiver
//媒体按键按下动作
activityMediaControlReceiver = new ActivityMediaControlReceiver();
IntentFilter filter = new IntentFilter(MediaControlReceiver.ACTION_MEDIA_BUTTON);
registerReceiver(activityMediaControlReceiver, filter);
Log.v("媒体按键监听", "注册了间接媒体按键监听器");
//拔出耳机的广播接收器
IntentFilter noiseFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
registerReceiver(new NoisyAudioStreamReceiver(), noiseFilter);
Log.v("媒体按键监听", "注册了耳机拔出监听器");
}
其中间接广播接收器的定义如下,主要完成音频的播放、暂停、停止、音量调整、上一首、下一首。
//媒体按键BroadcastReceiver的实现,接收设备按键,控制音频播放
public class ActivityMediaControlReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (MediaControlReceiver.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
KeyEvent event =(KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); //
if(event.getAction()!=KeyEvent.ACTION_UP) //这里只处理弹起事件
return;
Log.v("简介媒体按键监听器", "接收到按键"+event);
switch (event.getKeyCode()) {
case (KeyEvent.KEYCODE_HEADSETHOOK) : //接听/挂断 电话,播放/暂停音乐视频 多功能按键
if (mediaPlayer.isPlaying())
{
pause();
Log.v("音频播放", "暂停");
}
else
{
play();
Log.v("音频播放", "播放");
}
break;
case (KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) : //如果是暂停或暂停键就控制音频播放或暂停
if (mediaPlayer.isPlaying())
{
pause();
Log.v("音频播放", "暂停");
}
else
{
play();
Log.v("音频播放", "播放");
}
break;
case (KeyEvent.KEYCODE_MEDIA_PLAY) : //如果是播放键,就播放
play(); break;
case (KeyEvent.KEYCODE_MEDIA_PAUSE) : //如果是暂停键,就暂停
pause(); break;
case (KeyEvent.KEYCODE_MEDIA_NEXT) : //如果是下一首,就跳转到下一首
skip(); break;
case (KeyEvent.KEYCODE_MEDIA_PREVIOUS) : //如果是上一首,就跳转到上一首
previous(); break;
case (KeyEvent.KEYCODE_MEDIA_STOP) : //如果是停止键,就停止
stop(); break;
default: break;
}
}
}
}
耳机拔出事件的广播接收器如下
//当拔出耳机时暂停播放
private class NoisyAudioStreamReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
pause();
Log.v("媒体按键监听", "耳机拔出");
}
}
}
音频的播放和停止设计的音频焦点的设计。由于在安卓系统中只允许一个窗口获取当前音频焦点。所以在开始播放音频时,应该主动获取音频焦点,以便其他播放器app,停止音频播放,在丢失音频焦点时,主动放弃音频播放。
定义一个音频焦点的变更事件
//音频焦点变化响应。在用户设备有多个媒体播放app时,非活动状态app交出媒体按键控制权。活动状态app获取媒体按键控制权
//分为暂时丢失、丢失、永久丢失。这是安装系统的架构
private OnAudioFocusChangeListener focusChangeListener = new OnAudioFocusChangeListener() {
public void onAudioFocusChange(int focusChange) {
AudioManager am = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
Log.v("按键广播监听", "音频焦点变化"+focusChange);
switch (focusChange) {
case (AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) : //暂时丢失,支持音量降低
//降低音量
mediaPlayer.setVolume(0.2f, 0.2f);
break;
//暂停
case (AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) : //丢失
pause();
break;
//停止播放
case (AudioManager.AUDIOFOCUS_LOSS) : //永久丢失
stop();
ComponentName component = new ComponentName(AudioPlayerActivity.this,MediaControlReceiver.class);
am.unregisterMediaButtonEventReceiver(component); //注销广播响应程序
break;
case (AudioManager.AUDIOFOCUS_GAIN) : //暂时丢失后,重获焦点
//将音量恢复到正常大小,并且如果音频流已被暂停,则恢复音频流
mediaPlayer.setVolume(1f, 1f);
mediaPlayer.start();
break;
default: break;
}
}
};
音频播放函数
//请求音频焦点(音频焦点只能由一个app获取)。
public void play() {
AudioManager am = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
//请求音频焦点
int result = am.requestAudioFocus(focusChangeListener, //(音频焦点丢失响应)
//使用音频流
AudioManager.STREAM_MUSIC,
//请求永久焦点
AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { //如果焦点请求成功
mediaPlayer.start(); //播放音频
}
音频停止
//音频播放完成后,放弃音频焦点。
public void stop() {
mediaPlayer.stop();
Log.v("音频播放", "放弃音频焦点");
//放弃音频焦点
AudioManager am = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
am.abandonAudioFocus(focusChangeListener);
}
音频的暂停、上一首、下一首就根据自己想要的的功能来实现
//暂停函数,包含设置远程控制窗口
public void pause() {
mediaPlayer.pause();
myRemoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
}
//跳到下一首
public void skip() {
//自己实现
}
//跳到前一首
public void previous() {
//自己实现
}