1987WEB视界-分享互联网热点话题和事件

您现在的位置是:首页 > 短视频 > 正文

短视频

如何用纯前端实现一个Web版剪映?

1987web2023-06-08短视频216
一、前言Github地址:GitHub-SartreShao/video-editor.webassembly:PureFront-endVideoEditor

一、前言

Github 地址:GitHub - SartreShao/video-editor.webassembly: Pure Front-end Video Editor

本文描述了如何使用纯前端实现一个「Web 版剪映的时间轴功能」,它包含了如下几个 Feature:

放大 / 缩小 / 缩放到合适比例纯前端的实时视频读帧渲染完整的动态加载缓存方案

项目预览视频:

关于实现一个 Web 版剪映的实践

涉及到的技术细节:(本文未做完整阐述,请感兴趣的同学自行查阅资料)

C++ / FFmpeg:音视频处理基础库WebAssembly / Emscripten:Web 环境运行 C++Web Worker:JavaScript 多线程编程HTML CanvasFile、Blob、Stream API、ArrayBufferVideo KeyFrame 理解

二、聊聊产品

在聊技术之前,想要先聊两句产品

2.1 ClipChamp:国外的 Web 版剪映

ClipChamp,一家澳大利亚的创业公司,做的是「Web 版剪映」这件事儿。

他们在完成了3 次种子轮融资、1 次 A 轮融资(共计 $15.3M)后,被微软以未知价格收购。(如下图所示)

来源 CrunchBase.com

目前 ClipChamp 已经成为了微软主推的视频剪辑软件,放入了 Office 套件中进行推广。

结合着微软这家大 SaaS 公司的战略规划,一切都 Web 化、云端化似乎显得是那么合理。

2.2 何为 SaaS 产品:创投圈内 SaaS 含义的变迁

上文中我们谈到了 SaaS 这个词,正好最近我也正想把自己的最近职业生涯定义为SaaS 产品经理

所以,我想在本篇文章里聊聊:

——什么是 SaaS 产品、它有什么特征、现在的 SaaS 和当年的 SaaS 有哪些区别这些问题。

问题 1:何为 SaaS?(古早期)

答:最早的 SaaS(Software as a Service),其产生的语境是这样子的:

最早所有的软件都是需要下载安装的,比如在 Windows 上,都需要下载一个 .exe 格式的可执行文件,然后才能运行。但是后来,有一种软件直接运行在 Web 端,比如一些 CRM 软件,这种软件就被称之为 SaaS。

但是随着技术发展,越来越多的软件,都可以直接运行在 Web 上。

但是我们现在并不是把所有的软件都叫做 SaaS——比如我们肯定不会认为 Bilibili 是一个 SaaS,虽然它也是运行在 Web 上。

问题 2:何为 SaaS?(现代)

答:现在创投圈的语境下,SaaS 更多的代表的是一个「工具型软件」,而不是一个「运行在 Web 端的软件」。

当今的 SaaS 产品往往拥有如下的特征:

运行在 Web 端商业模式为订阅制(而不是广告)服务对象为 B 端用户

近期火热又典型的 SaaS 类产品有:

Notion——笔记类 SaaSFigma——设计类 SaaS

他们都在各自的领域革了原来老大哥们的命。。。(Word 与 Sketch)

问题 3:何为 B 端用户 / C 端用户?

上文中提到的 SaaS 产品的用户,一般都是 B 端用户。可能又有朋友有疑惑了——什么是 B 端,什么是 C 端呢?

在这里我想说说我对这个问题的思考:

C 端用户:使用软件解决本身的需求,该需求与商业无关B 端用户:使用软件解决商业相关的需求

或者,我们可以通过很简单的一个条件去 Check:

——即:看该用户用这个产品是否是为了继续赚钱(For Business)

比如——美图秀秀也是一个工具,但是它服务的用户明显是 C 端用户,因为用户是因为兴趣使用它的,而不是为了 P 图后继续用该图完成商业目标(当然不排除个别照骗钓鱼骗钱)。

而类似于「广联达」或者是「蓝湖」这类软件的用户,明显可以定义为 B 端用户——广联达的用户使用其做预算工程报价,蓝湖的用户使用其交付设计图给研发。这些行为背后明显指向一个商业的目标。

问题 4:为何 SaaS 产品的用户一定是 B 端用户呢?

答:是因为「订阅制(年费会员、月费会员)」的商业模式。

我们想想,如何让用户为一款工具掏钱呢?(还是年年掏、月月掏、持久的掏)

一定是满足「你的工具产品可以让用户挣更多的钱」这一先决条件,用户才会去付费。

否则,就只能去通过「三方市场」的商业模式去挣规模效应的钱——即用广告挣流量的钱。

三、聊聊技术

前前后后大概编写了两个月左右

3.1 时间轴的缩放

问题 1:何为时间轴的缩放?

答:时间轴的缩放,简单来讲,就是当我们点击时间轴右上方的「+ / -」按钮的时候:

