你不能不知道的Flutter:Flutter影片滾動播放解決方案

yilian發表於2020-01-03
你不能不知道的Flutter:Flutter影片滾動播放解決方案

如題,本文分享的內容為:影片列表滾動播放。

分類

影片列表的播放規則一般需要和具體產品、互動確認,播放一般都是靜音的,根據露出座標規律,常見的有兩大類:

固定位置播放

如滑動螢幕的中間位時,延遲若干毫秒自動播放。

固定索引+屏佔比播放

如第一個符合屏佔比的影片可以自動播放;屏佔比可以是當前影片元件的高度百分比,也可以是螢幕上的固定位置;當我們把屏佔比定位60%時,第一個影片的可見區小於60%會暫停播放,觸發可見的下一個影片。

更多相關學習內容看GitHub:
以下影片內容學習vx:xx13414521

你不能不知道的Flutter:Flutter影片滾動播放解決方案

有料影片流

在開發 安居客-有料內容Feed流時,我們遇到的互動是第二種,由於影片貼出現不固定,待播放的位置也不固定。 在這種情況下要實現與Native一致的效果是一個不小的難題。

我們在開發過程中將這個問題進行了分解:

  • 滾動檢測:拋開影片,單純設計一個元件,能夠按需檢測特定Widget
  • 影片播放元件:引入影片播放,將影片控制元件根據滾動檢測要求,進行接入

元件原型

下面看一下我們設計的原型元件效果。

video-play.gif
video-play.gif

我們將影片貼子用一個色塊進行佔位。透過監聽滑動事件,當停止後,從螢幕頂端開始,遍歷當前視窗範圍內的控制元件,如果是影片控制元件,並且在螢幕上的露出比符合預期,那麼將其返回。供開發者後續處理。

核心流程如下圖所示:

你不能不知道的Flutter:Flutter影片滾動播放解決方案

基於這個思路,我們開發了一個 ScrollDetectListener元件:

  • 使用ScrollDetectListener包裹ListView,ListView為業務相關的影片流帖子。
  • 影片帖子使用MetaConsumer包裹;
你不能不知道的Flutter:Flutter影片滾動播放解決方案
MetaConsumer(
  index: index,  data: data,  builder: (BuildContext context, VideoPlayModel model, Widget child) {    var play = model.playIndex == index;    return Container(
      width: MediaQuery.of(context).size.width,      alignment: Alignment.center,      height: 100,      padding: EdgeInsets.zero,      child: play ? Text('Playing $data') : Text('$data'),      color: play ? Colors.redAccent : Colors.grey[100] ?? Colors.grey,
  );
})

影片檢測元件

透過原型完成必要的除錯和最佳化之後,我們可以嘗試接入影片控制元件。

Demo1

你不能不知道的Flutter:Flutter影片滾動播放解決方案

Demo2

你不能不知道的Flutter:Flutter影片滾動播放解決方案

在處理影片的時候,需要注意的點也很多,比如:

  • 影片貼的首幀預覽圖/封面圖,採用影片首幀,會遇到影片開頭是黑屏的問題,採用獨立封面圖相對效果會更好。
  • 影片高寬比控制
  • 帖子狀態控制:待播放,載入中,播放,迴圈/靜音等
你不能不知道的Flutter:Flutter影片滾動播放解決方案

其他方案

除了我們的實現方案,目前還可以找到的一個開源庫: inview_notifier_list

這個開源庫它支援固定位置播放,也就是我們說的以第一種型別。

該專案提供了預覽效果和demo,非常良心的作者。

Demo1

你不能不知道的Flutter:Flutter影片滾動播放解決方案

Demo2

你不能不知道的Flutter:Flutter影片滾動播放解決方案

他的基本原理如下:

  • 設計了一個InViewState容器,繼承了ChangeNotify,用於記錄列表中的子Widget的context資訊, InViewState state = InViewNotifierList.of(context);
  • 當影片Widget執行build時,呼叫InViewState.addContext加入集合中
  • 同時影片Widget本身需要用AnimatedBuilder將InViewState和影片狀態進行關聯,也就是當InViewState更新時,重新構建影片Widget
  • 什麼時候更新InViewState呢?答案是滑動過程中。滑動的時候開始遍歷前面加入的影片Widget的contxt,其實就是為了透過context難道控制元件最終的座標,判斷是不是符合規則
  • 如果座標位置命中規則,就會吧當前的控制元件的標識id快取到一個集合中 _currentInViewIds,然後通知觀察者資料變化了,所以AnimatedBuilder會觸發child的build
  • 在child的build時(也就是前面提到的影片空間build),就會在判斷當前影片的id是否在 _currentInViewIds中,進行差異化繪製。

我們用一張圖來概括性大致流程:

image
image

可以看到這裡面有兩個集合,分別用那個有快取控制元件的Context和名中控制元件資料。為了避免遍歷的效能問題,作者引入了快取閾值,可以由使用者根據實際情況調整快取context的個數。比如我們可以把個數設定為一個螢幕最多能展示的影片條數。這樣很可能不超過5個。

///Add the widget's context and an unique string id that needs to be notified.void addContext({@required BuildContext context, @required String id}) {
  _contexts.add(_WidgetData(context: context, id: id));
}///Keeps the number of widget's contexts the InViewNotifierList should stored/cached for///the calculations thats needed to be done to check if the widgets are inView or not.///Defaults to 10 and should be greater than 1\. This is done to reduce the number of calculations being performed.void removeContexts(int letRemain) {  if (_contexts.length > letRemain) {
    _contexts = _contexts.skip(_contexts.length - letRemain).toSet();
  }
}

這個實現方案基本滿足的場景一的規則。並且實現了區域性重新整理的監聽。

下面我們看下場景二,如果要基於當前影片的位置做計算,這個庫支援不了。

可以看到它暴露的引數: 影片控制元件上邊緣差值,下邊緣差值值,視窗高度H

//Check if the item is in the viewport by evaluating the provided widget's isInViewPortCondition condition.
  isInViewport = _isInViewCondition(deltaTop, deltaBottom, vpHeight);

如果我們要計算影片控制元件自身的露出情況,需要單獨獲取控制元件的座標和大小來計算。如果進行二次開發,只需要將判定函式擴充套件一些引數即可。

小結

這兩種方案,在檢測影片貼的邏輯上分別採用了主動檢測和被動檢測; 最大差異是我們沒有將影片貼提前新增到集合中,所以不用維護一個固定容量的快取集合。

目前我們的影片滾動播放方案已經完成元件改造,很快將對外開源,敬請期待。



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2671873/,如需轉載,請註明出處,否則將追究法律責任。

相關文章