网易首页 > 网易号 > 正文 申请入驻

120 行代码实现纯 Web 剪辑视频

0
分享至

作者: 翁佳瑞 来源:微医大前端技术

前言

前几天偶尔看到一篇 webassembly 的相关文章,对这个技术还是挺感兴趣的,在了解一些相关知识的基础上,看下自己能否小小的实践下。

什么是 webasembly?

WebAssembly(wasm)就是一个可移植、体积小、加载快并且兼容 Web 的全新格式。可以将 C,C++等语言编写的模块通过编译器来创建 wasm 格式的文件,此模块通过二进制的方式发给浏览器,然后 js 可以通过 wasm 调用其中的方法功能。

WebAssembly 的优势

网上对于这个相关的介绍应该有很多了,WebAssembly 优势性能好,运行速度远高于 Js,对于需要高计算量、对性能要求高的应用场景如图像/视频解码、图像处理、3D/WebVR/AR 等,优势非常明显,我们可以将现有的用 C、C++等语言编写的库直接编译成 WebAssembly 运行到浏览器上,并且可以作为库被 JavaScript 引用。那就意味着我们可以将很多后端的工作转移到前端,减轻服务器的压力。.........

WebAssembly 最简单的实践调用

我们编写一个最简单的 c 文件

  • int add(int a,int b) {
    return a + b;

然后对于安装的 Emscripten 编译器Emscripten 安装指南

  • emcc test.c -Os -s WASM=1 -s SIDE_MODULE=1 -o test.wasm

然后我们在 html 中引入使用即可

  • fetch('./test.wasm').then(response =>
    response.arrayBuffer()
    ).then(bytes =>
    WebAssembly.instantiate(bytes)
    ).then(results => {
    const add = results.instance.exports.add
    console.log(add(11,33))

这时我们即可在控制台看到对应的打印日志,成功调用我们编译的代码啦

正式开动

既然我们已经知道如何能快速的调用到一些已经成熟的 C,C++的类库,那我们离在线剪辑视频预期目标更进一步了。

最终 demo 演示

由于录制操作的电脑 cpu 不太行,所以可能耗时比较久,但整体的效果还是能看的到滴

demo 仓库地址(https://github.com/Dseekers/clip-video-by-webassembly)

FFmpeg

在这个之前你得稍微的了解下啥是 FFmpeg? 以下根据维基百科的目录解释

FFmpeg 是一个开放源代码的自由软件,可以运行音频和视频多种格式的录影、转换、流功能[1],包含了 libavcodec——这是一个用于多个项目中音频和视频的解码器库,以及 libavformat——一个音频与视频格式转换库。

简单的说这个就是由 C 语言编写的视频处理软件,它的用法也是相当的简单

我主要将这次需要用到的命令给调了出来,如果你还可能用到别的命令,可以根据他的官方文档查看 ,还可以了解下阮一峰大佬的文章 (https://www.ruanyifeng.com/blog/2020/01/ffmpeg.html)

  • ffmpeg -ss [start] -i [input] -to [end] -c copy [output]

start 为开始时间 end 为结束时间 input 为需要操作的视频源文件 output 为输出文件的位置名称

这一行代码就是我们需要用到的剪辑视频的命令了

获取相关的FFmpeg的wasm

由于通过 Emscripten 编译 ffmpeg 成 wasm 存在较多的环境问题,所以我们这次直接使用在线已经编译好的 CDN 资源

这边就直接使用了这个比较成熟的库 https://github.com/ffmpegwasm/ffmpeg.wasm

为了本地调试方便,我把其相关的资源都下了下来 一共 4 个资源文件

  • ffmpeg.min.js
    ffmpeg-core.js
    ffmpeg-core.wasm
    ffmpeg-core.worker.js

我们使用的时候只需引入第一个文件即可,其它文件会在调用时通过 fetch 方式去拉取资源

最小的功能实现

前置功能实现: 在我们本地需要实现一个 node 服务,因为使用 ffmpeg 这个模块会出现如果没在服务器端设置响应头, 会报错 SharedArrayBuffer is not defined,这个是因为系统的安全漏洞,浏览器默认禁用了该 api,若要启用则需要在 header 头上设置

  • Cross-Origin-Opener-Policy: same-origin
    Cross-Origin-Embedder-Policy: require-corp

我们启动一个简易的 node 服务

  • const Koa = require('koa');
    const path = require('path')
    const fs = require('fs')
    const router = require('koa-router')();
    const static = require('koa-static')
    const staticPath = './static'
    const app = new Koa();
    app.use(static(
    path.join(__dirname, staticPath)
    // log request URL:
    app.use(async (ctx, next) => {
    console.log(`Process ${ctx.request.method} ${ctx.request.url}...`);
    ctx.set('Cross-Origin-Opener-Policy', 'same-origin')
    ctx.set('Cross-Origin-Embedder-Policy', 'require-corp')
    await next();

    router.get('/', async (ctx, next) => {
    ctx.response.body = '';
    router.get('/:filename', async (ctx, next) => {
    console.log(ctx.request.url)
    const filePath = path.join(__dirname, ctx.request.url);
    console.log(filePath)
    const htmlContent = fs.readFileSync(filePath);
    ctx.type = "html";
    ctx.body = htmlContent;
    app.use(router.routes());
    app.listen(3000);
    console.log('app started at port 3000...');

我们做一个最小化的 demo 来实现下这个剪辑功能,剪辑视频的前一秒钟 新建一个 demo.html 文件,引入相关资源

  • 选择原始视频文件: 开始剪辑视频 原视频 处理后的视频
  • let originFile
    $(document).ready(function () {
    $('#select_origin_file').on('change', (e) => {
    const file = e.target.files[0]
    originFile = file
    const url = window.webkitURL.createObjectURL(file)
    $('#origin-video').attr('src', url)
    $('#start_clip').on('click', async function () {
    const { fetchFile, createFFmpeg } = FFmpeg;
    ffmpeg = createFFmpeg({
    log: true,
    corePath: './assets/ffmpeg-core.js',
    const file = originFile
    const { name } = file;
    if (!ffmpeg.isLoaded()) {
    await ffmpeg.load();
    ffmpeg.FS('writeFile', name, await fetchFile(file));
    await ffmpeg.run('-i', name, '-ss', '00:00:00', '-to', '00:00:01', 'output.mp4');
    const data = ffmpeg.FS('readFile', 'output.mp4');
    const tempURL = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
    $('#handle-video').attr('src', tempURL)

其代码的含义也是相当简单,通过引入的 FFmpeg 去创建一个实例,然后通过 ffmpeg.load()方法去加载对应的 wasm 和 worker 资源 没有进行优化的 wasm 的资源是相当滴大,本地文件竟有 23MB,这个若是需要投入生产的可是必须通过 emcc 调节打包参数的方式去掉无用模块。然后通 fetchFile 方法将选中的 input file 加载到内存中去,接下来就可以通过 ffmpeg.run 运行和 本地命令行一样的 ffmpeg 命令行参数了参数基本一致

这时我们的核心功能已经实现完毕了。

做一点小小的优化

剪辑的话最好是可以选择时间段,我这为了方便直接把 element 的以 cdn 方式引入使用 通过 slider 来截取视频区间,我这边就只贴 js 相关的代码了,具体代码可以去 github 仓库里面仔细看下

  • class ClipVideo {
    constructor() {
    this.ffmpeg = null
    this.originFile = null
    this.handleFile = null
    this.vueInstance = null
    this.currentSliderValue = [0, 0]
    this.init()
    init() {
    console.log('init')
    this.initFfmpeg()
    this.bindSelectOriginFile()
    this.bindOriginVideoLoad()
    this.bindClipBtn()
    this.initVueSlider()
    initVueSlider(maxSliderValue = 100) {
    console.log(`maxSliderValue ${maxSliderValue}`)
    if (!this.vueInstance) {
    const _this = this
    const Main = {
    data() {
    return {
    value: [0, 0],
    maxSliderValue: maxSliderValue
    },
    watch: {
    value() {
    _this.currentSliderValue = this.value
    },
    methods: {
    formatTooltip(val) {
    return _this.transformSecondToVideoFormat(val);

    const Ctor = Vue.extend(Main)
    this.vueInstance = new Ctor().$mount('#app')
    } else {
    this.vueInstance.maxSliderValue = maxSliderValue
    this.vueInstance.value = [0, 0]

    transformSecondToVideoFormat(value = 0) {
    const totalSecond = Number(value)
    let hours = Math.floor(totalSecond / (60 * 60))
    let minutes = Math.floor(totalSecond / 60) % 60
    let second = totalSecond % 60
    let hoursText = ''
    let minutesText = ''
    let secondText = ''
    if (hours <� 10) {
    hoursText = `0${hours}`
    } else {
    hoursText = `${hours}`
    if (minutes <� 10) {
    minutesText = `0${minutes}`
    } else {
    minutesText = `${minutes}`
    if (second <� 10) {
    secondText = `0${second}`
    } else {
    secondText = `${second}`
    return `${hoursText}:${minutesText}:${secondText}`
    initFfmpeg() {
    const { createFFmpeg } = FFmpeg;
    this.ffmpeg = createFFmpeg({
    log: true,
    corePath: './assets/ffmpeg-core.js',

    bindSelectOriginFile() {
    $('#select_origin_file').on('change', (e) => {
    const file = e.target.files[0]
    this.originFile = file
    const url = window.webkitURL.createObjectURL(file)
    $('#origin-video').attr('src', url)

    bindOriginVideoLoad() {
    $('#origin-video').on('loadedmetadata', (e) => {
    const duration = Math.floor(e.target.duration)
    this.initVueSlider(duration)

    bindClipBtn() {
    $('#start_clip').on('click', () => {
    console.log('start clip')
    this.clipFile(this.originFile)

    async clipFile(file) {
    const { ffmpeg, currentSliderValue } = this
    const { fetchFile } = FFmpeg;
    const { name } = file;
    const startTime = this.transformSecondToVideoFormat(currentSliderValue[0])
    const endTime = this.transformSecondToVideoFormat(currentSliderValue[1])
    console.log('clipRange', startTime, endTime)
    if (!ffmpeg.isLoaded()) {
    await ffmpeg.load();
    ffmpeg.FS('writeFile', name, await fetchFile(file));
    await ffmpeg.run('-i', name, '-ss', startTime, '-to', endTime, 'output.mp4');
    const data = ffmpeg.FS('readFile', 'output.mp4');
    const tempURL = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
    $('#handle-video').attr('src', tempURL)

    $(document).ready(function () {
    const instance = new ClipVideo()

这样文章开头的效果就这样实现啦

小结

webassbembly 还是比较新的一项技术,我这边只是应用了其中一小部分功能,值得我们探索的地方还有很多,欢迎大家多多交流哈

参考资料

WebAssembly 完全入门——了解 wasm 的前世今生

(https://juejin.cn/post/6844903709806182413)

使用 FFmpeg 与 WebAssembly 实现纯前端视频截帧 (https://toutiao.io/posts/7as4kva/preview)

前端视频帧提取 ffmpeg + Webassembly (https://juejin.cn/post/6854573219454844935)

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
特朗普称在对伊行动中已“取得胜利”

特朗普称在对伊行动中已“取得胜利”

国际在线
2026-03-25 06:38:24
玄学提醒:如果一个人还在穿着10年前的衣服,只说明3个问题

玄学提醒:如果一个人还在穿着10年前的衣服,只说明3个问题

洞读君
2026-03-04 14:30:12
字节跳动发布2026年首份纪律通报:65名员工触碰纪律红线被辞退,其中7人因涉嫌刑事犯罪被移交司法机关处理

字节跳动发布2026年首份纪律通报:65名员工触碰纪律红线被辞退,其中7人因涉嫌刑事犯罪被移交司法机关处理

大风新闻
2026-03-27 16:50:05
清理六人交易得到两人阵容完美!特纳和吉迪无缝衔接,火箭升空

清理六人交易得到两人阵容完美!特纳和吉迪无缝衔接,火箭升空

民哥台球解说
2026-03-27 17:16:07
正式退出,19岁全红婵无缘亚运?安置岗位或效仿田亮,陈芋汐祝福

正式退出,19岁全红婵无缘亚运?安置岗位或效仿田亮,陈芋汐祝福

懂球社
2026-03-26 18:35:44
央企“最牛女副处长”落马:两年与上司开房410次,细节曝光

央企“最牛女副处长”落马:两年与上司开房410次,细节曝光

西门老爹
2025-12-16 15:35:31
配合美国制裁,全球顶级会议拒绝中企和院校投稿,中国当即反制

配合美国制裁,全球顶级会议拒绝中企和院校投稿,中国当即反制

丁丁鲤史纪
2026-03-27 16:45:25
1982年戴安娜艰难生下威廉,女王先看耳朵,一句评论让查尔斯心塞

1982年戴安娜艰难生下威廉,女王先看耳朵,一句评论让查尔斯心塞

小鹿姐姐情感说
2026-03-27 16:08:21
台湾统一的风向:赖清德由独转统,或能成就统一功绩

台湾统一的风向:赖清德由独转统,或能成就统一功绩

混沌录
2026-03-15 16:17:03
6月1日起执行!国务院831号令:家家户户喝水将迎7大变化!

6月1日起执行!国务院831号令:家家户户喝水将迎7大变化!

鲸探所长
2026-03-26 14:50:25
突发!一国宣布与以色列结盟,准备参战打伊朗

突发!一国宣布与以色列结盟,准备参战打伊朗

大国之翼
2026-03-27 06:18:10
第83波打击!特朗普担心的事发生,中俄在北京对表,双方直奔主题

第83波打击!特朗普担心的事发生,中俄在北京对表,双方直奔主题

健身狂人
2026-03-28 00:03:27
广州警方又打掉5个涉车“碰瓷”犯罪团伙!刑事拘留73人

广州警方又打掉5个涉车“碰瓷”犯罪团伙!刑事拘留73人

南方都市报
2026-03-27 12:44:04
权恩妃,有容乃大,真不是盖的!!这谁忍得住不爱?

权恩妃,有容乃大,真不是盖的!!这谁忍得住不爱?

动物奇奇怪怪
2026-03-25 15:23:30
7旬男子陪老伴住院,医生看了他一眼发现其面部有猝死先兆,将其从死亡边缘拉回

7旬男子陪老伴住院,医生看了他一眼发现其面部有猝死先兆,将其从死亡边缘拉回

观威海
2026-03-26 09:55:04
委内瑞拉总统马杜罗夫妇将出庭审判,主权国家的尊严如何在美国法律面前被践踏?

委内瑞拉总统马杜罗夫妇将出庭审判,主权国家的尊严如何在美国法律面前被践踏?

隐于山海
2026-03-28 00:44:49
“LV老板娘”来香港弹琴,何超琼捧场!嫁首富35年,稳坐豪门C位

“LV老板娘”来香港弹琴,何超琼捧场!嫁首富35年,稳坐豪门C位

商务范
2026-03-27 18:45:58
Axios称伊朗最高领袖未同意谈判,革命卫队封锁海峡劝返三艘船只

Axios称伊朗最高领袖未同意谈判,革命卫队封锁海峡劝返三艘船只

山河路口
2026-03-27 23:03:38
难怪红霉素软膏越来越好,这7大用途超厉害,早知道早受益!

难怪红霉素软膏越来越好,这7大用途超厉害,早知道早受益!

妙招酷
2026-03-26 22:33:25
日本一天两记重拳砸向中国!对华敌意再次升级!中国警告已发出!

日本一天两记重拳砸向中国!对华敌意再次升级!中国警告已发出!

闻香阁
2026-03-26 21:53:19
2026-03-28 01:19:00
Nodejs开发
Nodejs开发
分享只有程序员懂的干货
648文章数 823关注度
往期回顾 全部

科技要闻

杨植麟张鹏夏立雪罗福莉,聊龙虾、聊涨价

头条要闻

男医生给孕妻做彩超 丈夫崩溃撞墙:不过了 明天就离婚

头条要闻

男医生给孕妻做彩超 丈夫崩溃撞墙:不过了 明天就离婚

体育要闻

邵佳一:足球就像一场马拉松

娱乐要闻

范玮琪加盟,官宣《浪姐7》遭全网抵制

财经要闻

我在小吃培训机构学习“科技与狠活”

汽车要闻

与众08,金标大众不能输的一战

态度原创

家居
时尚
旅游
艺术
数码

家居要闻

曲线华尔兹 现代简约

推广中奖名单-更新至2026年3月11日推广

旅游要闻

日照岚山“打飞的”赏春成新时尚

艺术要闻

黑白呈现的迷离媚态人像,不看你就亏大了!

数码要闻

洛斐QQ音乐联名外设泄露:极地苔原色,瞬间激活432Hz自然声

无障碍浏览 进入关怀版