MediaPlayer之音乐播放器完整解析

MediaPlayer 是一款音乐播放器也是一款视频吧哦,这篇先介绍一个音乐播放器吧!

先看一下做好的效果吧!
MediaPlayer之音乐播放器完整解析_第1张图片

这次介绍的功能点:
1、本地Sd卡中搜索音乐,并保存在List中
2、服务与Activity交互,可以用AIDL 、Binder,这次我们采用AIDL
3、SeekBar与音量关联同步 并修改时间
4、自定义TextView画歌词 并实现缓缓移动效果,并实现歌词与进度条同步
5、解析本地歌词
6、自定义通知栏

OK。先做一下准备工作。

首先我们来看一下MadiaPlayer的工作原理:
MediaPlayer之音乐播放器完整解析_第2张图片

介绍之前大家先看一下这个图,然后看我的项目以后,回过头来再看一遍,会清晰好多。

在本地SD卡中查找音乐并且保存在List中:代码如下

    /**
     * 从本地sdcard获取数据
     * 1、遍历sdcard,通过后缀名获取(效率比较慢)
     * 2、通过内容提供者数据库中获取(Android系统内部有一个扫描器 ———— “媒体扫描器”,当手机开机完成后,或者sdcard插好后,
     *    系媒体扫描器会发送一个广播开始工作,开始扫描sdcard,然后把数据存到数据库中,从而提供给第三方使用)
     *   todo 内容提供者:解决应用与应用共享数据,通过内容提供者暴露出来使用。
     * 3、6.0动态权限
     */
    public void getDataFromLocal() {
        // 新建一个线程,比较耗时
        new Thread(){
            public void run(){
                // 获取内容提供者
                ContentResolver contentResolver = getContext().getContentResolver();
                Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                String[] obj = {
                        MediaStore.Audio.Media.DISPLAY_NAME,//音频文件在sdcard的名称
                        MediaStore.Audio.Media.DURATION,//音频总时长
                        MediaStore.Audio.Media.SIZE,//音频的文件大小
                        MediaStore.Audio.Media.DATA,//音频的绝对地址(音频的播放地址)
                        MediaStore.Audio.Media.ARTIST,//歌曲的演唱者
                };
                // 查询方法。 参数1:uri.  参数2: 查询的指令
                Cursor cursor = contentResolver.query(uri, obj, null, null, null);
                if(cursor != null) {
                    while(cursor.moveToNext()) {
                        MediaItem mediaItem = new MediaItem();
                        mList.add(mediaItem);
                        // TODO: 2018/1/4  查询的0 1等位置是根据contentResolver.query()方法传入的obj参数获取的。
                        //音频的名称
                        String name = cursor.getString(0);
                        mediaItem.setName(name);
                        //音频的时长
                        long duration = cursor.getLong(1);
                        mediaItem.setDuration(duration);
                        //音频的文件大小
                        long size = cursor.getLong(2);
                        mediaItem.setSize(size);
                        //音频的播放地址
                        String data = cursor.getString(3);
                        mediaItem.setData(data);
                        //艺术家
                        String artist = cursor.getString(4);
                        mediaItem.setArtist(artist);
                    }
                    cursor.close();
                }
                // 更新UI
                handler.sendEmptyMessage(MAG_HANDLER);
            }
        }.start();
    }

    /**
     * 6.0动态获取sd卡权限
     */
    public  boolean isGrantExternalRW(Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && activity.checkSelfPermission(
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            activity.requestPermissions(new String[]{
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
            }, 1);
            return false;
        }
        return true;
    }

创建AIDL让服务与Activity通信:
1、创建服务
2、在清单文件中添加:

        <service android:name=".serveice.MusicPlayerService">
            <intent-filter>
                <action android:name="com.atguigu.mobileplayer_OPENAUDIO" />
            intent-filter>
        service>

