metroluffy / blog

用于记录平时开发中所遇到问题的解决方法、笔记等
9 stars 1 forks source link

使用Canvas获取视频第一帧(Use Canvas to get the first frame of the video) #18

Open metroluffy opened 6 years ago

metroluffy commented 6 years ago

最近在做的一个项目中视频上传的功能需要获取视频的第一帧(要显示预览,没有第一帧图放个空白块实在不美观),而后端没法及时的返回,用JS会更方便点。之前也有类似的需求,应该是比较常见的需求了,这里整理一下。 一般来说,我们的视频是本地上传的,在监听到input['file'] onchange的事件后,可以如下这样做,

    let video = document.createElement('video')
    video.onloadedmetadata = () => {
            // 这里可以打印视频时长
            console.log(this.duration);
            // 这里取得视频封面
            var canvas = document.createElement('canvas');
            canvas.width = 300;
            canvas.height = 300 * this.videoHeight / this.videoWidth;
            canvas.getContext('2d').drawImage(this, 0, 0, canvas.width, canvas.height);
            // 这个就是第一帧的图片了,Base64格式的
            canvas.toDataURL()
    }

不出意外的话,有的时候你会看到图片是黑的。。。第一反应就是哪有出了问题,,实际上这是因为有些视频第一帧就是黑的。所以这里可以做点优化,比如取视频的第n秒做预览图,除非你这视频前几秒都是黑的~~,如下:

// 获取封面,默认第一秒
// 这里可以稍微关注一下HTTPS的问题(CORB)
function getVideoImage(path, callback, secs = 1) {
  var me = this,
    video = document.createElement('video');
  video.onloadedmetadata = function () {
    if ('function' === typeof secs) {
      secs = secs(this.duration);
    }
    this.currentTime = Math.min(Math.max(0, (secs < 0 ? this.duration : 0) + secs), this.duration);
  };
  video.onseeked = function (e) {
    var canvas = document.createElement('canvas');
    canvas.height = video.videoHeight;
    canvas.width = video.videoWidth;
    var ctx = canvas.getContext('2d');
    ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
    var img = new Image();
    img.src = canvas.toDataURL();
    callback.call(me, img, this.currentTime, e);
  };
  video.onerror = function (e) {
    callback.call(me, undefined, undefined, e);
  };
  video.src = path;
}

既然拿到了图片数据,自然也就可以上传到服务器保存起来,通过以下函数将Base64的图像资源(dataURI)转换成blob对象,然后以FormData的形式提交,

/**
 * Base64 转 Blob对象
 * @param dataURI
 * @returns {Blob}
 */
function dataURItoBlob(dataURI) {
  // convert base64/URLEncoded data component to raw binary data held in a string
  let byteString
  if (dataURI.split(',')[0].indexOf('base64') >= 0) {
    byteString = atob(dataURI.split(',')[1])
  } else {
    byteString = unescape(dataURI.split(',')[1])
  }

  // separate out the mime component
  let mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]

  // write the bytes of the string to a typed array
  let ia = new Uint8Array(byteString.length)
  for (let i = 0; i < byteString.length; i++) {
    ia[i] = byteString.charCodeAt(i)
  }

  return new Blob([ia], {
    type: mimeString
  })
}

后端一般来说会对视频进行转码或者加密,在这个过程中也可以获取到视频的第一帧,也可以使用一些云服务来处理视频,比如腾讯的云点播(Vod)。

这样就可以比较好的处理了这个问题。 这里有一个demo的地址