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>