demo仅测试了以file对象做为源,格式为mp4
有几点可以考虑做成配置项:画布宽高;转 base64 或 blob 时,图片的格式以及质量;是否需要返回 base64 或 blob 的数组以及视频时长;
我在实际应用中仅使用了base64的数组,选择某一个图片时,再将单个的 base64转blob
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>提取视频帧</title> <style type="text/css"> #imgBox img { width: 50%; } </style> </head> <body> <input type="file" id="input" accept="video/*" /> <br> <br> <div id="imgBox"></div> </body> <script type="text/javascript"> /** * 在视频中提取帧画面 * @param { File | String } videoSource * @param { String } [interval = 1 / 30] - 以30fps提取每一帧,或传入指定间隔(s) * @returns { Object } obj: { base64Frames, blobFrames, duration } */ function extractFramesFromVideo(videoSource, interval = 1 / 30) { return new Promise(async (resolve) => { let videoBlob; // 如果视频源是视频文件对象,直接赋值 if (typeof videoSource === "object") { videoBlob = videoSource } else { // 如果是url路径,要先完全下载(无缓冲) videoBlob = await fetch(videoSource).then(r => r.blob()); } const videoObjectUrl = URL.createObjectURL(videoBlob); const video = document.createElement("video"); let seekResolve; video.addEventListener('seeked', async function() { // 音视频移动/跳跃到新的位置,并寻址完成后执行此函数 if (seekResolve) seekResolve(); }); // 当前帧的数据可用时执行 video.addEventListener('loadeddata', async function() { const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); // 画布宽高为视频原始宽高(考虑要不要做成配置项) const [w, h] = [video.videoWidth, video.videoHeight] canvas.width = w; canvas.height = h; // base64格式与blob对象格式的帧数组 const base64Frames = [], blobFrames = []; let currentTime = 0; const duration = video.duration; while (currentTime < duration) { video.currentTime = currentTime; // 设置完时间点后等待寻址完成 await new Promise(r => seekResolve = r); context.drawImage(video, 0, 0, w, h); let base64ImageData = canvas.toDataURL(); base64Frames.push(base64ImageData); canvas.toBlob((blob) => { blobFrames.push(blob) }) // 提取画面的时间步进(间隔) currentTime += interval; } resolve({ base64Frames, // base64格式的字符串数组 blobFrames, // blob对象格式的文件对象数组 duration // 视频总时长 }); }); // 在设置视频路径前先注册好监听事件,防止资源加载太快,事件发生在注册监听之前 video.src = videoObjectUrl; }); } const input = document.getElementById("input") input.onchange = function(e) { const file = input.files[0] console.log("视频处理中……"); console.time("耗时") // 以10秒的间隔取帧(取每一帧非常耗显卡算力) extractFramesFromVideo(file, 10).then(data => { console.timeEnd("耗时"); console.log(data); const box = document.getElementById("imgBox") data.base64Frames.forEach(item => { const img = new Image() img.src = item box.appendChild(img) }) }) } </script> </html>