Flutter 音視頻播放器的實現思路及設計理念

爲何要寫本文

   寫本文的目的是爲了提升本身閱讀、提煉源碼的能力 🐳 ,優化本身的學習路線。由於平時在工做開發中會有不少碎片化 🧩 、重複化的淺顯知識點 💡 ,經過此類源碼總結和分析來加深本身技術棧的深度。git

爲何是音視頻播放器 📽

   隨着Flutter在愈來愈多大廠的業務落地,你們不難發現,音視頻是一塊繞不開的業務。短視頻、IM、新媒體等相對較重的業務中都會有音視頻的身影 👀 ,那麼如何經過一個強大的跨平臺框架去實現一個強大 💪 、高性能、可控的音視頻播放功能呢?咱們是否還僅僅停留在使用插件的上層API 🔌 ?相信能耐心看完本文會,你對Flutter上的音視頻實現會比以前有更深刻的理解。github

Flutter 音頻播放器的兩種實現思路 🤔

   開始以前,你們能夠先思考一下若是是你來作一個Flutter的視頻播放器,你會如何去實現?你會遇到哪些困難呢?帶着問題來看文章每每會更有收穫 🔖 。可能很大一部分同窗都會和我同樣首先跳出來一個詞 —— PlatformView🔍 。確實,PlatformView看起來是個不錯的方案,PlatformView做爲一個在Flutter 1.0即發佈的技術方案,算是比較成熟且上手較快的方案。但很顯然,今天咱們的主角不是它 😅 ,爲何不是這個可愛的方案呢?請你們思考這樣一個業務場景:

  好比咱們想調用攝像頭 📷 來拍照或錄視頻,但在拍照和錄視頻的過程當中咱們須要將預覽畫面顯示到咱們的Flutter UI中,若是咱們要用Flutter定義的消息通道機制來實現這個功能,就須要將攝像頭採集的每一幀圖片都要從原生傳遞到Flutter中,這樣作代價將會很是大,由於將圖像或視頻數據經過消息通道實時傳輸必然會引發內存和CPU的巨大消耗!—— Flutter中文網

  也正是由於有這個業務場景,可能咱們今天的主角就要登場了💥 ——Texture(外接紋理),會不會有很大一部分好兄弟一臉懵逼?

  簡單的介紹一下:Texture能夠理解爲GPU內保存將要繪製的圖像數據的一個對象,Flutter engine會將Texture的數據在內存中直接進行映射(而無需在原生和Flutter之間再進行數據傳遞),Flutter會給每個Texture分配一個id,同時Flutter中提供了一個Texture組件。🖼 順便附上一個簡單的結構源碼:api

const Texture({
  Key key,
  @required this.textureId,
})
複製代碼

video_player

  video_playerFlutter官方plugin中的音視頻播放插件,咱們不妨以這個插件爲例,細看其中的一些端倪。我會經過幾部分的我的認爲比較關鍵的源碼,給各位點出該插件的實現方案。數據結構

Native Source Code 🍎

  因爲本人對iOS相對熟悉,Android不敢妄自推測,本文的Native部分也將以iOS爲例。但能夠保證每位小夥伴都看得懂,而且看完之後再看Android部分也是思路清晰(親測有效🤪 )框架

FLTVideoPlayer

  首先咱們能夠看到源碼中封裝了一個叫FLTVideoPlayer的類,很顯然,若是僅僅是PlatformView的簡單展現,此處無需本身封裝如此複雜的一個Player類,我對類中的方法和參數都作了註解(寫文章不易啊,爲了你們都能看懂,我給每一行都扣了註釋 🤣 ,由於源碼比較複雜,用Markdown語法寫的代碼塊看起來很不美觀,這邊我就直接截圖了,方便各位閱讀)



  注意,其實這個所謂的FLTVideoPlayer的核心點並非那個看似亮眼的play方法💎 ,這裏我要給你們介紹的是上面用虛線標出的初始化方法。看源碼就能夠發現,不管是加載本地Asset音頻,或是url的音頻,都調用了該方法。附上加載本地音視頻代碼:



  那麼這個方法到底作了什麼呢?🤔 其實調用的是另外一個初始化方法,經過PlayerItem進行初始化,AVPlayerItem提供了AVPlayer播放須要的媒體文件,時間、狀態、文件大小等信息,是AVPlayer媒體文件的載體。這裏咱們已經能夠看出咱們應該是要經過載體獲取一些視頻的信息📜 。ide

  繼續追查!🔍 來看看到底這些個初始化方法幹了什麼,因而咱們追到了最下層createVideoOutputAndDisplayLink方法,咱們能夠看到咱們在FLTVideoPlayer這個類中定義的好幾個變量都被使用了,而且看了源碼還能發現videoOutputdisplayLink僅在此處被賦值,可見他是一個核心的方法。這個方法作了什麼?🤯 我來給不熟悉iOS這邊的同窗解釋一下,咱們經過AVPlayerItemVideoOutput,得到了視頻解碼後的數據,同時咱們開啓了一個計時器,進行定時回調,同時咱們的定時器CADisplayLink的回調次數是根據屏幕刷新頻率來的,這樣咱們就達到了一個逐幀獲取視頻解碼後的數據的目的!👏 巧妙~fantastic!這個數據對咱們來講過重要了。
  那咱們拿到這個數據是怎樣一個類型呢?你們能夠看到咱們下面這個方法中有一個NSDictionary字典類型,裏面定義了咱們總體的一個數據結構,包括多個系統級的枚舉值kCVPixelFormatType_32BGRA, kCVPixelBufferPixelFormatTypeKey, kCVPixelBufferIOSurfacePropertiesKey這樣一個字典結構設計的目的是什麼呢?目的是將咱們獲取到的數據注入到一個叫CVPixelBuffer的類當中性能

  • CVPixelBufferRef 是一種像素圖片類型,屬於CoreVideo 模塊

