Android-MediaPlayer-音訊播放-非同步準備
阿新 • • 發佈:2018-12-19
在上一篇部落格,Android-MediaPlayer-音訊播放-普通準備,介紹了普通準備的播放;
一般在開發中,要使用非同步準備比較好,因為準備是要去準備硬體來播放,是耗效能的
非同步準備和普通準備的區別
普通準備:一直是主執行緒,會發生阻塞
非同步準備:主執行緒 + 一個子執行緒,不會發生阻塞
MediaPlayer是Android設計的媒體播放器,不僅僅可以播放音訊檔案,還可以播放視訊檔案
播放:Audio(音訊,.mp3)相關
播放:Video(視訊,.mp4)相關
以下圖,是Android官方提供:MediaPlayer時序圖:
只要會看這個圖:就能實現音訊/視訊播放,暫停,繼續,停止,重播,等等
看圖規律:
1.藍色橢圓形是狀態,例如:Initialized已初始化狀態,Prepared準備狀態,Started啟動狀態,Stopped停止狀態,End結束狀態,等等;
2.單箭頭是方法呼叫:例如:呼叫reset方法重置,呼叫prepare方法準備,呼叫start方法播放,等等;
3.雙箭頭是監聽回撥:例如:onError回撥錯誤,等等
此MediaPlayer播放使用非同步準備
package liudeli.my_media1;import android.database.Cursor; import android.media.MediaPlayer; import android.net.Uri; import android.os.Bundle; import android.os.SystemClock; import android.provider.MediaStore; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button;import android.widget.TextView; import android.widget.Toast; /** * 此MediaPlayer播放使用使用非同步準備,不是普通準備 */ public class MediaPlayAsyncAudioActivity extends AppCompatActivity { private TextView tvPlayerPath; // 顯示播放的路徑 private TextView tvAudioInfo; // 歌曲時長/歌手/專輯 private TextView tvAudioThisDuration; // 當前播放的時長 private TextView tv_play_state; // 播放的狀態 /** * 媒體播放器,可以播放(音訊/視訊) * 播放(音訊/視訊)操作一模一樣 */ private MediaPlayer mediaPlayer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_play_audio); tvPlayerPath = findViewById(R.id.tv_player_path); tvAudioInfo = findViewById(R.id.tv_audio_info); tvAudioThisDuration = findViewById(R.id.tv_audio_this_duration); tv_play_state = findViewById(R.id.tv_play_state); mediaPlayer = new MediaPlayer(); // 為了測試,這樣寫,真實開發中,不這樣寫 new Thread(){ @Override public void run() { super.run(); while (true) { runOnUiThread(new Runnable() { @Override public void run() { if (mediaPlayer.isPlaying()) { tvAudioThisDuration.setText("當前時長:" + postions(mediaPlayer.getCurrentPosition())); } } }); SystemClock.sleep(1000); } } }.start(); /** * 監聽播放完成 */ mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { Log.d("mp", "播放完成"); Toast.makeText(MediaPlayAsyncAudioActivity.this, "播放完成", Toast.LENGTH_SHORT).show(); tvAudioThisDuration.setText("當前時長:-"); } }); /** * 監聽播放錯誤 */ mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { tv_play_state.setText("播放異常"); return false; } }); /** * 去獲取第一條外接儲存的音訊檔案.mp3 的路徑 * 通過Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 獲取外接儲存音訊檔案 * getContentResolver.query(uri) */ initAudioPlayerPath(); tv_play_state.setText("---"); } /** * 去獲取第六條外接儲存的音訊檔案.mp3 的路徑 */ private void initAudioPlayerPath() { Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; // 查詢的列 String[] projection = new String[]{MediaStore.Audio.Media.DATA, // 音訊路徑 MediaStore.Audio.Media.DURATION, // 音訊時長 MediaStore.Audio.Media.ARTIST, // 歌手 MediaStore.Audio.Media.ALBUM // 專輯 }; // 讓Android系統也會去讀取外接儲存 Cursor cursor = getContentResolver().query(uri, projection, null, null, null, null); /** * 把遊標移到第一行:cursor.moveToFirst() * 把遊標移到第六行:cursor.moveToPosition(6) */ if (cursor.moveToPosition(6)) { // if (cursor.moveToFirst()) { tvPlayerPath.setText(cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA))); String duration = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION)); String artist = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST)); String album = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM)); tvAudioInfo.setText("歌曲時長:" + postions(Integer.parseInt(duration)) + " \n歌手:" + artist + " \n專輯:" + album); } } /** * 轉換時長值 */ private String postions(int postion) { int musicTime = postion / 1000; return musicTime / 60 + ":" + musicTime % 60; } /** * 開始播放:此次播放使用普通準備,不使用非同步準備 * @param view */ public void player(View view) { try { // 重置 mediaPlayer.reset(); // 設定音訊檔案路徑 mediaPlayer.setDataSource(tvPlayerPath.getText().toString().trim()); // 非同步準備並播放 asyncPrepare(); } catch (Exception e) { e.printStackTrace(); } } /** * 非同步準備的行為 */ private void asyncPrepare() { // 準備:是操作硬體在播放,所以需要準備 mediaPlayer.prepareAsync(); // 監聽非同步準備,一旦準備完成,就會呼叫此方法 mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { // 呼叫此方法,代表非同步準備完成✅ // 開始播放 mediaPlayer.start(); tv_play_state.setText("播放中..."); } }); } /** * 暫停播放 繼續播放 * @param view */ public void pause(View view) { /** * 這種方式可以拿到控制元件 */ Button pause_continue = (Button) view; if (mediaPlayer.isPlaying()) { pause_continue.setText("繼續"); // 暫停 mediaPlayer.pause(); tv_play_state.setText("暫停中..."); } else { pause_continue.setText("暫停"); // 繼續播放 mediaPlayer.start(); tv_play_state.setText("播放中..."); } } /** * 停止播放 */ public void stop(View view) { // 停止播放 mediaPlayer.stop(); tv_play_state.setText("---"); } /** * 重播 * @param view */ public void recorded(View view) { try { // 先停止 mediaPlayer.stop(); // 非同步準備並播放 asyncPrepare(); } catch (Exception e) { e.printStackTrace(); } finally { if (mediaPlayer.isPlaying()) { tv_play_state.setText("播放中..."); } } } /** * 此Activity銷燬後,一定要 * mediaPlayer.release(); * mediaPlayer = null; * 因為 MediaPlayer 是操作硬體在播放,所以一定要釋放資源 */ @Override protected void onDestroy() { super.onDestroy(); mediaPlayer.release(); mediaPlayer = null; System.gc(); } }
AndroidManifest.xml 配置 外部儲存讀取許可權:
Android系統也會去讀取外接儲存,需要讀取外部儲存的許可權
<!-- getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, ...) Android系統也會去讀取外接儲存,需要讀取外部儲存的許可權 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
佈局檔案:
<?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" tools:context=".MediaPlayAudioActivity" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="10dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="播放音訊.mp3路徑:" /> <TextView android:id="@+id/tv_player_path" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="/mnt/sdcard/" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="播放" android:onClick="player" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="暫停" android:onClick="pause" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="停止" android:onClick="stop" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="重播" android:onClick="recorded" /> </LinearLayout> <TextView android:id="@+id/tv_audio_info" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dp" android:text="test" /> <TextView android:id="@+id/tv_audio_this_duration" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dp" android:text="歌曲時長:-" /> <TextView android:id="@+id/tv_play_state" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20sp" android:padding="30dp" android:layout_gravity="center_horizontal" /> </LinearLayout>
非同步準備效能比普通準備要好: