使用工具
视频播放器API:西瓜播放器
截图:canvas
实现步骤
1. 创建 canvas 元素,创建 canvas 上下文对象
const canvas = document.createElement('canvas');
const canvasCtx = canvas.getContext('2d');
2. 获取 video 元素 DOM节点,在 React 中也可以使用 useRef 获取
const video = document.getElementById('mse')?.firstElementChild;
3. 设置 canvas 画布宽高(此处设置为画布宽高与视频宽高相同)
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
4. 使用 drawImage() 方法将视频画面画在 canvasCtx 上
drawImage() 参数介绍
drawImage(image, dx, dy)
drawImage(image, dx, dy, dw, dh)
drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
- image:必选,要截取的 image 或者 video 元素
- sx:可选,被截取图片的裁剪开始位置的 x 坐标
- sy:可选,被截取图片的裁剪开始位置的 y 坐标
- sw:可选,被截取图片的裁剪宽度
- sh:可选,被截取图片的裁剪高度
- dx:必选,裁剪图片放在画布上位置的 x 坐标
- dy:必选,裁剪图片放在画布上位置的 y 坐标
- dw:可选,裁剪图片放在画布上的宽度(放大或缩小)
- dh:可选,裁剪图片放在画布上的高度(放大或缩小)
canvasCtx.drawImage(
video,
0,
0,
video.videoWidth,
video.videoHeight,
0,
0,
video.videoWidth,
video.videoHeight,
);
5. 使用 toDataURL() 将 canvas 转为图片(base64)
const MIME_TYPE = 'image/png'; // 保存文件类型
const imgURL = canvas.toDataURL(MIME_TYPE);
6. 最后使用 a 标签将图片下载到本地
const dlLink = document.createElement('a');
dlLink.download = '截图'; // 文件名
dlLink.href = imgURL;
dlLink.dataset.downloadurl = [MIME_TYPE, dlLink.download, dlLink.href].join(':');
document.body.appendChild(dlLink);
dlLink.click();
document.body.removeChild(dlLink);
至此,即可实现基本的视频截图保存到本地的功能了。如下

截图为:

改进点
大部分时候,我们的播放器会固定宽高,视频需要根据播放器大小做自适应,所以在 video 视图中有一部分留白,如果我们想把这一部分留白也截到截图中,使得到的图片宽高就是 video 视图宽高,这里提供一个手动补全背景的方案。
1. 设置 canvas 画布宽高(设置为 video 视图宽高)
canvas.width = video.offsetWidth;
canvas.height = video.offsetHeight;
2. 手动在 canvasCtx 上填充播放器的背景色
canvasCtx.fillStyle = '#222125'; // 西瓜播放器背景色
canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
3. 计算视频画面等比缩放后在画布中的大小
const imgWidth = Math.min(canvas.width, video.videoWidth * canvas.height / video.videoHeight);
const imgHeight = Math.min(canvas.height, video.videoHeight * canvas.width / video.videoWidth);
4. 使用 drawImage() 方法将视频画面画在 canvasCtx 上
canvasCtx.drawImage(
video,
0,
0,
video.videoWidth,
video.videoHeight,
(canvas.width - imgWidth) / 2,
(canvas.height - imgHeight) / 2,
imgWidth,
imgHeight,
);
其余步骤按照之前的完成,改进后截图效果如下:

踩坑点
1. 如果遇到画布被污染,即
Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
的报错信息,原因是你使用的视频资源是跨域请求。
这时我们需要在 video 标签中添加一个属性 crossorigin="anonymous"。这时,可成功截图。
注意:在给 video 标签动态添加 crossorigin 属性时,必须要在赋值 video.src 之前赋值 video.crossorigin,否则该属性不会生效。
2. (非常规问题,不感兴趣可以不看)笔者在做截图时遇到了另一个跨域的问题,应该算是西瓜播放器的问题吧,也在这里记录一下。如果请求的视频资源被重定向的话,video 的 src 属性为重定向前的地址,但实际资源是重定向后的地址,这样的情况会被 Safari 浏览器判定为跨域请求,所以会报画布污染的错误,笔者在这里的解决方法是:手动 fetch 一下,拿到视频重定向后的 url 再赋值给 video,这样即可绕过重定向。
完整代码
const canvas = document.createElement('canvas');
const canvasCtx = canvas.getContext('2d');
const video = document.getElementById('mse')?.firstElementChild;
canvas.width = video.offsetWidth;
canvas.height = video.offsetHeight;
canvasCtx.fillStyle = '#222125';
canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
const imgWidth = Math.min(canvas.width, video.videoWidth * canvas.height / video.videoHeight);
const imgHeight = Math.min(canvas.height, video.videoHeight * canvas.width / video.videoWidth);
canvasCtx.drawImage(
video,
0,
0,
video.videoWidth,
video.videoHeight,
(canvas.width - imgWidth) / 2,
(canvas.height - imgHeight) / 2,
imgWidth,
imgHeight,
);
const MIME_TYPE = 'image/png'; // 保存文件类型
const imgURL = canvas.toDataURL(MIME_TYPE);
const dlLink = document.createElement('a');
dlLink.download = '截图'; // 文件名
dlLink.href = imgURL;
dlLink.dataset.downloadurl = [MIME_TYPE, dlLink.download, dlLink.href].join(':');
document.body.appendChild(dlLink);
dlLink.click();
document.body.removeChild(dlLink);
这篇博客介绍了如何利用JavaScript、HTML5的canvas元素和视频播放器API(以西瓜播放器为例)来实现视频截图功能。主要内容包括:创建canvas元素,获取video元素DOM节点,设置canvas画布大小,使用drawImage()方法绘制视频帧,转换为base64格式并下载图片。还讨论了如何改进截图效果以适应播放器大小,并解决了画布污染的跨域问题,包括为video标签添加crossorigin属性以及处理视频重定向导致的跨域错误。
2717

被折叠的 条评论
为什么被折叠?