刻度该如何变化视频、音频等元素该如何变化

这个问题的本质,我思考了很久,最终得出一个结论——就是在计算「帧宽度 FrameWidth」的过程。

问题 2:何为「帧宽度 FrameWidth」?

答:在解答帧宽度之前,我们需要先知道 2 个概念:

帧率 FPS:玩过游戏的同学应该都知道,即 1s 内有多少帧(比如:电影是 24 FPS,游戏是 120 FPS)像素单位 PX:即——电脑屏幕上渲染的最小物理单位

当我们理解了「帧率 FPS」与「像素单位 PX」后,我们思考一个问题:

我们该如何计算时间轴上视频的元素的宽度?

如图所示的一个电影,被我加载到了时间轴上。

这部电影时长 1 小时 50 分钟(6600 秒),那么它的宽度是多少 px 呢?

答案是:通过「帧宽度 FrameWidth」可以计算得出。具体计算路径如下:

1 秒的宽度:FrameWidth * FPS -> 一部电影的宽度:FrameWidth * FPS * MovieDruationTime">帧的宽度:秒的宽度:一部电影的宽度:1帧的宽度:FrameWidth−>1秒的宽度:FrameWidth∗FPS−>一部电影的宽度:FrameWidth∗FPS∗MovieDruationTime1 帧的宽度:FrameWidth -> 1 秒的宽度:FrameWidth * FPS -> 一部电影的宽度:FrameWidth * FPS * MovieDruationTime

问题 3:时间轴缩放的难点

理解了时间轴缩放的本质是操作「帧宽度 FrameWidth」后,剩下的问题就如同做数学题一样简单了。

通过「纯函数式编程」+「Vue3 响应式组件」的组合,帮助我有效的构建了时间轴缩放组件。

但是,在实现过程中,还有两个难点是花费了我大量时间的:

时间轴刻度时间轴刻度 DOM 的动态渲染

首先,聊聊时间轴的刻度问题,在这里我录了一个屏:

剪映的时间轴缩放

上文中说到,当我们操作时间轴右上角的「+ / -」按钮的时候,我们本质上改变的是「帧宽度 FrameWidth」。

但是我们仔细观察视频上面的「刻度尺」,我们会发现一开始它的单位还是「秒」,缩放到很小后又会变成「小时」,放大到一定程度后又会变成「帧」。

为了实现这个效果,我思考良久。最后我总结得出——这是一段分段函数:不同的帧宽度,对应不同的刻度尺单位。

我自己撰写了一段分段函数。其枚举代码在https://github.com/SartreShao/video-editor.webassembly/blob/new-master/src/map/index.js中的 Frame2Gird 函数中可以找到。

我认为,这是可以作为时间轴刻度尺的一个通用解决方案的。

另一个难点:「时间轴的动态渲染」

其核心在于监听时间轴的「横向滚动条的左偏移量」与「时间轴物理宽度」这两个值。

我们其实不需要渲染那么多格子的,我们只需要渲染「当前显示在屏幕上的格子」即可。

如视频所示:

时间轴 DOM 的动态渲染

3.2 实时读帧

问题 1:何为视频读帧呢?

答:即——纯前端实现的视频读帧并根据当前帧宽度、屏幕滚动轴横向偏移量,动态渲染视频帧至时间轴视频元素的过程。

就是下图这个玩意:

问题 2:视频读帧的本质技术有哪些?

答:跑通整个视频读帧的过程,还是比较复杂的,需要如下技术的支持:

Webassembly 技术:通过 Emscripten 编译 C++ / FFmpeg 的代码,实现高性能读帧的底层能力理解 File、Blob、Canvas 的底层原理:将 WebAssembly 传回来的数据,转换成 Blob URL,存入 Vue3 的响应式数组理解 CSS Image 的 background-position 技术:将 Blob URL 按照合理的位置,渲染到屏幕上

问题 3:实现视频读帧的技术细节?

实时读帧这里的技术细节,今天晚上看来是写不完了。

大概把架构画了出来:

架构解释:

屏幕监听器 ScreenListener:充当 Trigger 触发器的作用,一旦某些被监听的值发生变化,则会触发整个渲染流程。视频帧缓存器 VideoFrameBuffer:作为整个渲染器的「单一数据源」,向渲染目标提供帧图像的 blobUrl读帧任务栈 ReadFrameTaskStack:读帧函数 WASM.readFrame() 的核心调度器,确保了 WASM.readFrame() 中至多有 1 个任务在进行处理,并确保下一个任务始终是当前优先级最高的任务渲染器 VideoFrameRender:负责渲染 framesList 数据到一个 div 上(通过 background-image: url() / background-position 两个属性)读帧函数 WASM.readFrame():核心读帧函数,接收一个 File 类型的数据、需要的帧节点,返回帧图像(blobUrl)

如果想要仔细看视频读帧技术细节的兄弟,还请查阅项目源码(可运行):