Skip to content

Commit a3e92f7

Browse files
Fix Safari AudioContext initialisation. (#99)
Current version seem to require the user interaction to be on the stack when creating/resuming the context. Switch to a single context and resume it if it starts suspended. Add some clean-up so we can keep the single context for the lifetime of the app (across stop/start). Closes #97
1 parent fc2093b commit a3e92f7

File tree

2 files changed

+28
-8
lines changed

2 files changed

+28
-8
lines changed

src/board/audio/index.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,9 @@ export class Audio {
2727
defaultAudioCallback,
2828
speechAudioCallback,
2929
}: AudioOptions) {
30-
this.context = new AudioContext({
31-
// The highest rate is the sound expression synth.
32-
sampleRate: 44100,
33-
});
34-
30+
if (!this.context) {
31+
throw new Error("Context must be pre-created from a user event");
32+
}
3533
this.muteNode = this.context.createGain();
3634
this.muteNode.gain.setValueAtTime(
3735
this.muted ? 0 : 1,
@@ -62,6 +60,16 @@ export class Audio {
6260
);
6361
}
6462

63+
async createAudioContextFromUserInteraction(): Promise<void> {
64+
this.context = new AudioContext({
65+
// The highest rate is the sound expression synth.
66+
sampleRate: 44100,
67+
});
68+
if (this.context.state === "suspended") {
69+
return this.context.resume();
70+
}
71+
}
72+
6573
playSoundExpression(expr: string) {
6674
const soundEffects = parseSoundEffects(replaceBuiltinSound(expr));
6775
const onDone = () => {
@@ -138,6 +146,9 @@ export class Audio {
138146

139147
boardStopped() {
140148
this.stopOscillator();
149+
this.speech?.dispose();
150+
this.soundExpression?.dispose();
151+
this.default?.dispose();
141152
}
142153

143154
private stopOscillator() {
@@ -188,4 +199,9 @@ class BufferedAudio {
188199
}
189200
source.start(startTime);
190201
}
202+
203+
dispose() {
204+
// Prevent calls into WASM when the buffer nodes finish.
205+
this.callback = () => {};
206+
}
191207
}

src/board/index.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,10 @@ export class Board {
230230
this.initializePlayButton();
231231
// We start stopped.
232232
this.displayStoppedState();
233-
this.playButton.addEventListener("click", () =>
234-
this.notifications.onRequestFlash()
235-
);
233+
this.playButton.addEventListener("click", async () => {
234+
await this.audio.createAudioContextFromUserInteraction();
235+
this.notifications.onRequestFlash();
236+
});
236237

237238
this.updateTranslationsInternal();
238239
this.notifications.onReady(this.getState());
@@ -481,6 +482,9 @@ export class Board {
481482
* @returns A promise that resolves when the simulator is stopped.
482483
*/
483484
async stop(brief: boolean = false): Promise<void> {
485+
// Preemptively stop audio so that we don't call into WASM for more data
486+
this.audio.boardStopped();
487+
484488
if (this.panicTimeout) {
485489
clearTimeout(this.panicTimeout);
486490
this.panicTimeout = null;

0 commit comments

Comments
 (0)