天天看點

第6天使用MediaPlayer完成音樂播放器(内容提供者獲得本地所有音頻)

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

    • 效果圖
    • 一.項目需求:
    • 二.MediaPlayer的生命周期圖
    • 三.MediaPlayer常用的方法:
    • 四.MediaPlayer使用注意的地方:
    • 五.詳細代碼:

效果圖

一.項目需求:

1.播放本地音樂:mediaPlayer.setDataSource(檔案路徑);

2.播放網絡音樂:mediaPlayer.setDataSource(“http://www.ytmp3.cn/down/57799.mp3”);

3.完成音樂清單,實作播放/暫停/上一首/下一首/播放模式切換(随機播放/單曲循環/順序播放)

二.MediaPlayer的生命周期圖

詳細說明:https://www.jianshu.com/p/0131ab02e0fe

第6天使用MediaPlayer完成音樂播放器(内容提供者獲得本地所有音頻)

三.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) {

    }
}