Skip to content

Commit 8cf9525

Browse files
committed
fix: prevent InvalidStateError from double-closing AudioContext in encoder and decoder
1 parent e61319e commit 8cf9525

2 files changed

Lines changed: 17 additions & 6 deletions

File tree

src/services/fskDecoder.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,13 @@ export async function startReceiver(onStatus: RxStatusCallback): Promise<() => v
9696
let stopped = false;
9797

9898
const stop = () => {
99+
if (stopped) return; // Guard: prevent double-stop
99100
stopped = true;
100-
if (pollTimer !== null) clearInterval(pollTimer);
101+
if (pollTimer !== null) { clearInterval(pollTimer); pollTimer = null; }
101102
stream?.getTracks().forEach(t => t.stop());
102-
ctx?.close();
103+
// Only close if not already closed — prevents InvalidStateError
104+
if (ctx && ctx.state !== 'closed') ctx.close();
105+
ctx = null;
103106
};
104107

105108
try {

src/services/fskEncoder.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,14 @@ const INTER_CHUNK_SILENCE_S = 0.1;
9999
export function transmitText(text: string, onStatus: TxStatusCallback): () => void {
100100
let ctx: AudioContext | null = null;
101101
let aborted = false;
102+
let ctxClosed = false; // Tracks whether we already closed the AudioContext
103+
104+
const safeClose = () => {
105+
if (ctx && !ctxClosed && ctx.state !== 'closed') {
106+
ctxClosed = true;
107+
ctx.close();
108+
}
109+
};
102110

103111
// We schedule everything ahead of time using AudioContext.currentTime,
104112
// then use real-time callbacks (setTimeout) to update UI progress.
@@ -148,21 +156,21 @@ export function transmitText(text: string, onStatus: TxStatusCallback): () => vo
148156

149157
// Wait until the timeline finishes, then close the context.
150158
const totalWallTimeMs = (cursor - ctx.currentTime) * 1000;
151-
setTimeout(async () => {
159+
setTimeout(() => {
152160
if (!aborted) {
153161
onStatus({ type: 'complete' });
154162
}
155-
await ctx?.close();
163+
safeClose();
156164
}, totalWallTimeMs + 200);
157165

158166
} catch (err) {
159167
onStatus({ type: 'error', message: String(err) });
160-
await ctx?.close();
168+
safeClose();
161169
}
162170
})();
163171

164172
return () => {
165173
aborted = true;
166-
ctx?.close();
174+
safeClose();
167175
};
168176
}

0 commit comments

Comments
 (0)