Android 使用MediaPlayer播放网络音频

一、前言

MediaPlayer是Android自带的一个多媒体播放类,可以播放音视频流或者本地音视频文件。MediaPlayer方法的调用需要在一定的状态下,下图是一个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();
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352