Chrome扩展Manifest v3迁移:Service Worker无MediaRecorder替代方案求助
解决方案
核心思路
将录制逻辑拆分到三个独立模块,解决Manifest v3下Service Worker无DOM、页面刷新导致录制中断的问题:
- Service Worker(后台):负责定时调用
chrome.tabs.captureVisibleTab捕获页面截图,监听页面状态变化,传递截图数据到持久化录制上下文。 - 持久化录制上下文(侧边栏):承载
canvas和MediaRecorder实例,接收后台传来的截图并绘制到canvas,持续录制流——该上下文独立于目标页面生命周期,不会随页面刷新销毁。 - 内容脚本(可选):若需捕获页面内音频或交互事件,可在此处理后传递到录制上下文。
具体实现步骤
1. 配置Manifest v3权限
确保manifest.json包含所需权限与配置:
{ "manifest_version": 3, "name": "网页会话录制工具", "version": "1.0", "permissions": ["tabs", "activeTab", "sidePanel"], "background": { "service_worker": "background.js" }, "side_panel": { "default_path": "sidepanel.html" }, "action": { "default_title": "打开录制侧边栏" } }
2. 后台Service Worker(background.js)
负责截图捕获与状态管理:
// 捕获帧率设置(30帧/秒) const CAPTURE_INTERVAL = 1000 / 30; let captureTimer = null; let targetTabId = null; // 点击扩展图标打开侧边栏并记录目标标签页 chrome.action.onClicked.addListener(async (tab) => { targetTabId = tab.id; await chrome.sidePanel.open({ tabId: tab.id }); }); // 接收侧边栏的录制控制指令 chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === 'START_RECORDING') { startCapture(); sendResponse({ status: 'started' }); } else if (message.type === 'STOP_RECORDING') { stopCapture(); sendResponse({ status: 'stopped' }); } }); // 启动定时截图捕获 function startCapture() { if (captureTimer) return; captureTimer = setInterval(async () => { if (!targetTabId) return; try { // 直接捕获为Blob,避免base64的性能损耗 const blob = await chrome.tabs.captureVisibleTab(null, { format: 'png' }); const arrayBuffer = await blob.arrayBuffer(); // 传递截图数据到侧边栏 chrome.runtime.sendMessage({ type: 'NEW_SCREENSHOT', data: arrayBuffer, tabId: targetTabId }); } catch (err) { console.error('截图捕获失败:', err); } }, CAPTURE_INTERVAL); } // 停止截图捕获 function stopCapture() { if (captureTimer) { clearInterval(captureTimer); captureTimer = null; } } // 监听页面刷新状态,避免录制空白帧 chrome.tabs.onUpdated.addListener((tabId, changeInfo) => { if (tabId === targetTabId && changeInfo.status === 'loading') { clearInterval(captureTimer); captureTimer = null; } else if (tabId === targetTabId && changeInfo.status === 'complete') { if (!captureTimer) startCapture(); } });
3. 侧边栏页面(sidepanel.html)
提供录制控制UI与持久化上下文:
<!DOCTYPE html> <html> <head> <style> body { width: 300px; padding: 10px; } button { width: 100%; padding: 8px; margin: 5px 0; } #status { margin: 10px 0; } </style> </head> <body> <button id="startBtn">开始录制</button> <button id="stopBtn">停止录制</button> <div id="status">未录制</div> <script src="sidepanel.js"></script> </body> </html>
4. 侧边栏脚本(sidepanel.js)
处理截图绘制与MediaRecorder录制逻辑:
let canvas = null; let ctx = null; let mediaRecorder = null; let recordedChunks = []; // 初始化录制用canvas function initCanvas() { canvas = document.createElement('canvas'); ctx = canvas.getContext('2d'); // 按目标页面分辨率设置canvas尺寸(可根据实际调整) canvas.width = 1920; canvas.height = 1080; } // 开始录制按钮逻辑 document.getElementById('startBtn').addEventListener('click', async () => { initCanvas(); // 从canvas获取可录制的MediaStream const stream = canvas.captureStream(30); mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm' }); recordedChunks = []; // 收集录制数据 mediaRecorder.ondataavailable = (e) => { if (e.data.size > 0) recordedChunks.push(e.data); }; // 录制结束后生成视频文件 mediaRecorder.onstop = () => { const blob = new Blob(recordedChunks, { type: 'video/webm' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `会话录制-${Date.now()}.webm`; a.click(); URL.revokeObjectURL(url); document.getElementById('status').textContent = '录制已停止,视频已下载'; }; mediaRecorder.start(); document.getElementById('status').textContent = '正在录制...'; // 通知后台启动截图捕获 await chrome.runtime.sendMessage({ type: 'START_RECORDING' }); }); // 停止录制按钮逻辑 document.getElementById('stopBtn').addEventListener('click', async () => { if (mediaRecorder && mediaRecorder.state !== 'inactive') { mediaRecorder.stop(); await chrome.runtime.sendMessage({ type: 'STOP_RECORDING' }); } }); // 接收后台传来的截图并绘制到canvas chrome.runtime.onMessage.addListener(async (message) => { if (message.type === 'NEW_SCREENSHOT' && canvas) { const blob = new Blob([message.data], { type: 'image/png' }); const imgBitmap = await createImageBitmap(blob); ctx.drawImage(imgBitmap, 0, 0, canvas.width, canvas.height); imgBitmap.close(); } });
关键优化点
- 避免Base64传递:直接用Blob/ArrayBuffer传输截图,大幅提升效率、降低延迟。
- 持久化录制上下文:侧边栏独立于目标页面,页面刷新时录制不会中断,彻底解决分轨问题。
- 页面状态监听:页面刷新时暂停捕获,加载完成后恢复,避免录制空白帧。
- 高效绘制:使用
createImageBitmap处理截图,比传统Image对象加载更快,适配高帧率录制需求。
内容的提问来源于stack exchange,提问作者Avinash Dixit
相关产品推荐
相关产品推荐

