当前位置: 首页 > 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…...

[标准库]STM32F103R8T6 高级定时器--PWM输出和带死区互补PWM输出

前言 STM32F103系列的MCU&#xff0c;相比普通的51单片机&#xff0c;在输出硬件PWM这个功能上要强不少&#xff0c;两者实现的方式都类似&#xff0c;都是通过一个定时器来启用硬件PWM输出&#xff0c;不过在输出PWM通道的数量上&#xff0c;32F103要强上不少。仅通过一个高级…...

Camtasia2023最新版电脑视频录屏记录编辑软件

在Mac或Wind上有各种可用的视频记录和编辑软件&#xff0c;其中Camtasia被称为视频记录器和视频编辑器。录屏软件Camtasia2023到底有什么特色功能&#xff1f;本文将帮助您选择理想的选择来开始视频捕获&#xff0c;创建和编辑。Camtasia2023是Mac/win平台上一款使用非常简单的…...

管理用户安全性

每个数据库用户帐户都包括以下项&#xff1a;唯一的用户名验证方法 默认表空间临时表空间用户概要文件初始使用者组帐户状态验证用户口令验证、外部验证、全局验证管理员验证操作系统安全性&#xff1a;• DBA 必须具有创建或删除文件的操作系统权限。• 普通数据库用户不应具有…...

分享113个JS菜单导航,总有一款适合您

分享113个JS菜单导航&#xff0c;总有一款适合您 113个JS菜单导航下载链接&#xff1a;https://pan.baidu.com/s/1d4nnh-UAxNnSp9kfMBmPAw?pwdcw23 提取码&#xff1a;cw23 Python采集代码下载链接&#xff1a;https://wwgn.lanzoul.com/iKGwb0kye3wj base_url "http…...

RuoYi-Cloud 部署

RuoYi-Cloud部署 1. 下载 点击右侧链接可以进入gitee的源码下载地址&#xff1a; 偌依微服务源码gitee下载地址 2. 数据库部署 依据如下步骤创建系统所需数据环境&#xff0c;脚本执行没有先后次序要求&#xff1a; 在Mysql 中创建 ry-cloud 主数据库&#xff0c;并执行 …...

DockerFile文件详解

一、DockerFile文件说明1、概述 Dockerfile是用来构建Docker镜像的文本文件&#xff0c;文本内容包含了一条条构建镜像所需的指令、参数和说明。可以在Docker文件中使用RUN&#xff0c;CMD&#xff0c;FROM&#xff0c;EXPOSE&#xff0c;ENV等指令。即&#xff1a;Dockerfile仅…...

Java程序运行机制

Java语言既具有编译型语言的特征&#xff0c;又具有解释型语言的特征&#xff0c;Java程序要经过先编译后解释两个阶段。高级语言的运行机制&#x1f4cd;编译型语言使用专门的编译器&#xff0c;针对特定的平台&#xff08;移植性差&#xff09;&#xff0c;将高级语言的源代码…...

LeetCode刷题------字符串

目录 LeetCode&#xff1a;344.反转字符串 LeetCode&#xff1a;541. 反转字符串II LeetCode&#xff1a;剑指Offer 05.替换空格 LeetCode&#xff1a;151.翻转字符串里的单词 LeetCode&#xff1a;剑指Offer58-II.左旋转字符串 LeetCode&#xff1a;28. 实现 strStr() …...

区块链技术与应用2——BTC-数据结构

文章目录比特币中的数据结构1. 区块链&#xff08;block chain&#xff09;2. 默克尔树&#xff08;Merkle tree&#xff09;3.哈希指针的问题比特币中的数据结构 1. 区块链&#xff08;block chain&#xff09; 哈希指针&#xff1a; &#xff08;1&#xff09;保存数值的位置…...

BiseNet v1论文及其代码详解

来源&#xff1a;投稿 作者&#xff1a;蓬蓬奇 编辑&#xff1a;学姐 BiSeNet v1说明&#xff1a; 文章链接&#xff1a;https://arxiv.org/abs/1808.00897 官方开源代码&#xff1a;https://github.com/CoinCheung/BiSeNet &#xff08;本文未使用&#xff09; 文章标题&am…...

创做网站/电商推广和网络推广的区别

Heres an action item for you, dear reader, and finally a decent reason to put your USB key to work other than just holding family photos. 亲爱的读者&#xff0c;这是为您准备的一项行动&#xff0c;最后一个体面的理由是使USB密钥可以正常工作&#xff0c;而不仅仅…...

黄埔网站建设价格/谷歌seo公司

Token校验可以使用拦截器或过滤器一、JWT1. Maven依赖com.auth0java-jwt3.2.02. JWT工具类package com.ruhuanxingyun.utils;import com.auth0.jwt.JWT;import com.auth0.jwt.algorithms.Algorithm;import com.auth0.jwt.interfaces.DecodedJWT;import com.ruhaunxingyun.commo…...

阿里巴巴批发网站上面怎么做微商/网络营销建议

如何修改windows 2008&#xff08;R2&#xff09;的远程桌面连接数 前几天给32个核的工作站上装了个windows 2008 r2 很不错&#xff0c;好多人都想远程使用&#xff0c;但发现最多只能两个同时使用。 对2003比较熟悉 2008应该也差不多&#xff0c;在组策略里找呀找&#xff0c…...

手机app制作网站/微商怎么做推广加好友

我正在xamp中尝试upgrade mysql。我正在使用需要mariaDB v10.2.2的laravel。所以我从mariaDB website下载了latest msi package。现在我遵循以下几点来安装它们&#xff1a;安装MySQL到C&#xff1a;\ TEMP。将旧的安装文件夹设为mysql_old。将以下文件夹“bin&#xff0c;incl…...

网站建设一般多少/公众号怎么做文章推广

大家在黑苹果安装完后经常出现核显没有驱动上&#xff0c;表现为查看显存只有6M、7M之类&#xff0c;会有卡顿&#xff0c;浏览器新建标签页会花屏等现象。开始之前请注意你的显示器接口以及是DVI、HDMI、DP之类的高清接口&#xff0c;使用VGA在本教程是无法驱动的。以下内容转…...

什么是培训网站建设/新手做外贸怎么入门

2019独角兽企业重金招聘Python工程师标准>>> RMI:远程方法调用(Remote Method Invocation)。能够让在某个java虚拟机上的对象像调用本地对象一样调用另一个java 虚拟机中的对象上的方法。 RMI远程调用步骤&#xff1a; 1&#xff0c;客户对象调用客户端辅助对象上的…...