一、前言
MediaPlayer是Android自带的一个多媒体播放类,可以播放音视频流或者本地音视频文件。MediaPlayer方法的调用需要在一定的状态下,下图是一个MediaPlayer对象被支持的播放控制操作驱动的声明周期和状态。其中,椭圆代表MediaPlayer可能驻留的状态,弧线表示驱动MediaPlayer在各个状态之间迁移的播放控制操作。这里有两种类型的弧线。由单箭头开始的弧线代表同步方法调用,而以双箭头开头的弧线代表异步方法调用。图片介绍来源于官方文。
详细介绍可通过Android -- 多媒体播放之MediaPlayer基础简介了解更多
二、播放网络音频
现在写一个支持本地缓存的网络音频播放器,并添加了唤醒锁、WiFi锁和音频焦点等功能。
1、自定义MediaPlayer
直接通过MediaPlayer获取播放状态有时不够准确,所以自定义ManagedMediaPlayer继承MediaPlayer,拓展MediaPlayer的功能,控制播放状态
public class ManagedMediaPlayer extends MediaPlayer implements MediaPlayer.OnCompletionListener {
public enum Status {
IDLE, INITIALIZED, STARTED, PAUSED, STOPPED, COMPLETED
}
private Status mState;
private OnCompletionListener mOnCompletionListener;
public ManagedMediaPlayer() {
super();
mState = Status.IDLE;
super.setOnCompletionListener(this);
}
@Override
public void reset() {
super.reset();
mState = Status.IDLE;
}
@Override
public void setDataSource(String path) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
super.setDataSource(path);
mState = Status.INITIALIZED;
}
@Override
public void start() {
super.start();
mState = Status.STARTED;
}
@Override
public void setOnCompletionListener(OnCompletionListener listener) {
this.mOnCompletionListener = listener;
}
@Override
public void onCompletion(MediaPlayer mp) {
mState = Status.COMPLETED;
if (mOnCompletionListener != null) {
mOnCompletionListener.onCompletion(mp);
}
}
@Override
public void stop() throws IllegalStateException {
super.stop();
mState = Status.STOPPED;
}
@Override
public void pause() throws IllegalStateException {
super.pause();
mState = Status.PAUSED;
}
public void setState(Status mState) {
this.mState = mState;
}
public Status getState() {
return mState;
}
public boolean isComplete() {
return mState == Status.COMPLETED;
}
}
2、唤醒锁、WiFi锁
app在长时间后台运行时,手机有可能会进入休眠,这是CUP和WiFi可能会停止运行,影响到app的正常运行,所以我们需要加入唤醒锁和WiFi锁保证我们在后台长时间播放音频的稳定。
初始化时MediaPlayer时使用唤醒锁并初始化WiFi锁
// 使用唤醒锁
mMediaPlayer.setWakeMode(MyApplication.getContext(), PowerManager.PARTIAL_WAKE_LOCK);
// 初始化wifi锁
WifiManager.WifiLock wifiLock = ((WifiManager) MyApplication.getContext().getApplicationContext().getSystemService(Context.WIFI_SERVICE)).createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
在开始播放的时候启用WiFi锁
// 启用wifi锁
wifiLock.acquire();
在暂定和释放资源的时候关闭WiFi锁
// 关闭wifi锁
if (wifiLock.isHeld()) {
wifiLock.release();
}
3、音频焦点
Android系统是一个多任务操作系统,因此同一时刻允许许多任务同时工作。但是这对音频类应用来说是个挑战,因为如果多个音频同时播放的话,很多情况下用户体验会相当的差。当你需要播放音乐或者发送一个通知的时候,你可以去要求获得音频焦点。一旦获得,就可以自由的使用音频输出设备。但是同时它也在时时刻刻的监听着音频焦点的变化。当音频焦点变化时,你需要去合适的处理你的音频输出。
自定义AudioFocusManager实现音频焦点变化监听
public class AudioFocusManager implements AudioManager.OnAudioFocusChangeListener{
private static final String TAG = "AudioFocusManager";
private AudioManager audioManager;
private boolean isPausedByFocusLossTransient;
public AudioFocusManager(Context context) {
audioManager = (AudioManager) context.getSystemService(AUDIO_SERVICE);
}
public boolean requestAudioFocus() {
return audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN)
== AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
public void abandonAudioFocus() {
audioManager.abandonAudioFocus(this);
}
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
// 重新获得焦点
case AudioManager.AUDIOFOCUS_GAIN:
if (isPausedByFocusLossTransient) {
// 通话结束,恢复播放
AudioPlayer.getInstance().resume();
}
// 恢复音量
AudioPlayer.getInstance().getMediaPlayer().setVolume(1f, 1f);
isPausedByFocusLossTransient = false;
Log.d(TAG, "重新获得焦点");
break;
// 永久丢失焦点,如被其他播放器抢占
case AudioManager.AUDIOFOCUS_LOSS:
PlayerService.stopPlayerService();
abandonAudioFocus();
Log.d(TAG, "永久丢失焦点,如被其他播放器抢占");
break;
// 短暂丢失焦点,如来电
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
AudioPlayer.getInstance().pause();
isPausedByFocusLossTransient = true;
Log.d(TAG, "短暂丢失焦点,如来电");
break;
// 瞬间丢失焦点,如通知
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// 音量减小为一半
AudioPlayer.getInstance().getMediaPlayer().setVolume(0.5f, 0.5f);
Log.d(TAG, "瞬间丢失焦点,如通知");
break;
default:
break;
}
}
}
在初始化MediaPlayer时初始化AudioFocusManager
// 初始化音频焦点管理器
AudioFocusManager audioFocusManager = new AudioFocusManager(MyApplication.getContext());
在开始播放的时候获取音频焦点
// 获取音频焦点
if (!audioFocusManager.requestAudioFocus()) {
Log.e(TAG, "获取音频焦点失败");
}
在暂定、停止和释放资源的时候取消音频焦点
// 取消音频焦点
if (audioFocusManager != null) {
audioFocusManager.abandonAudioFocus();
}
4、缓存
这里使用了AndroidVideoCache做本地缓存。
添加AndroidVideoCache依赖
dependencies {
implementation'com.danikula:videocache:2.7.0'
}
自定义缓存文件命名规则
public class CacheFileNameGenerator implements FileNameGenerator {
private static final String TAG = "CacheFileNameGenerator";
/**
* @param url
* @return
*/
@Override
public String generate(String url) {
Uri uri = Uri.parse(url);
List<String> pathSegList = uri.getPathSegments();
String path = null;
if (pathSegList != null && pathSegList.size() > 0) {
path = pathSegList.get(pathSegList.size() - 1);
} else {
path = url;
}
Log.d(TAG, "generate return " + path);
return path;
}
}
创建单例的AndroidVideoCache实例的方法
public class HttpProxyCacheUtil {
private static HttpProxyCacheServer audioProxy;
public static HttpProxyCacheServer getAudioProxy() {
if (audioProxy== null) {
audioProxy= new HttpProxyCacheServer.Builder(MyApplication.getContext())
.cacheDirectory(CachesUtil.getMediaCacheFile(CachesUtil.AUDIO))
.maxCacheSize(1024 * 1024 * 1024) // 缓存大小
.fileNameGenerator(new CacheFileNameGenerator())
.build();
}
return audioProxy;
}
}
使用AndroidVideoCache进行缓存,只要将url经过HttpProxyCacheServer转化就可以了,AndroidVideoCache会处理缓存
String url = "XXXXXXXXXXX";
HttpProxyCacheServer proxy = HttpProxyCacheUtil.getAudioProxy()
url = proxy.getProxyUrl(url);
5、UI响应
监听播放时不同的状态,并通过广播的形式通知UI做出响应。
创建一个基类的BasePlayReceiver,接收初始化信息完成、资源准备完成、资源播放完成、播放状态改变、缓冲进度和播放错误等广播
public abstract class BasePlayReceiver extends BroadcastReceiver {
public static String ACTION = "com.example.android.myapplication.PLAY_RECEIVER";
public static String EXTRA_TYPE = "type";
public static String TYPE_ON_INIT_SOURCE = "onInitSource";
public static String EXTRA_SOURCE = "source";
public static String TYPE_ON_PREPARED = "onPrepared";
public static String TYPE_ON_COMPLETION = "onCompletion";
public static String TYPE_ON_PLAY_STATUS = "onPlayStatus";
public static String TYPE_ON_BUFFERING_UPDATE = "onBufferingUpdate";
public static String EXTRA_PERCENT = "percent";
public static String TYPE_ON_ERROR = "onError";
public static String EXTRA_WHAT = "what";
public static String EXTRA_EXTRA = "extra";
public static void registerReceiver(Context context, BasePlayReceiver basePlayReceiver) {
IntentFilter filter = new IntentFilter();
filter.addAction(BasePlayReceiver.ACTION);
//注册
context.registerReceiver(basePlayReceiver, filter);
}
public static void unregisterReceiver(Context context, BasePlayReceiver basePlayReceiver) {
if (basePlayReceiver != null) {
context.unregisterReceiver(basePlayReceiver);
}
}
public static void sendBroadcastInitSource(Context context, AlbumProgramItemBean song) {
Intent intent = new Intent();
intent.setAction(BasePlayReceiver.ACTION);
intent.putExtra(BasePlayReceiver.EXTRA_TYPE, TYPE_ON_INIT_SOURCE);
intent.putExtra(BasePlayReceiver.EXTRA_SOURCE, song);
context.sendBroadcast(intent);
}
public static void sendBroadcastPrepared(Context context) {
Intent intent = new Intent();
intent.setAction(BasePlayReceiver.ACTION);
intent.putExtra(BasePlayReceiver.EXTRA_TYPE, TYPE_ON_PREPARED);
context.sendBroadcast(intent);
}
public static void sendBroadcastCompletion(Context context) {
Intent intent = new Intent();
intent.setAction(BasePlayReceiver.ACTION);
intent.putExtra(BasePlayReceiver.EXTRA_TYPE, TYPE_ON_COMPLETION);
context.sendBroadcast(intent);
}
public static void sendBroadcastPlayStatus(Context context) {
Intent intent = new Intent();
intent.setAction(BasePlayReceiver.ACTION);
intent.putExtra(BasePlayReceiver.EXTRA_TYPE, TYPE_ON_PLAY_STATUS);
context.sendBroadcast(intent);
}
public static void sendBroadcastBufferingUpdate(Context context, int percent) {
Intent intent = new Intent();
intent.setAction(BasePlayReceiver.ACTION);
intent.putExtra(BasePlayReceiver.EXTRA_TYPE, TYPE_ON_BUFFERING_UPDATE);
intent.putExtra(BasePlayReceiver.EXTRA_PERCENT, percent);
context.sendBroadcast(intent);
}
public static void sendBroadcastError(Context context, int what, int extra) {
Intent intent = new Intent();
intent.setAction(BasePlayReceiver.ACTION);
intent.putExtra(BasePlayReceiver.EXTRA_TYPE, TYPE_ON_ERROR);
intent.putExtra(BasePlayReceiver.EXTRA_WHAT, what);
intent.putExtra(BasePlayReceiver.EXTRA_EXTRA, extra);
context.sendBroadcast(intent);
}
@Override
public void onReceive(Context context, Intent intent) {
if (!BasePlayReceiver.ACTION.equals(intent.getAction()) || intent.getExtras() == null) {
return;
}
Bundle bundle = intent.getExtras();
String type = bundle.getString(EXTRA_TYPE);
if (TYPE_ON_INIT_SOURCE.equals(type)) {
onInitSource((AlbumProgramItemBean) bundle.getParcelable(EXTRA_SOURCE));
} else if (TYPE_ON_PREPARED.equals(type)) {
onPrepared();
} else if (TYPE_ON_COMPLETION.equals(type)) {
onCompletion();
} else if (TYPE_ON_PLAY_STATUS.equals(type)) {
onPlayStatus();
} else if (TYPE_ON_BUFFERING_UPDATE.equals(type)) {
onBufferingUpdate(bundle.getInt(EXTRA_PERCENT));
} else if (TYPE_ON_ERROR.equals(type)) {
onError(bundle.getInt(EXTRA_WHAT), bundle.getInt(EXTRA_EXTRA));
}
}
/**
* 初始化信息
*
* @param source
*/
protected abstract void onInitSource(AlbumProgramItemBean source);
/**
* 资源准备完成
*/
protected abstract void onPrepared();
/**
* 资源播放完成
*/
protected abstract void onCompletion();
/**
* 播放状态的改变
*/
protected abstract void onPlayStatus();
/**
* 缓存进度
*
* @param percent
*/
protected abstract void onBufferingUpdate(int percent);
/**
* 播放错误
*
* @param what
* @param extra
*/
protected abstract void onError(int what, int extra);
}
在需要改变UI的地方,继承BasePlayReceiver,实现其中的方法并动态注册即可。
6、播放控制类
新建单例的音频播放控制类AudioPlayer,方便对音频进行控制,并在操作音频之前都做了状态判断,尽可能的减少错误的发生。在AudioPlayer中可设置播放模式(顺序、列表循环、随机和单曲循环),播放,暂停,上下曲,seekTo,停止,获取播放总时间和获取当前播放进度等功能,并保存全局的播放列表和当前播放的音频,方便获取相关信息。
public class AudioPlayer implements MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnErrorListener {
private static final String TAG = "AudioPlayer";
/**
* 播放方式
*/
public enum PlayMode {
/**
* 顺序
*/
ORDER,
/**
* 列表循环
*/
LOOP,
/**
* 随机
*/
RANDOM,
/**
* 单曲循环
*/
REPEAT
}
private ManagedMediaPlayer mMediaPlayer;
private List<AlbumProgramItemBean> mQueue;
private int mQueueIndex;
private PlayMode mPlayMode = PlayMode.ORDER;
private AlbumProgramItemBean nowPlaying;
private WifiManager.WifiLock wifiLock;
private AudioFocusManager audioFocusManager;
private HttpProxyCacheServer proxy;
private static class SingletonHolder {
private static AudioPlayer instance = new AudioPlayer();
}
public static AudioPlayer getInstance() {
return SingletonHolder.instance;
}
@Override
public void onCompletion(MediaPlayer mp) {
BasePlayReceiver.sendBroadcastCompletion(MyApplication.getContext());
}
@Override
public void onPrepared(MediaPlayer mp) {
start();
BasePlayReceiver.sendBroadcastPrepared(MyApplication.getContext());
}
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
BasePlayReceiver.sendBroadcastBufferingUpdate(MyApplication.getContext(), percent);
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Log.e(TAG, "MediaPlayer onError what " + what + " extra " + extra);
release();
next();
BasePlayReceiver.sendBroadcastError(MyApplication.getContext(), what, extra);
return false;
}
public void init() {
mMediaPlayer = new ManagedMediaPlayer();
// 使用唤醒锁
mMediaPlayer.setWakeMode(MyApplication.getContext(), PowerManager.PARTIAL_WAKE_LOCK);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setOnCompletionListener(this);
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.setOnBufferingUpdateListener(this);
mMediaPlayer.setOnErrorListener(this);
// 初始化wifi锁
wifiLock = ((WifiManager) MyApplication.getContext().getApplicationContext().getSystemService(Context.WIFI_SERVICE)).createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
// 初始化音频焦点管理器
audioFocusManager = new AudioFocusManager(MyApplication.getContext());
// 初始化AndroidVideoCache
proxy = HttpProxyCacheUtil.getAudioProxy();
}
public void setPlayIndex(int index) {
this.mQueueIndex = index;
}
public void setQueue(List<AlbumProgramItemBean> mQueue) {
this.mQueue = mQueue;
}
public void setQueueAndIndex(List<AlbumProgramItemBean> mQueue, int mQueueIndex) {
this.mQueue = mQueue;
this.mQueueIndex = mQueueIndex;
}
private void play(AlbumProgramItemBean source) {
if (source == null) {
Log.e(TAG, "没有可用资源");
return;
}
if (mMediaPlayer == null) {
init();
}
if (getStatus() == ManagedMediaPlayer.Status.INITIALIZED) {
Log.e(TAG, "正在准备上一个资源,请稍候");
return;
}
// 更新播放器状态
mMediaPlayer.reset();
nowPlaying = source;
// 更新Notification
Notifier.getInstance().showPlayInfo(source);
// 向MainActivity发送EventBus
EventBus.getDefault().post(new MainActivityEvent());
// 发送初始化资源信息的广告
BasePlayReceiver.sendBroadcastInitSource(MyApplication.getContext(), source);
// 获取音频地址(音频地址一般私有)
Call<BaseCallBackVo<String>> call = HttpClientFactory.getAppApiClientInstance().getAlbumAddress(source.getId());
call.enqueue(new Callback<BaseCallBackVo<String>>() {
@Override
public void onResponse(Call<BaseCallBackVo<String>> call, Response<BaseCallBackVo<String>> response) {
if (response.code() == 200 && response.body() != null) {
if (response.body().getStatus() == 200) {
String url = response.body().getData();
url = proxy.getProxyUrl(url);
play(url);
}
}
}
@Override
public void onFailure(Call<BaseCallBackVo<String>> call, Throwable t) {
BasePlayReceiver.sendBroadcastPrepared(MyApplication.getContext());
}
});
}
private void play(String dataSource) {
// mMediaPlayer.reset();
try {
mMediaPlayer.setDataSource(dataSource);
mMediaPlayer.prepareAsync();
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "该资源无法播放");
BasePlayReceiver.sendBroadcastPrepared(MyApplication.getContext());
}
}
private void start() {
// 获取音频焦点
if (!audioFocusManager.requestAudioFocus()) {
Log.e(TAG, "获取音频焦点失败");
}
mMediaPlayer.start();
// 启用wifi锁
wifiLock.acquire();
// 更新notification
Notifier.getInstance().showPlayInfo(nowPlaying);
// 向MainActivity发送EventBus
EventBus.getDefault().post(new MainActivityEvent());
// 发送播放状态的广播
BasePlayReceiver.sendBroadcastPlayStatus(MyApplication.getContext());
}
public void pause() {
if (getStatus() == ManagedMediaPlayer.Status.STARTED) {
mMediaPlayer.pause();
// 关闭wifi锁
if (wifiLock.isHeld()) {
wifiLock.release();
}
// 发送播放状态的广播
BasePlayReceiver.sendBroadcastPlayStatus(MyApplication.getContext());
// 更新notification
Notifier.getInstance().showPlayInfo(nowPlaying);
// 向MainActivity发送EventBus
EventBus.getDefault().post(new MainActivityEvent());
// 取消音频焦点
if (audioFocusManager != null) {
audioFocusManager.abandonAudioFocus();
}
}
}
public void resume() {
if (getStatus() == ManagedMediaPlayer.Status.PAUSED) {
start();
}
}
public void stop() {
if (getStatus() == ManagedMediaPlayer.Status.STARTED
|| getStatus() == ManagedMediaPlayer.Status.PAUSED
|| getStatus() == ManagedMediaPlayer.Status.COMPLETED) {
mMediaPlayer.stop();
// 发送播放状态的广播
BasePlayReceiver.sendBroadcastPlayStatus(MyApplication.getContext());
// 更新notification
Notifier.getInstance().showPlayInfo(nowPlaying);
// 向MainActivity发送EventBus
EventBus.getDefault().post(new MainActivityEvent());
// 取消音频焦点
if (audioFocusManager != null) {
audioFocusManager.abandonAudioFocus();
}
}
}
public void release() {
if (mMediaPlayer == null) {
return;
}
nowPlaying = null;
Log.d(TAG, "release");
mMediaPlayer.release();
mMediaPlayer = null;
// 取消音频焦点
if (audioFocusManager != null) {
audioFocusManager.abandonAudioFocus();
}
// 关闭wifi锁
if (wifiLock.isHeld()) {
wifiLock.release();
}
wifiLock = null;
audioFocusManager = null;
proxy = null;
// 向MainActivity发送EventBus
EventBus.getDefault().post(new MainActivityEvent());
}
public void seekTo(int msec) {
if (getStatus() == ManagedMediaPlayer.Status.STARTED
|| getStatus() == ManagedMediaPlayer.Status.PAUSED
|| getStatus() == ManagedMediaPlayer.Status.COMPLETED) {
mMediaPlayer.seekTo(msec);
}
}
/**
* 播放
*/
public void play() {
AlbumProgramItemBean albumProgramItemBean = getPlaying(mQueueIndex);
play(albumProgramItemBean);
}
public void next() {
AlbumProgramItemBean albumProgramItemBean = getNextPlaying();
play(albumProgramItemBean);
}
public void previous() {
AlbumProgramItemBean albumProgramItemBean = getPreviousPlaying();
play(albumProgramItemBean);
}
public AlbumProgramItemBean getNowPlaying() {
if (nowPlaying != null) {
return nowPlaying;
} else {
return getPlaying(mQueueIndex);
}
}
public int getCurrentPosition() {
if (getStatus() == ManagedMediaPlayer.Status.STARTED
|| getStatus() == ManagedMediaPlayer.Status.PAUSED) {
return mMediaPlayer.getCurrentPosition();
}
return 0;
}
public int getDuration() {
if (getStatus() == ManagedMediaPlayer.Status.STARTED
|| getStatus() == ManagedMediaPlayer.Status.PAUSED) {
return mMediaPlayer.getDuration();
}
return 0;
}
public ManagedMediaPlayer.Status getStatus() {
if (mMediaPlayer != null) {
return mMediaPlayer.getState();
} else {
return ManagedMediaPlayer.Status.STOPPED;
}
}
public MediaPlayer getMediaPlayer() {
return mMediaPlayer;
}
public PlayMode getPlayMode() {
return mPlayMode;
}
public void setPlayMode(PlayMode playMode) {
mPlayMode = playMode;
}
public int getQueueIndex() {
return mQueueIndex;
}
public List<AlbumProgramItemBean> getQueue() {
return mQueue == null ? new ArrayList<AlbumProgramItemBean>() : mQueue;
}
private AlbumProgramItemBean getNextPlaying() {
switch (mPlayMode) {
case ORDER:
mQueueIndex = mQueueIndex + 1;
return getPlaying(mQueueIndex);
case LOOP:
mQueueIndex = (mQueueIndex + 1) % mQueue.size();
return getPlaying(mQueueIndex);
case RANDOM:
mQueueIndex = new Random().nextInt(mQueue.size()) % mQueue.size();
return getPlaying(mQueueIndex);
case REPEAT:
return getPlaying(mQueueIndex);
default:
break;
}
return null;
}
private AlbumProgramItemBean getPreviousPlaying() {
switch (mPlayMode) {
case ORDER:
mQueueIndex = mQueueIndex - 1;
return getPlaying(mQueueIndex);
case LOOP:
mQueueIndex = (mQueueIndex + mQueue.size() - 1) % mQueue.size();
return getPlaying(mQueueIndex);
case RANDOM:
mQueueIndex = new Random().nextInt(mQueue.size()) % mQueue.size();
return getPlaying(mQueueIndex);
case REPEAT:
return getPlaying(mQueueIndex);
default:
break;
}
return null;
}
private AlbumProgramItemBean getPlaying(int index) {
if (mQueue != null && !mQueue.isEmpty() && index >= 0 && index < mQueue.size()) {
return mQueue.get(index);
} else {
return null;
}
}
}
7、通知栏
创建通知栏
public class Notifier {
public static final String CHANNEL_ID = "channel_id_audio";
public static final String CHANNEL_NAME = "channel_name_audio";
public static final String CHANNEL_ID_DEFAULT = "channel_id_default";
public static final String EXTRA_NOTIFICATION = "com.sktcm.app.doctor.utils.audio.notification_dark";
private static final int NOTIFICATION_ID = 0x111;
private PlayerService playerService;
private NotificationManager notificationManager;
private boolean isDark;
private String packageName;
public static Notifier getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static Notifier instance = new Notifier();
}
private Notifier() {
}
public void init(PlayerService playerService) {
this.playerService = playerService;
this.notificationManager = (NotificationManager) playerService.getSystemService(NOTIFICATION_SERVICE);
// 前台服务
this.playerService.startForeground(NOTIFICATION_ID, buildNotification(playerService, AudioPlayer.getInstance().getNowPlaying()));
this.packageName = MyApplication.getContext().getPackageName();
this.isDark = isDarkNotificationBar(playerService);
}
public void stopForeground() {
this.playerService.stopForeground(true);
}
public void showPlayInfo(AlbumProgramItemBean source) {
this.notificationManager.notify(NOTIFICATION_ID, buildNotification(playerService, source));
}
private Notification buildNotification(Context context, AlbumProgramItemBean source) {
Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
//适配安卓8.0的消息渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
channel.enableLights(false);
channel.enableVibration(false);
notificationManager.createNotificationChannel(channel);
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setContentIntent(pendingIntent)
.setSmallIcon(R.mipmap.ic_launcher)
.setContent(getRemoteViews(playerService, source));
return builder.build();
}
private RemoteViews getRemoteViews(Context context, AlbumProgramItemBean source) {
int layoutId = isDark ? R.layout.notification_dark : R.layout.notification_light;
final RemoteViews remoteViews = new RemoteViews(context.getPackageName(), layoutId);
if (source == null) {
remoteViews.setTextViewText(R.id.tvTitle, "资源名称");
remoteViews.setViewVisibility(R.id.tvSubtitle, View.GONE);
remoteViews.setViewVisibility(R.id.btnPlay, View.GONE);
remoteViews.setViewVisibility(R.id.btnNext, View.GONE);
remoteViews.setImageViewResource(R.id.ivIcon, R.mipmap.ic_launcher);
} else {
remoteViews.setTextViewText(R.id.tvTitle, source.getName());
remoteViews.setViewVisibility(R.id.btnPlay, View.VISIBLE);
remoteViews.setViewVisibility(R.id.btnNext, View.VISIBLE);
remoteViews.setImageViewResource(R.id.btnPlay, getPlayIconRes());
if (Variables.nowPlayingAlbumData != null && !TextUtils.isEmpty(Variables.nowPlayingAlbumData.getName())) {
remoteViews.setViewVisibility(R.id.tvSubtitle, View.VISIBLE);
remoteViews.setTextViewText(R.id.tvSubtitle, Variables.nowPlayingAlbumData.getName());
Glide.with(context).load(Variables.nowPlayingAlbumData.getHead()).asBitmap().placeholder(R.mipmap.ic_launcher)
.error(R.drawable.icon_img_err).into(new SimpleTarget<Bitmap>(128, 128) {
@Override
public void onResourceReady(Bitmap bitmap, GlideAnimation glideAnimation) {
remoteViews.setImageViewBitmap(R.id.ivIcon, bitmap);
}
});
} else {
remoteViews.setViewVisibility(R.id.tvSubtitle, View.GONE);
remoteViews.setImageViewResource(R.id.ivIcon, R.mipmap.ic_launcher);
}
Intent playIntent = new Intent(NotificationReceiver.ACTION_STATUS_BAR);
playIntent.putExtra(NotificationReceiver.EXTRA, NotificationReceiver.EXTRA_PLAY);
PendingIntent playPendingIntent = PendingIntent.getBroadcast(context, 1, playIntent, PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.btnPlay, playPendingIntent);
Intent nextIntent = new Intent(NotificationReceiver.ACTION_STATUS_BAR);
nextIntent.putExtra(NotificationReceiver.EXTRA, NotificationReceiver.EXTRA_NEXT);
PendingIntent nextPendingIntent = PendingIntent.getBroadcast(context, 2, nextIntent, PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.btnNext, nextPendingIntent);
}
return remoteViews;
}
private int getPlayIconRes() {
if (AudioPlayer.getInstance().getStatus() == ManagedMediaPlayer.Status.STARTED) {
return getStartIcon();
} else {
return getPauseIcon();
}
}
private int getStartIcon() {
return isDark ? R.drawable.selector_play : R.drawable.selector_play_light;
}
private int getPauseIcon() {
return isDark ? R.drawable.selector_pause : R.drawable.selector_pause_light;
}
/**********************************************************************************************/
private static final double COLOR_THRESHOLD = 180.0;
private String DUMMY_TITLE = "DUMMY_TITLE";
private int titleColor = 0;
/**
* 判断是否Notification背景是否为黑色
*
* @param context
* @return
*/
public boolean isDarkNotificationBar(Context context) {
return !isColorSimilar(Color.BLACK, getNotificationTitleColor(context));
}
/**
* 获取Notification 标题的颜色
*
* @param context
* @return
*/
private int getNotificationTitleColor(Context context) {
int color = 0;
if (context instanceof AppCompatActivity) {
color = getNotificationColorCompat(context);
} else {
color = getNotificationColorInternal(context);
}
return color;
}
/**
* 判断颜色是否相似
*
* @param baseColor
* @param color
* @return
*/
public boolean isColorSimilar(int baseColor, int color) {
int simpleBaseColor = baseColor | 0xff000000;
int simpleColor = color | 0xff000000;
int baseRed = Color.red(simpleBaseColor) - Color.red(simpleColor);
int baseGreen = Color.green(simpleBaseColor) - Color.green(simpleColor);
int baseBlue = Color.blue(simpleBaseColor) - Color.blue(simpleColor);
double value = Math.sqrt(baseRed * baseRed + baseGreen * baseGreen + baseBlue * baseBlue);
return value < COLOR_THRESHOLD;
}
/**
* 获取标题颜色
*
* @param context
* @return
*/
private int getNotificationColorInternal(Context context) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID_DEFAULT);
builder.setContentTitle(DUMMY_TITLE);
Notification notification = builder.build();
RemoteViews contentView = notification.contentView;
if (contentView != null) {
ViewGroup notificationRoot = (ViewGroup) contentView.apply(context, new FrameLayout(context));
TextView title = (TextView) notificationRoot.findViewById(android.R.id.title);
if (title == null) {
//如果ROM厂商更改了默认的id
iteratorView(notificationRoot, new Filter() {
@Override
public void filter(View view) {
if (view instanceof TextView) {
TextView textView = (TextView) view;
if (DUMMY_TITLE.equals(textView.getText().toString())) {
titleColor = textView.getCurrentTextColor();
}
}
}
});
return titleColor == 0 ? Color.WHITE : titleColor;
} else {
return title.getCurrentTextColor();
}
} else {
return Color.BLACK;
}
}
private int getNotificationColorCompat(Context context) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
Notification notification = builder.build();
int layoutId = notification.contentView.getLayoutId();
ViewGroup notificationRoot = (ViewGroup) LayoutInflater.from(context).inflate(layoutId, null);
TextView title = (TextView) notificationRoot.findViewById(android.R.id.title);
if (title == null) {
final List<TextView> textViews = new ArrayList<>();
iteratorView(notificationRoot, new Filter() {
@Override
public void filter(View view) {
if (view instanceof TextView) {
textViews.add((TextView) view);
}
}
});
float minTextSize = Integer.MIN_VALUE;
int index = 0;
for (int i = 0, j = textViews.size(); i < j; i++) {
float currentSize = textViews.get(i).getTextSize();
if (currentSize > minTextSize) {
minTextSize = currentSize;
index = i;
}
}
textViews.get(index).setText(DUMMY_TITLE);
return textViews.get(index).getCurrentTextColor();
} else {
return title.getCurrentTextColor();
}
}
private void iteratorView(View view, Filter filter) {
if (view == null || filter == null) {
return;
}
filter.filter(view);
if (view instanceof ViewGroup) {
ViewGroup container = (ViewGroup) view;
for (int i = 0, j = container.getChildCount(); i < j; i++) {
View child = container.getChildAt(i);
iteratorView(child, filter);
}
}
}
interface Filter {
void filter(View view);
}
}
操作通知栏的按钮时发送广播,自定义NotificationReceiver,并静态注册,在NotificationReceiver中控制音频播放
public class NotificationReceiver extends BroadcastReceiver {
public static final String ACTION_STATUS_BAR = "com.sktcm.app.doctor.utils.audio.NOTIFICATION_ACTIONS";
public static final String EXTRA = "extra";
public static final String EXTRA_PLAY = "play_pause";
public static final String EXTRA_NEXT= "play_next";
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null || TextUtils.isEmpty(intent.getAction())) {
return;
}
String extra = intent.getStringExtra(EXTRA);
if (EXTRA_PLAY.equals(extra)) {
if (AudioPlayer.getInstance().getStatus() == ManagedMediaPlayer.Status.STARTED) {
AudioPlayer.getInstance().pause();
} else if (AudioPlayer.getInstance().getStatus() == ManagedMediaPlayer.Status.PAUSED){
AudioPlayer.getInstance().resume();
}
} else if (EXTRA_NEXT.equals(extra)){
AudioPlayer.getInstance().next();
}
}
}
8、前台服务
使用前台服务开始播放音频
public class PlayerService extends Service {
private static String ACTION_START = "ACTION_START";
private static String ACTION_PREVIOUS = "ACTION_PREVIOUS";
private static String ACTION_NEXT = "ACTION_NEXT";
private static String ACTION_GONE = "ACTION_GONE";
private static String ACTION_STOP = "ACTION_STOP";
public static void startPlayerService() {
Intent intent = new Intent(MyApplication.getContext(), PlayerService.class);
intent.setAction(ACTION_START);
MyApplication.getContext().startService(intent);
}
public static void playerPreviousService() {
Intent intent = new Intent(MyApplication.getContext(), PlayerService.class);
intent.setAction(ACTION_PREVIOUS);
MyApplication.getContext().startService(intent);
}
public static void playerNextService() {
Intent intent = new Intent(MyApplication.getContext(), PlayerService.class);
intent.setAction(ACTION_NEXT);
MyApplication.getContext().startService(intent);
}
public static void stopPlayerService() {
Intent intent = new Intent(MyApplication.getContext(), PlayerService.class);
MyApplication.getContext().stopService(intent);
}
@Override
public void onCreate() {
super.onCreate();
// 开始前台服务
Notifier.getInstance().init(this);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null && intent.getAction() != null) {
if (ACTION_START.equals(intent.getAction())) {
AudioPlayer.getInstance().play ();
} else if (ACTION_PREVIOUS.equals(intent.getAction())) {
AudioPlayer.getInstance().previous();
} else if (ACTION_NEXT.equals(intent.getAction())) {
AudioPlayer.getInstance().next();
}
}
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
AudioPlayer.getInstance().release();
Notifier.getInstance().stopForeground();
}
}
9、开始播放
传入播放列表,并开始服务即可播放
AudioPlayer.getInstance().setQueueAndIndex(list, 0);
PlayerService.startPlayerService();