圖片來源:bz.zzzmh.cn/前端
本文做者:王永亮git
在網易雲音樂 8.0 改版中,接到一個播放中的視頻能夠點擊「小窗」按鈕收起到 mini 播放條中繼續播放的需求,剛接到這個需求時心裏是崩潰的,要知道網易雲音樂的 mini 播放條是一個可能會出如今 App 中的任何 Activity 上的 View,在不一樣 Activity 之間跳轉時,如何能保證視頻能夠從一個 Activity 「無縫」轉移到另外一個 Activity 呢?github
通常簡單的視頻播放功能我會使用系統自帶的 VideoView,只需幾行代碼就可讓視頻播放起來,系統自帶的 VideoView 繼承自 SurfaceView,而且將 MediaPlayer 的具體調用,包括 Surface 和 MediaPlayer 的綁定封裝在裏面,這樣封裝的優點是簡單易用,可是也存在一些問題,SurfaceView 和 MediaPlayer 徹底綁定在一塊兒,一個 MediaPlayer 只能對應一個 SurfaceView,而小窗播放想作到的是 MediaPlayer 和 SurfaceView 能夠一對多,在頁面切換時 MediaPlayer 能夠綁定新的 SurfaceView,就像一臺電腦對應多個顯示器。咱們的視頻播放框架很好的解決了這個問題,以下圖所示:緩存
因爲 App 中有一些對視頻作動畫的場景,因此框架中使用的是 TextureView,TextureView 和 MediaPlayer 使用 AIDL 進行通訊,以下圖所示:markdown
從上面兩圖能夠看出,視頻播放框架中把全部的 MediaPlayer 放到了一個單獨的 Video 進程的緩存池中來管理,正在使用的放在 Active 的池子中,閒置在 idle 池子中,閒置的 MediaPlayer 超過上限時會被回收,啓動新頁面時 VideoView 能夠從 Video 進程的池子中獲取閒置的 MediaPlayer,其餘進程中的 VideoView 經過 AIDL 同 Video 進程中的 MediaPlayer 通訊。架構
這種架構使不一樣 Activity 中的 VideoView 能夠很方便的替換其綁定的 MediaPlayer,因爲播放能力都在 MediaPlayer 中,因此在 MediaPlayer 同 TextureView 解綁時並不會致使播放的中斷,新頁面啓動時,只要將正在播放的 MediaPlayer 同 TextureView 從新綁定,新的頁面就能馬上展現播放中的畫面了。實際上視頻播放框架最先並非服務於無縫播放的場景,設計最先是出於如下緣由:app
因此綜合以上需求咱們設計了這套 MediaPlayer 和 TextureView 隔離的方案,若是是比較簡單場景也能夠考慮使用單例持有 MediaPlayer,因爲這套方案已經很好的將 MediaPlayer 和 TextureView 隔離,因此咱們只須要經過給 MediaPlayer 池子增長一些獲取被複用播放器的方法就能夠很容易的支持 VideoView 和 MediaPlayer的換綁,從得到無縫播放的效果了。框架
具體的換綁 MediaPlayer 流程以下圖:ide
在原有 Activity 中,若是播放器是要被複用的,咱們會將播放器的惟一 id 和正在播放的資源 id 保存在一個全局位置,以此做爲播放器可複用的標誌。在新頁面啓動時,新頁面的 VideoView 被建立,在新頁面中會調用 VideoView 的 setDataSource 設置要播放的內容,setDataSource 會根據當前播放的內容和保存的全局播放器 id 在播放器池子中從新找到原來正在播放的播放器,並將 Surface 經過 AIDL 發送給被複用的 MediaPlayer 從新綁定,這樣在不打斷當前播放的狀況下,視頻播放的畫面就無縫被轉移到新的 Activity 中了。其中要注意的一個知識點是Surface自己就是支持跨進程傳遞的:函數
public class Surface implements Parcelable
複製代碼
另外這個方案中使用 MediaPlayer 對象的 hashCode 做爲播放器的惟一 id,若是使用這個方案,你們也能夠結合本身的狀況設計惟一id。
換綁方案的核心是 MediaPlayer 同 VideoView 的從新綁定,從新綁定只須要作到下面兩步:
這個方案基本上能知足絕大部分的無縫播放需求,不過也並不是沒有缺點,這個方案主要有如下幾個問題:
終實現效果以下圖:
在換綁方案以外,網易雲音樂中也有一些其餘的無縫播放方案實現,首先介紹一種實現比較簡單,也是在網易雲中比較早使用的一種方案,「假」頁面切換方案,由名字能夠知道,這種方案不是真正的在 Activity 之間進行跳轉,而是利用 TextureView 能夠像普通 View 同樣移動、作動畫的特性,利用過渡動畫,讓效果看起來像是從一個頁面跳轉到了另外一個頁面,效果以下圖所示:
在網易雲音樂的視頻 Feed 流中,視頻播放時,點擊熱區能夠在不暫停播放的狀況下「展開」到播放詳情頁,具體的實現方法是將視頻播放的 View 放在 Fragment 中,Fragment 的 Container 放在整個 ViewTree 的最頂層,點擊播放時,將視頻播放 Fragment 移動到須要展現視頻的位置並開始播放,須要點擊進入詳情頁時,只須要對視頻播放的 Fragment 作平移和縮放動畫,在視頻播放的 Fragment 下方再添加評論等其餘的 Fragment。這裏能夠參考 Android 原生的 VideoView 的封裝思想來實現:
public class VideoView extends SurfaceView
implements MediaPlayerControl, SubtitleController.Anchor {
複製代碼
參考 VideoView 源碼能夠將 SurfaceView 替換爲 TextureView ,再對應處理下 onSurfaceTextureAvailable 等回調便可。這種方案應用仍是比較普遍的,好比京東、淘寶等的商品詳情的介紹視頻。這種方案雖然簡單可是侷限也比較大,只能解決在同一個 Activity 中的場景,若是需求是在不一樣 Activity 中無縫播放切換這個方案就沒法知足了。
實現跨 Activity 場景無縫播放的另外一個方案是打開新的頁面時,在新的頁面中使用新播放器從新打開資源,並根據原來保存的進度從新 seek 後再續播,這種方案其實並不能保證真正的「無縫」播放,畢竟 Activity 啓動也要消耗一兩百毫秒的時間,不過這個方案最大的優點是一些老邏輯進行不多的更改就能夠支持無縫播放功能,好比在一些不是很重要的頁面中,視頻播放功能可能已經存在而且播放邏輯耦合了很重的業務邏輯,這時 seek 方案就比較合適了。
這個方案雖然簡單可是也有一些須要注意的地方:
關鍵幀被稱爲 I 幀,能夠被看作是一幀沒有壓縮過的畫面,解碼的時候無需依賴其餘幀,關鍵幀之間還存在 B 幀和 P 幀這樣的壓縮幀,須要依賴其餘幀才能解碼出完整的畫面,兩個關鍵幀之間的間隔被稱爲一個 GOP,在 GOP 內的幀系統播放器是沒辦法直接 seek 的。
View 跨 Activity 複用是指手動使用 ApplicationContext 建立須要被複用的 View,而且使用單例 Manager 持有該 View,添加刪除可複用 View 能夠統一在 Activity 生命週期函數中實現,示例代碼以下:
object Manager : ActivityLifecycleCallbacks
override fun onActivityStarted(activity: Activity) {
...
removePlayerBarFromWindow(activity)
addPlayerBarToWindow(activity)
}
override fun onActivityPaused(activity: Activity) {
...
if (activity.isFinishing && getMiniPlayerBarParentContext() == activity) {
removePlayerBarFromWindow(activity, true)
}
}
複製代碼
private fun getPlayerBar(activityBase: Activity): MiniPlayerBar {
synchronized(this) {
if (miniPlayerBar == null) {
miniPlayerBar = MiniPlayerBar(activityBase.applicationContext)
}
...
return miniPlayerBar!!
}
}
複製代碼
理論上這是一種更加靈活的方案,使用 Application 做爲 View 的 Context 也不用擔憂泄漏問題,不過因爲在此次小窗的需求中涉及到老的頁面和新頁面的播放器複用,在不少場景下並非一個統一的播放View,因此沒有采用這種方案,不過這個方案在網易雲音樂的音街 App 的 mini 播放條上已經被使用,有興趣的小夥伴也能夠嘗試下。
以上是網易雲音樂中一些無縫播放的方案的總結,主要介紹了一下網易雲音樂中幾種無縫播放能力的實現思路,給你們方案選型作參考,若是有其餘的方案也歡迎交流。網易雲音樂中的方案是從簡單到複雜逐漸演進而來,隨着需求不斷迭代變成今天的樣子,我的理解設計方案時不用過度的追求大而全,適合當前場景的纔是最好的,好的架構不只要靠好的設計,也要靠不斷的改進優化。
本文發佈自 網易雲音樂大前端團隊,文章未經受權禁止任何形式的轉載。咱們常年招收前端、iOS、Android,若是你準備換工做,又剛好喜歡雲音樂,那就加入咱們 grp.music-fe(at)corp.netease.com!