用過MediaPlayer來實現(xiàn)音視頻播放功能的同學(xué)應(yīng)該都知道MediaPlayer是基于狀態(tài)的启绰,也就是說MediaPlayer內(nèi)部保留著一個狀態(tài)機(jī)埋哟,用于管理內(nèi)部的狀態(tài)捡遍。在編寫代碼時必須始終注意牵祟,某些操作僅在播放器處于特定狀態(tài)時才有效痪蝇。如果在錯誤的狀態(tài)下執(zhí)行某項操作攻旦,則系統(tǒng)可能會拋出異秤骺酰或?qū)е缕渌涣夹袨椤?/p>
下圖展示了android MediaPlayer 的所有狀態(tài)以及狀態(tài)之間的轉(zhuǎn)換,該圖說明了從哪些狀態(tài)可以通過調(diào)用哪些方法從而將MediaPlayer變?yōu)榱硪环N狀態(tài)牢屋。圖中且预,藍(lán)色的橢圓表示MediaPlayer對象可能駐留的狀態(tài)±游蓿弧線表示驅(qū)動MediaPlayer對象狀態(tài)轉(zhuǎn)換的播放控制操作(也即可以調(diào)用的方法)锋谐。有兩種類型的弧線,具有單箭頭的弧表示同步方法調(diào)用截酷,而具有雙箭頭的弧表示異步方法調(diào)用涮拗。
下面通過文字來對上圖的狀態(tài)變化做一些說明:
當(dāng)一個MediaPlaye對象剛剛通過 new 方法創(chuàng)建,或者在調(diào)用reset()
方法之后迂苛,它將處于Idle狀態(tài)三热。并且在 release()方法被調(diào)用之后,它將處于End狀態(tài)三幻。在這兩種狀態(tài)之間是MediaPlayer對象的完整生命周期就漾。-
雖然通過new方法新創(chuàng)建的MediaPlayer對象和調(diào)用reset()方法之后的MediaPlayer對象都處于Idle狀態(tài),但二者之間卻存在著細(xì)微又非常重要的區(qū)別:
- 首先在兩種Idle狀態(tài)下調(diào)用getCurrentPosition(), getDuration(), getVideoHeight(),getVideoWidth(), setAudioAttributes(android.media.AudioAttributes),setLooping(boolean), setVolume(float, float), pause(), start(), stop(), seekTo(long, int), prepare()或prepareAsync()
等方法都將引起程序上的錯誤念搬。 - 其次當(dāng)上述方法是在通過new新創(chuàng)建的MediaPlayer對象上調(diào)用時,即便用戶設(shè)置了OnErrorListener.onError()回調(diào)抑堡,onError()方法也不會被調(diào)用,并且MediaPlayer的 狀態(tài)保持不變锁蠕。相反夷野,當(dāng)上述方法是在reset()方法之后調(diào)用,那么用戶將收到OnErrorListener.onError()回調(diào)荣倾,并且MediaPlayer的狀態(tài)將變?yōu)?em>Error態(tài)悯搔。
- 首先在兩種Idle狀態(tài)下調(diào)用getCurrentPosition(), getDuration(), getVideoHeight(),getVideoWidth(), setAudioAttributes(android.media.AudioAttributes),setLooping(boolean), setVolume(float, float), pause(), start(), stop(), seekTo(long, int), prepare()或prepareAsync()
在Idle狀態(tài)下通過調(diào)用setDataSource(java.io.FileDescriptor), setDataSource(java.lang.String), setDataSource(android.content.Context, android.net.Uri), setDataSource(java.io.FileDescriptor, long, long), setDataSource(android.media.MediaDataSource)等方法MediaPlayer將跳轉(zhuǎn)到Initialized狀態(tài)。
然后,必須使用 prepare() 或 prepareAsync() 方法完成準(zhǔn)備工作妒貌。當(dāng) 準(zhǔn)備就緒后通危,便會進(jìn)入Prepared狀態(tài),
進(jìn)入Prepared狀態(tài)后可以通過調(diào)用 start() 使其播放媒體內(nèi)容灌曙。start()成功返回后 菊碟,MediaPlayer對象處于Started狀態(tài)≡诖蹋可以通過調(diào)用isPlaying()來測試MediaPlayer對象是否處于Started狀態(tài)逆害。
可以通過調(diào)用 start()、pause()和 seekTo() 等方法在Started蚣驼、Paused和PlaybackCompleted狀態(tài)之間切換魄幕。
調(diào)用stop()方法可以讓MediaPlayer停止播放,并使處于Started颖杏,Paused纯陨,Prepared 或PlaybackCompleted狀態(tài)的MediaPlayer 進(jìn)入 Stopped狀態(tài)。不過請注意留储,當(dāng)調(diào)用 stop()時翼抠,除非再次調(diào)用prepare()或prepareAsync()將MediaPlayer對象設(shè)置為Prepared狀態(tài),否則將無法再次調(diào)用 start()進(jìn)行播放获讳。此外調(diào)用stop()對已經(jīng)處于Stopped狀態(tài)的MediaPlayer對象無效阴颖。
當(dāng)播放到達(dá)音視頻流的結(jié)尾時,播放完成丐膝。此時 如果循環(huán)模式isLooping 為true 那么MediaPlayer對象將保持在Started狀態(tài)并將從頭開始重新播放膘盖。如果isLooping 為false并且預(yù)先注冊了OnCompletionListener,則播放器引擎將調(diào)用用戶提供的回調(diào)方法OnCompletion.onCompletion()尤误∠琅希回調(diào)的調(diào)用表明該對象現(xiàn)在處于PlaybackCompleted狀態(tài)。PlaybackCompleted 狀態(tài)下损晤,再次調(diào)用start()可以從音頻/視頻源的開頭重新開始播放软棺。
在編寫與 MediaPlayer相關(guān)的代碼時,請始終牢記該狀態(tài)圖尤勋,因為從錯誤的狀態(tài)調(diào)用其方法是導(dǎo)致錯誤發(fā)生的常見原因喘落。
最后附上MediaPlayer各接口方法的調(diào)用時機(jī):
方法名稱 ? ? ? ? ? | 有效狀態(tài) | 無效狀態(tài) | 說明 |
---|---|---|---|
attachAuxEffect | {Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Idle, Error} | 此方法必須在setDataSource之后調(diào)用。調(diào)用它不會更改對象狀態(tài) |
getCurrentPosition | {Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Error} | 在有效狀態(tài)下成功調(diào)用此方法不會更改狀態(tài)最冰。在無效狀態(tài)下調(diào)用此方法會將對象轉(zhuǎn)移到錯誤狀態(tài) |
getDuration | {Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Idle, Initialized, Error} | 在有效狀態(tài)下成功調(diào)用此方法不會更改狀態(tài)瘦棋。在無效狀態(tài)下調(diào)用此方法會將對象轉(zhuǎn)移到錯誤狀態(tài) |
getVideoHeight | {Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Error} | 在有效狀態(tài)下成功調(diào)用此方法不會更改狀態(tài)。在無效狀態(tài)下調(diào)用此方法會將對象轉(zhuǎn)移到錯誤狀態(tài) |
getVideoWidth | {Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Error} | 在有效狀態(tài)下成功調(diào)用此方法不會更改狀態(tài)暖哨。在無效狀態(tài)下調(diào)用此方法會將對象轉(zhuǎn)移到錯誤狀態(tài) |
isPlaying | {Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Error} | 在有效狀態(tài)下成功調(diào)用此方法不會更改狀態(tài)赌朋。在無效狀態(tài)下調(diào)用此方法會將對象轉(zhuǎn)移到錯誤狀態(tài) |
pause | {Started, Paused, PlaybackCompleted} | {Idle, Initialized, Prepared, Stopped, Error} | 在有效狀態(tài)下成功調(diào)用此方法會將對象轉(zhuǎn)移到“ 暫停”狀態(tài)。在無效狀態(tài)下調(diào)用此方法會將對象轉(zhuǎn)移到錯誤狀態(tài) |
prepare | {Initialized, Stopped} | {Idle, Prepared, Started, Paused, PlaybackCompleted, Error} | 在有效狀態(tài)下成功調(diào)用此方法會將對象轉(zhuǎn)移到Prepared狀態(tài)沛慢。在無效狀態(tài)下調(diào)用此方法將引發(fā)IllegalStateException |
prepareAsync | {Initialized, Stopped} | {Idle, Prepared, Started, Paused, PlaybackCompleted, Error} | 在有效狀態(tài)下成功調(diào)用此方法會將對象轉(zhuǎn)移到“ 準(zhǔn)備”狀態(tài)赡若。在無效狀態(tài)下調(diào)用此方法將引發(fā)IllegalStateException |
release | Any | release()之后該對象不再可用 | |
reset | {Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error} | {} | reset之后,對象就像剛剛創(chuàng)建 |
seekTo | {Prepared, Started, Paused, PlaybackCompleted} | {Idle, Initialized, Stopped, Error} | 在有效狀態(tài)下成功調(diào)用此方法不會更改狀態(tài)团甲。在無效狀態(tài)下調(diào)用此方法會將對象轉(zhuǎn)移到錯誤狀態(tài) |
setDataSource | {Idle} | {Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error} | 在有效狀態(tài)下成功調(diào)用此方法會將對象轉(zhuǎn)移到初始化狀態(tài)逾冬。在無效狀態(tài)下調(diào)用此方法將引發(fā)IllegalStateException |
setLooping | {Idle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted} | {Error} | 在有效狀態(tài)下成功調(diào)用此方法不會更改狀態(tài)。在無效狀態(tài)下調(diào)用此方法會將對象轉(zhuǎn)移到錯誤狀態(tài) |
setVolume | {Idle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted} | {Error} | 成功調(diào)用此方法不會更改狀態(tài) |
start | {Prepared, Started, Paused, PlaybackCompleted} | {Idle, Initialized, Stopped, Error} | 在有效狀態(tài)下成功調(diào)用此方法會將對象轉(zhuǎn)移到“ 開始”狀態(tài)躺苦。在無效狀態(tài)下調(diào)用此方法會將對象轉(zhuǎn)移到錯誤狀態(tài) |
stop | {Prepared, Started, Stopped, Paused, PlaybackCompleted} | {Idle, Initialized, Error} | 在有效狀態(tài)下成功調(diào)用此方法會將對象轉(zhuǎn)移到“已停止”狀態(tài)身腻。在無效狀態(tài)下調(diào)用此方法會將對象轉(zhuǎn)移到錯誤狀態(tài) |