这个系列仅仅包含控制部分 , 不包含音频操作代码 , 如 pauseAudio(); 我不会说这个方法里是怎么操作的 , 大家需要结合自己的音频播放处理来实现.
一个完整的多媒体播放器应该有的基础功能:
- 通过耳机按钮来控制歌曲 播放/暂停 上/下一首歌曲
- 当有线耳机/蓝牙耳机 断开连接和重新连接的时候 我们应该对应做出 暂停音频 恢复音频
- 使用系统提供的Notification.MediaStyle来控制歌曲 , 可以顺利兼容 android 4.x ~ android 7.x 现在的系统样式
[[广告]] https://github.com/ocwvar/DarkPurple
需要代码样例的同学可以在我的这个项目中查看 这个项目是个完整的音频播放器 这里的代码均是从里面提取 , 同时还有均衡器调节 频谱动画显示等.. 目前正在不断完善中 , 但由于上班 , 代码更新可能不及时.
MediaSessionCompat sessionCompat = new MediaSessionCompat();
在这个构造方法中 , 我们使用这个:
MediaSessionCompat(Context context, String tag, ComponentName mbrComponent, PendingIntent mbrIntent)
//第一个参数 context: 这个没有什么好讲的,大家都懂的
//第二个参数 tag: 这个是用于调试用的,随便填写即可
//第三个参数 mbrComponent: 这个是用于API21以下的时候传递耳机按钮事件用的MediaSessionCompat.
//第四个参数 mbrIntent: 这个是给API21以下传递的时候携带的,一般设为 NULL即可
//例如:
//其中的HeadsetButtonReceiver是我们的API21以下实用的监听器 , 我们稍候再讲
ComponentName cn = new ComponentName(this.context.getApplicationContext().getPackageName(),
HeadsetButtonReceiver.class.getName())
sessionCompat = new MediaSessionCompat(this.context.getApplicationContext(), "test", cn, null);
sessionCompat = new MediaSessionCompat(...);
//设置MediaSession回调监听,主要用于设置API21+的耳机按钮监听
sessionCompat.setCallback(new MediaSessionCallback());
//设置FLAG,FLAG的用途一看名字就知道了
sessionCompat.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS);
//设置MediaSession启动 (很重要,不启动则无法接受到数据)
sessionCompat.setActive(true);
//创建完成后用MediaSessionCompat.setCallback设置上即可使用
private class MediaSessionCallback extends MediaSessionCompat.Callback {
@Override
public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
//接收到监听事件
}
}
//在代码中创建一个单独的类文件,而不能作为一个内部类
public class HeadsetButtonReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//接收到监听事件
}
}
//然后在注册文件中注册这个接收器
<receiver
android:name=".HeadsetButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON"/>
</intent-filter>
</receiver>
//最后在创建MediaSessionCompat对象的时候使用即可
MediaStyle是什么样子的Notification呢?
在**Android 4.x**的是这样的 ![这里写图片描述](http://img.blog.csdn.net/20161107152026146)
在**Android 5.x ~ 6.x** 的是这样的 ![这里写图片描述](http://img.blog.csdn.net/20161107152048581)
在**Android 7.0** 的是这样的 ![这里写图片描述](http://img.blog.csdn.net/20161107152312833) MediaStyle使用的都是系统资源 , 除了布局之外 , 可以自定义的有: > 封面图片 标题 副标题 按钮样式 背景颜色 使用MediaStyle的优点是不用担心因系统变化而导致布局的变化 , 以及可以使用MediaSession作为我们的控制中介 , 虽然这部分不能自定义但个人认为还是挺值得的 .
对应的一个还有一个是Style是 **DecoratedMediaCustomViewStyle** 这个是可以提供部分自定义 , 但是修改部分仅是标题文字那部分的布局而已 , 并没有什么太大的作用 .
####**Notification.MediaStyle 创建代码** PS:我们分开一段段讲 ( 代码太长了不好排版…. )
####**Part.1 基础部分**
//创建 Notification 构建器
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
//创建 MediaStyle 对象
final NotificationCompat.MediaStyle mediaStyle = new NotificationCompat.MediaStyle(builder);
//在折叠的视图中显示的按钮序号 根据下面设置的ACTION顺序相关
mediaStyle.setShowActionsInCompactView(0,1);
//设置上面创建的MediaSession
mediaStyle.setMediaSession(sessionCompat.getSessionToken());
//设置 MediaStyle 风格
builder.setStyle(mediaStyle);
//不显示默认的通知开始时间
builder.setShowWhen(false);
//仅通知一次
builder.setOnlyAlertOnce(true);
//设置为当前正在运行状态,不能清除
builder.setOngoing(true);
//设置点击时不隐藏Notification
builder.setAutoCancel(false);
//设置状态栏上面显示的小图标
builder.setSmallIcon(R.drawable.ic_action_small_icon);
//设置锁屏是否显示 在 Android5.0+ 的锁屏界面可以隐藏Notification内容
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setVisibility(Notification.VISIBILITY_PUBLIC);
}
//设置通知优先度,让我们的 Notification 显示在最上面
builder.setPriority(Notification.PRIORITY_MAX);
//设置通知类别为服务
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setCategory(Notification.CATEGORY_SERVICE);
}
//点击通知操作 使得用户点击Notification空白区域的时候打开指定的Activity
Intent intent = new Intent(context, SelectMusicActivity.class);
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
builder.setContentIntent(pendingIntent);
/** * 生成按钮Action * * @param icon 图标资源 * @param intentAction 按钮产生的广播Action * @return 按钮Action */
private NotificationCompat.Action generateAction(int icon, String intentAction ) {
return new NotificationCompat.Action( icon, intentAction, PendingIntent.getBroadcast(context, 0, new Intent(intentAction), PendingIntent.FLAG_CANCEL_CURRENT) );
}
//根据播放状态不同 , 设置不同主按钮样式 , 这里的设置顺序影响到折叠界面显示的顺序
//上一首 按钮ACTION
builder.addAction(generateAction(android.R.drawable.ic_media_previous,MediaNotificationReceiver.BUTTON_PREV));
switch (audioStatus) {
case Paused:
//播放 按钮ACTION
builder.addAction(generateAction(android.R.drawable.ic_media_play,MediaNotificationReceiver.BUTTON_PLAY));
break;
case Playing:
//暂停 按钮ACTION
builder.addAction(generateAction(android.R.drawable.ic_media_pause,MediaNotificationReceiver.BUTTON_PAUSE));
break;
}
//下一首 按钮ACTION
builder.addAction(generateAction(android.R.drawable.ic_media_next,MediaNotificationReceiver.BUTTON_NEXT));
####**Part.3 设置显示歌曲信息**
//更新标题
builder.setContentTitle(songItem.getTitle());
//更新作者
builder.setContentText(songItem.getArtist());
//更新封面
builder.setLargeIcon( 歌曲封面的Bitmap对象 );
最后就 builder.build(); 得到最终的成品 Notification
我们需要在耳机拔出的时候 暂停音乐 在耳机重新插入的时候 恢复音乐
/** * 耳机插入广播接收器 */
public class HeadsetPlugInReceiver extends BroadcastReceiver {
final IntentFilter filter;
public HeadsetPlugInReceiver() {
filter = new IntentFilter();
if (Build.VERSION.SDK_INT >= 21) {
filter.addAction(AudioManager.ACTION_HEADSET_PLUG);
} else {
filter.addAction(Intent.ACTION_HEADSET_PLUG);
}
}
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null && intent.hasExtra("state") && AppConfigs.isResumeAudioWhenPlugin) {
//通过判断 "state" 来知道状态
final boolean isPlugIn = intent.getExtras().getInt("state") == 1;
}
}
}
####耳机拔出/断开连接 广播接收器
/** * 耳机拔出广播接收器 */
private class HeadsetReceiver extends BroadcastReceiver {
final IntentFilter filter;
final BluetoothAdapter bluetoothAdapter;
public HeadsetReceiver() {
filter = new IntentFilter();
filter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY); //有线耳机拔出变化
filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); //蓝牙耳机连接变化
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
@Override
public void onReceive(Context context, Intent intent) {
if (isRunningForeground) {
//当前是正在运行的时候才能通过媒体按键来操作音频
switch (intent.getAction()) {
case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
if (
bluetoothAdapter != null &&
BluetoothProfile.STATE_DISCONNECTED == bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET) &&
core.getCurrectStatus() == AudioCore.AudioStatus.Playing
)
{
//蓝牙耳机断开连接 同时当前音乐正在播放 则将其暂停
pause();
}
break;
case AudioManager.ACTION_AUDIO_BECOMING_NOISY:
if (core.getCurrectStatus() == AudioCore.AudioStatus.Playing) {
//有线耳机断开连接 同时当前音乐正在播放 则将其暂停
pause();
}
break;
}
}
}
}
最后用 registerReceiver() 来注册广播监听器即可
我们需要在有电话来的时候和拨通电话的时候 暂停音频 , 在通话结束之后 恢复音频
要使得能接受到通话状态 , 我们需要注册一个权限 android.permission.READ_PHONE_STATE 这个权限在Android 6.0+上是需要用户授予的
/** *电话状态广播接收器 */
public class PhoneStatusReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null && intent.getExtras() != null){
final String status = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
switch (status){
// "IDLE" 则代表通话结束或振铃结束
case "IDLE":
resume();
break;
//其他状态包括了拨通电话和新来电振铃
default:
pause();
break;
}
}
}
}
在代码端写完之后 , 我们还需要在注册清单中注册一下:
<receiver android:name=".PhoneStatusReceiver">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE"/>
</intent-filter>
</receiver>