js捕捉video视频第一帧或指定帧的画面并绘制

4 篇文章 0 订阅
4 篇文章 0 订阅

下方代码运行环境非原生环境,是基于Vue2 + Element-UI的。
但别担心,因为实现方法完全基于原生JS实现,你完全可以直接拿过去任意JS项目使用。而我,只是用了export导出一下方法而已。
就是Async/await、Promise

一、准备视频文件或远程地址

准备好可以正常访问的视频素材:
(1)视频文件。使用input上传的File类型的文件
(2)远程地址。blob地址、http地址、本地路径等

二、视频处理

想必大家在网上搜半天了,发现几乎都是基于页面已有的video标签来直接处理的,那么接下来我将反其道而行之,在不实际添加DOM的情况下使用视频。

1. 处理视频地址

(1)视频文件

// 生成视频文件的blob类型地址;
const generateVideoFileBlobUrl = (file) => {
  return new Promise((resolve, reject) => {
    let fileReader = new FileReader();

    fileReader.onload = e => {
      let blob = new Blob([e.target.result]);
      resolve(blob)
    }

    fileReader.onerror = e => {
      reject(e)
    };

    fileReader.readAsArrayBuffer(file);
  })
}

(2)远程地址
远程地址无需转换,直接使用

2. 准备视频容器

视频处理完成,将之前创建的视频

/**
 * 获取视频的特定帧
 * @param {*} source 源文件
 * @param {*} frameTime // 绘制帧的时间 单位:ms
 * @param {*} width 绘制图片宽度 
 * @param {*} height 绘制图片高度 
 * @returns 
 */
function getVideoSpecialFrame({ source, frameTime, width = 320, height = 240 }) {
  return new Promise(async (resolve, reject) => {
	let videoSrc = "";
	
	// 处理视频链接
	console.log(typeof source)
	if (source instanceof File) {
	  // 文件类型 调用文件转换blob的处理方法
	  let blob = await generateVideoFileBlobUrl(source).then(data => data).catch(err => null);
	  if (!blob) return reject(new Error("文件处理错误,转换失败"));
	  videoSrc = window.URL.createObjectURL(blob); // 生成blob url
	} else if (typeof source === "string") {
	  // 远程地址 无需转换
	  videoSrc = source;
	} else {
	  return reject(new Error("文件类型参数无效,无法转换"))
	}
	
	if (!videoSrc) return reject(new Error("视频链接失效,转换失败"))
	
	let video = document.createElement("video");
	video.src = videoSrc;
	// 根据实际需要决定是否限定宽高
   	// video.width = width;
    // video.height = height;
    
    // do something by yourself......
  }
}

3. 捕获特定帧并绘制

根据用户传递的时间点,定位该镇,捕获该帧的画面并绘制。

video.addEventListener("loadeddata", function () {
	// 如果用户未传递
    if (frameTime) {
      video.currentTime = frameTime / 1000;
    }

    let duration = parseInt(video.duration * 1000);

    // 留给页面绘制的时间,当然,这里300ms等待时间是有点想当然的。
    // 不同主机不同配置都有影响,可能极快也可能极慢。
    setTimeout(() => {
      console.log(video.videoWidth)
      let { videoWidth, videoHeight } = video
      let canvas = document.createElement("canvas");
      canvas.width = width;
      canvas.height = height;

      // 为了能够绘制出符合指定比例的图,我需要对所绘制图片输出的宽高进行转换
      let scale = videoWidth / width; // 确定缩放系数
      let frameWidth = width;
      let frameHeight = videoHeight / scale;

      // 确定绘制的图片处于画布的中心位置
      let framePositionLeft = (width - frameWidth) / 2;
      let framePositionHeight = (height - frameHeight) / 2;

      // 一切就绪,就差绘制
      canvas.getContext("2d").drawImage(video, framePositionLeft, framePositionHeight, frameWidth, frameHeight);

      // 绘制完成,转base64出图!
      let frameBase64 = canvas.toDataURL('image/jpeg');

	  // 这里根据你自己的情况去返回你需要的参数吧
	  // base64都有了,转化为文件导出下载也不是不行,看你自己的咯
      resolve({
        duration: duration,
        video: videoSrc,
        poster: frameBase64
      });
    }, 300)
  });
})

三、综上所述

总的来讲,就是 拿到视频源->处理视频格式->加载视频->监听加载事件->跳转指定帧->canvas绘图->转base64

代码代码!!!

下边我把整体的代码贴近来,放张图给大伙儿看看。

/**
 * 获取视频的特定帧
 * @param {*} source 源文件
 * @param {*} frameTime // 绘制帧的时间 单位:ms
 * @param {*} width 绘制图片宽度 
 * @param {*} height 绘制图片高度 
 * @returns 
 */
export function getVideoSpecialFrame({ source, frameTime, width = 320, height = 240 }) {
  // 生成视频文件的blob类型地址;
  const generateVideoFileBlobUrl = (file) => {
    return new Promise((resolve, reject) => {
      let fileReader = new FileReader();

      fileReader.onload = e => {
        let blob = new Blob([e.target.result]);
        resolve(blob)
      }

      fileReader.onerror = e => {
        reject(e)
      };

      fileReader.readAsArrayBuffer(file);
    })
  }

  return new Promise(async (resolve, reject) => {
    let videoSrc = "";

    // 处理视频链接
    console.log(typeof source)
    if (source instanceof File) {
      let blob = await generateVideoFileBlobUrl(source).then(data => data).catch(err => null);
      if (!blob) return reject(new Error("文件处理错误,转换失败"));
      videoSrc = window.URL.createObjectURL(blob);
    } else if (typeof source === "string") {
      videoSrc = source;
    } else {
      return reject(new Error("文件类型参数无效,无法转换"))
    }

    if (!videoSrc) return reject(new Error("视频链接失效,转换失败"))

    let video = document.createElement("video");
    video.setAttribute('crossOrigin', 'anonymous')
    video.src = videoSrc;
    // video.width = width;
    // video.height = height;

    video.addEventListener("loadeddata", function () {
      if (frameTime) {
        video.currentTime = frameTime / 1000;
      }

      let duration = parseInt(video.duration * 1000);

      // 留给页面绘制的时间
      setTimeout(() => {
        console.log(video.videoWidth)
        let { videoWidth, videoHeight } = video
        let canvas = document.createElement("canvas");
        canvas.width = width;
        canvas.height = height;

        let scale = videoWidth / width;
        let frameWidth = width;
        let frameHeight = videoHeight / scale;

        let framePositionLeft = (width - frameWidth) / 2;
        let framePositionHeight = (height - frameHeight) / 2;

        canvas.getContext("2d").drawImage(video, framePositionLeft, framePositionHeight, frameWidth, frameHeight);

        let frameBase64 = canvas.toDataURL('image/jpeg');

        resolve({
          duration: duration,
          video: videoSrc,
          poster: frameBase64
        });
      }, 300)
    });
  })
}

这是基于ElementUI的一个滑块组件,懒得自己实现这个了。Elementui滑块实现的视频拖动切换封面Elementui滑块实现的视频拖动切换封面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值