一、前言

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();

更多相关文章

  1. Android自定义view实现圆形waveview
  2. Android非UI线程中更新UI界面
  3. Android(安卓)简单音乐播放器开发
  4. Android(安卓)Canvas绘制直方图
  5. WorkManager流程分析和源码解析
  6. android默认焦点设置的方法
  7. Android(安卓)图案解锁
  8. android 控件颜色随焦点变化实例
  9. Android(安卓)View获取焦点

随机推荐

  1. Android跳转到系统Wifi界面的方式
  2. android加载进度条
  3. 《android 学习》三、Intent 的概念及应
  4. Android Studio Mac 下卸载与安装
  5. Android 之两点触摸技术
  6. android 可编辑的下拉框 Demo
  7. Android开发插件Eclipse ADT
  8. android支付宝客户端html5网页偶尔无法自
  9. scrollTo 以及 scrollBy方法使用说明
  10. Android——XML解析