3、在工程目录下,选中main 然后右键创建AIDL文件,此文件就是声明方法,暴露的方法让Activity调用,从而Aidl在调用服务中的方法。AIDL可以说是一个代理类。完成服务与页面的交互:
我把所有的方法都写出来了,方便后期大家查询

interface IMusicPlayerService {
      /**
        * 根据位置打开对应的音频文件
        * @param position
        */
        void openAudio(int position);

       /**
        * 播放音乐
        */
        void start();

       /**
        * 播暂停音乐
        */
        void pause();

       /**
        * 停止
        */
        void stop();

       /**
        * 得到当前的播放进度
        * @return
        */
        int getCurrentPosition();

       /**
        * 得到当前音频的总时长
        * @return
        */
        int getDuration();

       /**
        * 得到艺术家
        * @return
        */
        String getArtist();

       /**
        * 得到歌曲名字
        * @return
        */
        String getName();

       /**
        * 得到歌曲播放的路径
        * @return
        */
        String getAudioPath();

       /**
        * 播放下一个视频
        */
        void next();

       /**
        * 播放上一个视频
        */
        void pre();

       /**
        * 设置播放模式
        * @param playmode
        */
        void setPlayMode(int playmode);

       /**
        * 得到播放模式
        * @return
        */
        int getPlayMode();

        /**
        * 是否正在播放
        */
         boolean isPlaying();

         /**
          拖动音频
         */
         void seekTo(int position);

         int getAudioSessionId();
         }

4、在服务中创建AIDL,然后AIDL调用服务的方法。如下:
我这里只是粘贴了一部分方法,全部的方法跟AIDL声明的方法一样。 最重要的不要忘记在服务中把stub返回去。 这样通过AIDL一个代理类就可以了

    @Override
    public IBinder onBind(Intent intent) {
        return stub;
    }
  /**
     * 创建AIDL 服务代理类, 通过代理类与服务交互
     */
    private IMusicPlayerService.Stub stub = new IMusicPlayerService.Stub(){
        /** 创建服务*/
        MusicPlayerService service = MusicPlayerService.this;

        @Override
        public IBinder asBinder() {
            return stub;
        }

        @Override
        public void openAudio(int position) {
            service.position = position;
            service.openAudio(position);
        }

        @Override
        public void start() throws RemoteException {
            service.start();
        }

        @Override
        public void pause() throws RemoteException {
            service.pause();
        }
}

