From b22b27db75b4ab481be54d7896bc01d4c9bc7f7c Mon Sep 17 00:00:00 2001 From: imbytecat Date: Sun, 1 Mar 2026 05:30:47 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=BD=95=E9=9F=B3?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E5=BF=AB=E9=80=9F=E6=8C=89=E6=94=BE=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E6=8C=89=E9=92=AE=E5=8D=A1=E6=AD=BB=E7=9A=84=E5=BC=82?= =?UTF-8?q?=E6=AD=A5=E7=AB=9E=E6=80=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/app.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/web/app.js b/web/app.js index c1ecae7..e8bd0d7 100644 --- a/web/app.js +++ b/web/app.js @@ -36,6 +36,8 @@ import audioProcessorUrl from "./audio-processor.js?worker&url"; ws: null, connected: false, recording: false, + pendingStart: false, + startCancelled: false, audioCtx: null, workletNode: null, stream: null, @@ -185,16 +187,25 @@ import audioProcessorUrl from "./audio-processor.js?worker&url"; state.audioCtx = audioCtx; } async function startRecording() { - if (state.recording) return; + if (state.recording || state.pendingStart) return; + state.pendingStart = true; + state.startCancelled = false; try { await initAudio(); + if (state.startCancelled) { state.pendingStart = false; return; } // Ensure AudioContext is running (may suspend between recordings) if (state.audioCtx.state === "suspended") { await state.audioCtx.resume(); } + if (state.startCancelled) { state.pendingStart = false; return; } const stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, channelCount: 1 }, }); + if (state.startCancelled) { + stream.getTracks().forEach((t) => t.stop()); + state.pendingStart = false; + return; + } state.stream = stream; const source = state.audioCtx.createMediaStreamSource(stream); const worklet = new AudioWorkletNode(state.audioCtx, "audio-processor"); @@ -208,15 +219,23 @@ import audioProcessorUrl from "./audio-processor.js?worker&url"; worklet.port.postMessage({ command: "start" }); // Don't connect worklet to destination (no playback) state.workletNode = worklet; + state.pendingStart = false; state.recording = true; sendJSON({ type: "start" }); micBtn.classList.add("recording"); setPreview("", false); } catch (err) { + state.pendingStart = false; showToast(`麦克风错误: ${err.message}`); } } function stopRecording() { + // Cancel pending async start if still initializing + if (state.pendingStart) { + state.startCancelled = true; + micBtn.classList.remove("recording"); + return; + } if (!state.recording) return; state.recording = false; // Stop worklet