From 15801a01ba6b5d8957a546df6832894b7e92be6a Mon Sep 17 00:00:00 2001 From: Joseph Campuzano Date: Sat, 24 Jan 2026 12:16:53 -0600 Subject: [PATCH] =?UTF-8?q?fix:=20add=20state=20to=20pause=20existing=20au?= =?UTF-8?q?dio=20for=20demo=20menus,=20add=20support=20fo=E2=80=A6=20(#104?= =?UTF-8?q?28)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/src/components/settings-general.tsx | 32 +++++++++++++++---- packages/app/src/utils/sound.ts | 9 +++++- packages/ui/src/components/select.tsx | 1 + 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 75acf4f74..272aeb314 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -7,6 +7,26 @@ import { useSettings, monoFontFamily } from "@/context/settings" import { playSound, SOUND_OPTIONS } from "@/utils/sound" import { Link } from "./link" + +let demoSoundState = { + cleanup: undefined as (() => void) | undefined, + timeout: undefined as NodeJS.Timeout | undefined, +} + +// To prevent audio from overlapping/playing very quickly when navigating the settings menus, +// delay the playback by 100ms during quick selection changes and pause existing sounds. +const playDemoSound = (src: string) => { + if (demoSoundState.cleanup) { + demoSoundState.cleanup(); + } + + clearTimeout(demoSoundState.timeout) + + demoSoundState.timeout = setTimeout(() => { + demoSoundState.cleanup = playSound(src) + }, 100) +} + export const SettingsGeneral: Component = () => { const theme = useTheme() const language = useLanguage() @@ -211,12 +231,12 @@ export const SettingsGeneral: Component = () => { label={(o) => language.t(o.label)} onHighlight={(option) => { if (!option) return - playSound(option.src) + playDemoSound(option.src) }} onSelect={(option) => { if (!option) return settings.sounds.setAgent(option.id) - playSound(option.src) + playDemoSound(option.src) }} variant="secondary" size="small" @@ -235,12 +255,12 @@ export const SettingsGeneral: Component = () => { label={(o) => language.t(o.label)} onHighlight={(option) => { if (!option) return - playSound(option.src) + playDemoSound(option.src) }} onSelect={(option) => { if (!option) return settings.sounds.setPermissions(option.id) - playSound(option.src) + playDemoSound(option.src) }} variant="secondary" size="small" @@ -259,12 +279,12 @@ export const SettingsGeneral: Component = () => { label={(o) => language.t(o.label)} onHighlight={(option) => { if (!option) return - playSound(option.src) + playDemoSound(option.src) }} onSelect={(option) => { if (!option) return settings.sounds.setErrors(option.id) - playSound(option.src) + playDemoSound(option.src) }} variant="secondary" size="small" diff --git a/packages/app/src/utils/sound.ts b/packages/app/src/utils/sound.ts index d5e606c67..6dea812ec 100644 --- a/packages/app/src/utils/sound.ts +++ b/packages/app/src/utils/sound.ts @@ -106,5 +106,12 @@ export function soundSrc(id: string | undefined) { export function playSound(src: string | undefined) { if (typeof Audio === "undefined") return if (!src) return - void new Audio(src).play().catch(() => undefined) + const audio = new Audio(src) + audio.play().catch(() => undefined) + + // Return a cleanup function to pause the sound. + return () => { + audio.pause() + audio.currentTime = 0 + } } diff --git a/packages/ui/src/components/select.tsx b/packages/ui/src/components/select.tsx index b83f5f463..0386c329e 100644 --- a/packages/ui/src/components/select.tsx +++ b/packages/ui/src/components/select.tsx @@ -105,6 +105,7 @@ export function Select(props: SelectProps & Omit) }} onPointerEnter={() => move(itemProps.item.rawValue)} onPointerMove={() => move(itemProps.item.rawValue)} + onFocus={() => move(itemProps.item.rawValue)} > {local.children