第6天使用MediaPlayer完成音乐播放器

  • 效果图
  • 一.项目需求:
  • 二.MediaPlayer的生命周期图
  • 三.MediaPlayer常用的方法:
  • 四.MediaPlayer使用注意的地方:
  • 五.详细代码:


效果图

一.项目需求:

1.播放本地音乐:mediaPlayer.setDataSource(文件路径); 2.播放网络音乐:mediaPlayer.setDataSource
3.完成音乐列表,实现播放/暂停/上一首/下一首/播放模式切换(随机播放/单曲循环/顺序播放)

二.MediaPlayer的生命周期图

MediaPlayer设置循环播放 Android media player怎么单曲循环_播放音乐

三.MediaPlayer常用的方法:

  • void setDataSource(String path) :通过一个具体的路径来设置MediaPlayer的数据源,path可以是本地的一个路径,也可以是一个网络路径
  • int getCurrentPosition() 获取当前播放的位置
  • int getAudioSessionId() 返回音频的session ID
  • int getDuration() 得到文件的时间
  • boolean isLooping () 是否循环播放
  • boolean isPlaying() 是否正在播放
  • void pause () 暂停
  • void start () 开始
  • void stop () 停止
  • void prepare() 同步的方式装载流媒体文件。
  • void prepareAsync() 异步的方式装载流媒体文件。
  • void reset() 重置MediaPlayer至未初始化状态。
  • void release () 回收流媒体资源。
  • void seekTo(int msec) 指定播放的位置(以毫秒为单位的时间)
  • void setLooping(boolean looping) 设置是否单曲循环
  • setOnCompletionListener(MediaPlayer.OnCompletionListener listener) 网络流媒体播放结束时回调
  • setOnErrorListener(MediaPlayer.OnErrorListener listener) 发生错误时回调 **
  • setOnPreparedListener(MediaPlayer.OnPreparedListener listener):当装载流媒体完毕的时候回调。

注意:reset方法是将数据清空。 release方法是将媒体对象回收掉

四.MediaPlayer使用注意的地方:

  1. 尽量保证在一个App中只有MediaPalyer对象(单例模式)
  2. 在使用start()播放流媒体之前,需要装载流媒体资源。这里最好使用prepareAsync()用异步的方式装载流媒体资源。因为流媒体资源的装载是会消耗系统资源的,在一些硬件不理想的设备上,如果使用prepare()同步的方式装载资源,可能会造成UI界面的卡顿,这是非常影响用于体验的。因为推荐使用异步装载的方式,为了避免还没有装载完成就调用start()而报错的问题,需要绑定MediaPlayer.setOnPreparedListener()事件,它将在异步装载完成之后回调。异步装载还有一个好处就是避免装载超时引发ANR((Application Not Responding)错误。
mediaPlayer = new MediaPlayer();
                mediaPlayer.setDataSource(path);
                // 通过异步的方式装载媒体资源
                mediaPlayer.prepareAsync();
                mediaPlayer.setOnPreparedListener(new OnPreparedListener() {                    
                    @Override
                    public void onPrepared(MediaPlayer mp) {
                        // 装载完毕回调
                        mediaPlayer.start();
                    }
                });
  1. 使用完MediaPlayer需要回收资源。MediaPlayer是很消耗系统资源的,所以在使用完MediaPlayer,不要等待系统自动回收,最好是主动回收资源。
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
            mediaPlayer.stop();
            mediaPlayer.release();
            mediaPlayer = null;
        }
  1. 对于单曲循环之类的操作,除了可以使用setLooping()方法进行设置之外,还可以为MediaPlayer注册回调函数,MediaPlayer.setOnCompletionListener(),它会在MediaPlayer播放完毕被回调。
// 设置循环播放
//                mediaPlayer.setLooping(true);
                mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
                    @Override
                    public void onCompletion(MediaPlayer mp) {
                        // 在播放完毕被回调
                        play();                        
                    }
                });
  1. 因为MediaPlayer一直操作的是一个流媒体,所以无可避免的可能一段流媒体资源,前半段可以正常播放,而中间一段因为解析或者源文件错误等问题,造成中间一段无法播放问题,需要我们处理这个错误,否则会影响Ux(用户体验)。可以为MediaPlayer注册回调函数setOnErrorListener()来设置出错之后的解决办法,一般重新播放或者播放下一个流媒体即可。

五.详细代码:

(0)在Activity中主动获取权限:
在安卓6.0以后除了需要在manifest文件中注册各权限外,还需要在代码中动态调用安卓API申请权限。

public class MediaActivity extends AppCompatActivity {
    private ViewPager viewPager;
    private TabLayout tabLayout;
    private ArrayList<Fragment> list=new ArrayList<>();
    private MyFragmentPagerAdapter myFragmentPagerAdapter;
    //列出需要主动获取的权限WRITE_EXTERNAL_STORAGE/READ_EXTERNAL_STORAGE读写SD卡权限
    private String[] permissiones=new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE};

    @RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_media);
        initView();//初始化布局 ViewPager_TabLayout_Fragent
        checkPermission();//检测这些权限是否已经申请过

    }
    @RequiresApi(api = Build.VERSION_CODES.M)
    private void checkPermission(){
        int readCode = ActivityCompat.checkSelfPermission(this,permissiones[0]);
        int writeCode = ActivityCompat.checkSelfPermission(this,permissiones[1]);
        if (!(readCode== PackageManager.PERMISSION_GRANTED)||!(writeCode==PackageManager.PERMISSION_GRANTED)){//只要有一项没有申请到
            //主动去申请权限
            requestPermissions(permissiones,100);//要动态申请的权限;100请求状态码->判断哪次动态申请的
        }
    }

    //动态权限申请回调方法->获取动态申请结果
    //permissions->动态申请的权限
    //grantResults->动态权限申请结果->若干结果中1个权限申请失败->申请失败
    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (100==requestCode){
            boolean flag = true;
            //遍历请求结果数组->若有一个权限失败->flag=false->本次申请失败
            for (int code : grantResults){
                if (code!=PackageManager.PERMISSION_GRANTED) {
                    flag = false;
                    break;
                }
            }
            if (flag)
                Toast.makeText(this,"申请成功",Toast.LENGTH_SHORT).show();
            else
                Toast.makeText(this,"申请失败",Toast.LENGTH_SHORT).show();
        }
    }

    private void initView() {
        viewPager=findViewById(R.id.vp);
        tabLayout=findViewById(R.id.tab);
        list.add(new Fragment_one());
        list.add(new Fragment_two());
        myFragmentPagerAdapter=new MyFragmentPagerAdapter(getSupportFragmentManager(),list);
        viewPager.setAdapter(myFragmentPagerAdapter);
        tabLayout.setupWithViewPager(viewPager);
    }
}

