生成一个thumbnail/snapshot的特定时间文件输入选中的视频文件

Generate a thumbnail/snapshot of a video file selected by a file input at a specific time

如何在视频中的特定时间在后台静默地抓取通过 <input type="file"> 选择的视频文件的快照(即没有可见元素、闪烁、声音等)?

主要有四个步骤:

  1. 创建 <canvas><video> 个元素。
  2. URL.createObjectURL 生成的视频文件的 src 加载到 <video> 元素中,并等待它加载 通过侦听被触发的特定事件.
  3. 将视频时间设置为您要拍摄快照的时间点并监听其他事件
  4. 使用canvas抓图。

第 1 步 - 创建元素

这很简单:只需创建一个 <canvas> 和一个 <video> 元素并将它们附加到 <body>(或任何地方,这并不重要):

var canvasElem = $( '<canvas class="snapshot-generator"></canvas>' ).appendTo(document.body)[0];
var $video = $( '<video muted class="snapshot-generator"></video>' ).appendTo(document.body);

请注意视频元素具有属性 muted。不要放置 autoplaycontrols 等任何其他属性。另请注意,它们都有 class snapshot-generator。这样我们就可以为它们设置样式,使它们不碍事:

.snapshot-generator {
  display: block;
  height: 1px;
  left: 0;
  object-fit: contain;
  position: fixed;
  top: 0;
  width: 1px;
  z-index: -1;
}

有些浏览器将它们设置为 display: none,但其他浏览器会遇到严重问题,除非它们呈现在页面上,因此我们只是将它们做得很小,以便它们基本上不可见。 (不过不要将它们移出视口,否则您可能会在页面上看到一些丑陋的滚动条。)

第 2 步 - 加载视频

事情开始变得棘手了。您需要收听事件以了解何时继续。不同的浏览器会触发不同的事件、不同的时间和不同的顺序,所以我会为你省力。在视频准备好之前,必须始终至少触发三个事件;他们是:

  • 加载元数据
  • 加载数据
  • 暂停

为这些事件设置事件处理程序并跟踪触发了多少事件。一旦所有三个都开火,你就可以继续了。请记住,由于其中一些事件可能会触发不止一次,因此您只想处理每种类型的第一个被触发的事件,并丢弃后续触发。 我使用了 jQuery 的 .one,负责处理这个问题。

var step_2_events_fired = 0;
$video.one('loadedmetadata loadeddata suspend', function() {
    if (++step_2_events_fired == 3) {
        // Ready for next step
    }
}).prop('src', insert_source_here);

源应该只是通过 URL.createObjectURL(file) 创建的对象 URL,其中 file 是文件对象。

第 3 步 - 设置时间

这个阶段与之前的阶段类似:设置时间然后监听一个事件。在我们之前代码的 if 块中:

$video.one('seeked', function() {
    // Ready for next step
}).prop('currentTime', insert_time_here_in_seconds);

幸运的是这次只有一个事件,所以它非常清晰简洁。终于...

第 4 步 - 获取快照

这部分只是使用<canvas>元素来抓取屏幕截图。在我们的 seeked 事件处理程序中:

canvas_elem.height = this.videoHeight;
canvas_elem.width = this.videoWidth;
canvas_elem.getContext('2d').drawImage(this, 0, 0);
var snapshot = canvas_elem.toDataURL();

// Remove elements as they are no longer needed
$video.remove();
$(canvas_elem).remove();

canvas 需要匹配视频的尺寸( 而不是 <video> 元素)才能获得正确的图像。此外,我们正在设置 canvas 的内部 .height.width 属性, 而不是 canvas height/width CSS 样式值。

snapshot的值是一个数据URI,基本上就是一个以data:image/jpeg;base64开头的字符串,然后是base64数据。

我们最终的 JS 代码应该是这样的:

var step_2_events_fired = 0;
$video.one('loadedmetadata loadeddata suspend', function() {
  if (++step_2_events_fired == 3) {
    $video.one('seeked', function() {
      canvas_elem.height = this.videoHeight;
      canvas_elem.width = this.videoWidth;
      canvas_elem.getContext('2d').drawImage(this, 0, 0);
      var snapshot = canvas_elem.toDataURL();

      // Delete the elements as they are no longer needed
      $video.remove();
      $(canvas_elem).remove();
    }).prop('currentTime', insert_time_here_in_seconds);
  }
}).prop('src', insert_source_here);

庆祝!

你的图片是 base64 格式的!将其发送到您的服务器,将其作为 <img> 元素的 src 或其他任何内容。

比如解码成二进制,直接写入文件(trim前缀在前),就变成了JPEG图片文件。

您还可以使用它在上传视频时提供视频预览。如果您将其作为 <img>src,请使用完整数据 URI (不要删除前缀).