Android(安卓)使用MediaPlayer播放网络音频
一、前言
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 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 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 mQueue) { this.mQueue = mQueue; } public void setQueueAndIndex(List 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> call = HttpClientFactory.getAppApiClientInstance().getAlbumAddress(source.getId()); call.enqueue(new Callback>() { @Override public void onResponse(Call> call, Response> 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> 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 getQueue() { return mQueue == null ? new ArrayList() : 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(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 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();
更多相关文章
- Android自定义view实现圆形waveview
- Android非UI线程中更新UI界面
- Android(安卓)简单音乐播放器开发
- Android(安卓)Canvas绘制直方图
- WorkManager流程分析和源码解析
- android默认焦点设置的方法
- Android(安卓)图案解锁
- android 控件颜色随焦点变化实例
- Android(安卓)View获取焦点