(1)Music实体类:

public class Music implements Serializable{
    private String name;
    private String singer;
    private long duration;
    private String data;

    public Music(String name, String singer, long duration, String data) {
        this.name = name;
        this.singer = singer;
        this.duration = duration;
        this.data = data;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSinger() {
        return singer;
    }

    public void setSinger(String singer) {
        this.singer = singer;
    }

    public long getDuration() {
        return duration;
    }

    public void setDuration(long duration) {
        this.duration = duration;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}

(3)使用内容提供者获得音频资料并展现在RecyclerView列表中

public class Fragment_one extends Fragment implements BaseRecyclerViewAdapter.OnItemClick {

    private  ContentResolver contentResolver;//内容解析者
    private Uri uri;//uri
    private String[] strs;//要查询到字段
    private ArrayList<Music> list=new ArrayList<>();
    private RecyclerView rv;
    private BaseRecyclerViewAdapter<Music> adapter;//万能适配器

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_one, null);
        rv=view.findViewById(R.id.music_rv);
        initdata();//获取数据
        return view;
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    private void initdata() {
        contentResolver= getActivity().getContentResolver();//获得解析者
        uri= MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;//确定Uri
        //TITLE:歌名 DATA:文件路径 DURATION:总时长,毫秒值为单位 ARTIST:歌手名称
        strs=new String[]{MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.DURATION, MediaStore.Audio.Media.ARTIST};
        Cursor cursor = contentResolver.query(uri, strs,null,null,null,null);
        while (cursor.moveToNext()){
            list.add(new Music(cursor.getString(0),cursor.getString(3),cursor.getLong(2),cursor.getString(1)));
        }
        //万能适配
        adapter=new BaseRecyclerViewAdapter<Music>(R.layout.item_one,getActivity(),list) {
            @Override
            public void bind(BaseViewHolder holder, int position) {
                holder.setText(R.id.tv_singer,list.get(position).getSinger());
                holder.setText(R.id.tv_title,list.get(position).getName());
            }
        };
        rv.setAdapter(adapter);
        rv.setLayoutManager(new LinearLayoutManager(getContext()));
        adapter.setClick(this);//设置点击事件
    }
    private MediaPlayer mediaPlayer;

    @Override
    public void ItemClick(int index) {
        Intent intent = new Intent(getActivity(),MusicActivity.class);
        intent.putExtra("music",list.get(index));
        startActivity(intent);
    }
}

(4)点击列表中的一项播放音乐

public class MusicActivity extends AppCompatActivity implements View.OnClickListener,SeekBar.OnSeekBarChangeListener {
    private SeekBar seekBar;
    private Button paly,pause,stop,pre,next;
    private SingleMediaPlayer  mediaPlayer;
    private Music music;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_music);
        initView();
        initData();

    }

    private void initData() {
        seekBar.setMax((int) music.getDuration());//设置进度条的最大值为音乐的时长
        music = (Music) getIntent().getSerializableExtra("music");//音乐对象
        if(mediaPlayer==null){
            mediaPlayer=SingleMediaPlayer.getMediaPlayer();//单粒模式的mediaPlayer对象
        }
        try {
            mediaPlayer.reset();//重置数据
            mediaPlayer.setDataSource(music.getData());//设置新的数据源
            mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {//设置准备播放音乐
                @Override
                public void onPrepared(MediaPlayer mediaPlayer) {
                    mediaPlayer.start();//开始播放音乐
                }
            });

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

        //定时器更新进度条
        Timer timer=new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        seekBar.setProgress(mediaPlayer.getCurrentPosition());
                    }
                });
            }
        },0,1000);

    }


    private void initView() {
        seekBar=findViewById(R.id.seekbar);
        paly=findViewById(R.id.play);
        pause=findViewById(R.id.pause);
        stop=findViewById(R.id.stop);
        pre=findViewById(R.id.pre);
        next=findViewById(R.id.next);
        paly.setOnClickListener(this);
        pause.setOnClickListener(this);
        stop.setOnClickListener(this);
        pre.setOnClickListener(this);
        next.setOnClickListener(this);
        seekBar.setOnSeekBarChangeListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.play:
                if(!mediaPlayer.isPlaying()){
                    mediaPlayer.start();
                }
                break;
            case R.id.pause:
                if(mediaPlayer.isPlaying()){
                    mediaPlayer.pause();
                }
                break;
            case R.id.pre:

                break;
            case R.id.next:

                break;
            case R.id.stop:
                mediaPlayer.stop();
                break;
        }
    }

    @Override
    public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
        if(b){//判断是否来自用户的拖动
            mediaPlayer.seekTo(i);
        }
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {

    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {

    }
}