1 介绍
MediaPlayer类是Android开发中用于控制音频/视频文件和流的播放。
下图显示了MediaPlayer对象的生命周期和状态。 椭圆表示MediaPlayer对象可能驻留的状态。弧表示驱动对象状态转换的回放控制操作。 有两种类型的弧线。 带有单箭头的弧表示同步方法调用,而带有双箭头的则表示异步方法调用。
从上图可以知道MediaPlayer有以下状态:
- 当一个MediaPlayer对象刚刚使用new创建,或者reset()被调用后,它处于Idle状态(闲置状态); 在release()被调用后,它处于End状态。 这两个状态之间是MediaPlayer对象的生命周期。
1) 新构造的MediaPlayer对象和在调用reset()方法之后的MediaPlayer对象之间存在细微但重要的区别。在两种情况下,在idle状态下调用诸如getCurrentPosition(), getDuration(), getVideoHeight(), getVideoWidth(), setAudioAttributes(AudioAttributes), setLooping(boolean), setVolume(float, float), pause(), start(), stop(), seekTo(long, int), prepare() 或者prepareAsync() 之类的方法会出现错误。如果在构造MediaPlayer对象后立即调用这些方法中的任何一个,则用户提供的回调方法OnErrorListener.onError()将不会被内部播放器引擎调用,并且对象状态保持不变; 但是如果这些方法在reset()之后被调用,那么用户提供的 OnErrorListener.onError()方法会被内部的播放器引擎调用,并且这个对象将被转移到Error状态。
2) 建议一旦MediaPlayer对象不再使用,应该立即调用release()方法,以便与MediaPlayer对象关联的内部播放器引擎使用的资源可以立即释放。 资源可能包括硬件加速组件等单一资源。一旦MediaPlayer对象处于End状态,就不能再使用它,并且无法将其返回到任何其他状态。
3) 此外,使用new创建的MediaPlayer对象处于Idle状态,而使用重载的便捷创建方法之一创建的对象不处于Idle状态。 事实上,如果使用create方法创建成功,则这些对象处于Prepared状态。 - 一般来说,由于各种原因,如音频/视频格式不支持,音频/视频交织不良,分辨率过高等,某些播放控制操作可能会失败。 因此,在这种情况下,错误报告和恢复是一个重要的问题.在所有这些错误情况下,如果通过setOnErrorListener(android.media.MediaPlayer.OnErrorListener)事先注册了OnErrorListener,则内部播放器引擎将调用用户提供的OnErrorListener.onError()方法。
1) 需要注意的是,一旦发生错误,即使应用程序未注册错误侦听器,MediaPlayer对象也会进入错误状态。
2) 为了重用处于“错误”状态的MediaPlayer对象并从错误中恢复,可以调用reset()将对象恢复到其空闲状态。 - 调用setDataSource(FileDescriptor)或setDataSource(String)或setDataSource(Context,Uri)或setDataSource(FileDescriptor,long,long)或setDataSource(MediaDataSource)将处于空闲状态的MediaPlayer对象转换为Initialized状态。
1) 如果在任何其他状态下调用setDataSource(),则会引发IllegalStateException。 - MediaPlayer对象必须先进入准备状态,然后才能开始播放。
1) 有两种方式(同步和异步),可以达到Prepared状态:调用prepare()(同步),在方法调用返回时将对象转移到Prepared状态,或调用prepareAsync()( 异步),在调用返回之后首先将对象转移到准备状态,而内部播放引擎继续进行准备工作的其余部分,直到准备工作完成。 准备完成或prepare()调用返回时,如果预先通过setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener)注册了OnPreparedListener,则内部播放器引擎将调用用户提供的OnPreparedListener接口的onPrepared()的回调方法。
2) 处于“准备”状态时,可通过调用相应的设置方法来调整诸如音频/音量,屏幕亮度,循环等属性。 - 要开始播放,必须调用start()。 在start()成功返回后,MediaPlayer对象处于Started状态。 可以调用isPlaying()来测试MediaPlayer对象是否处于Started状态。
1) 当处于Started状态时,如果已经通过setOnBufferingUpdateListener(OnBufferingUpdateListener)事先注册了OnBufferingUpdateListener,则内部播放器引擎会调用用户提供的OnBufferingUpdateListener.onBufferingUpdate()回调方法。 此回调允许应用程序在流式传输音频/视频时跟踪缓冲状态。
2) 调用start()对已处于Started状态的MediaPlayer对象是没有影响的。 - 可以暂停和停止播放,并可以调整当前的播放位置。 可以通过暂pause()暂停播放。 当对pause()的调用返回时,MediaPlayer对象进入暂停状态。 请注意,从启动状态到暂停状态(反之亦然)在播放引擎中异步发生。 在调用isPlaying()方法更新状态之前可能需要一段时间,对于流式内容,可能需要几秒钟的时间。
1) 调用start()以恢复已暂停的MediaPlayer对象的播放,并且恢复的播放位置与暂停的位置相同。 当对start()的调用返回时,暂停的MediaPlayer对象将返回到Started状态。
2) 调用pause()对已处于暂停状态的MediaPlayer对象没有影响。 - 调用stop()将停止播放,并使MediaPlayer处于已启动,已暂停,准备或播放完成状态以进入已停止状态。
1) 一旦处于停止状态,直到调用prepare()或prepareAsync()以再次将MediaPlayer对象设置为“准备”状态,才能开始播放。
2) 调用stop()对已处于停止状态的MediaPlayer对象没有影响。 - 可以通过调用seekTo(long,int)来调整播放位置。
1) 尽管seekTo(long,int)异步调用立即返回,但实际的搜索操作可能需要一段时间才能完成,特别是对于正在流式传输的音频/视频。当实际的查找操作完成时,如果已经通过setOnSeekCompleteListener(OnSeekCompleteListener)事先注册了OnSeekCompleteListener,则内部播放器引擎会调用用户提供的OnSeekComplete.onSeekComplete()。
2) 请注意seekTo(long,int)也可以在其他状态下调用,例如Prepared,Paused和PlaybackCompleted状态。当在这些状态下调用seekTo(long,int)时,如果流有视频并且请求的位置有效,则将显示一个视频帧。
3) 此外,可以通过调用getCurrentPosition()来获取当前的实际播放位置。 - 当播放到达流尾时,播放完成。
1) 如果使用setLooping(boolean)将循环模式设置为true,则MediaPlayer对象应保持在Started状态。
2) 如果循环模式设置为false,则如果通过setOnCompletionListener(OnCompletionListener)事先注册了OnCompletionListener,则播放器引擎将调用用户提供的回调方法OnCompletion.onCompletion()。调用回调信号表明对象现在处于PlaybackCompleted状态。
3) 在PlaybackCompleted状态下,调用start()可以从音频/视频源的开头重新开始播放。 - 如果使用setLooping(boolean)将循环模式设置为true,则MediaPlayer对象应保持在Started状态。
1) 如果循环模式设置为false,则如果通过setOnCompletionListener(OnCompletionListener)事先注册了OnCompletionListener,则播放器引擎将调用用户提供的回调方法OnCompletion.onCompletion()。 调用回调信号表明对象现在处于PlaybackCompleted状态。
2) 在PlaybackCompleted状态下,调用start()可以从音频/视频源的开头重新开始播放。
下面是MediaPlay提供的方法:
2 案列一-音乐播放
我们通过混合开启服务方式实现音乐播放案例:
MainActivity代码:
/**
* //1.播放音乐后台进行 需要在服务中进行 创建服务
//2.在服务中 bind服务 服务返回 代理人对象
//3.混合开启服务 start bind 在activity 销毁的时候记得解除绑定
//4.activity调用服务的方法
//5.音乐播放的逻辑添加进去
//6.音乐播放的进度
//1 添加上一个 sb
//2在服务中 用player 获取 最大值 和进度
//3定义一个定时器 每个1秒发送一个 当前的播放位置的 进度 给sb handler
//4在activity 中 给sb设置最大值和 进度
//5拖动 需要给sb设置个 sb 发生改变的监听 获取到 拖动的位置
//6在服务中 添加一个方法 到指定位置播放 seekto
//7调用 callseek方法设置音乐的 拖动播放的位置
//8当音乐播放完成的时候 关闭定时器
*/
public class MainActivity extends Activity {
private static SeekBar sb;
private MusicService.MyBinder myBinder;
private MyServiceConnection conn;
public static Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
int duration = msg.arg1;
int progress = msg.arg2;
sb.setMax(duration);
sb.setProgress(progress);
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sb = ((SeekBar) findViewById(R.id.sb));
sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
int progress = seekBar.getProgress();
myBinder.callSeekTo(progress);
}
});
// 开启服务 绑定服务
Intent intent = new Intent();
intent.setClass(this, MusicService.class);
startService(intent);
conn = new MyServiceConnection();
bindService(intent, conn, BIND_AUTO_CREATE);
}
//播放
public void play(View view) {
myBinder.callPlay(this);
}
//暂停
public void pause(View view) {
myBinder.callPause();
}
//继续播放
public void replay(View view) {
myBinder.callResume();
}
public class MyServiceConnection implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myBinder = (MusicService.MyBinder) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(conn);
}
}
MusicService代码:
public class MusicService extends Service {
private String TAG = "MusicService";
private MediaPlayer player;
public MusicService() {
}
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
@Override
public void onCreate() {
super.onCreate();
player = new MediaPlayer();
}
public class MyBinder extends Binder implements MusicInterface{
@Override
public void callPlay(Activity activity) {
play(activity);
}
@Override
public void callPause() {
pause();
}
@Override
public void callResume() {
resume();
}
@Override
public void callSeekTo(int position) {
seekTo(position);
}
}
private void seekTo(int position) {
Log.e(TAG, "当前位置" + position);
player.seekTo(position);
}
private void resume() {
Log.e(TAG, "继续播放");
player.start();
}
private void pause() {
Log.e(TAG, "暂停");
player.pause();
}
private void play(Activity context) {
Log.e(TAG, "播放");
player.reset();
PermisionUtils.verifyStoragePermissions(context);//动态获取权限
File file = new File(Environment.getExternalStorageDirectory(), "123.mp3");
if(file.exists()){
Log.e(TAG, "音乐文件存在");
try {
player.setDataSource(file.getAbsolutePath());
player.prepare();
} catch (IOException e) {
e.printStackTrace();
}
player.start();
seekBar();
}else {
Log.e(TAG, "音乐文件不存在");
}
}
private void seekBar() {
final int duration = player.getDuration();
//定时器
final Timer timer = new Timer();
final TimerTask timerTask=new TimerTask() {
@Override
public void run() {
int currentPosition = player.getCurrentPosition();
Message msg = Message.obtain();
msg.what = 0;
msg.arg1 = duration;
msg.arg2 = currentPosition;
MainActivity.handler.sendMessage(msg);
}
};
timer.schedule(timerTask, 500, 1000);
player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
timer.cancel();
timerTask.cancel();
}
});
}
}
MusicInterface代码:
public interface MusicInterface {
public void callPlay(Activity activity);
public void callPause();
public void callResume();
public void callSeekTo(int position);
}
PermisionUtils代码:
public class PermisionUtils {
// Storage Permissions
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE};
/**
* Checks if the app has permission to write to device storage
* If the app does not has permission then the user will be prompted to
* grant permissions
*
* @param activity
*/
public static void verifyStoragePermissions(Activity activity) {
// Check if we have write permission
int permission = ActivityCompat.checkSelfPermission(activity,
Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permission != PackageManager.PERMISSION_GRANTED) {
// We don't have permission so prompt the user
ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,
REQUEST_EXTERNAL_STORAGE);
}
}
}
3 案列二-视频播放
源码如下:
public class MainActivity extends AppCompatActivity {
private SurfaceView surfaceView;
private SurfaceHolder holder;
private MediaPlayer player;
private int currentPosition;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
surfaceView = (SurfaceView) findViewById(R.id.sfv);
holder = surfaceView.getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
player = new MediaPlayer();
try {
player.setDataSource("http://10.0.2.2:8080/NetWork/test.mp4");
player.setDisplay(holder);
player.prepareAsync();
player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
player.start();
if(currentPosition!=0){
player.seekTo(currentPosition);
}
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (player!=null){
currentPosition = player.getCurrentPosition();
player.stop();
player.reset();
player.release();
}
}
});
}
}