当前位置: 首页 > news >正文

前端也能悄悄对视频截图?js实现对视频按帧缓存

前言

虽然最后没有采用这种方案来实现滚动控制视频进度,但是仍然想自己试试这种方案的实现,毕竟应用范围也挺广的。 核心代码并不多,算是一篇小短文~。

掘金好像不允许放站外演示链接,所以这里就用动图大概展示下最终的效果吧。

头像转动特效其实是一个视频,视频本身是60帧的,录屏GIF上传帧率比较低,所以看起来卡卡的,实际不是。

实现原理

获取视频video节点,根据配置循环控制视频的currentTime来控制进度,然后新建一个canvas来将每一刻的图像存储起来,实现起来还是有不少细节,具体往后看。

代码实现-创建节点

做下准备工作,新建一个视频节点,preload=“auto” loop autoPlay mute这三个属性,使视频可以循环自动播放,另外这里用了webm格式的视频(是不是想到了webp?没错,类似的),比较节约空间,但是实测在高速拖动情况下不如mp4格式流畅,兼容性也差点,不过当前场景没关系,这里就不展开细说。

const videoFile = require('../../assets/imgs/head_video.webm')
<video ref={videoRef} className={"video"} preload="auto" loop autoPlay muted={true} src={videoFile}/> 

代码实现-函数基本结构

改变视频进度来获取每一帧,这个操作毫无疑问是异步的,所以理想状态下是这样的,创建一个取帧的函数bufferVideoFrames(videoFile, frameRate),接收一个视频对象,以及帧率参数,返回一个promise。

