赞
踩
参数 | 描述 | 取值范围 |
---|---|---|
t | 截图时间 | 单位ms,[0,视频时长] |
w | 截图宽度,如果指定为0则自动计算 | 像素值:[0,视频宽度] |
h | 截图高度,如果指定为0则自动计算,如果w和h都为0则输出为原视频宽高 | 像素值:[0,视频高度] |
m | 截图模式,不指定则为默认模式,根据时间精确截图,如果指定为fast则截取该时间点之前的最近的一个关键帧 | 枚举值:fast |
f | 输出图片格式 | 枚举值:jpg、png |
获取本地视频文件封面,实现本地预览功能,支持指定秒数
URL.createObjectURL(file.raw)
得到视频url本地地址onloadeddata
方法设置具体时间点,并添加监听事件addEventListener
GetVideoCover({
url: URL.createObjectURL(fileList[0].raw),
time: 2,
success: (res) => {
//封面地址res.base64
//this.videocover=res.base64
}
});
GetVideoCover.js
// 压缩图片 function clipAndCompressCover({ media, currentWidth, currentHeight, success }) { // eslint-disable-next-line no-unused-vars const that = this const canvas = document.createElement('canvas') const area = canvas.getContext('2d') const currentScale = currentWidth / currentHeight const targetScale = 750 / 420 let targetWidth = 0 let targetHeight = 0 let clipWidth = 0 let clipHeight = 0 let quality = 0.95 // 不要用1,会额外增大base64大小。 // // 根据视频宽高,决定截图大小 // if (currentScale >= targetScale) { // targetHeight = currentHeight > 420 ? 420 : currentHeight // targetWidth = targetHeight * currentScale // } else { // targetWidth = currentWidth > 750 ? 750 : currentWidth // targetHeight = targetWidth / currentScale // } // clipWidth = targetWidth > 750 ? 750 : targetWidth // clipHeight = targetHeight > 420 ? 420 : currentHeight canvas.width = currentWidth canvas.height = currentHeight area.drawImage( media, 0, 0, currentWidth, currentHeight, ) var handler = function() { const base64 = canvas.toDataURL('image/jpeg', quality) getMediaSize({ src: base64 }).then(response => { success && success(base64) }) } handler() } // 绘制视频封面图片 const GetVideoCover = ({ url, time, success }) => { const video1 = document.createElement('video') video1.src = url video1.style.cssText = 'position:fixed; top:0; left:-100%; visibility:hidden' video1.onloadeddata = function() { const currentTime = time // 截图时间点 video1.addEventListener('timeupdate', function() { // console.log('video1', video1.videoWidth, video1.videoHeight) clipAndCompressCover({ media: video1, currentWidth: video1.videoWidth, currentHeight: video1.videoHeight, success: function(base64) { video1.remove(video1) success({ base64 }) } }) }) video1.currentTime = currentTime < 0 ? 1 : currentTime } // edge浏览器必须要追加到dom中,才能顺利执行相关事件。 document.body.appendChild(video1) } // 获取媒体资源的大小,返回一个Promise对象。用于解决无法直接获取视频或图片的文件大小。 function getMediaSize({ src }) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest() // eslint-disable-next-line no-unused-vars xhr.onreadystatechange = _ => { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) { resolve({ size: xhr.response.size }) } else { reject() } } } xhr.open('get', URL.createObjectURL(transformBase64ToBlob(src))) xhr.responseType = 'blob' xhr.send() }) } function transformBase64ToBlob(base64) { let byteString if (base64.split(',')[0].indexOf('base64') >= 0) { byteString = atob(base64.split(',')[1]) } else { byteString = unescape(base64.split(',')[1]) } const mimeString = base64.split(',')[0].split(':')[1].split(';')[0] const ia = new Uint8Array(byteString.length) for (let i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i) } const blob = new Blob([ia], { type: mimeString }) return blob } export { GetVideoCover }
<canvas>
标签遍历上一步处理好的canvas数组,直接展示绘制好的canvas,这样用户可以通过键盘的上下左右箭头预览视频的每一帧的画面,也可以根据项目添加滚动条或者输入框做预览帧交互。<video :src="coverFileUrl" ref="video" id="video" controls muted autoplay style="width:300px;" ></video> <!-- coverFileUrl --> <canvas v-show="editImg.index==n" v-for="(i,n) in canvasList" :key="n" :id="'myCanvas'+n" :width="videoCig.width" :height="videoCig.height" :style="{transform:`scale(${scale})`}" @mousedown="cropMove($event,'start')" @mouseup="cropMove($event,'end')" ></canvas>
//给视频添加监听函数 initVideoplayer() { if (!this.videoPlayer && this.$refs.video) { this.videoPlayer = this.$refs.video; // 视频播放 -- 点击播放 this.videoPlayer.addEventListener("play", e => { let { frame, duration } = this.videoCig; this.timer = setInterval(() => { this.no += 1; if (this.no > (duration - 1) * frame) { clearInterval(this.timer); this.timer = null; // this.listenKeyDown(); //视频帧全部截取完毕再添加 } else { this.cutPicture(null, this.no); } }, 1000 / frame); }); // 视频播放 -- 播放结束 this.videoPlayer.addEventListener("ended", e => { console.log("视频播放结束"); }); } } //截取当前帧的图片 cutPicture(item, index) { let { width, height } = this.videoCig; var v = item ? document.getElementById("img" + index) : document.querySelector("video"); let canvas = document.getElementById("myCanvas" + index); var ctx = canvas.getContext("2d"); ctx.drawImage(v, 0, 0, width, height); let copyImg = ctx.getImageData(0, 0, width, height); var oGrayImg = canvas.toDataURL("image/jpeg"); this.canvasList.splice(index, 1, copyImg); if (index == 0) { //将首帧设为封面 this.video.cover = oGrayImg; } }
ffmpeg
插件截帧(推荐)需引入ffmpeg插件,详见文章vue2+vite利用ffmpeg实现纯前端视频剪切
可指定开始帧率、输出张数、尺寸、格式
缺点是加载该插件需要一定时间
优点是相比第三种视频播放+canvas的方法要快很多,二十多秒的视频几秒钟就处理完了,也不用担心浏览器崩的问题,除非你的视频文件很长。
参考文档: FFmpeg基本命令使用 、FFmpeg官网、 FFmpeg——在Vue项目中使用FFmpeg(安装、配置、使用)
关键代码
// 上传视频后解析视频帧 async getVideoFrames() { try { showLoading(); let { name, file, duration, ccbl } = this.ffVideo; ffmpeg.FS("writeFile", name, await FFmpeg.fetchFile(file)); // 计算每秒需要抽的帧数 let step = Math.ceil(10 / duration), allNum = Math.floor(step * duration); await ffmpeg.run( "-i", name,//视频文件名 "-r", `${step}`,//每秒抽取帧数 "-ss", "0",//从0帧开始 "-vframes", `${allNum}`,//所需截取总张数 "-f", "image2",//图片格式 "-s", `88*${Math.ceil((88 * ccbl[1]) / ccbl[0])}`, //图片尺寸需要整数,此处按比例计算高 "image-%02d.png"//图片命名 ); // ffmpeg -i 2.mp4 -r 1 -ss 0 -vframes 5 -f image2 -s 352x240 image-%02d.jpeg for (let i = 0; i < allNum; i++) { // await ffmpeg.run('-i', 'source.mp4', '-y', '-f', '-ss', averageDura * i, '1', 'frame.png') let temp = i + 1; if (temp < 10) { temp = "0" + temp; } this.frames.pics.push( arrayBufferToBase64(ffmpeg.FS("readFile", "image-" + temp + ".png")) ); } hideLoading(); retutn this.frames.pics } catch (err) { console.error("getVideoFrames", err); hideLoading(); // throw err; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。