js使用canvas实现视频截图

这篇博客介绍了如何利用JavaScript、HTML5的canvas元素和视频播放器API(以西瓜播放器为例)来实现视频截图功能。主要内容包括:创建canvas元素,获取video元素DOM节点,设置canvas画布大小,使用drawImage()方法绘制视频帧,转换为base64格式并下载图片。还讨论了如何改进截图效果以适应播放器大小,并解决了画布污染的跨域问题,包括为video标签添加crossorigin属性以及处理视频重定向导致的跨域错误。

使用工具

视频播放器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);

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值