This commit is contained in:
2026-05-13 00:28:07 +08:00
parent ef4f91a75e
commit 01c5ab985a
101 changed files with 10635 additions and 2292 deletions

View File

@@ -0,0 +1,69 @@
export const DEFAULT_RUNTIME_CLICK_SOUND_SRC = '/audio/ui-click-soft.wav';
export const DEFAULT_RUNTIME_LEVEL_CLEAR_SOUND_SRC =
'/audio/ui-level-clear.wav';
export const DEFAULT_RUNTIME_COUNTDOWN_SOUND_SRC =
'/audio/ui-countdown-warning.wav';
export const DEFAULT_RUNTIME_COUNTDOWN_WARNING_THRESHOLD_MS = 5_000;
export const DEFAULT_RUNTIME_LEVEL_AUDIO_CONFIG = {
clickSoundSrc: DEFAULT_RUNTIME_CLICK_SOUND_SRC,
levelClearSoundSrc: DEFAULT_RUNTIME_LEVEL_CLEAR_SOUND_SRC,
countdownSoundSrc: DEFAULT_RUNTIME_COUNTDOWN_SOUND_SRC,
countdownWarningThresholdMs: DEFAULT_RUNTIME_COUNTDOWN_WARNING_THRESHOLD_MS,
} as const;
const runtimeAudioCache = new Map<string, HTMLAudioElement>();
function clampRuntimeAudioVolume(value: number) {
if (!Number.isFinite(value)) {
return 0.6;
}
return Math.max(0, Math.min(1, value));
}
export function playRuntimeClickSound(
source = DEFAULT_RUNTIME_CLICK_SOUND_SRC,
volume = 0.6,
) {
if (import.meta.env.MODE === 'test' || typeof Audio === 'undefined') {
return;
}
const normalizedSource = source.trim();
if (!normalizedSource) {
return;
}
const audio =
runtimeAudioCache.get(normalizedSource) ?? new Audio(normalizedSource);
runtimeAudioCache.set(normalizedSource, audio);
audio.currentTime = 0;
audio.volume = clampRuntimeAudioVolume(volume);
try {
const playResult = audio.play();
void playResult?.catch?.(() => {
// 中文注释:浏览器可能在用户手势外拒绝播放,点击反馈不应中断主交互。
});
} catch {
// 中文注释:测试环境或极端浏览器可能未实现 play同样不能影响主交互。
}
}
export function playRuntimeLevelClearSound(volume = 0.6) {
playRuntimeClickSound(DEFAULT_RUNTIME_LEVEL_CLEAR_SOUND_SRC, volume);
}
export function playRuntimeCountdownSound(volume = 0.6) {
playRuntimeClickSound(DEFAULT_RUNTIME_COUNTDOWN_SOUND_SRC, volume);
}
export function resolveRuntimeCountdownSecondBucket(remainingMs: number) {
if (
!Number.isFinite(remainingMs) ||
remainingMs <= 0 ||
remainingMs > DEFAULT_RUNTIME_COUNTDOWN_WARNING_THRESHOLD_MS
) {
return null;
}
return Math.max(1, Math.ceil(remainingMs / 1000));
}