爲了方便先重複貼一下MediaPlayer的狀態圖和MediaPlayer 的基本框架
總的分爲幾個模塊,爲方便後續文章的書寫,各模塊後續統一用括號裏面的名詞
- java層MediaPlayer(MediaPlayer)
- jni層(jni)
- mediaplayer client端(mediaplayer)
- MediaPlayer service端 (MediaPlayerService)
- native mediplayer ,即播放器功能最終實現模塊,不同方案會有不同的實現(NuPlayer)
MediaPlayer的狀態變量是由mediaplayer記錄的
系統初始化
MediaPlayerService是一個native系統服務,在系統初始化階段,具體是init進程解析rc文件,並在後續初始化過程中創建的。該服務同其他服務一樣會在ServiceManager中註冊一個實名binder,這樣後續Android其他模塊就可以通過ServiceManager的getService接口來獲取MediaPlayerService的服務
Idle
應用創建MediaPlayer實例或MediaPlayer實例已經創建reset()進入個狀態。
- 創建播放器
- new mediaplayer實例
- 設置 mediaplayer --> jni --> MediaPlayer 回調listener
- reset()
- 銷燬MediaPlayerService 跟mediaplayer服務端 binder通訊實例mClient
- 將NuPlayer的notify回調設置爲0
- 銷燬NuPlayer實例
- 銷燬mediaplayer 跟MediaPlayerService服務端 binder通訊實例mPlayer
Initialized
執行完setDataSource()會進入 Initialized,主要做了以下幾件事情
-
建立mediaplayer 和 MediaPlayerService binder通訊
mediaplayer和MediaPlayerService 通訊 並不是同MediaPlayerService在ServiceManager中註冊的binder通訊的,而是通過下面兩個binder來通訊的
IMediaPlayer (mediaplayer --> MediaPlayerService)
IMediaPlayerClient (MediaPlayerService --> mediaplayer)
但這兩個爲匿名binder,需要藉助實名的binder建立連接,而這個實名binder即爲MediaPlayerService在ServiceManager中註冊的服務 -
new NuPlayer實例
-
設置 NuPlayer 回調 MediaPlayerService的回調函數notify
notify回調函數是在createPlayer時一起作爲參數傳遞過去的。至此 NuPlayer --> MediaPlayerService notify --> mediaplayer notify的回調鏈路就建立,client的notify又會調用Idle狀態設置的listener。所以NuPlayer -> MediaPlayer的回調鏈路就建立了
在Idle以外的其他狀態調用 setDataSource() 都會拋出IllegalStateException,可以理解一個MediaPlayer實例只能有一個NuPlayer實例和對應的回調鏈路
Prepared ,Preparing
解析視頻源,demux, 創建decode,建立視頻播放管道(不同播放器會有不同的實現方式)
Started
開始播放,即音視頻流在播放通路 src -> demux -> decode -> render持續處理
Pause
暫停,即音視頻流會暫停流動
PlaybackCompleted
音視頻流播放完,可通過start()重頭開始播放。可以理解成prepare創建的播放器管道沒有銷燬,只是數據流已經處理完了。
Stoped
MediaPlayer在Started, Paused, Prepared or PlaybackCompleted這個幾個狀態下調用stop()會進到Stop狀態。
處於Stoped狀態需要重新調用prepare()或prepareAsync()才能重新開始播放。
可以理解prepare創建的播放器管道銷燬,需要重新建立才能播放
End
當release()被調用後,所有的資源會被釋放,處於End狀態。
- 將MediaPlayer 的所有listener置爲null
- 釋放對surface的引用
- 將mediaplayer 回調jni的listener置爲null
- 銷燬MediaPlayerService 跟mediaplayer服務端 binder通訊實例mClient
- 將NuPlayer的notify回調設置爲0
- 銷燬NuPlayer實例
- 銷燬mediaplayer 跟MediaPlayerService服務端 binder通訊實例mPlayer
- 銷燬mediaplayer實例
Error
由於某些原因,比如無法識別音視頻封裝格式,poorly interleaved audio/video,分辨率過高,流媒體網絡通訊超時等會導致播放操作發生錯誤,會進入Error狀態
不合理的MediaPlayer接口調用也會進入Error狀態
這是底層的播放器即NuPlayer發生了錯誤,需要重新調用reset()方法,才能重新使用,即銷燬NuPlayer,再重新創建。
SDK的文檔裏有一段
在構造函數創建後,立即調用getCurrentPosition(), getDuration(), getVideoHeight(), getVideoWidth(), setAudioAttributes(AudioAttributes), setLooping(boolean), setVolume(float, float), pause(), start(), stop(), seekTo(long, int), prepare() or prepareAsync() 等這些函數,MediaPlayer不會處於Error狀態,不會拋出error的消息。而在reset()之後再調用則會使MediaPlayer轉化爲Error狀態,並拋出異常消息