前言:利用web浏览器原生API进对Canvas画面进行录屏并转换成mp4的具体操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
| (async function () { const SELECTOR = "canvas"; const COUNTDOWN = 10; const FPS = 60; const WIDTH = 1920; const HEIGHT = 1080; const srcCanvas = document.querySelector(SELECTOR); if (!srcCanvas) { console.error("未找到 canvas 元素,请检查选择器:", SELECTOR); return; } console.log(`原 canvas 尺寸: ${srcCanvas.width}x${srcCanvas.height}, devicePixelRatio: ${window.devicePixelRatio}`); const offscreen = document.createElement("canvas"); offscreen.width = WIDTH; offscreen.height = HEIGHT; offscreen.style.width = WIDTH + "px"; offscreen.style.height = HEIGHT + "px"; const ctx = offscreen.getContext("2d"); ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = "high"; let drawing = true; function drawFrame() { if (!drawing) return; ctx.drawImage(srcCanvas, 0, 0, srcCanvas.width, srcCanvas.height, 0, 0, WIDTH, HEIGHT); requestAnimationFrame(drawFrame); } drawFrame(); const canvasStream = offscreen.captureStream(FPS); let displayStream = null; let audioTrack = null; try { console.log("请在弹窗中选择当前标签页,并勾选「分享标签页音频」"); displayStream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: true, preferCurrentTab: true, selfBrowserSurface: "include", }); audioTrack = displayStream.getAudioTracks()[0]; if (!audioTrack) { throw new Error("未获得音轨:请选择「Chrome 标签页」并勾选「分享标签页音频」"); } console.log("已获取标签页音频:", audioTrack.label || "未命名音轨"); } catch (e) { drawing = false; displayStream?.getTracks().forEach((t) => t.stop()); canvasStream.getTracks().forEach((t) => t.stop()); console.error("无法录制当前标签页音频,已取消录制:", e.message); return; } const combinedStream = new MediaStream(); canvasStream.getVideoTracks().forEach((t) => combinedStream.addTrack(t)); if (audioTrack) combinedStream.addTrack(audioTrack); console.log(`${COUNTDOWN} 秒后开始录制...`); for (let i = COUNTDOWN; i > 0; i--) { console.log(` ${i}...`); await new Promise((r) => setTimeout(r, 1000)); } const chunks = []; const recorder = new MediaRecorder(combinedStream, { mimeType: "video/webm;codecs=vp9,opus", videoBitsPerSecond: 8000000, }); recorder.ondataavailable = (e) => { if (e.data.size > 0) chunks.push(e.data); }; const startTime = Date.now(); recorder.onstop = () => { drawing = false; displayStream.getTracks().forEach((t) => t.stop()); canvasStream.getTracks().forEach((t) => t.stop()); const duration = ((Date.now() - startTime) / 1000).toFixed(1); const blob = new Blob(chunks, { type: "video/webm" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "canvas-recording.webm"; a.click(); URL.revokeObjectURL(url); console.log(`录制完成!时长 ${duration}s,视频已下载为 canvas-recording.webm`); console.log("转 mp4: ffmpeg -i canvas-recording.webm -c:v libx264 -c:a aac -crf 18 output.mp4"); delete window.stop; }; recorder.start(); console.log(`正在录制 ${WIDTH}x${HEIGHT} @ ${FPS}fps...`); console.log("在控制台输入 stop() 停止录制"); window.stop = () => { recorder.stop(); console.log("录制结束,正在生成视频..."); };
})();
|