第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
三.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使用注意的地方:
- 盡量保證在一個App中隻有MediaPalyer對象(單例模式)
- 在使用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();
}
});
- 使用完MediaPlayer需要回收資源。MediaPlayer是很消耗系統資源的,是以在使用完MediaPlayer,不要等待系統自動回收,最好是主動回收資源。
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
- 對于單曲循環之類的操作,除了可以使用setLooping()方法進行設定之外,還可以為MediaPlayer注冊回調函數,MediaPlayer.setOnCompletionListener(),它會在MediaPlayer播放完畢被回調。
// 設定循環播放
// mediaPlayer.setLooping(true);
mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
// 在播放完畢被回調
play();
}
});
- 因為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) {
}
}