MediaPlayer類可用于控制音頻/視頻文件或流的播放。關于如何使用這個類的方法還可以閱讀
VideoView
類的文檔。
1.狀態圖
對播放音頻/視頻文件和流的控制是通過一個狀態機來管理的。下圖顯示一個
MediaPlayer
對象被支持的播放控制操作驅動的生命周期和狀態。橢圓代表
MediaPlayer
對象可能駐留的狀態。弧線表示驅動
MediaPlayer
在各個狀態之間遷移的播放控制操作。
這里有兩種類型的弧線。由一個箭頭開始的弧代表同步的方法調用,而以雙箭頭開頭的代表的弧線代表異步方法調用
。
通過這張圖,我們可以知道一個
MediaPlayer
對象有以下的狀態:
1)當一個
MediaPlayer
對象被剛剛用
new
操作符創建或是調用了
reset()
方法后,它就處于
Idle
狀態。當調用了
release()
方法后,它就處于
End
狀態。這兩種狀態之間是
MediaPlayer
對象的生命周期。
1.1) 在一個新構建的
MediaPlayer
對象和一個調用了
reset()
方法的
MediaPlayer
對象之間有一個微小的但是十分重要的差別。在處于
Idle
狀態時,調用
getCurrentPosition()
,
getDuration()
,
getVideoHeight()
,
getVideoWidth()
,
setAudioStreamType(int)
,
setLooping(boolean)
,
setVolume(float, float)
,
pause()
,
start()
,
stop()
,
seekTo(int)
,
prepare()
或者
prepareAsync()
方法都是編程錯誤。當一個
MediaPlayer
對象剛被構建的時候,內部的播放引擎和對象的狀態都沒有改變,在這個時候調用以上的那些方法,框架將無法回調客戶端程序注冊的
OnErrorListener.onError()
方法;但若這個MediaPlayer對象調用了
reset
()
方法之后,再調用以上的那些方法,內部的播放引擎就會回調客戶端程序注冊的
OnErrorListener.onError()
方法了,并將錯誤的狀態傳入。
1.2) 我們建議,一旦一個
MediaPlayer
對象不再被使用,應立即調用
release()
方法來釋放在內部的播放引擎中與這個
MediaPlayer
對象關聯的資源。資源可能包括如硬件加速組件的單態組件,若沒有調用
release()
方法可能會導致之后的
MediaPlayer
對象實例無法使用這種單態硬件資源,從而退回到軟件實現或運行失敗。一旦
MediaPlayer
對象進入了
End
狀態,它不能再被使用,也沒有辦法再遷移到其它狀態。
1.3) 此外,使用
new
操作符創建的
MediaPlayer
對象處于
Idle
狀態,而那些通過重載的
create()
便利方法創建的
MediaPlayer
對象卻不是處于
Idle
狀態。事實上,如果成功調用了重載的
create()
方法,那么這些對象已經是
Prepare
狀態了。
2)
在一般情況下,由于種種原因一些播放控制操作可能會失敗,如不支持的音頻/視頻格式,缺少隔行掃描的音頻/視頻,分辨率太高,流超時等原因,等等。因此,錯誤報告和恢復在這種情況下是非常重要的。有時,由于編程錯誤,在處于無效狀態的情況下調用了一個播放控制操作可能發生。在所有這些錯誤條件下,內部的播放引擎會調用一個由客戶端程序員提供的
OnErrorListener.onError()
方法。客戶端程序員可以通過調用
MediaPlayer.setOnErrorListener(android.media.MediaPlayer.OnErrorListener)
方法來注冊
OnErrorListener
.
2.1) 一旦發生錯誤,
MediaPlayer
對象會進入到
Error
狀態。
2.2) 為了重用一個處于
Error
狀態的
MediaPlayer
對象,可以調用
reset()
方法來把這個對象恢復成
Idle
狀態。
2.3) 注冊一個
OnErrorListener
來獲知內部播放引擎發生的錯誤是好的編程習慣。
2.4) 在不合法的狀態下調用一些方法,如
prepare()
,
prepareAsync()
和
setDataSource()
方法會拋出
IllegalStateException
異常。
3)
調用
setDataSource(FileDescriptor)
方法,或
setDataSource(String)
方法,或
setDataSource(Context,Uri)
方法,或
setDataSource(FileDescriptor,long,long)
方法會使處于
Idle
狀態的對象遷移到
Initialized
狀態。
3.1) 若當此
MediaPlayer
處于其它的狀態下,調用
setDataSource()
方法,會拋出
IllegalStateException
異常。
3.2) 好的編程習慣是不要疏忽了調用
setDataSource()
方法的時候可能會拋出的
IllegalArgumentException
異常和
IOException
異常。
4) 在開始播放之前,
MediaPlayer
對象必須要進入
Prepared
狀態。
4.1) 有兩種方法(同步和異步)可以使
MediaPlayer
對象進入
Prepared
狀態:要么調用
prepare()
方法(
同步
),此方法返回就表示該
MediaPlayer
對象已經進入了
Prepared
狀態;要么調用
prepareAsync()
方法(
異步
),此方法會使此
MediaPlayer
對象進入
Preparing
狀態并返回,而內部的播放引擎會繼續未完成的準備工作。當同步版本返回時或異步版本的準備工作完全完成時就會調用客戶端程序員提供的
OnPreparedListener.onPrepared()
監聽方法。可以調用
MediaPlayer.setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener)
方法來注冊
OnPreparedListener
.
4.2)
Preparing
是一個中間狀態,在此狀態下調用任何具備邊影響的方法的結果都是未知的!
4.3) 在不合適的狀態下調用
prepare()
和
prepareAsync()
方法會拋出
IllegalStateException
異常。當
MediaPlayer
對象處于
Prepared
狀態的時候,可以調整音頻/視頻的屬性,如音量,播放時是否一直亮屏,循環播放等。
5) 要開始播放,必須調用
start()
方法。當此方法成功返回時,
MediaPlayer
的對象處于
Started
狀態。
isPlaying()
方法可以被調用來測試某個
MediaPlayer
對象是否在
Started
狀態。
5.1) 當處于
Started
狀態時,內部播放引擎會調用客戶端程序員提供的
OnBufferingUpdateListener.onBufferingUpdate()
回調方法,此回調方法允許應用程序追蹤流播放的緩沖的狀態。
5.2) 對一個已經處于
Started
狀態的
MediaPlayer
對象調用
start()
方法沒有影響。
6) 播放可以被暫停,停止,以及調整當前播放位置。當調用
pause()
方法并返回時,會使
MediaPlayer
對象進入
Paused
狀態。注意
Started
與
Paused
狀態的相互轉換在內部的播放引擎中是異步的。所以可能需要一點時間在
isPlaying()
方法中更新狀態,若在播放流內容,這段時間可能會有幾秒鐘。
6.1) 調用
start()
方法會讓一個處于
Paused
狀態的
MediaPlayer
對象從之前暫停的地方恢復播放。當調用
start()
方法返回的時候,
MediaPlayer
對象的狀態會又變成
Started
狀態。
6.2) 對一個已經處于
Paused
狀態的
MediaPlayer
對象
pause()
方法沒有影響。
7) 調用
stop()
方法會停止播放,并且還會讓一個處于
Started
,
Paused
,
Prepared
或
PlaybackCompleted
狀態的
MediaPlayer
進入
Stopped
狀態。
7.1) 對一個已經處于
Stopped
狀態的
MediaPlayer
對象
stop()
方法沒有影響。
8) 調用
seekTo()
方法可以調整播放的位置。
8.1)
seekTo(int)
方法是異步執行的,所以它可以馬上返回,但是實際的定位播放操作可能需要一段時間才能完成,尤其是播放流形式的音頻/視頻。當實際的定位播放操作完成之后,內部的播放引擎會調用客戶端程序員提供的
OnSeekComplete.onSeekComplete()
回調方法。可以通過
setOnSeekCompleteListener(OnSeekCompleteListener)
方法注冊。
8.2) 注意,
seekTo(int)
方法也可以在其它狀態下調用,比如
Prepared
,
Paused
和
PlaybackCompleted
狀態。此外,目前的播放位置,實際可以調用
getCurrentPosition()
方法得到,它可以幫助如音樂播放器的應用程序不斷更新播放進度
9) 當播放到流的末尾,播放就完成了。
9.1) 如果調用了
setLooping(boolean)
方法開啟了循環模式,那么這個
MediaPlayer
對象會重新進入
Started
狀態。
9.2) 若沒有開啟循環模式,那么內部的播放引擎會調用客戶端程序員提供的
OnCompletion.onCompletion()
回調方法。可以通過調用
MediaPlayer.setOnCompletionListener(OnCompletionListener)
方法來設置。內部的播放引擎一旦調用了
OnCompletion.onCompletion()
回調方法,說明這個
MediaPlayer
對象進入了
PlaybackCompleted
狀態。
9.3) 當處于
PlaybackCompleted
狀態的時候,可以再調用
start()
方法來讓這個
MediaPlayer
對象再進入
Started
狀態。
?
#################################################################################################
這張狀態轉換圖清晰的描述了MediaPlayer的各個狀態,也列舉了主要的方法的調用時序,每種方法只能在一些特定的狀態下使用,如果使用時MediaPlayer的狀態不正確則會引發IllegalStateException異常。
?
Idle 狀態:當使用new()方法創建一個MediaPlayer對象或者調用了其reset()方法時,該MediaPlayer對象處于idle狀態。這兩種方法的一個重要差別就是:如果在這個狀態下調用了getDuration()等方法(相當于調用時機不正確),通過reset()方法進入idle狀態的話會觸發OnErrorListener.onError(),并且MediaPlayer會進入Error狀態;如果是新創建的MediaPlayer對象,則并不會觸發onError(),也不會進入Error狀態。
?
End 狀態:通過release()方法可以進入End狀態,只要MediaPlayer對象不再被使用,就應當盡快將其通過release()方法釋放掉,以釋放相關的軟硬件組件資源,這其中有些資源是只有一份的(相當于臨界資源)。如果MediaPlayer對象進入了End狀態,則不會在進入任何其他狀態了。
Initialized 狀態:這個狀態比較簡單,MediaPlayer調用setDataSource()方法就進入Initialized狀態,表示此時要播放的文件已經設置好了。
Prepared 狀態:初始化完成之后還需要通過調用prepare()或prepareAsync()方法,這兩個方法一個是同步的一個是異步的,只有進入Prepared狀態,才表明MediaPlayer到目前為止都沒有錯誤,可以進行文件播放。
?
Preparing 狀態:這個狀態比較好理解,主要是和prepareAsync()配合,如果異步準備完成,會觸發OnPreparedListener.onPrepared(),進而進入Prepared狀態。
?
Started 狀態:顯然,MediaPlayer一旦準備好,就可以調用start()方法,這樣MediaPlayer就處于Started狀態,這表明MediaPlayer正在播放文件過程中。可以使用isPlaying()測試MediaPlayer是否處于了Started狀態。如果播放完畢,而又設置了循環播放,則MediaPlayer仍然會處于Started狀態,類似的,如果在該狀態下MediaPlayer調用了seekTo()或者start()方法均可以讓MediaPlayer停留在Started狀態。
?
Paused 狀態:Started狀態下MediaPlayer調用pause()方法可以暫停MediaPlayer,從而進入Paused狀態,MediaPlayer暫停后再次調用start()則可以繼續MediaPlayer的播放,轉到Started狀態,暫停狀態時可以調用seekTo()方法,這是不會改變狀態的。
?
Stop 狀態:Started或者Paused狀態下均可調用stop()停止MediaPlayer,而處于Stop狀態的MediaPlayer要想重新播放,需要通過prepareAsync()和prepare()回到先前的Prepared狀態重新開始才可以。
?
PlaybackCompleted狀態:文件正常播放完畢,而又沒有設置循環播放的話就進入該狀態,并會觸發OnCompletionListener的onCompletion()方法。此時可以調用start()方法重新從頭播放文件,也可以stop()停止MediaPlayer,或者也可以seekTo()來重新定位播放位置。
?
Error狀態:如果由于某種原因MediaPlayer出現了錯誤,會觸發OnErrorListener.onError()事件,此時MediaPlayer即進入Error狀態,及時捕捉并妥善處理這些錯誤是很重要的,可以幫助我們及時釋放相關的軟硬件資源,也可以改善用戶體驗。通過setOnErrorListener(android.media.MediaPlayer.OnErrorListener)可以設置該監聽器。如果MediaPlayer進入了Error狀態,可以通過調用reset()來恢復,使得MediaPlayer重新返回到Idle狀態。