今天要写的是实现安卓自带的播放器也就是mediaplayer的实现示例。至于要写自己开发的播放器,本人水平还远远不够,而且光是那些音频视频的格式处理就够有的忙了。好了,我们正式开始讲讲mediaplayer类的实现方法吧。
首先,我们先讲讲关于mediaplayer的lifecircle。
一、Mediaplayer有两大类的创建方式:
1、 MediaPlayer mediaplayer = MediaPlayer.create(Context context,Uri uri). 这是其中一大类中的一种创建方式,主要是用MediaPlayer的静态方法create()来创建,其中参数有多种。
2、 MediaPlayer mediaplayer=new MediaPlayer() 这是第二种方式
这两种创建方式的区别在于,我们可以在第一种方式中,通过参数传入要播放的文件地址,并且不需要再执行prepare()或prepareAsync()方法。但是对于第二种方法,我们就需要通过setdataSource()来设置文件播放地址,以及接下来还要使用prepare()或prepareAsync()方法。
二、Mediaplayer的一些常见的方法
1、 mediaplayer.start() //开始播放
2、mediaplayer.pause() //暂停播放
3、mediaplayer.stop() //停止播放
4、mediaplayer.seekto(int position) //在position处开始播放
这些方法看上去很简单,但是要将这些方法结合起来,完成一个简单的播放器,却是不简单的。因为这就要讲到Mediaplayer的3个回调方法了。
三、Mediaplayer的3个回调方法
1、mediaPlayer.setOnPreparedListener(new OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {}
}
该回调方法在mediaplayer对象生成之后会执行一次,以后将不会执行。
2、mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {}
}
该回调方法在mediaplayer对象完成播放之后会执行。
3、mediaPlayer.setOnErrorListener(new OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {}
}
该回调方法在mediaplayer对象播放错误之后会执行。
我建议大家在使用的时候一定要实现这三个方法,特别是第三个回调方法,因为我们总是不可避免的在实现mediaplayer逻辑处理的时候,会出现一些问题。简单的实现是,我都是在第一个回调方法里用start()方法来启动播放,在第二个回调方法里,将mediaplayer对象的资源释放。
我感觉我要讲的基础知识也就这样了,接下来就是关于我做的播放器的实现方法来跟大家说一说。先说一下我的示例的功能吧,在文本框输入的路径,路径可以为本地的mp3或者是网络的MP3地址。但是我仅仅实现了本地播放时的进度条拖动处理,至于网络播放的进度条处理,需要调用seekto的回调方法,应该也是简单的,所以我没去弄。本例可以实现按钮控制音乐播放中的暂停,重播,停止,以及进度条的拖动处理。先上个截图吧。
比较简陋,其实我应该去用帧布局去实现这个界面的,感觉会高大上一点。
接下来就具体谈谈我的实现方法吧:
先讲逻辑处理:
1.点击播放按钮后,歌曲开始播放,播放按钮设置为不可点击(比较简单的一种处理方式,如果用单例模式来处理这种逻辑会比较好),暂停、重播按钮如果为不可点击,则设置为可点击状态,进度条开始工作。
2.点击暂停按钮后,音乐停止播放,进度条停止工作,暂停按钮的文字变为“继续”。再次点击后,音乐继续播放,进度条开始工作,按钮的文字变为“暂停”。
3.点击重播按钮后,音乐重新开始播放,进度条也是从0处开始播放,如果此时暂停按钮的文字为“继续”,则变为“暂停”
4.点击停止按钮后,音乐停止播放,进度条也停止工作,暂停,重播按钮设置为不可点击,播放按钮设置为可点击。
5.拖动进度条到任意处,音乐从该处开始播放,如果暂停按钮的文字为“继续”,则设置为“暂停”。
我的示例只能实现按顺序或按规律的点击按钮来触发事件的话,我也没必要写这么一篇博文了,要的就是可随意点击,针对用户不同的点击,做出正确的处理,而不出现“程序已停止工作”的错误发生,这就要求进行逻辑处理的代码的严谨和可靠。不过再讲具体的代码之前,还要再讲讲我的示例的整个的实现框架(不知道是否这个词用的对不对)吧。
音乐播放是可以在后台实现的一种功能,所以我采用了服务的方式来进行音乐播放,那么如何将服务和activity连接在一起呢,这就用到了充当代理人角色的对象“IBinder”,我只要在activity里获取到了这个对象,那么我就可以对它进行操作,实现service里的方法。下面是绑定服务的代码:
bindService(service, myconn, BIND_AUTO_CREATE);
service和BIND_AUTO_CREATE这两个参数我就不说了,大家都应该能明白,先看一下myconn这个接口的实现代码吧:
private class Myconn implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
musicPlayer = (MusicPlayer) service;
System.out.println("onServiceConnected");
//准备播放前的准备工作
musicPlayer.preplay(mediaPlayer, et_path.getText().toString(),
play, pause, surfaceHolder, handler);
ChangeProgress();
}
@Override
public void onServiceDisconnected(ComponentName name) {
System.out.println("onServiceDisconnected");
}
}
这里要讲的最重要的一点是MusicPlayer是我声明的一个接口,这就说到了一个问题,我们为什么不直接使用IBinder对象,而要将它转换成一个接口。为了安全,当然本例无关这个安全的问题,我这么写是为了养成良好的习惯。我们在Service类里会创建一个IBinder的子类,在这个子类里实现其中的方法。但是这个内部类为了安全性(有的方法我不想被访问到),使用private来进行封装,那就产生了一个问题在onServiceConnected(ComponentName name, IBinder service)我就拿不到这个内部类了。比如说我的这个内部类为myBinder。那么我就没法做Service.myBinder,这个操作是实现不了的,大家可以试试。所以
问题来了,怎样能一举两得,即能够保证内部类的安全,又要能实现里面的方法,答案就是接口。在接口里定义我要实现的方法,再将myBinder实现这个接口,具体重写接口里面的方法。这样在activity里我就能取到这个接口,就像我的实现代码一样musicPlayer = (MusicPlayer) service;。
接下来看一下接口中定义的方法。
public interface MusicPlayer {
public void preplay(MediaPlayer mediaPlayer, String path, Button play,
Button pause, SurfaceHolder surfaceHolder, Handler handler);
public void play(int position);
public void pause();
public void replay();
public void stop();
}
接下来就讲具体的实现代码了:
Preplay()将传递进来的参数赋都做,this.xxx=xxx;
@Override
public void play(final int position) {
//点击播放按钮后,将按钮设置为不可点击状态。开启一个新的线程,开始播放音乐
play.setEnabled(false);
new Thread() {
public void run() {
Playing(position);
}
}.start();
}
关于Playing(position)的实现代码:
public void Playing(final int position) {
try {
System.out.println("playing");
// 当改变进度条或者点重播按钮时,会进入if语句。如果mediaplayer对象不存在了
// 就需要重新生成。
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
// 开始播放
mediaPlayer.start();
// 从position位置开始播放
mediaPlayer.seekTo(position);
} else {
// 重新生成一个mediaplayer对象
//为了防止点击“暂停”按钮后,拖动进度条,会产生mediaPlayer.isPlaying()返回false
//但是此时mediaplayer还是不为空的,所以需要将此资源释放,重新生成mediaplayer对象,
//再次调用OnPreparedListener的回调方法
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
loadplay();//有关此方法的代码见下
}
// 开启子线程更新进度条
new Thread() {
public void run() {
isplaying = true;
while (isplaying) {
// 当mediaplayer不为空且在播放状态时更新进度,防止getcurrentposition()方法出错
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
// 获取当前播放到的位置
int prograss = mediaPlayer.getCurrentPosition();
Message message = handler.obtainMessage();
message.what = UPDATEROGRESS;
message.arg1 = prograss;
handler.sendMessage(message);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
}.start();
// 播放前的回调方法,生成mediaplayer对象后,只调用一次
mediaPlayer.setOnPreparedListener(new OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
System.out.println("onPrepared========");
// 将播放文件的长度传给handler对象,设置进度条的最大值
if (mediaPlayer != null) {
System.out.println("mediaplayer不为空");
Message message = handler.obtainMessage();
message.what = MAXPROGRESS;
message.arg1 = mediaPlayer.getDuration();
handler.sendMessage(message);
}
mediaPlayer.start();
mediaPlayer.seekTo(position);
}
});
// 播放完成后的回调方法
mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
System.out.println("onCompletion");
// 播放完成
isplaying = false;
// 释放资源
mediaPlayer.release();
mediaPlayer = null;
// 播放按钮可点击
play.setEnabled(true);
}
});
// 播放出错的回调方法
mediaPlayer.setOnErrorListener(new OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Toast.makeText(getApplicationContext(), "播放失败",
Toast.LENGTH_SHORT).show();
System.out.println(what);
isplaying = false;
return false;
}
});
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
关于loadplay()的实现代码:
public void loadplay() {
try {
// 初始化mediaplayer对象
mediaPlayer = new MediaPlayer();
// 设置音频流类型
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
// 设置数据来源
mediaPlayer.setDataSource(path);
// 异步准备,因为下面有用到线程
mediaPlayer.prepareAsync();
// 设置播放视频的容器
mediaPlayer.setDisplay(sh);
System.out.println("loadplay");
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
关于pause()的实现代码:
@Override
public void pause() {
System.out.println("pause");
//如果暂停按钮的文字为”继续“,则进行音乐的播放,将文字改为”暂停“
if ("继续".equals(pause.getText().toString())) {
mediaPlayer.start();
pause.setText("暂停");
return;
}
//如果当前对象存在并且正在播放中,则停止音乐播放,将文字改为”继续“
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
pause.setText("继续");
}
}
关于replay()的实现代码:
@Override
public void replay() {
System.out.println("replay");
//当正在播放时以及播放完成时,都要可以进行重播
if (mediaPlayer != null) {
//如果暂停按钮的文字为“继续“,则将暂停按钮的文字改为“暂停“
if ("继续".equals(pause.getText().toString())) {
pause.setText("暂停");
}
play.setEnabled(false);
//从位置0开始播放
play(0);
}
}
有关stop()的实现代码:
@Override
public void stop() {
System.out.println("stop");
//当前对象不为空并且正在播放时 ,执行停止操作
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
Toast.makeText(getApplicationContext(), "停止播放",
Toast.LENGTH_SHORT).show();
mediaPlayer.stop();
// 停止后要释放资源
mediaPlayer.release();
// 将mediaplayer变成空
mediaPlayer = null;
// "播放按钮"设置为可点击
play.setEnabled(true);
// 标记该文件为“不在播放”
isplaying = false;
}
}
}
下面是关于activity中按钮点击的一些设置:
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.play:
musicPlayer.play(0);
if(!replay.isEnabled()){
replay.setEnabled(true);
}if(!pause.isEnabled()){
pause.setEnabled(true);
}
break;
case R.id.pause:
musicPlayer.pause();
break;
case R.id.replay:
musicPlayer.replay();
break;
case R.id.stop:
musicPlayer.stop();
replay.setEnabled(false);
pause.setEnabled(false);
break;
}
}
接下来就是更新进度条的操作了:
// 更新进度条
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
//获取歌曲的长度,设置为最大进度
case MAXPROGRESS:
int max = msg.arg1;
sb.setMax(max);
System.out.println("max:" + max);
break;
//获取歌曲当前的位置,更新进度条
case UPDATEROGRESS:
int update = msg.arg1;
sb.setProgress(update);
//System.out.println("ACTIVITY里更新进度:" + update);
break;
}
}
};
接下来是进度条的进度改变的监听事件的设置:
sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
//当放开对进度条的拖动时,进行从该位置开始播放音乐
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// 获取当前进度
int position = seekBar.getProgress();
// 从当前进度开始播放
musicPlayer.play(position);
//改变“暂停按钮”的文字为“暂停”
if("继续".equals(pause.getText().toString())){
pause.setText("暂停");
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
//System.out.println("onProgressChanged");
}
});
代码就这些了。果然贴代码是最爽的,有想要工程文件的可以私信给我。