Vue项目extractFrames视频抽帧函数无法返回帧数组问题
Vue 视频抽帧函数无法返回结果修复方案
问题表现
实现上传视频抽帧逻辑时,调试可见帧数据正常写入frames数组,但函数始终无法正常返回,await/.then()调用都拿不到返回值,Vuex存储动作提前执行,存入的视频数据缺失frames字段。
核心问题点
- drawImage上下文指向错误:seeked事件回调、while循环内的
this指向混乱,没有明确传入video元素,实际绘制逻辑存在隐性错误 - Promise写法不规范:async函数额外嵌套一层Promise,没有捕获视频加载、播放阶段的错误,容易出现Promise悬停不resolve的情况
- 时序逻辑完全错乱:没有等video元素加载新上传的视频资源就开始抽帧,调用抽帧方法时没加await,甚至抽帧还在执行就重置了
this.currentVideo临时对象,赋值引用直接失效 - 浏览器策略拦截:非静音视频调用
play()会被自动播放策略拦截,抛出的错误没有被捕获,导致整个Promise链卡住
修复后代码
抽帧函数
async extractFramesFromVideo(videoEl, duration) { // 等待视频元数据加载完成,支持seek跳转 await new Promise((resolve, reject) => { if (videoEl.readyState >= 2) return resolve(); const onLoaded = () => { cleanup(); resolve(); }; const onError = (err) => { cleanup(); reject(err); }; const cleanup = () => { videoEl.removeEventListener('loadeddata', onLoaded); videoEl.removeEventListener('error', onError); }; videoEl.addEventListener('loadeddata', onLoaded); videoEl.addEventListener('error', onError); }); // 静音规避自动播放策略拦截 videoEl.muted = true; await videoEl.play(); videoEl.pause(); let seekResolve; const onSeeked = () => { if (seekResolve) { const fn = seekResolve; seekResolve = null; fn(); } }; videoEl.addEventListener('seeked', onSeeked); const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const [width, height] = [videoEl.videoWidth, videoEl.videoHeight]; canvas.width = width; canvas.height = height; const frames = []; const interval = 0.1; let currentTime = 0; while (currentTime < duration) { videoEl.currentTime = currentTime; await new Promise(resolve => seekResolve = resolve); // 明确传入视频元素,避免this指向错误 ctx.drawImage(videoEl, 0, 0, width, height); frames.push(canvas.toDataURL('image/png')); currentTime += interval; // 修正浮点精度误差,避免边界判断异常 currentTime = Math.round(currentTime * 10) / 10; } // 移除事件监听避免内存泄漏 videoEl.removeEventListener('seeked', onSeeked); return frames; }
媒体加载方法
async loadMedia(media) { const file = media[0]; const videoUrl = URL.createObjectURL(file); const duration = await getBlobDuration(file); // 先给video元素绑定src,不要直接拿空的元素去抽帧 const baseVideo = document.getElementById('baseVideo'); baseVideo.src = videoUrl; // 等待抽帧完全完成,拿到所有帧数据再走后续逻辑 const frames = await this.extractFramesFromVideo(baseVideo, duration); // 所有字段准备齐全再赋值给临时对象 this.currentVideo = { videoSrc: videoUrl, name: file.name, mimeType: file.type, duration: duration, frames: frames }; // 数据完整后再提交到Vuex this.addVideoToStudio(this.currentVideo); // 所有操作执行完再重置临时变量、标记加载完成 this.currentVideo = { videoSrc: "", mimeType: "", name: "", duration: 0, frames: [], }; this.videoLoaded = true; }
优化提示
- 抽帧间隔0.1秒对应10fps,如果视频时长较长,生成的dataURL会占用大量内存,可根据实际需求调大抽帧间隔,或者转成Blob/ImageBitmap减少内存占用
- 不需要调用
video.play()也能做seek抽帧,直接等待loadeddata事件触发后设置currentTime即可,能减少播放状态切换带来的时序问题 - 抽帧过程中如果用户切换页面、删除上传的视频,需要加中断逻辑,避免无效计算占用资源
内容的提问来源于stack exchange,提问作者cpppatrick
相关产品推荐
相关产品推荐

