Android MediaPlay的使用以及實現音頻播放器

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


}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章