Preload audio clips on mount so first playthrough timing stays in sync.
This commit is contained in:
@@ -1,6 +1,46 @@
|
|||||||
import type { MediaConfig } from './mediaConfig';
|
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 {
|
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);
|
const audio = new Audio(src);
|
||||||
void audio.play();
|
void audio.play();
|
||||||
return audio;
|
return audio;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { DEFAULT_MEDIA_CONFIG, mergeMediaConfig, type MediaConfig } from './mediaConfig';
|
import { DEFAULT_MEDIA_CONFIG, mergeMediaConfig, type MediaConfig } from './mediaConfig';
|
||||||
|
import { preloadAudioAssets } from './mediaSounds';
|
||||||
|
|
||||||
export function useMediaConfig(): MediaConfig {
|
export function useMediaConfig(): MediaConfig {
|
||||||
const [config, setConfig] = useState<MediaConfig>(DEFAULT_MEDIA_CONFIG);
|
const [config, setConfig] = useState<MediaConfig>(DEFAULT_MEDIA_CONFIG);
|
||||||
@@ -22,5 +23,9 @@ export function useMediaConfig(): MediaConfig {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
preloadAudioAssets(config);
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user