Files
Genarrative/src/services/runtimeAudioFeedback.ts
高物 a45e358e83 Add generationStatus and match3d/runtime fixes
Introduce persistent generationStatus to work summaries (puzzle & match3d) and propagate generation recovery rules across docs and frontend/backends so "generating" is restored from server-side work summary rather than ephemeral front-end notices. Update API server image/asset handling (improve match3d material sheet green/alpha decontamination and promote generatedItemAssets background fields) and add runtime improvements: alpha-based hotspot hit-testing, tray insertion/three-match animation behavior, and session re-read on client-side VectorEngine timeouts/lock-screen interruptions. Many docs, tests and related frontend modules updated/added to reflect these contract and behavior changes.
2026-05-16 22:59:02 +08:00

76 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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_MERGE_SOUND_SRC =
DEFAULT_RUNTIME_LEVEL_CLEAR_SOUND_SRC;
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 playRuntimeMergeSound(volume = 0.6) {
playRuntimeClickSound(DEFAULT_RUNTIME_MERGE_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));
}