// 调用获取结果,返回一个base64数组
const res = bufferVideoFrames(videoFile, 30)// 缓存函数xxxxxx
const bufferVideoFrames = async (videoFile, frameTotal = 24) => {return new Promise(async (resolve) => {.......................................}
} 

完善缓存函数-配置监听

首先,我们对视频的截图操作一定是要等视频当前帧加载完才能去进行的,所以需要监听视频当前帧加载完成, 此时缓存函数扩充一下,监听“loadeddata”事件:

 // 缓存函数xxxxxx
const bufferVideoFrames = async (videoFile, frameTotal = 24) => {return new Promise(async (resolve) => {// 当浏览器已加载视频的当前帧时触发videoNode.addEventListener('loadeddata', async function () {// 在这里执行具体代码}
} 

但是我们并不是一定要页面加载完就触发,我们可能是点击某个按钮才触发缓存,所以我们能不在页面初始化的时候就去注册这个监听,而是在这个函数调用的时候才注册,为了调用函数的时候能触发这个监听,我们手动将参数里的视频对象,再次挂载到视频节点上即可。

// 获取视频标签节点
let videoNode = document.createElement("video");
// 挂载一次视频对象,这一步会触发加载完成事件监听
videoNode.src = videoFile; 

也就是说此时缓存函数结构应该是:

1.获取节点
2.注册监听
3.挂载视频,触发监听

完善代码,此时的缓存函数:

 // 缓存函数xxxxxx
const bufferVideoFrames = async (videoFile, frameTotal = 24) => {return new Promise(async (resolve) => {// 获取视频标签节点let videoNode = document.createElement("video");// 当浏览器已加载视频的当前帧时触发videoNode.addEventListener('loadeddata', async function () {// 在这里执行具体代码}// 挂载一次视频对象,这一步会触发加载完成事件监听videoNode.src = videoFile;
} 

完善缓存函数-使用canvas保存帧

到了这一步,基本结构完成,距离完成已经是临门一脚,在监听内容里新建canvas,循环改变视频进度,保存当前帧到结果数组就好,代码如下,这里补充了一些配置类的变量:

 videoNode.addEventListener('loadeddata', async function () {// 新建一个canvas画布承载当前帧视频let canvas = document.createElement('canvas');let context = canvas.getContext('2d');// 尺寸根据视频宽高设置let [w, h] = [videoNode.videoWidth, videoNode.videoHeight]canvas.width = w;canvas.height = h;let frames = [];// 存放取出的帧let interval = 1 / frameTotal;// 计算每帧时长,例如60帧就是1/60,每帧16.6mslet currentTime = 0;// 起始时间let duration = videoNode.duration;// 视频总时长while (currentTime < duration) {// 不断按每帧时长,移动播放位置,直到视频结束videoNode.currentTime = currentTime;// 保存帧到canvas的contextcontext.drawImage(videoNode, 0, 0, w, h);// 将canvas转为base64的图片格式let base64ImageData = canvas.toDataURL();// 存入结果数组frames.push(base64ImageData);// 移动视频进度currentTime += interval;}resolve(frames);}); 

完善缓存函数-处理currentTime的异步问题

不出意外的话还是出意外的了,按上述的方法来实现,最终获取到的图像竟然是空白的!果然没有那么顺利。

 // 不断按每帧时长,移动播放位置,直到视频结束videoNode.currentTime = currentTime;// 保存帧到canvas的contextcontext.drawImage(videoNode, 0, 0, w, h); 

在这里设置currentTime之后,视频的帧实际上还没渲染完成,设置currentTime是一个异步的操作,这样写根本生成不了context的内容,因为还没等帧渲染完,这个循环就已经执行完成了。

所以我们要等设置进度的操作执行完,再去保存canvas的内容,查了一下,还好有设置进度完成的监听事件——“seeked”,await的好处来了,很简单,中间加一步,我们等到“seeked”监听触发,再去保存即可。

// 关键,通过await等待视频移动完成后,才执行后续帧的保存,通过seeked事件监听
// eslint-disable-next-line no-loop-funcawait new Promise(r => seeked = r);
// 保存帧到canvas的contextcontext.drawImage(videoNode, 0, 0, w, h); 

提前把“seeked”监听在调用函数的时候注册一下,最终的版本就完成了

const bufferVideoFrames = async (videoFile, frameTotal = 24) => {return new Promise(async (resolve) => {// 获取视频标签节点let videoNode = document.createElement("video");let seeked;// 监听设置视频播放位置动作结束videoNode.addEventListener('seeked', async function () {if (seeked) seeked();});// 当浏览器已加载视频的当前帧时触发videoNode.addEventListener('loadeddata', async function () {// 新建一个canvas画布承载当前帧视频let canvas = document.createElement('canvas');let context = canvas.getContext('2d');// 尺寸根据视频宽高设置let [w, h] = [videoNode.videoWidth, videoNode.videoHeight]canvas.width = w;canvas.height = h;let frames = [];// 存放取出的帧let interval = 1 / frameTotal;// 计算每帧时长,例如60帧就是1/60,每帧16.6mslet currentTime = 0;// 起始时间let duration = videoNode.duration;// 视频总时长while (currentTime < duration) {// 不断按每帧时长,移动播放位置,直到视频结束videoNode.currentTime = currentTime;// 关键,通过await等待视频移动完成后,才执行后续帧的保存,通过seeked事件监听// eslint-disable-next-line no-loop-funcawait new Promise(r => seeked = r);// 保存帧到canvas的contextcontext.drawImage(videoNode, 0, 0, w, h);// 将canvas转为base64的图片格式let base64ImageData = canvas.toDataURL();// 存入结果数组frames.push(base64ImageData);// 移动视频进度currentTime += interval;}resolve(frames);});// 一步会触发加载完成事件监听videoNode.src = videoFile;});
} 

结语

帧率越高,每一帧之间更接近,过渡更平滑,但是如果原视频只有30帧,那么按60帧取帧就会有一半的帧是重复的,并无意义。

前端场景下的应用:可用于抽帧合成、视频截图、动画进度控制(Apple官网),用途还是很广的。

缺点就是占用内存,在处理一些高清比如一个1280*720的1分钟的视频,按照40帧来进行缓存,内存占用将达到G级别。

最后

为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

相关文章:

前端也能悄悄对视频截图?js实现对视频按帧缓存

前言 虽然最后没有采用这种方案来实现滚动控制视频进度&#xff0c;但是仍然想自己试试这种方案的实现&#xff0c;毕竟应用范围也挺广的。 核心代码并不多&#xff0c;算是一篇小短文&#xff5e;。 掘金好像不允许放站外演示链接&#xff0c;所以这里就用动图大概展示下最终…...

TCP、UDP网络编程面试题

TCP、UDP、Socket、HTTP网络编程面试题 什么是网络编程 网络编程的本质是多台计算机之间的数据交换。数据传递本身没有多大的难度&#xff0c;不就是把一个设备中的数据发送给其他设备&#xff0c;然后接受另外一个设备反馈的数据。现在的网络编程基本上都是基于请求/响应方式…...

用网络调试助手测试PLC-Reocrder收听模式的过程

目录 一、测试环境 二、步骤及要点说明 1、PLC-Recorder的通道配置 2、PLC-Recorder启动采集 3、配置网络调试助手 4、启动调试助手的连接&#xff0c;并点击“启动批量发送” 5、停止发送&#xff0c;查看发送和接收的情况 三、小结 一、测试环境 Windows10操作系统&a…...

牛客小白月赛66

牛客小白月赛66_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ (nowcoder.com)冒着期末挂科的风险打了打&#xff0c;缓解了一下网瘾&#xff0c;感觉还行最近为了期末鸽了很多期的div3&#xff0c;一学期末就手痒想训&#xff0c;感觉再不打人要没了&#xff0c;结果…...

加载sklearn新闻数据集出错 fetch_20newsgroups() HTTPError: HTTP Error 403: Forbidden解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。喜欢通过博客创作的方式对所学的知识进行总结与归纳,不仅形成深入且独到的理…...

图解LeetCode——剑指 Offer 53 - I. 在排序数组中查找数字 I

一、题目 统计一个数字在排序数组中出现的次数。 二、示例 示例 1 【输入】nums [5,7,7,8,8,10], target 8 【输出】2 示例 2: 【输入】nums [5,7,7,8,8,10], target 6 【输出】0 提示&#xff1a; 0 < nums.length < 10^5-10^9 < nums[i] < 10^9nums 是一…...

python 实现热门音乐分析 附代码+数据 +论文

项目概述: 本选取了抖音当下最热门的 400 首音乐,通过一系列方法提取每首歌的波形特征,再经过降维以及机器学习等手段,进行无监督学习对音乐数据进行聚类的同时训练并使用监督学习分类器进行音乐流派分类,并通过可视化方法呈现分类聚类效果。 关键词:特征提取,PCA 主成分…...

【2335. 装满杯子需要的最短总时长】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 现有一台饮水机&#xff0c;可以制备冷水、温水和热水。每秒钟&#xff0c;可以装满 2 杯 不同 类型的水或者 1 杯任意类型的水。 给你一个下标从 0 开始、长度为 3 的整数数组 amount &#xff0c;…...

再不跳槽,就晚了

从时间节点上来看&#xff0c;3月、4月是每年跳槽的黄金季&#xff01; 以 BAT 为代表的互联网大厂&#xff0c;无论是薪资待遇、还是平台和福利&#xff0c;都一直是求职者眼中的香饽饽&#xff0c;“大厂经历” 在国内就业环境中无异于一块金子招牌。在这金三银四的时间里&a…...

Java 内存结构解密

程序计数器 物理上被称为寄存器&#xff0c;存取速度很快。 作用 记住下一条jvm指令的执行地址。 特点 线程私有&#xff0c;和线程一块出生。 不存在内存溢出。 虚拟机栈 每个线程运行时所需要的内存&#xff0c;称为虚拟机栈。 每个栈由多个栈帧组成&#xff0c;…...

ROS小车研究笔记2/11/2023:使用ssh远程登录小车

1 SSH简介&#xff1a; SSH全称Secure Shell&#xff0c;是一种建立在应用层的安全网络协议。其安全性又非对称加密(RSA)实现 对称加密&#xff1a;使用同一密钥对信息进行加密和解密&#xff0c;但是一旦该密钥被窃取就会威胁通信安全 非对称加密&#xff1a;使用公钥和私钥。…...

koa ts kick off 搭建项目的基本架子

koa ts kick off 使用ts开发koa项目的基本架子&#xff0c;便于平时随手调研一些技术 项目结构 ├── src │ ├── controller //controller层 │ ├── service //service层 │ ├── routes.ts //路由 │ └── index.ts //项目入…...

h2database源码解析-查询优化器原理

目录一、成本计算规则二、单表查询三、多表关联查询一、成本计算规则 h2的查询优化器基于成本的&#xff0c;因此在执行查询前&#xff0c;会基于成本计算使用哪个索引&#xff0c;如果涉及多表关联&#xff0c;还会计算不同表关联顺序的成本&#xff0c;最终基于最小成本得出…...

2月11日,30秒知全网,精选7个热点

///国产邮轮首制船将于今年5月底出坞&#xff0c;年底交付 浦东新区近期将发布相关政策支持包括外高桥造船在内的船舶产业发展 ///首批个人养老金理财产品名单发布&#xff1a;3家机构7只产品 中国理财网发布的信息显示&#xff0c;首批个人养老金理财产品名单发布&#xff0…...

vue组件的构成 <template> <script> <style>节点的使用 <

1.vue组件组成结构 每个.vue组件都由3部分构成&#xff0c;分别是: template ->组件的模板结构script ->组件的JavaScript行为style ->组件的样式 其中&#xff0c;每个组件中必须包含template模板结构&#xff0c;而script行为和style样式是可选的组成部分。 2.组…...

windows + vscode + rust

1 安装VSCODE略2 安装rust插件1、说明&#xff1a;第4步本人是一个一个点击状态。上图禁用按钮在没装之前是显示“安装”按钮&#xff0c;应该点击“安装”也可以。2、还需要安装C插件&#xff0c;搜索C即可&#xff0c;装微软的3 创建rust工程由于初次使用&#xff0c;不知道目…...

二十九、异常处理

目录 ①前言: ②常见的运行时异常 ③常见的编译时异常 ④异常的处理机制 ⑤自定义异常 ①前言: 1.什么是异常&#xff1f; 异常是程序在“编译”或者“执行”的过程中可能出现的问题&#xff0c;注意&#xff1a;语法错误不算在异常体系中。 比如: 数据索引越界异常&…...

RTOS之二环境搭建初识RTOS

参考&#xff1a;https://blog.csdn.net/kouxi1/article/details/123650688视频&#xff1a;https://www.bilibili.com/video/BV1b14y1c783/RTOS本质就是切换线程栈&#xff0c;栈换了环境就换了&#xff0c;一个重要的结构tcb&#xff08;linux叫PCB或thread_info&#xff09;…...

【Java】 JAVA Notes

JAVA语言帮助笔记Java的安装与JDKJava命名规范JAVA的数据类型自动类型转换强制类型转换JAVA的运算符取余运算结果的符号逻辑运算的短路运算三元运算符运算符优先级JAVA的流程控制分支结构JAVA类Scanner类Math 类random方法获取随机数Java的安装与JDK JDK安装网站&#xff1a;h…...

Java笔记-volatile和AtomicInteger

目录1. volatile1.1.什么是volatile1.2.JMM-Java内存模型2 验证volatile的特性2.1 可见性2.2.验证volatile不保证原子性2.3 volatile实现禁止指令重排序3.使用AtomicInteger解决volatile的不能实现原子性的问题3.2 AtomicInteger的方法说明&#xff1a;3.3 CAS3.4 应用1. volat…...

19c补丁后oracle属主变化,导致不能识别磁盘组

补丁后服务器重启&#xff0c;数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后&#xff0c;存在与用户组权限相关的问题。具体表现为&#xff0c;Oracle 实例的运行用户&#xff08;oracle&#xff09;和集…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

使用Spring AI和MCP协议构建图片搜索服务

目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式&#xff08;本地调用&#xff09; SSE模式&#xff08;远程调用&#xff09; 4. 注册工具提…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

GO协程(Goroutine)问题总结

在使用Go语言来编写代码时&#xff0c;遇到的一些问题总结一下 [参考文档]&#xff1a;https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现&#xff1a; 今天在看到这个教程的时候&#xff0c;在自己的电…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用

在工业制造领域&#xff0c;无损检测&#xff08;NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统&#xff0c;以非接触式光学麦克风技术为核心&#xff0c;打破传统检测瓶颈&#xff0c;为半导体、航空航天、汽车制造等行业提供了高灵敏…...

Ubuntu Cursor升级成v1.0

0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开&#xff0c;快捷键也不好用&#xff0c;当看到 Cursor 升级后&#xff0c;还是蛮高兴的 1. 下载 Cursor 下载地址&#xff1a;https://www.cursor.com/cn/downloads 点击下载 Linux (x64) &#xff0c;…...

论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving

地址&#xff1a;LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂&#xff0c;正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...