fix: 修复录音按钮快速按放导致按钮卡死的异步竞态问题
This commit is contained in:
21
web/app.js
21
web/app.js
@@ -36,6 +36,8 @@ import audioProcessorUrl from "./audio-processor.js?worker&url";
|
|||||||
ws: null,
|
ws: null,
|
||||||
connected: false,
|
connected: false,
|
||||||
recording: false,
|
recording: false,
|
||||||
|
pendingStart: false,
|
||||||
|
startCancelled: false,
|
||||||
audioCtx: null,
|
audioCtx: null,
|
||||||
workletNode: null,
|
workletNode: null,
|
||||||
stream: null,
|
stream: null,
|
||||||
@@ -185,16 +187,25 @@ import audioProcessorUrl from "./audio-processor.js?worker&url";
|
|||||||
state.audioCtx = audioCtx;
|
state.audioCtx = audioCtx;
|
||||||
}
|
}
|
||||||
async function startRecording() {
|
async function startRecording() {
|
||||||
if (state.recording) return;
|
if (state.recording || state.pendingStart) return;
|
||||||
|
state.pendingStart = true;
|
||||||
|
state.startCancelled = false;
|
||||||
try {
|
try {
|
||||||
await initAudio();
|
await initAudio();
|
||||||
|
if (state.startCancelled) { state.pendingStart = false; return; }
|
||||||
// Ensure AudioContext is running (may suspend between recordings)
|
// Ensure AudioContext is running (may suspend between recordings)
|
||||||
if (state.audioCtx.state === "suspended") {
|
if (state.audioCtx.state === "suspended") {
|
||||||
await state.audioCtx.resume();
|
await state.audioCtx.resume();
|
||||||
}
|
}
|
||||||
|
if (state.startCancelled) { state.pendingStart = false; return; }
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
audio: { echoCancellation: true, noiseSuppression: true, channelCount: 1 },
|
audio: { echoCancellation: true, noiseSuppression: true, channelCount: 1 },
|
||||||
});
|
});
|
||||||
|
if (state.startCancelled) {
|
||||||
|
stream.getTracks().forEach((t) => t.stop());
|
||||||
|
state.pendingStart = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
state.stream = stream;
|
state.stream = stream;
|
||||||
const source = state.audioCtx.createMediaStreamSource(stream);
|
const source = state.audioCtx.createMediaStreamSource(stream);
|
||||||
const worklet = new AudioWorkletNode(state.audioCtx, "audio-processor");
|
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" });
|
worklet.port.postMessage({ command: "start" });
|
||||||
// Don't connect worklet to destination (no playback)
|
// Don't connect worklet to destination (no playback)
|
||||||
state.workletNode = worklet;
|
state.workletNode = worklet;
|
||||||
|
state.pendingStart = false;
|
||||||
state.recording = true;
|
state.recording = true;
|
||||||
sendJSON({ type: "start" });
|
sendJSON({ type: "start" });
|
||||||
micBtn.classList.add("recording");
|
micBtn.classList.add("recording");
|
||||||
setPreview("", false);
|
setPreview("", false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
state.pendingStart = false;
|
||||||
showToast(`麦克风错误: ${err.message}`);
|
showToast(`麦克风错误: ${err.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function stopRecording() {
|
function stopRecording() {
|
||||||
|
// Cancel pending async start if still initializing
|
||||||
|
if (state.pendingStart) {
|
||||||
|
state.startCancelled = true;
|
||||||
|
micBtn.classList.remove("recording");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!state.recording) return;
|
if (!state.recording) return;
|
||||||
state.recording = false;
|
state.recording = false;
|
||||||
// Stop worklet
|
// Stop worklet
|
||||||
|
|||||||
Reference in New Issue
Block a user