閒魚直播flutter化實踐
背景
直播帶貨已成為近年來最熱的“風口”,已成為電商升級的新突破口。閒魚作為國內最大的二手交易平臺市場,直播帶貨也成為推動成交的強烈需求。但是閒魚直播原先接入外部提供的直播sdk,存在以下幾個痛點問題:
-
業務定製困難。接外部sdk都存在“改不動,不敢改”困境。目前閒魚直播和sdk提供方業務特點存在一定差異,產品提出的定製化需求難以得到滿足
-
雙端一致性難以保證。android,ios各提供一套sdk,兩端表現並非完全一致,存在bug也不敢輕易升級sdk(直播sdk還是2019年版本)
-
排查輿情問題難度大。目前直播sdk裡面日誌有哪些,以及日誌系統在哪裡都不清楚,有問題得求助別人排查。
因此閒魚想基於直播sdk的一些能力,做一套跨平臺直播sdk,減少開發維護成本。考慮到閒魚在flutter技術棧上有一定技術積累,新版直播通過Flutter實現跨平臺開發(支援ios,android),再接入閒魚的全鏈路日誌系統,能解決目前閒魚直播面臨的問題。
整體設計
基本原則
在設計開發之初,我們定下了幾條原則:
-
複用原生業務無關的核心能力: 這裡主要考慮到開發成本因素。比如底層影片編解碼能力,webview容器等這些,native已經做的相當成熟,如果在flutter重新做,勢必會大大延長開發時間。通過外掛化複用原生層能力,將加快開發進度
-
native層儘量做薄,flutter層儘量做厚: 我們知道flutter是個移動UI框架,很擅長做UI。上一條原則強調複用原生能力,我們很容易陷入一個誤區:業務邏輯作為中間的地帶(不屬於UI,也不屬於底層核心能力),放到哪都可以。
如果放到native層,那麼flutter就會做的很薄,僅提供有限的介面能力。這樣會帶來一個問題:如果我們想修改一個業務邏輯,實際上還得改三處程式碼(android,ios,flutter),開發成本和原來差不多。
如果放到flutter層,那麼修改業務程式碼只需要改flutter一處即可,真正做到跨平臺。
功能劃分
對照線上老native閒魚直播間的很多功能,我們發現核心部分主要有:影片播放,評論,分享,關注,分享,寶貝口袋等等(下圖左)。這其中難點涉及到如何劃分:哪些是核心能力,必須依賴native能力?哪些能flutter化?
首先我們從視覺角度分析,可將直播間拆解為以下三層:播放渲染層,互動容器層,UI層(見下圖右)。
播放渲染層: 作用是渲染拉流資料,完成影片播放,業務無關。我們可以複用底層拉流和編解碼能力,播控控邏輯上移到flutter層。
互動渲染層: 基於動態容器完成渲染,完成一些互動玩法(例如直播間紅包,拍賣元件等)。這個動態容器是基於native層,但是依賴上層橋能力可以進行上移到flutter,完成動態化。
UI層: UI互動層,比如分享,評論,關注,點贊等以及feed流框架等。這層純UI互動,完全可以用flutter化。
最終我們得到如下新版flutter直播框架圖
關鍵模組實現
播放器
直播必然離不開影片播放,播放器作為核心能力,方案選型至關重要。目前開源的flutter播放器主要分為以下幾類:
-
官方提供的videoPlayer,以及基於官方外掛開發的 chewie,betterplayer,yoyo-player
-
基於ijkplayer開發的播放器,如fijkplayer等
播放器型別 |
介紹 |
特點 |
video_player |
跨三端播放器(android/ios/web) 底層依賴Exoplayer(android),AVPlayer(ios) |
官方支援,相容性好,迭代及時。api使用較為複雜;底層依賴難以切換 |
ijkplayer系列播放器 |
基於ijkplayer封裝的播放器外掛 |
相比官方外掛,迭代頻率低,點贊人數少些 |
引入第三方外掛播放器會帶來幾個問題:造成包體積增大,維護風險未知;閒魚底層播放器並非基exoplayer,這樣造成播放器管理不統一;引入外來庫容易造成庫版本衝突。因此我們決定自研一套基於閒魚環境的播放器。採用方案是基於外接紋理,將native建立的紋理id傳遞到flutter層進行展示,具體原理自行查閱,不在此展開。
其中,我們遇到一個難題:播放器的狀態管理
做過播放器業務的同學都知道,播放器內部是個狀態機。如果呼叫時序不合理,很容易發生crash。常見的做法是在播放器上再封裝一層sdk層,內部做狀態維護和管理,過濾掉不合法的狀態操作。
這次我們決定將sdk層業務邏輯全部上移到flutter層,但是flutter和native直接是通過methodChannel進行通訊的,methodChannel是非同步的。這會造成上層播放狀態和底層的播放狀態是不一致的。
考慮兩種case:
-
如果播放器呼叫load方法立馬呼叫play函式會不生效。這是因為播放器會認為非法狀態,直接跳過(下圖左)
-
頻繁呼叫play,pause容易造成頁面卡頓。實際上我們真正生效的是最後一次操作(下圖右)
// 影片prepare
void load() async {
...
}
// 播放
void play async() {
...
}
// 暫停
void pause async() {
...
}
// 情況1:載入中立即呼叫play
load();
play(); // 不生效
// 情況2:頻繁點選暫停/播放
play();
pause();
play();
pause();
....
呼叫不生效
執行緒卡頓
針對上述問題存在的問題,我們在flutter層做了更為細緻狀態管理。對於非同步呼叫加入了另外兩種狀態: 過渡態 和 期望態。
例如:
當我們有載入動作load,會將播放器狀態設定為過渡態(load_pending),處於此狀態下將不會響應特定的操作(比如play);
如果此時呼叫了play方法,播放器僅僅會記錄下期望態wantedState=playing;
當載入完成後native會通知flutter層狀態改變,從load_pending->loaded。此時我們會校驗當前狀態是否和期望態是否相等。由於當前為loaded 不等於期望態playing,我們會幫使用者補上一次play操作(見下圖)。
同樣當頻繁呼叫play/pause方法時,我們會將播放器狀態設定成過渡態(play_pending/pause_pending),此狀態不響應播放暫停操作,而僅僅改變使用者的期望態;當native通知flutter層操作狀態改變完成後,再校驗一次期望態是否和當前狀態是否相等,如果不相等再做響應操作
過渡態/期望態
互動容器層
對於帶貨直播來說,互動玩法是個必不可少的重要元素。每逢重點促銷節日,各種運營活動接踵而來。一個不需要跟版,全版本覆蓋的動態層容器有著非常大的吸引力。我們參考了直播sdk中native實現方案,將動態層容器橋接到原生的h5容器(可以理解成webView)。
PlatformView橋接到動態容器層
事實上,我們將動態層容器橋接到native這是遠遠不夠的。我們最需要解決的問題是: 容器依賴的能力注入。
容器依賴能力就是h5互動元件通過jsBridge,獲取到原生native執行時環境資料能力。
原先直播sdk中將apiBridge能力設計成全域性單例,這在feed流的直播場景下存在一定隱患。例如從第一個直播間將滑到第二個直播間過程中,可能同時存在兩個native容器向apiBridge拿資料。如果這個資料是全域性公用的,那沒問題。但是有個apiBridge方法是獲取當前直播間詳情資料(getLiveDetailData),這必然造成有個直播間拿到的資料不正確。
因此我們在設計API Bridge的時候,採用多例項方案。每個直播間分配一個bridge例項(並不耗記憶體),同時為每個直播間建立一個單獨的methdChannel,native容器和bridge單獨走這個通道進行通訊。這樣便能徹底解決上面訊息的隱患問題(下圖左)。
我們將apidBridge邏輯徹底flutter化的同時,也提供了動態註冊api Bridge的能力,不需要修改native側程式碼(上圖右)。目前互動層容器已隨flutter直播間上線並且經歷了雙十一的考驗。目前線上執行的h5元件包括紅包元件,拍賣元件,更多直播元件等。
當然我們在實踐的過程也遇到很多坑:
-
PlatfomView手勢衝突問題,見 《Flutter PlatformView 在閒魚直播業務中的實踐》
-
PlatfromView黑屏問題。在OppoR15 容器出現動態層黑屏現象,定位原因是platformView背景是透明的情況下,容易復現。通過將官方提供AndroidView替換成PlatformViewLink可以解決這個問題,但是會引起畫面卡頓掉幀。
無縫小窗播放
直播場景下小窗播放並不是什麼新鮮事情。當從直播頁面切到後臺,螢幕上會顯示另外一個小窗顯示直播畫面。android平臺下,實現原理是通過windowManager將渲染層view add到window中去。
我們在實現第一版小窗播放的時候,是通過新起一個播放器的方式將畫面渲染到一個window 的SurfaceView中,由於載入耗時的影響,這會造成播放不連貫。
我們做了進一步的優化,複用原來的直播播放器,切換渲染層surface。將原來的外接紋理surface進行detach,attach小窗surface。這樣影片流並沒有中斷,只是切換了渲染層,做到了真正的無縫切換效果。
訊息通道
直播間存各種各樣訊息需要傳遞,可靠的底層訊息通道至關重要。我們需要抽離出單獨的訊息模組便於業務複用。訊息模組flutter化,抹平端差異。考慮到直播有相當成熟的訊息中介軟體,我們講訊息模組做成單獨的外掛,上層提供統一的訊息處理介面。
其中訊息傳遞免不了native和dart層通訊。考慮到訊息有兩大型別:控制類和資料類,我們採用了雙管道通訊,其中控制訊息類採用methodChannel(基於方法粒度);native層產生的資料訊息採用eventChannel通知到flutter層(頻繁通訊,單向)。
元件化
設計之初我們想做的是一個“直播場”,任何直播源都能接入進來。比如接入淘寶直播,優酷直播,或者閒魚自己的直播等等。但是不同來源的直播就會面臨資料協議不同,UI樣式也無法統一的難題。因此保留元件動態替換能力是非常有必要的。
常見做法是站在元件UI的角度,UI雛形下擁有一定的定製能力,由子類覆寫。比如元件種icon圖示的定製,圖示點選事件處理等等。這種繼承方式會犧牲一定的靈活性,定製化能力不夠強。
而我們站在直播能力的角度,為每個元件賦予不同能力,通過組合的方式進行組裝,這樣靈活性會更強。
目前閒魚直播間大致劃分成上面幾個元件:LivePage/LiveCell/LiveInteractive/LivePlayer/LiveComment等。每個元件會承擔一定能力角色,具體分工如下:
不同元件之間難免會有資料進行共享和通訊,這樣會造成一定的耦合。為了最大程度的降低這種耦合,這邊基於輕量級的Knight框架進行通訊,其基本原理仍然採用觀察者模式。比如:LivePage作為直播間詳情資料提供者,它會將共享資料宣告出去(declare),使用方(LiveCell/LiveInteractive)回通過require方法進行訂閱。當共享資料來源發生變化,會通知到使用方。
效果
目前新版直播間在7.2.40版本已經全量上線,總版本uv佔比達到九成以上,暫無線上輿情問題
經歷了雙十一流量峰值考驗,線上紅包問題取得超預期效果 間,給直播間使用者增長帶來很大促進作用。
最後
這篇文章系統講述了我們閒魚創新小組在flutter直播化所做的一些努力,希望能給大家帶來一些啟發和收穫。未來我們還有許多工作需要進一步探索:1 PlatformView 相容性問題需要解決,以及相關記憶體流暢度優化 2 做一款真正意義上的flutter播放器,將編解碼資料直接對接到flutter層,實現真正意義上的跨平臺。道阻且長,行則將至,行而不輟,則未來可期。
團隊介紹
- Flutter富文字編輯器系列文章3——互動篇
- Flutter富文字編輯器系列文章3——互動篇
- 打造Flutter高效能富文字編輯器——渲染篇
- 節日獻禮:Flutter圖片庫重磅開源!
- 節日獻禮:Flutter圖片庫重磅開源!
- 關於閒魚測試資料構造,我有幾條心得
- 關於閒魚測試資料構造,我有幾條心得
- 打造Flutter高效能富文字編輯器——協議篇
- 打造Flutter高效能富文字編輯器——協議篇
- 閒魚前端技術體系的背後——魔魚(良心推薦,從思路到實踐)
- 閒魚如何保障交易鏈路質量
- Flutter 音影片開發的新思路
- 實效性與準確性的背後:多系統資料聚合展示
- Flutter滑動體驗對齊原生-滑動曲線篇
- 閒魚搜尋-成交寬度優化實踐
- 閒魚策略中樞業務擴充套件模組實現
- 閒魚互動玩法標準化建設
- 一條慢SQL引發的改造
- Flutter切面的應用與擴充套件
- 程式設計師如何保持學習成長?