第6天使用MediaPlayer完成音乐播放器
- 效果图
- 一.项目需求:
- 二.MediaPlayer的生命周期图
- 三.MediaPlayer常用的方法:
- 四.MediaPlayer使用注意的地方:
- 五.详细代码:
效果图
一.项目需求:
1.播放本地音乐:mediaPlayer.setDataSource(文件路径); 2.播放网络音乐:mediaPlayer.setDataSource
3.完成音乐列表,实现播放/暂停/上一首/下一首/播放模式切换(随机播放/单曲循环/顺序播放)
二.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使用注意的地方:
- 尽量保证在一个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) {
}
}