本章目錄
- Part One:播放器狀態
- Part Two:播放背景音樂
一般來說,我們播放音樂使用的是MediaPlayer類,播放音效使用的是SoundPool。而說到MediaPlayer,就必然會出現下面這個圖:
該圖描繪了MediaPlayer的各種狀態,也列出了主要方法的調用順序。需要注意的是,每個方法的應用需要特定的狀態,如果不正確調用,會拋出IllegalStateException異常。
Part One:播放器狀態
Idle狀態:當使用new方法創建一個MediaPlayer對象且調用reset()方法,MediaPlayer對象會處于Idle狀態。如果在該狀態先使用(getCurrentPosition(), getDuration(), getVideoHeight(), getVideoWidth(), setAudioStreamType(int), setLooping(boolean), setVolume(float, float), pause(), start(), stop(), seekTo(int), prepare() or prepareAsync())等方法(相當于在不對的時機調用),然后通過reset()方法進入Idle狀態會觸發OnErrorListener.onError(),同時MediaPlayer會進入Error狀態。如果是一個新創建的MediaPlayer對象,是不會觸發OnErrorListener.onError(),也就是說不會進入Error狀態。
End狀態:通過release()方法會進入End狀態,同時MediaPlayer對象將不再使用。如果無需使用MediaPlayer,應該立即通過release()方法釋放資源。如果MediaPlayer對象進入End狀態,就不會進入其它狀態了。
Initialized狀態:這個狀態相對來說比較簡單,MediaPlayer調用setDataSource ()會進入此狀態,相當于已經配置好將要播放的資源了。
Prepared狀態:當初始化完畢后,通過調用prepare()(同步的)或者prepareAsync()(異步的)方法,會處于Prepared狀態。它標示著當前MediaPlayer沒有錯誤,音樂可以播放了。
Preparing狀態:Preparing狀態很好理解,如果調用prepareAsync ()方法,當異步準備完畢后,會觸發OnPreparedListener.onPrepared (),從而進入Prepared狀態。也就是數,prepareAsync()方法會比prepare()方法多這么一個狀態。
Started狀態:很顯然,一旦MediaPlayer準備完畢,就可以調用start()方法,從而MediaPlayer進入Started狀態。這也標志著MediaPlayer進入到播放音樂階段,可以用isPlaying()方法測試是否在此狀態。如果播放完畢,同時設置loop循環,那么MediaPlayer會繼續待在Started狀態。同理,如果MediaPlayer調用seekTo()或者start()方法,可以使MediaPlayer保留在此狀態。
Paused狀態:當MediaPlayer處于Started狀態時,調用pause()方法會中止,并進入Paused狀態。調用start()方法會讓一個處于Paused狀態的MediaPlayer對象從之前暫停的地方恢復播放。Paused狀態可以調用seekTo()方法,對一個已經處于Paused狀態的MediaPlayer對象pause()方法沒有影響。
Stop狀態:Started狀態或者Paused狀態的MediaPalyer可以通過stop()方法停止。如果要恢復播放,需要通過prepareAsync()或者prepare()方法恢復到先前的狀態。
PlaybackCompleted狀態:如果文件正常播放完畢,并且沒有設置循環loop會進入此狀態,同時會觸發OnCompletionListener的onCompletion()方法。在這個狀態下,可以調用start()方法播放音樂,或者stop()停止,或者seekTo()重新定位要播放的音樂。
Error狀態:如果因為某些原因,MediaPlayer發生了錯誤,會觸發OnErrorListener.onError()事件,同時進入Error狀態。及時捕捉并妥善處理這些錯誤是很重要的,可以幫助我們及時釋放相關的軟硬件資源,也可以改善用戶體驗。如果MediaPlayer進入Error狀態,通過reset()方法可以恢復到Idle狀態。
Part Two:播放背景音樂
相對于一款音樂播放器的邏輯來說,播放背景音樂的實現十分的簡單。至于更深層次的功能擴展, 比如音樂播放器,錄音等,也只是在現有的結構上多添加一些方法而已,就不再贅述了,有興趣的可以自行研究。
- 要播放背景音樂,首先要將MP3文件導入到項目的assets資產目錄中。
assets目錄下的文件在打包后會原封不動的保存在apk包中,不會被編譯成二進制。一般拷貝數據庫到sdcard,游戲數據或者Hybrid開發等需要用到此目錄,我們的音頻文件不想被編譯,所以也要放到這里。
操作步驟為:
- 鼠標右鍵選中項目,依次點擊 new -> Folder -> Assets Folder ->finish,即可完成目錄創建。
- 將事先準備好的MP3文件拷貝到該目錄,注意不要超過50MB。
- 通過AssetManager獲取到音樂文件。
代碼比較簡單,就是獲取AssetManager對象,然后通過文件名獲得FileDescriptor對象:
@Nullable
private AssetFileDescriptor getAssetFileDescriptor() {
AssetManager assetManager = getAssets();
AssetFileDescriptor fileDescriptor = null;
try {
fileDescriptor = assetManager.openFd("Westlife - Beautiful in white.mp3");
} catch (IOException e) {
e.printStackTrace();
}
return fileDescriptor;
}
- 播放背景音樂
播放背景音樂的邏輯可參考之前說的MediaPlayer狀態圖,依次調用相應的方法,走完Idle - > Initialized -> Preparing -> Prepared - > Started這幾個狀態即可。
之所以使用prepareAsync()方法是為了異步加載資源,避免資源過大,阻礙主線程。
private void playBGM(AssetFileDescriptor fileDescriptor) {
mediaPlayer = new MediaPlayer();
try {
mediaPlayer.setDataSource(fileDescriptor.getFileDescriptor(), fileDescriptor.getStartOffset(), fileDescriptor.getLength());
mediaPlayer.setLooping(true);
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mediaPlayer.start();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
背景音樂的初步需求已經實現了,但是使用上還有很大的局限性,要想完善它,需要引入Service和BroadcastReceiver這倆個組件,下一節再細說。