5、Activity中获取AIDL:

    /**
     * 启动绑定服务
     */
    private void startBindService() {
        Intent intent = new Intent(this, MusicPlayerService.class);
        intent.setAction("com.atguigu.mobileplayer_OPENAUDIO");
        bindService(intent, con, Context.BIND_AUTO_CREATE);
        // 加上startService 当多次掉此方法时候, 不会实例化多个服务。
        startService(intent);
    }
   /**
     * 服务回掉结果
     */
    ServiceConnection con = new ServiceConnection() {
        /**
         * 服务链接成功
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder iBinder) {
            // 赋值代理类,与服务交互
            service = IMusicPlayerService.Stub.asInterface(iBinder);                     
        }

        /**
         * 服务链接失败
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {           
        }
    };

介绍来介绍以下MadiaPlayer的核心API
mMediaPlayer.start();//播放音乐
mMediaPlayer.pause();//播暂停音乐
mMediaPlayer.stop();//停止
mMediaPlayer.getDuration()//得到当前音频的总时长
mMediaPlayer.getCurrentPosition()//得到当前的播放进度
mMediaPlayer.isPlaying()//是否在播放音频
mMediaPlayer.seekTo(position);//拖动音频
mMediaPlayer.reset()// 重置
mMediaPlayer.setDataSource(mediaItem.getData())//设置url
mMediaPlayer.prepareAsync()//异步准备
//设置监听:准备好,播放完成,播放出错
mMediaPlayer.setOnPreparedListener(onpreparedListener);
mMediaPlayer.setOnCompletionListener(onCompletionListener);
mMediaPlayer.setOnErrorListener(onErrorListener);

首先我们先让音乐播放起来,首先我们是要捋顺清楚,是在Activity中服务的监听中调用openAudio()把要播放的文件传过去,然后在MadiaPlayer准备的监听中播放。代码如下:
Activity服务操作:

    /**
     * 启动绑定服务
     */
    private void startBindService() {
        Intent intent = new Intent(this, MusicPlayerService.class);
        intent.setAction("com.atguigu.mobileplayer_OPENAUDIO");
        bindService(intent, con, Context.BIND_AUTO_CREATE);
        // 加上startService 当多次掉此方法时候, 不会实例化多个服务。
        startService(intent);
    }

    /**
     * 服务回掉结果
     */
    ServiceConnection con = new ServiceConnection() {
        /**
         * 服务链接成功
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder iBinder) {
            // 赋值代理类,与服务交互
            service = IMusicPlayerService.Stub.asInterface(iBinder);
            if (service != null) {
                try {
                    // 根据位置打开对应的音频文件
                    if (!notification) {//从列表
                        service.openAudio(position);
                    } else {
                        // 初始化页面的信息
                        showViewData();

                    }

                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

        /**
         * 服务链接失败
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {
            try {
                if (service != null) {
                    service.stop();
                    service = null;
                }

            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    };

服务中MadiaPlayer 准备好监听中操作:

    /**
     * 播放器准备好监听
     */
    MediaPlayer.OnPreparedListener onpreparedListener = new MediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(MediaPlayer mp) {
            if(mMediaPlayer != null) {
                // 发送广播(通知外面获取歌曲名称)
                notifyChange(OPENAUDIO);

                // 播放
                start();

                // 设置状态信息
                setTileIconData();
            }
        }
    };

这样音乐就播放起来了! 接下来就是做一下初始化操作,就是通过 准备监听发送的广播中做操作,请看Activity中代码

    private void initData() {
        // 注册广播
        receiver = new MyReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(MusicPlayerService.OPENAUDIO);
        registerReceiver(receiver, intentFilter);
    }

    /**
     * 接收准备好广播
     */
    class MyReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            // 更新歌词,开始歌词同步
            showLyric();
            // 更新进度 时间 名称等
            showViewData();
        }
    }

我们先不说歌词,后面再说,先说进度条和SeekBar与音乐的同步:

    private void showViewData() {
        try {
            // 设置艺术家
            tvArtist.setText(service.getArtist());
            // 设置名称
            tvName.setText(service.getName());
            //设置进度条的最大值
            seekbarAudio.setMax(service.getDuration());
            // 设置时间
            tvTime.setText(utils.stringForTime(service.getCurrentPosition()) + "/" + utils.stringForTime(service.getDuration()));
            //发消息
            mHandler.sendEmptyMessage(PROGRESS);

        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

准备好以后,设置了歌曲的名称,作者等,还设置了进度条:
1、绑定进度条与歌曲时长做绑定, seekbarAudio.setMax(service.getDuration());
2、在Handler中更新进度条即可:

private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case PROGRESS:
                    // 更新seekbar
                    try {
                        // 更新时间
                        tvTime.setText(utils.stringForTime(service.getCurrentPosition()) + "/" + utils.stringForTime(service.getDuration()));

                        // 更新seekBar
                        seekbarAudio.setProgress(service.getCurrentPosition());

                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    mHandler.sendEmptyMessageDelayed(PROGRESS, 1000);
                    break;
                default:
                    break;
            }
        }
    };

OK,这就完成seekBar与音乐的同步了。和时间了

下面我们解析一下本地的歌词:
解析之前我们先看一下歌词的格式有两种 一种是 “[03:37.32][00:59.73]我在这里欢笑” 另一种是“[03:37.32]我在这里欢笑” ,每一句时间都对应一个时间戳,我们最终都要解析第二种一个时间戳对应一局歌词。

