一.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.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 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) {
}}
二。第二部分
surfaceview播放视频和弹幕
一.思路:
采用SurfaceView控件,因为需要频繁的更新UI,需要2个SurfaceView,一个用于播放视频,一个用于发送弹幕
注意:发送弹幕的surfaceview需要设置为透明背景,方法如下:
1
二.代码
1.xml布局代码
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.ytx0904.myapplication.MainActivity">
<!--第一个显示弹幕的-->
<SurfaceView
android:id="@+id/surface_text"
android:layout_width="match_parent"
android:layout_height="200dp" />
<!--第二个播放视频-->
<SurfaceView
android:id="@+id/surface_media"
android:layout_width="match_parent"
android:layout_height="200dp" />
</FrameLayout>
2.Java代码
package com.example.ytx0904.myapplication;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.media.MediaPlayer;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.IOException;
import javax.security.auth.login.LoginException;
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {
private MediaPlayer mediaPlayer;//播放视频和音频
private SurfaceView surfaceView,surfaceView2;//UI控件,显示画面
private SurfaceHolder surfaceHolder,surfaceHolder2;//不能直接操作surfaceView,但是可以通过surfaceHolder来操作surfaceView
int x=20;//文字的初始位置
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//TODO 1:搞mediaPalyer
try {
initPlayer();
} catch (IOException e) {
e.printStackTrace();
}
//TODO 2:搞surfaceView ,在surfaceview create生命周期的时候二者绑定
initView();
}
//初始化surfaceView
private void initView() {
//播放视频的
surfaceView=findViewById(R.id.surface_view);
surfaceHolder=surfaceView.getHolder();
surfaceHolder.addCallback(this);//设置回调监听:获得surfaceView的生命周期
//显示弹幕的,需要将surfaceView2设置为透明
surfaceView2=findViewById(R.id.surface_view2);
surfaceHolder2=surfaceView2.getHolder();
surfaceHolder2.addCallback(this);
//TODO :设置surfaceView2透明
surfaceView2.setZOrderOnTop(true);
surfaceHolder2.setFormat(PixelFormat.TRANSLUCENT);
}
//初始化player对象
private void initPlayer() throws IOException {
if(mediaPlayer==null){
mediaPlayer=new MediaPlayer();
}
mediaPlayer.reset();
mediaPlayer.setDataSource("http://uvideo.spriteapp.cn/video/2019/0512/56488d0a-7465-11e9-b91b-1866daeb0df1_wpd.mp4");
mediaPlayer.prepare();
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {//准备完毕了
mediaPlayer.start();
}
});
}
//创建完成
@Override
public void surfaceCreated(SurfaceHolder holder) {
if(holder==surfaceHolder){ //将holder和player二者结合在一起
mediaPlayer.setDisplay(surfaceHolder);
}else if(holder==surfaceHolder2){//显示弹幕
new Thread(){
@Override
public void run() {
super.run();
while (true){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//画笔
Paint paint = new Paint();
paint.setStrokeWidth(5);
paint.setColor(Color.GREEN);
paint.setTextSize(30);//设置文字的大小
Canvas canvas = surfaceHolder2.lockCanvas();
if(canvas==null){
break;}
//TODO :这里不将画布刷白,刷透明
canvas.drawColor(PixelFormat.TRANSPARENT, PorterDuff.Mode.CLEAR);
canvas.drawText("猴子成精了",x+=20,50,paint);
surfaceHolder2.unlockCanvasAndPost(canvas);
}
}
}.start();
}
}
//改变了
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
//销毁了
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//释放mediaplayer的资源
mediaPlayer.release();//释放资源
mediaPlayer=null;//回收掉
}