FLTVideoPlayerPlugin

  以上部分介紹了實現音視頻播放插件的基礎類,接下來進入到咱們的插件具體實現部分🔦 。通俗來講,咱們在上面已經獲取到了CVPixelBufferRef類的數據,那咱們如何將Native層的數據傳輸到Dart層呢?這就是咱們插件要實現的部分。這部分直接貼出核心部分的代碼吧。你們能夠看到這裏,咱們選用的PlatformChannelEventChannel, 這個地方爲何不是methodChannel或者說BasicMessageChannel,其實答案已經在咱們的上文當中了,咱們須要將咱們獲取到的視頻數據進行傳輸,更貼切的是一個流式 🛀 的傳輸,而EventChannel就是爲了數據流而生的。
  再來仔細看看這個方法吧,方法中很顯然,咱們建立咱們的EventChannel,並無和以往簡單插件同樣用固定的channelName,此處咱們的channel和咱們的textureId相關。爲何這麼設計呢?實際上是爲了咱們的多窗口播放功能,也就是在插件的example展現的一個界面中多個播放畫面的效果,其實這一類的設計還能夠應用在視頻通話實現中的多窗口會話 📡 ,說白了就是能夠在Flutter中對應多個不一樣的Widget


 學習

Flutter Source Code

  有關Dart方面的具體實現策略也是主要經過EventChannel實現的,在EventChannel中會加入插件中支持的feature,包括暫停,輪播等。可是核心給你們介紹的也是如何和Native層創建連接。咱們在Dart層來仔細探究一下實現方法。(方法層層嵌套,設計很是巧妙,你們能夠跟着個人思路來找一找🔩 )。咱們首先確定能夠根據iOS中找到的EventChannel名字去找一下這個Channel🔫優化

  咱們首先找到了咱們的EventChannel定義處。看起來一切正常,惟一最大的疑問是,textureId是怎麼拿到的呢?是如何去和原生創建鏈接的呢?我們繼續往上找,該方法的調用在一個MethodChannelVideoPlayer類的方法中調用,但仍是看不出來textureId的來源。



  OK,那就繼續找,繼續找此處videoEventsFor的調用點,但仍是看不出來!僅僅看出來傳入了一個私有變量 🔬 ,很巧合的也叫textureId.



  那麼目標 📮 又變了,咱們如今要找的是_textureId的賦值點,咱們就找到了這裏!ui

  點擊跳轉到create方法的實現,哦豁!🤩 看到這個美麗的註解了嗎,咱們在這裏初始化VideoPlayer,同時返回他的textureId。結束了?No ~ No ~,不以爲這個方法很可疑嗎,僅僅只有一個報錯處理?如何實現所描述的功能?

  因而咱們確定要想,是有extends存在⛓ ,果真!在VideoPlayerPlatformextendsMethodChannelVideoPlayer中找到了實現方法,走到這一步,終於有點眉目了,但仍然沒有結束,看其中的回調,來自的是_api.create()方法,這個方法又作了什麼呢?首先咱們找到咱們的_api實際上是VideoPlayerApi()類。

  終於,咱們到達盡頭,盡頭是一個BasicMessageChannel,咱們在這裏經過BasicMessageChannelFlutterNative層進行通訊,在其中回調咱們的textureId。至此,謎底所有解開。能看到這裏的讀者應該給本身點一個贊 👍 。

總結

  本文主要給各位介紹了Flutter中實現音視頻的一種方案 🤗 ,外接紋理(Texture),這也是Flutter官方視頻插件所採用的方案。應該也顛覆了各位以往對Flutter插件的一些理解。再來回憶一下整個流程:iOSCVPixelBufferRef將渲染出來的數據存在內存中,Flutter engine會將Texture的數據在內存中直接進行映射無需經過Channel傳輸,而後Texture Widget就能夠把你提供的這些數據顯示出來。在咱們傳輸數據的時候會須要將其與 TextureID 綁定,綁定的過程經過BasicMessageChannel實現數據流的傳輸,以作到實時展現的效果 🚀。附上一張流程圖,方便你們理解:

  那麼咱們在選擇實現方案時是選擇PlatformView仍是Texture呢?這裏引用一張圖可讓各位更好的瞭解。

參考文獻

做者:多肉葡萄五分糖
相關文章
相關標籤/搜索