Files
egyptian-escape-room/src/mediaSounds.ts
T

72 lines
1.7 KiB
TypeScript

import type { MediaConfig } from './mediaConfig';
const audioElements = new Map<string, HTMLAudioElement>();
function ensureAudio(src: string): HTMLAudioElement {
let audio = audioElements.get(src);
if (!audio) {
audio = new Audio();
audio.preload = 'auto';
audio.src = src;
audioElements.set(src, audio);
}
return audio;
}
const AUDIO_URL_KEYS = [
'incorrectUrl',
'unlockConfirmUrl',
'celebrationPopUrl',
'celebrationHornUrl',
] as const satisfies readonly (keyof MediaConfig)[];
/** Fetch and decode audio clips so first playback is not delayed. */
export function preloadAudioAssets(config: MediaConfig): void {
for (const key of AUDIO_URL_KEYS) {
ensureAudio(config[key]).load();
}
}
export function playClip(src: string): HTMLAudioElement {
const cached = audioElements.get(src);
if (cached) {
if (cached.paused || cached.ended) {
cached.currentTime = 0;
void cached.play();
return cached;
}
const fallback = new Audio(src);
void fallback.play();
return fallback;
}
const audio = new Audio(src);
void audio.play();
return audio;
}
export function playIncorrectSound(incorrectUrl: string) {
playClip(incorrectUrl);
}
type CelebrationConfig = Pick<
MediaConfig,
| 'unlockConfirmUrl'
| 'celebrationPopUrl'
| 'celebrationHornUrl'
| 'celebrationHornDurationMs'
>;
/** Confirm, pop, and horn together; horn is trimmed — ends before unlock video audio. */
export function playCelebrationSounds(config: CelebrationConfig) {
playClip(config.unlockConfirmUrl);
playClip(config.celebrationPopUrl);
const horn = playClip(config.celebrationHornUrl);
window.setTimeout(() => {
horn.pause();
horn.currentTime = 0;
}, config.celebrationHornDurationMs);
}