一、MediaPlay状态机详解(MediaPlay的生命周期)
MediaPlayer状态机如下图所示
1、Idle(闲置)状态与End(结束)状态
MediaPlayer 对象声明周期 : 从 Idle 到 End 状态就是 MediaPlayer 整个生命周期;
生命周期开始 : 进入 Idle (闲置) 状态;
生命周期结束 : 进入 End (结束) 状态;
- Idle 和 End 状态转换
进入 Idle 状态 : new MediaPlayer() 或者 任何状态调用了 reset() 方法之后, 进入 Idle (闲置) 状态;
进入 End 状态 : 在 Idle 状态调用 release() 方法后, 会进入 End (结束) 状态(涉及到资源的释放),不能转换为其他状态;
注意:create()初始化的MediaPlayer直接进入Prepared状态
2、Error(错误)状态
Error状态转换:
进入Error状态:检测到异常,系统回调onError()进入Error状态
离开Error状态:可以使用reset()回到Idle状态
注册监听 : 注册一个 OnErrorListener 监听器重写OnError(), 用于获取 播放器引擎 内部发生的错误;
注册方法 : 调用 MediaPlayer.setOnErrorListener(OnErrorListener) 方法, 注册 OnErrorListener;
3、Initialized(初始化)状态
Initialized 状态转换 : 在 Idle 状态调用 setDataSource() 方法, MediaPlayer 会迁移到 Initialized 状态;
注意 : 只能在 Idle 状态调用该方法, 如果在其它状态调用该方法, 会报出 IllegalStateException 异常;
4、Prepared(就绪)和Preparing(准备中)状态
Prepared状态转移(两种方式)
Initialized状态 调用 prepared()进入Prepared状态 (同步操作,若数据量较大则容易造成主线程阻塞甚至ANR)
Initialized状态 调用prepareAsync()进入Preparing状态,注册OnPreparedListener.OnPrepared(),在将准备就绪后的操作放置OnPrepared()中(异步操作,便于操纵数据量大的情况,避免主线程阻塞)
5、Started(开始)状态
Started状态转移:
Prepared状态调用start()进入Started状态
判断MediaPlayer是否在Started状态:isPlaying():boolean
跟踪缓冲状态 : 在 Started 状态, 调用 OnBufferingUpdateListener.onBufferingUpdate() 方法, 可以获取视频音频流的缓冲状态;
6、Paused(暂停)状态
Paused状态转移:
Started状态调用paused()进入Paused状态
Paused状态调用start()进入Started状态
7、Stop状态
Stop状态转移
在 Prepared, Started, Paused, PlaybackCompleted 状态下 调用 stop() 方法, MediaPlayer 会迁移到 Stopped 状态;
注意Stop状态不能直接start(),要回到prepared状态(prepare()或prepareAsyn()),才能start
8、播放位置调整seekTo()
在 Prepared, Started, Paused, PlaybackCompleted 状态下 调用 stop() 方法, MediaPlayer 会迁移到 Stopped 状态;
seekTo() 方法说明 : 该方法异步, 调用后 播放器引擎还需要进行其它操作, 跳转才能完成;
进行的操作 : 播放器引擎会回调 OnSeekComplete.onSeekComplete()方法, 该方法通过 setOnSeekCompleteListener() 方法注册;
获取播放位置 : 调用 getCurrentPosition() 方法, 可以获取当前播放的位置, 可以帮助播放器更新进度条;
9、PlaybackCompleted (播放完毕) 状态
PlaybackCompleted 状态转移 : 如果设置了循环模式SetLooping(), 那么播放完毕之后会重新进入Started状态;若没设置循环,则调用 OnCompletion.onCompletion() 回调方法, MediaPlayer 会进入 PlaybackCompleted 状态;
也可以在该状态直接调用start()进入Started状态
二、MediaPlayer是Android系统自带的,可以用来播放音频、视频和流媒体。MediaPlayer包含了Audio和Video的播放功能,下面介绍一些常用方法。
常用方法
方法 | 说明 |
---|---|
create | 创建一多媒体 |
getCurrentPosition | 当前播放位置 |
getDuration | 文件的总时间 |
getVideoHeight | 视频的高度 |
getVideoWidth | 视频的宽度 |
isLooping | 是否循环播放 |
isPlaying | 是否正在播放 |
start | 开始播放 |
pause | 暂停 |
prepare | 准备(同步) |
prepareAsync | 准备(异步) |
stop | 停止播放 |
release | 释放相关资源 |
reset | 重置 |
seekTo | 指定 |
setAudioStreamType | 设置类型 |
setDataSource | 设多媒体数据来源 |
setDisplay | 设置显示多媒体的载体 |
setLooping | 是否循环播放 |
setOnButteringUpdateListener | 网络流媒体的缓冲监听 |
setOnErrorListener | 错误信息监听 |
setOnVideoSizeChangedListener | 视频尺寸监听 |
setScreenOnWhilePlaying | 设置是否保持屏幕常亮 |
setVolume | 设置音量 |
- void setDataSource(String path) 通过一个具体的路径来设置MediaPlayer的数据源,path可以是本地的一个路径,也可以是一个网络路径
- void setDataSource(Context context, Uri uri) 通过给定的Uri来设置MediaPlayer的数据源,这里的Uri可以是网络路径或是一个ContentProvider的Uri。
- void setDataSource(MediaDataSource dataSource) 通过提供的MediaDataSource来设置数据源
- void setDataSource(FileDescriptor fd) 通过文件描述符FileDescriptor来设置数据源
- int getCurrentPosition() 获取当前播放的位置
- int getAudioSessionId() 返回音频的session ID
- int getDuration() 得到文件的时间
- TrackInfo[] getTrackInfo() 返回一个track信息的数组
- boolean isLooping () 是否循环播放
- boolean isPlaying() 是否正在播放
- void pause () 暂停
- void start () 开始
- void stop () 停止
- void prepare() 同步的方式装载流媒体文件。
- void prepareAsync() 异步的方式装载流媒体文件。
- void reset() 重置MediaPlayer至未初始化状态。
- void release () 回收流媒体资源。
- void seekTo(int msec) 指定播放的位置(以毫秒为单位的时间)
- void setAudioStreamType(int streamtype) 指定流媒体类型
- void setLooping(boolean looping) 设置是否单曲循环
- void setNextMediaPlayer(MediaPlayer next) 当 当前这个MediaPlayer播放完毕后,MediaPlayer next开始播放
- void setWakeMode(Context context, int mode):设置CPU唤醒的状态。
- setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener) 网络流媒体的缓冲变化时回调
- setOnCompletionListener(MediaPlayer.OnCompletionListener listener) 网络流媒体播放结束时回调
- setOnErrorListener(MediaPlayer.OnErrorListener listener) 发生错误时回调
- setOnPreparedListener(MediaPlayer.OnPreparedListener listener):当装载流媒体完毕的时候回调。
- setOnInfoListener(OnInfoListener l) 信息监听
三、实现一个音频播放器,效果图如下
1、我们首先先写一个音频播放器类,该类主要实现了播放、暂停、重新播放、循环播放、停止播放、进度条等功能
public class MyMusicPlayer implements MediaPlayer.OnPreparedListener ,MediaPlayer.OnCompletionListener,MediaPlayer.OnErrorListener,MediaPlayer.OnSeekCompleteListener{
private static final String TAG = "MyMusicPlayer";
private MediaPlayer mediaPlayer;
private Timer timer;//定时器
private String path = "/storage/emulated/0/aatest/input.mp3";
private boolean isSeekbarChaning;//互斥变量,防止进度条和定时器冲突
private OnMyPreparedListener mOnMyPreparedListener;
public MyMusicPlayer(){
initMediaPlayer();
}
private void initMediaPlayer() {
mediaPlayer = new MediaPlayer();
try {
//设置播放音频文件路径
mediaPlayer.setDataSource(path);
//设置播放流媒体类型。
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
//设置循环播放
mediaPlayer.setLooping(false);
//同步的方式装载流媒体文件
//mediaPlayer.prepare();
// 通过异步的方式装载媒体资源
mediaPlayer.prepareAsync();
//当装载流媒体完毕的时候回调。
mediaPlayer.setOnPreparedListener(this);
//当流媒体播放完毕的时候回调。
mediaPlayer.setOnCompletionListener(this);
//当播放中发生错误的时候回调。
mediaPlayer.setOnErrorListener(this);
//当使用seekTo()设置播放位置的时候回调
mediaPlayer.setOnSeekCompleteListener(this);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
*
* @param time
* @return
*/
//public
//传入的数据为毫秒数
public String formattime(long time){
String min= (time/(1000*60))+"";
String second= (time%(1000*60)/1000)+"";
if(min.length()<2){
min=0+min;
}
if(second.length()<2){
second=0+second;
}
return min+":"+second;
}
//计算播放时间
public String calculateTime(int time){
int minute;
int second;
if(time > 60){
minute = time / 60;
second = time % 60;
//分钟再0~9
if(minute >= 0 && minute < 10){
//判断秒
if(second >= 0 && second < 10){
return "0"+minute+":"+"0"+second;
}else {
return "0"+minute+":"+second;
}
}else {
//分钟大于10再判断秒
if(second >= 0 && second < 10){
return minute+":"+"0"+second;
}else {
return minute+":"+second;
}
}
}else if(time < 60){
second = time;
if(second >= 0 && second < 10){
return "00:"+"0"+second;
}else {
return "00:"+ second;
}
}
return null;
}
//当装载流媒体完毕的时候回调。
@Override
public void onPrepared(MediaPlayer mp) {
Log.e(TAG, "onPrepared()");
}
//当流媒体播放完毕的时候回调。
@Override
public void onCompletion(MediaPlayer mp) {
Log.e(TAG, "onCompletion()");
mOnMyPreparedListener.onMyPrepared();
}
//当播放中发生错误的时候回调。
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Log.e(TAG, "onError()");
return false;
}
//当使用seekTo()设置播放位置的时候回调
@Override
public void onSeekComplete(MediaPlayer mp) {
mediaPlayer.seekTo(0);//在当前位置播放
}
/**
* 播放
* @param seekbar
*/
public void Play(final SeekBar seekbar){
if(mediaPlayer != null){
mediaPlayer.start();
int duration = mediaPlayer.getDuration();//获取音乐总时间
seekbar.setMax(duration);//将音乐总时间设置为Seekbar的最大值
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
if(!isSeekbarChaning){
seekbar.setProgress(mediaPlayer.getCurrentPosition());
}
}
},0,50);
}
}
/**
* 暂停播放
*/
public void Pause(){
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}
/**
* 重新播放
*/
public void Replay(){
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.seekTo(0);
}
}
/**
* 停止播放
*/
public void Stop(){
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
}
/**
*循环播放
* @param looping
*/
public void setLooping(boolean looping){
mediaPlayer.setLooping(looping);
}
/**
* 获取音乐总时长
* @return
*/
public int getDuration(){
int duration = mediaPlayer.getDuration();
return duration;
}
/**
* 获取当前播放的位置
* @return
*/
public int getCurrentPosition(){
int currentPosition = mediaPlayer.getCurrentPosition();
return currentPosition;
}
/**
* 设置当前MediaPlayer的播放位置,单位是毫秒
* @param progress
*/
public void setSeekto(int progress){
mediaPlayer.seekTo(progress);//在当前位置播放
}
/**
* 互斥变量,防止进度条和定时器冲突
* @param isSeekbar
*/
public void setSeekbarChaning(boolean isSeekbar){
isSeekbarChaning = isSeekbar;
}
/**
* 获取Seekbar状态
* @return
*/
public boolean isSeekbarChaning(){
return isSeekbarChaning;
}
/**
* 释放资源
*/
public void release(){
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
//回收流媒体资源
mediaPlayer.release();
mediaPlayer = null;
}
}
public void setOnMyPreparedListener(OnMyPreparedListener listener)
{
mOnMyPreparedListener = listener;
}
public interface OnMyPreparedListener
{
void onMyPrepared();
}
}
2、布局文件如下所示
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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=".MainActivity">
<Button
android:layout_width="match_parent"
android:onClick="onPlay"
android:text="播放"
android:layout_height="wrap_content"/>
<Button
android:layout_width="match_parent"
android:onClick="onPause"
android:text="暂停"
android:layout_height="wrap_content"/>
<Button
android:layout_width="match_parent"
android:onClick="onReplay"
android:text="重新播放"
android:layout_height="wrap_content"/>
<Button
android:layout_width="match_parent"
android:onClick="onLooping"
android:text="循环播放"
android:layout_height="wrap_content"/>
<Button
android:layout_width="match_parent"
android:onClick="onStop"
android:text="停止播放"
android:layout_height="wrap_content"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_marginTop="20dp"
android:layout_height="wrap_content">
<TextView
android:layout_marginLeft="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv_start" />
<SeekBar
android:layout_width="250dp"
android:layout_height="wrap_content"
android:id="@+id/seekbar" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv_end" />
</LinearLayout>
<TextView
android:id="@+id/sample_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:text="Hello World!"
/>
</LinearLayout>
3、在AndroidManifest.xml中添加权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
android:requestLegacyExternalStorage="true"
public class PermissionsManagement {
private static final String TAG = "PermissionsManagement";
public static void requestMyPermissions(Activity mActivity) {
if (ContextCompat.checkSelfPermission(mActivity,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
//没有授权,编写申请权限代码
ActivityCompat.requestPermissions(mActivity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
} else {
Log.d(TAG, "requestMyPermissions: 有写SD权限");
}
if (ContextCompat.checkSelfPermission(mActivity,
Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
//没有授权,编写申请权限代码
ActivityCompat.requestPermissions(mActivity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 100);
} else {
Log.d(TAG, "requestMyPermissions: 有读SD权限");
}
}
}
4、然后在MainActivity 实现功能。
public class MainActivity extends AppCompatActivity implements MyMusicPlayer.OnMyPreparedListener {
private static final String TAG = "wq892373445";
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
private MyMusicPlayer mMyMusicPlayer;
//显示流媒体的总播放时长
private TextView tv_end;
private SeekBar seekbar;
private TextView tv_start;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PermissionsManagement.requestMyPermissions(this);
seekbar = (SeekBar)findViewById(R.id.seekbar);
tv_end = (TextView)findViewById(R.id.tv_end);
tv_start = (TextView)findViewById(R.id.tv_start);
//绑定监听器,监听拖动到指定位置
seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int duration2 = mMyMusicPlayer.getDuration() / 1000;//获取音乐总时长
int position = mMyMusicPlayer.getCurrentPosition();//获取当前播放的位置
tv_start.setText(mMyMusicPlayer.calculateTime(position / 1000));//开始时间
tv_end.setText(mMyMusicPlayer.calculateTime(duration2));//总时长
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
mMyMusicPlayer.setSeekbarChaning(true);
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mMyMusicPlayer.setSeekbarChaning(false);
mMyMusicPlayer.setSeekto(seekBar.getProgress());//在当前位置播放
tv_start.setText(mMyMusicPlayer.formattime(mMyMusicPlayer.getCurrentPosition()));
}
});
mMyMusicPlayer = new MyMusicPlayer();
mMyMusicPlayer.setOnMyPreparedListener(this);
int duration2 = mMyMusicPlayer.getDuration() / 1000;//获取音乐总时长
int position = mMyMusicPlayer.getCurrentPosition();//获取当前播放的位置
tv_start.setText(mMyMusicPlayer.calculateTime(position / 1000));//开始时间
tv_end.setText(mMyMusicPlayer.calculateTime(duration2));//总时长
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
@Override
protected void onDestroy() {
mMyMusicPlayer.release();
super.onDestroy();
}
/**
* 播放
* @param view
*/
public void onPlay(View view) {
mMyMusicPlayer.Play(seekbar);
}
/**
* 暂停播放
* @param view
*/
public void onPause(View view) {
mMyMusicPlayer.Pause();
}
/**
* 重新播放
* @param view
*/
public void onReplay(View view) {
mMyMusicPlayer.Replay();
}
/**
* 停止播放
* @param view
*/
public void onStop(View view) {
mMyMusicPlayer.Stop();
}
/**
* 循环播放
* @param view
*/
public void onLooping(View view) {
mMyMusicPlayer.setLooping(true);
}
@Override
public void onMyPrepared() {
Log.d(TAG, "onPrepared()");
// 装载完毕回调
//获取流媒体的总播放时长,单位是毫秒。
tv_end.setText(mMyMusicPlayer.calculateTime((mMyMusicPlayer.getDuration()/ 1000)));
Log.d(TAG, "总的播放时长:"+mMyMusicPlayer.getDuration());
//获取当前流媒体的播放的位置,单位是毫秒
tv_start.setText(mMyMusicPlayer.calculateTime((mMyMusicPlayer.getCurrentPosition()/ 1000)));
}
}