下面我们看上面准备好广播中的showLyric(); // 更新歌词,开始歌词同步 方法:

 /**
     * 更新歌词
     */
    private void showLyric() {
        //解析歌词
        LyricUtils lyricUtils = new LyricUtils();

        try {
            String path = service.getAudioPath();//得到歌曲的绝对路径

            //传歌词文件
            //mnt/sdcard/audio/beijingbeijing.mp3
            //mnt/sdcard/audio/beijingbeijing.lrc
            path = path.substring(0,path.lastIndexOf("."));
            File file = new File(path + ".lrc");
            if(!file.exists()){
                file = new File(path + ".txt");
            }
            lyricUtils.readLyricFile(file);//解析歌词

            showLyricView.setLyrics(lyricUtils.getLyrics());

        } catch (RemoteException e) {
            e.printStackTrace();
        }

        // 如果解析歌词存在, 就更新发消息
        if(lyricUtils.getLyrics().size() > 0) {
            mHandler.sendEmptyMessage(SHOW_LYRIC);
        }
    }
public class LyricUtils {

    /**
     * 得到解析好的歌词列表
     * @return
     */
    public ArrayList getLyrics() {
        return lyrics;
    }

    private ArrayList lyrics;

    /**
     * 是否存在歌词
     * @return
     */
    public boolean isExistsLyric() {
        return isExistsLyric;
    }

    /**
     * 是否存在歌词

     */
    private boolean isExistsLyric  = false;

    /**
     * 读取歌词文件
     * @param file /mnt/scard/audio/beijingbeijing.txt
     */
    public void readLyricFile(File file){
        if(file == null || !file.exists()){
            //歌词文件不存在
            lyrics = null;
            isExistsLyric = false;
        }else{
            //歌词文件存在
            //1.解析歌词 一行的读取-解析
            lyrics = new ArrayList<>();
            isExistsLyric = true;
            BufferedReader reader = null;
            try {
                reader = new BufferedReader(new InputStreamReader(new FileInputStream(file),getCharset(file)));

                String line = "";
                while ((line = reader.readLine())!= null){
                    line = parsedLyric(line);//
                }

                reader.close();


            } catch (Exception e) {
                e.printStackTrace();
            }


            //2.排序
            Collections.sort(lyrics, new Comparator() {
                @Override
                public int compare(Lyric lhs, Lyric rhs) {
                    if(lhs.getTimePoint() < rhs.getTimePoint()){
                        return  -1;
                    }else if(lhs.getTimePoint() > rhs.getTimePoint()){
                        return  1;
                    }else{
                        return 0;
                    }

                }
            });

            //3.计算每句高亮显示的时间  后一句减掉 当前这句
            for(int i=0;iif(i+1 < lyrics.size()){
                    Lyric twoLyric = lyrics.get(i+1);
                    oneLyric.setSleepTime(twoLyric.getTimePoint()-oneLyric.getTimePoint());
                }
            }
        }

    }


