前端实现视频截图并生成图片文件

2,806 阅读4分钟

最近在做视频相关的项目,有一个需求是选取视频的任意一帧作为视频封面。实现这个需求可以是在前端填入截取视频的秒数,后台截取图片并返回图片链接,但有两个问题:一是不能精确知道视频某一帧的准确秒数,二是后端截图有网络传输时间延迟,无法实时预览截图效果,需要再次修改的话比较浪费资源。所以考虑到在前端实现截图并预览的功能,确认无误后直接上传选定的封面即可。

搜索了一下前端视频截图的方案,看到canvas有个drawImage()方法,可以以视频为源绘制一张图片 先来看一下drawImage()的介绍: CanvasRenderingContext2D(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

drawImage

image

绘制到上下文的元素。允许任何的 canvas 图像源(CanvasImageSource),例如:CSSImageValue,HTMLImageElement,SVGImageElement,HTMLVideoElement,HTMLCanvasElement,ImageBitmap 或OffscreenCanvas

sx 可选

需要绘制到目标上下文中的,image的矩形(裁剪)选择框的左上角 X 轴坐标

sy 可选

需要绘制到目标上下文中的,image的矩形(裁剪)选择框的左上角 Y 轴坐标。

sWidth 可选

需要绘制到目标上下文中的,image的矩形(裁剪)选择框的宽度。如果不说明,整个矩形(裁剪)从坐标的sx和sy开始,到image的右下角结束。

sHeight 可选

需要绘制到目标上下文中的,image的矩形(裁剪)选择框的高度。

dx

image的左上角在目标canvas上 X 轴坐标。

dy

image的左上角在目标canvas上 Y 轴坐标。

dWidth可选

image在目标canvas上绘制的宽度。 允许对绘制的image进行缩放。 如果不说明, 在绘制时image宽度不会缩放。

dHeight可选

image在目标canvas上绘制的高度。 允许对绘制的image进行缩放。 如果不说明, 在绘制时image高度不会缩放。

总结来说,s开头的参数是用来限制drawImage方法源图像的截取起始位置(sx,sy)和截取大小(sWidth,sHeight),d开头的参数是用来控制目标图像的在画布上的起始位置(dx,dy)和大小(dWidth,dHeight)。例如选取源文件以(21,23)为左上顶点,宽度为40,高度为50的一部分图片,绘制到画布(14,29)为左上顶点,生成的图片高度为20,宽度为25,函数为CanvasRenderingContext2D(image, 21, 23, 40, 50, 14, 29, 20, 25)

回到需求的实现上来,我们要得到一个和源视频某一帧大小一致的图片,则在源文件和目标画布上的偏移都为0,目标画布上图像的大小和视频大小一致,所以可以写出如下的方法:

<video controls width="320" src="video_url" id="video">
  Sorry, your browser doesn't support embedded videos.
</video>
getVideoScreenshot(index,row){
  let canvas = document.createElement("canvas");
  let canvasCtx = canvas.getContext("2d");
  let video = document.querySelector(`#video`);
  let imgWidth = canvas.width = video.offsetWidth;
  let imgHeight = canvas.height = video.offsetHeight;
  canvasCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, imgWidth, imgHeight);
  return canvas.toDataURL('image/png',1);
}

但是在这个方法调用的时候,最后一句却报错了: SecurityError: Failed to execute 'toBlob' on 'HTMLCanvasElement': Tainted canvases may not be exported.

搜索了一下这个报错的原因,是因为浏览器的CORS限制策略,如果在canvas中未添加CORS头的其他源图片,浏览器会认为该画布被污染,不再是安全的画布,而在被污染的画布上掉用getImageData(),toBlob(),toDataURL()方法时就会抛出安全性错误,这种机制可以避免未经许可拉取远程网站信息而导致的用户隐私泄露。

所以问题又归结到了跨域问题上,我们只需要在视频资源的响应头上声明相应的跨域访问头并在video标签上声明crossorigin="anonymous"即可解决问题.

因为需要在前端预览图片,所以我们调用toDataURL()方法生成了一个图片的url链接,但上传到后台时实际需要的是文件对象,所以图片上传时需要将base64格式的图片转化为文件形式:

getBlobBydataURI(dataURI,type) {
  var binary = atob(dataURI.split(',')[1]);
  var array = [];
  for(var i = 0; i < binary.length; i++) {
    array.push(binary.charCodeAt(i));
  }
  return new Blob([new Uint8Array(array)], {type:type });
}

当然也可以在生成链接时在canvas画布上调用toBlob()生成一个文件对象。