一、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)));
}
}