    /**
     * 判断文件编码
     * @param file 文件
     * @return 编码:GBK,UTF-8,UTF-16LE
     */
    public String getCharset(File file) {
        String charset = "GBK";
        byte[] first3Bytes = new byte[3];
        try {
            boolean checked = false;
            BufferedInputStream bis = new BufferedInputStream(
                    new FileInputStream(file));
            bis.mark(0);
            int read = bis.read(first3Bytes, 0, 3);
            if (read == -1)
                return charset;
            if (first3Bytes[0] == (byte) 0xFF && first3Bytes[1] == (byte) 0xFE) {
                charset = "UTF-16LE";
                checked = true;
            } else if (first3Bytes[0] == (byte) 0xFE
                    && first3Bytes[1] == (byte) 0xFF) {
                charset = "UTF-16BE";
                checked = true;
            } else if (first3Bytes[0] == (byte) 0xEF
                    && first3Bytes[1] == (byte) 0xBB
                    && first3Bytes[2] == (byte) 0xBF) {
                charset = "UTF-8";
                checked = true;
            }
            bis.reset();
            if (!checked) {
                int loc = 0;
                while ((read = bis.read()) != -1) {
                    loc++;
                    if (read >= 0xF0)
                        break;
                    if (0x80 <= read && read <= 0xBF)
                        break;
                    if (0xC0 <= read && read <= 0xDF) {
                        read = bis.read();
                        if (0x80 <= read && read <= 0xBF)
                            continue;
                        else
                            break;
                    } else if (0xE0 <= read && read <= 0xEF) {
                        read = bis.read();
                        if (0x80 <= read && read <= 0xBF) {
                            read = bis.read();
                            if (0x80 <= read && read <= 0xBF) {
                                charset = "UTF-8";
                                break;
                            } else
                                break;
                        } else
                            break;
                    }
                }
            }
            bis.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return charset;
    }

    /**
     * 解析一句歌词
     * @param line [02:04.12][03:37.32][00:59.73]我在这里欢笑
     * @return
     */
    private String parsedLyric(String line) {
        ////indexOf第一次出现[的位置
        int pos1 = line.indexOf("[");//0,如果没有返回-1

        int pos2 = line.indexOf("]");//9,如果没有返回-1

        if(pos1 ==0 && pos2 != -1){//肯定是由一句歌词

            //装时间
            long[] times = new long[getCountTag(line)];

            String strTime =line.substring(pos1+1,pos2) ;//02:04.12
            times[0] = strTime2LongTime(strTime);

            String content = line;
            int i = 1;
            while (pos1 ==0 && pos2 != -1){
                content = content.substring(pos2 + 1); //[03:37.32][00:59.73]我在这里欢笑--->[00:59.73]我在这里欢笑-->我在这里欢笑
                pos1 = content.indexOf("[");//0/-1
                pos2 = content.indexOf("]");//9//-1

                if(pos2 != -1 ){
                    strTime = content.substring(pos1 + 1, pos2);//03:37.32-->00:59.73
                    times[i] = strTime2LongTime(strTime);

                    if(times[i] == -1){
                        return  "";
                    }

                    i++;
                }

            }

            Lyric lyric = new Lyric();
            //把时间数组和文本关联起来,并且加入到集合中
            for(int j = 0;j < times.length;j++){

                if(times[j] !=0){//有时间戳

                    lyric.setContent(content);
                    lyric.setTimePoint(times[j]);
                    //添加到集合中
                    lyrics.add(lyric);
                    lyric = new Lyric();

                }
            }

            return  content;//我在这里欢笑
        }
        return "";
    }

    /**
     * 把String类型是时间转换成long类型
     * @param strTime 02:04.12
     * @return
     */
    private long strTime2LongTime(String strTime) {
        long result = -1;
        try{

            //1.把02:04.12按照:切割成02和04.12
            String[] s1 = strTime.split(":");
            //2.把04.12按照.切割成04和12
            String[] s2 = s1[1].split("\\.");

            //1.分
            long min = Long.parseLong(s1[0]);

            //2.秒
            long second = Long.parseLong(s2[0]);

            //3.毫秒
            long mil = Long.parseLong(s2[1]);

            result =  min * 60 * 1000 + second * 1000 + mil*10;
        }catch (Exception e){
            e.printStackTrace();
            result = -1;
        }

        return result;
    }

    /**
     * 判断有多少句歌词
     * @param line [02:04.12][03:37.32][00:59.73]我在这里欢笑
     * @return
     */
    private int getCountTag(String line) {
        int result = -1;
        String [] left = line.split("\\[");
        String [] right = line.split("\\]");

        if(left.length==0 && right.length ==0){
            result = 1;
        }else if(left.length > right.length){
            result = left.length;
        }else{
            result = right.length;
        }
        return result;
    }
}

其实这些做的操作就是,读取本地文件的歌词,一行一行的读歌词,以 [03:37.32]我在这里欢笑 这个形式保存在List中。 当然要把03:37.32转换成了long类型。那么歌词就解析好了。

下面我们自定义TextView画歌词,并实现缓缓移动效果:
public class ShowLyricView extends TextView {
/* 高亮画笔/
private Paint mLPaint;
/* 歌词画笔/
private Paint mCPaint;
/* 歌词存放集合/
private ArrayList mList;
/* 控件的宽/
private int width;
/* 空间的高/
private int height;
/* 歌词当前高亮的索引/
private int currentIndexes = 0;
/* 每个歌词的高/
private int textHight = 70;
/* 当前播放进度/
private int currentPositon;
/* 高亮显示的时间或者休眠时间/
private float sleepTime;
/* 时间戳,什么时刻到高亮哪句歌词/
private float timePoint;

public ShowLyricView(Context context) {
    this(context, null);
}

public ShowLyricView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public ShowLyricView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initView();
    initData();
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    width = w;
    height = h;
}

/**
 * 设置数据
 */
private void initData() {
    mList = new ArrayList<>();

    for (int i = 0; i < 10000; i++) {
        Lyric lyric = new Lyric();
        lyric.setContent("aaaaaaaaaaaaa" + i);
        lyric.setSleepTime(1000 + i);
        lyric.setTimePoint(1000 * i);
        mList.add(lyric);
    }
}

private void initView() {
    // 初始化高亮画笔
    mLPaint = new Paint();
    mLPaint.setColor(Color.GREEN);
    mLPaint.setTextSize(65);
    // 设置居中,  意思就是把锚点移动到View的中心点位置
    mLPaint.setTextAlign(Paint.Align.CENTER);

    // 初始化歌词画笔
    mCPaint = new Paint();
    mCPaint.setColor(Color.WHITE);
    mCPaint.setTextSize(48);
    // 设置居中,  意思就是把锚点移动到View的中心点位置
    mCPaint.setTextAlign(Paint.Align.CENTER);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    if(mList != null && mList.size() > 0) {

        //往上推移
        float plush = 0;
        if(sleepTime ==0){
            plush = 0;
        }else{
            //平移
            //这一句所花的时间 :休眠时间 = 移动的距离 : 总距离(行高)
            //移动的距离 =  (这一句所花的时间 :休眠时间)* 总距离(行高)
            //float delta = ((currentPositon-timePoint)/sleepTime )*textHeight;
            //屏幕的的坐标 = 行高 + 移动的距离
            plush = textHight + ((currentPositon-timePoint)/sleepTime )*textHight;
        }
        canvas.translate(0,-plush);

        // 画当前高亮歌词
        canvas.drawText(mList.get(currentIndexes).getContent(), width/2, height/2, mLPaint);
        int tempY = height / 2;//Y轴的中间坐标
        // 画前面部分歌词(循环画,以当前索引为基准,每次减减,计算每句歌词的高度)
        for(int i = currentIndexes - 1; i >= 0 ; i--) {
            String content = mList.get(i).getContent();
            tempY =  tempY- textHight;
            if(tempY < 0) {
                break;
            }
            canvas.drawText(content, width/2, tempY, mCPaint);
        }

        tempY = height / 2;//Y轴的中间坐标
        // 画后面歌词(循环画,以当前索引为基准,依次往后画)
        for(int j = currentIndexes + 1; j < mList.size(); j++) {
            String content = mList.get(j).getContent();
            tempY = tempY + textHight;
            if(tempY > height) {
                break;
            }
            canvas.drawText(content, width/2, tempY, mCPaint);
        }
    }else {
        canvas.drawText("没有歌词....", width/2, height/2, mLPaint);
    }
}

/**
 * 根据进度,计算那一句高亮
 * showNextLyric : 当前进度,也就是时间戳
 */
public void setshowNextLyric(int currentPosition) {
    this.currentPositon = currentPosition;
    if (mList == null || mList.size() == 0){
        return;
    }

    // 原理: 当前时间 < 下一句时间  &&  >= 上一句时间  找出i位置。就是当前的索引
    for(int i = 1; i < mList.size(); i++) {
      if(currentPosition < mList.get(i).getTimePoint()) {
          if(currentPosition >= mList.get(i - 1).getTimePoint()) {
              currentIndexes = i - 1;
              sleepTime = mList.get(currentIndexes).getSleepTime();
              timePoint = mList.get(currentIndexes).getTimePoint();
          }
      }
    }

    //重新绘制

// invalidate();//在主线程中
//子线程
new Thread(){
public void run(){
postInvalidate();
}
}.start();
}

public void setLyrics(ArrayList lyrics) {
    this.mList = lyrics;
}

}

自定义完成之后,我们就可以在Handler中 完成歌词和进度条同步了。
那么我们就差一个触发的时间了。其是触发时机已经写了,就是在上面更新歌词的时候,最后返回的List。如果不为null就发送Handler消息。下面我们直接看Handler代码。

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {        
                case SHOW_LYRIC:
                    // 更新歌词
                    try {
                        // 获取当前进度(其实就是时间)
                        int currentPosition = service.getCurrentPosition();

                        // 设置歌词同步
                        showLyricView.setshowNextLyric(currentPosition);

                        // 时时更新, 要先移除一下消息, 不然消息很快让栈溢出
                        mHandler.removeMessages(SHOW_LYRIC);
                        mHandler.sendEmptyMessage(SHOW_LYRIC);

                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;

                default:
                    break;
            }
        }
    };

