下方代码运行环境非原生环境,是基于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的一个滑块组件,懒得自己实现这个了。