浏览器扩展中麦克风/系统音频转录的重复录制请求问题排查与优化
浏览器扩展中麦克风/系统音频转录的重复录制请求问题排查与优化
嘿,看起来你在浏览器扩展的音频转录功能上遇到了头疼的重复录制问题——重复的进程不仅搞砸了转录结果,还白白占用内存,我来帮你一步步排查和解决这个问题!
一、先揪出问题的核心根源
从你的描述和代码片段来看,大概率是以下几个原因导致的重复录制:
- 状态变量失控:
isRecording这个标记没在正确的时机更新,导致多次触发录制逻辑 - 录制对象重复初始化:每次启动录制都新建
MediaRecorder或AudioContext,旧的实例没销毁还在后台偷偷运行 - 事件监听重复绑定:比如触发录制的按钮被多次绑定点击事件,点一次就启动一个新的录制进程
- 混合流未清理:每次录制都创建新的混合音频流,旧的流还在持续推送数据到API
二、针对性优化方案(结合你的contentScript代码)
基于你给出的代码片段,我们来一步步调整,解决重复录制和内存占用问题:
1. 严格管控录制状态,杜绝重复启动
首先要做的是:启动新录制前,必须确保旧的录制进程已经彻底停止。我们把录制逻辑封装成函数,严格控制状态:
// contentScript.js console.log("Content script has loaded."); let mediaRecorder; let isRecording = false; let audioContext; let mixedStream; let micStream; let systemStream; // 封装启动录制的函数 async function startRecording() { // 先检查是否正在录制,若已在录制则先停止 if (isRecording) { await stopRecording(); } try { // 初始化音频上下文(如果已关闭则重新创建) if (!audioContext || audioContext.state === 'closed') { audioContext = new AudioContext(); } // 获取麦克风和系统音频流(这里假设你有对应的获取逻辑) micStream = await navigator.mediaDevices.getUserMedia({ audio: true }); systemStream = await navigator.mediaDevices.getDisplayMedia({ audio: true, video: false }); // 混合麦克风和系统音频流 const micSource = audioContext.createMediaStreamSource(micStream); const systemSource = audioContext.createMediaStreamSource(systemStream); const destination = audioContext.createMediaStreamDestination(); micSource.connect(destination); systemSource.connect(destination); mixedStream = destination.stream; // 初始化MediaRecorder,选用兼容性好的音频格式 mediaRecorder = new MediaRecorder(mixedStream, { mimeType: 'audio/webm;codecs=opus' }); // 更新录制状态 isRecording = true; // 监听音频块生成,只在录制状态时发送到API mediaRecorder.ondataavailable = (e) => { if (e.data.size > 0 && isRecording) { sendAudioChunkToAPI(e.data); } }; // 监听录制结束,彻底清理资源 mediaRecorder.onstop = () => { // 停止所有媒体轨道 [micStream, systemStream, mixedStream].forEach(stream => { if (stream) stream.getTracks().forEach(track => track.stop()); }); // 关闭音频上下文,释放资源 audioContext.close(); // 重置所有状态变量 isRecording = false; mediaRecorder = null; audioContext = null; mixedStream = null; micStream = null; systemStream = null; }; // 开始录制,设置合适的时间片(根据你的API需求调整,比如1000ms) mediaRecorder.start(1000); } catch (err) { console.error("启动录制失败:", err); isRecording = false; } } // 封装停止录制的函数 async function stopRecording() { if (mediaRecorder && isRecording) { mediaRecorder.stop(); // 等待onstop事件处理完成,确保状态完全重置 await new Promise(resolve => { const checkStatus = () => { if (!isRecording) resolve(); else setTimeout(checkStatus, 100); }; checkStatus(); }); } } // 封装发送音频块到API的函数 function sendAudioChunkToAPI(blob) { // 过滤空Blob,避免无效请求 if (blob.size === 0) return; // 这里替换成你的API请求逻辑 fetch('https://your-api-endpoint.com/transcribe', { method: 'POST', body: blob, headers: { 'Content-Type': blob.type, // 可选:添加会话ID,API端可根据这个ID区分不同录制会话,避免跨会话重复数据 'X-Session-ID': crypto.randomUUID() } }).catch(err => { console.error("发送音频块失败:", err); }); }
2. 避免重复绑定触发事件
如果你的录制是通过页面按钮触发的,一定要确保事件只绑定一次,防止多次点击启动多个进程:
// 假设页面上有一个启动录制的按钮 const startBtn = document.getElementById('start-record-btn'); // 先移除旧的监听(如果有的话) startBtn.removeEventListener('click', startRecording); // 绑定新的监听,确保每次点击只触发一次录制逻辑 startBtn.addEventListener('click', startRecording);
3. 优化音频块发送,杜绝重复Blob
在ondataavailable事件中,我们已经加了isRecording状态判断和Blob大小检查,确保只有当前正在录制时才发送有效音频块。另外可以给每个录制会话生成唯一ID,API端可以根据这个ID过滤重复数据(如果需要的话)。
4. 内存优化:及时清理资源
在onstop事件中,我们会停止所有媒体轨道、关闭音频上下文并重置所有相关变量,这样能彻底释放内存,避免内存泄漏。
三、测试验证方法
- 打开浏览器开发者工具的Performance面板,录制整个流程,查看是否有重复的MediaRecorder实例或音频流
- 在Console中打印
isRecording、mediaRecorder的状态,确认每次启动录制前状态都是正确的 - 查看API的请求日志,检查是否还有重复的音频块请求
备注:内容来源于stack exchange,提问作者D M