现在已经实现所有的功能, 进度条与音乐 与歌词的同步。
下面我们来讲一下通知栏。
1、显示通知栏时机是在MaidaPlayer准备监听中实现的:

    /**
     * 播放器准备好监听
     */
    MediaPlayer.OnPreparedListener onpreparedListener = new MediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(MediaPlayer mp) {
            if(mMediaPlayer != null) {
                // 发送广播(通知外面获取歌曲名称)
                notifyChange(OPENAUDIO);

                // 播放
                start();

                // 设置状态信息
                setTileIconData();
            }
        }
    };

    /**
     * 设置状态栏信息
     */
    private void setTileIconData() {
        manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        //最主要 跳转到哪里
        Intent intent = new Intent(this, AudioPlayerActivity.class);
        intent.putExtra("notification",true);//标识来自状态拦
        PendingIntent pendingIntent = PendingIntent.getActivity(this,1,intent,PendingIntent.FLAG_UPDATE_CURRENT);// 2:请求吗 4:响应最后一次
        Notification notification = new Notification.Builder(this)
                .setSmallIcon(R.drawable.notification_music_playing)
                .setContentTitle("321音乐")
                .setContentText("正在播放:"+getName())
                .setContentIntent(pendingIntent)  // 包含一个意图,可以启动服务 广播 Activity
                .build();
        manager.notify(1, notification);
    }

2、取消状态栏是在暂停的时候:

  /**
     * 播暂停音乐
     */
    private void pause() {
        if(mMediaPlayer != null) {
            mMediaPlayer.pause();
            // 取消状态栏
            manager.cancel(1);
        }
    }

3、然后可以在AudioPlayerActivity 中通过 “notification” 标识来判断是通过哪里进来的:

     // 从状态传来的值  notification 为true 标题栏进来的 false正常进来的
        notification = getIntent().getBooleanExtra("notification", false);

        if (!notification) {
            position = getIntent().getIntExtra("position", 0);
        }
    }

OK整个音乐播放器就完了。

你可能感兴趣的:(技术分析)