diff --git a/src/lib/utils.ts b/src/lib/utils.ts index da140b5fd..ce8be6eb6 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -19,48 +19,43 @@ export type GetAudioContextOptions = AudioContextOptions & { }; const map: Map = new Map(); +let interacted = false; -export const audioContext: ( - options?: GetAudioContextOptions, -) => Promise = (() => { - const didInteract = new Promise((res) => { - window.addEventListener("pointerdown", res, { once: true }); - window.addEventListener("keydown", res, { once: true }); - }); +/** + * Waits for a user interfaction and then resumes an audio context. + * The web-browser prevents audio context from being used before user interaction + * to stop web sites abusing auto play, or auto record. + * @param audioCtx the audio context to unlock + */ +function unlockAudioContext(audioCtx:AudioContext) { + if (audioCtx.state !== 'suspended') { + return; + } + if(interacted) { + audioCtx.resume(); + return; + } + const events = ['touchstart','touchend', 'mousedown','keydown', 'pointerdown']; + events.forEach(e => window.addEventListener(e, unlock, false)); + function unlock() { interacted=true;audioCtx.resume().then(clean); } + function clean() { events.forEach(e => window.removeEventListener(e, unlock)); } +} - return async (options?: GetAudioContextOptions) => { - try { - const a = new Audio(); - a.src = - "data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"; - await a.play(); - if (options?.id && map.has(options.id)) { - const ctx = map.get(options.id); - if (ctx) { - return ctx; - } +export async function audioContext(options?: GetAudioContextOptions) : Promise { + if (options?.id && map.has(options.id)) { + const ctx = map.get(options.id); + if (ctx) { + unlockAudioContext(ctx); + return ctx; } - const ctx = new AudioContext(options); - if (options?.id) { - map.set(options.id, ctx); - } - return ctx; - } catch (e) { - await didInteract; - if (options?.id && map.has(options.id)) { - const ctx = map.get(options.id); - if (ctx) { - return ctx; - } - } - const ctx = new AudioContext(options); - if (options?.id) { - map.set(options.id, ctx); - } - return ctx; } - }; -})(); + const ctx = new AudioContext(options); + if (options?.id) { + map.set(options.id, ctx); + } + unlockAudioContext(ctx); + return ctx; +} export const blobToJSON = (blob: Blob) => new Promise((resolve, reject) => { @@ -83,4 +78,4 @@ export function base64ToArrayBuffer(base64: string) { bytes[i] = binaryString.charCodeAt(i); } return bytes.buffer; -} +} \ No newline at end of file