前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter 音视频播放器的实现思路及设计理念

Flutter 音视频播放器的实现思路及设计理念

作者头像
睡魔的谎言
修改2020-11-12 14:15:42
3.1K1
修改2020-11-12 14:15:42
举报

为什么要写本文

   写本文的目的是为了提高自己阅读、提炼源码的能力 🐳 ,优化自己的学习路线。因为平时在工作开发中会有很多碎片化 🧩 、重复化的浅显知识点 💡 ,通过此类源码总结和分析来加深自己技术栈的深度。

为什么是音视频播放器 📽

   随着Flutter在越来越多大厂的业务落地,大家不难发现,音视频是一块绕不开的业务。短视频、IM、新媒体等相对较重的业务中都会有音视频的身影 👀 ,那么如何通过一个强大的跨平台框架去实现一个强大 💪 、高性能、可控的音视频播放功能呢?我们是否还仅仅停留在使用插件的上层API 🔌 ?相信能耐心看完本文会,你对Flutter上的音视频实现会比之前有更深入的理解。

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组件。🖼 顺便附上一个简单的结构源码:

代码语言:javascript
复制
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媒体文件的载体。这里我们已经可以看出我们应该是要通过载体获取一些视频的信息📜 。

  继续追查!🔍 来看看到底这些个初始化方法干了什么,于是我们追到了最下层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的赋值点,我们就找到了这里!

  点击跳转到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呢?这里引用一张图可以让各位更好的了解。

参考文献

作者:多肉葡萄五分糖

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么要写本文
  • 为什么是音视频播放器 📽
  • Flutter 音频播放器的两种实现思路 🤔
  • video_player
    • Native Source Code 🍎
      • FLTVideoPlayer
      • FLTVideoPlayerPlugin
    • Flutter Source Code
    • 总结
    • 参考文献
    相关产品与服务
    云点播
    面向音视频、图片等媒体,提供制作上传、存储、转码、媒体处理、媒体 AI、加速分发播放、版权保护等一体化的高品质媒体服务。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档