From 61672c29e3611d08109b8772de1f08450892de35 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 05:01:17 +0000 Subject: [PATCH 1/2] Use Web Audio API for sound effects to avoid audio focus interruption Agent-Logs-Url: https://github.com/ProLoser/PeaceInTheMiddleEast/sessions/42a41ccb-97e5-4ef5-a386-ac9a4b61a3e6 Co-authored-by: ProLoser <67395+ProLoser@users.noreply.github.com> --- src/Utils.ts | 60 ++++++++++++++++++++++++++++++++++++++++++--------- src/index.tsx | 2 +- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/Utils.ts b/src/Utils.ts index 10ccd906..16aa075d 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -309,6 +309,8 @@ export function calculate(state: Game, from: number | Color | undefined | null, } const audioCache: { [key: string]: HTMLAudioElement } = {}; +const audioBufferCache: { [key: string]: Promise } = {}; +let audioContext: AudioContext | null = null; const checkerSounds = [ 'capture.mp3', @@ -319,17 +321,56 @@ const checkerSounds = [ 'promote.mp3', ]; -checkerSounds.forEach(file => { - const audio = new Audio(); - audio.preload = 'auto'; - audio.src = file; - audioCache[file] = audio; -}); +const getAudioElement = (source: string) => { + if (!audioCache[source]) { + const audio = new Audio(); + audio.preload = 'auto'; + audio.src = source; + audioCache[source] = audio; + } + return audioCache[source]; +}; + +const getAudioContext = () => { + if (audioContext) return audioContext; + const AudioContextConstructor = window.AudioContext || (window as typeof window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext; + if (!AudioContextConstructor) return null; + audioContext = new AudioContextConstructor(); + return audioContext; +}; + +const getAudioSource = (source: string | HTMLAudioElement) => { + if (typeof source === "string") return source; + return source.currentSrc || source.src; +}; -export const playAudio = (audio: HTMLAudioElement) => { - audio.currentTime = 0; +const getAudioBuffer = async (source: string, context: AudioContext) => { + if (!audioBufferCache[source]) { + audioBufferCache[source] = fetch(source) + .then(response => response.arrayBuffer()) + .then(arrayBuffer => context.decodeAudioData(arrayBuffer)); + } + return audioBufferCache[source]; +}; + +export const playAudio = (source: string | HTMLAudioElement) => { + const audioSource = getAudioSource(source); (async () => { try { + const context = getAudioContext(); + if (context && audioSource) { + if (context.state === "suspended") { + await context.resume(); + } + const buffer = await getAudioBuffer(audioSource, context); + const player = context.createBufferSource(); + player.buffer = buffer; + player.connect(context.destination); + player.start(0); + return; + } + const audio = typeof source === "string" ? getAudioElement(source) : source; + audio.currentTime = 0; await audio.play(); } catch (e) { if (e instanceof DOMException && e.name === "NotAllowedError") { @@ -344,8 +385,7 @@ export const playAudio = (audio: HTMLAudioElement) => { export const playCheckerSound = () => { const randomIndex = Math.floor(Math.random() * checkerSounds.length); const randomMp3 = checkerSounds[randomIndex]; - const audio = audioCache[randomMp3]; - playAudio(audio); + playAudio(randomMp3); }; const parseMoveLabel = (label: string, color: Color, ghosts: { [point: number]: number }, moved: { [point: number]: number }, ghostHit: { [point: number]: number }) => { diff --git a/src/index.tsx b/src/index.tsx index d10e2435..0656e152 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -36,7 +36,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render( ); -const diceSound = new Audio('./shake-and-roll-dice-soundbible.mp3'); +const diceSound = './shake-and-roll-dice-soundbible.mp3'; export function App() { const { t } = useTranslation(); From 1df7a71affbe5833b5843daefc2896dda5c729eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 05:02:43 +0000 Subject: [PATCH 2/2] Handle Web Audio buffer load failures safely Agent-Logs-Url: https://github.com/ProLoser/PeaceInTheMiddleEast/sessions/42a41ccb-97e5-4ef5-a386-ac9a4b61a3e6 Co-authored-by: ProLoser <67395+ProLoser@users.noreply.github.com> --- src/Utils.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Utils.ts b/src/Utils.ts index 16aa075d..d9cb0075 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -311,6 +311,7 @@ export function calculate(state: Game, from: number | Color | undefined | null, const audioCache: { [key: string]: HTMLAudioElement } = {}; const audioBufferCache: { [key: string]: Promise } = {}; let audioContext: AudioContext | null = null; +type WindowWithWebkitAudioContext = Window & { webkitAudioContext?: typeof AudioContext }; const checkerSounds = [ 'capture.mp3', @@ -333,7 +334,7 @@ const getAudioElement = (source: string) => { const getAudioContext = () => { if (audioContext) return audioContext; - const AudioContextConstructor = window.AudioContext || (window as typeof window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext; + const AudioContextConstructor = window.AudioContext || (window as WindowWithWebkitAudioContext).webkitAudioContext; if (!AudioContextConstructor) return null; audioContext = new AudioContextConstructor(); return audioContext; @@ -348,7 +349,11 @@ const getAudioBuffer = async (source: string, context: AudioContext) => { if (!audioBufferCache[source]) { audioBufferCache[source] = fetch(source) .then(response => response.arrayBuffer()) - .then(arrayBuffer => context.decodeAudioData(arrayBuffer)); + .then(arrayBuffer => context.decodeAudioData(arrayBuffer)) + .catch(error => { + delete audioBufferCache[source]; + throw error; + }); } return audioBufferCache[source]; };