/** Звук и длительность эффекта «Облако яда» (`public/oblako-yada.mp3`). */ const POISON_CLOUD_SFX_VOLUME = 0.92; /** Запас, если метаданные не прочитались. */ const DEFAULT_POISON_CLOUD_LIFE_MS = 1600; export function poisonCloudEffectSoundUrl(): string { return new URL('oblako-yada.mp3', window.location.href).href; } let cachedPoisonCloudSfxDurationMs: number | null = null; /** Длительность трека в мс (кэш после первого чтения метаданных). */ export function getPoisonCloudSfxDurationMs(): Promise { if (cachedPoisonCloudSfxDurationMs !== null) { return Promise.resolve(cachedPoisonCloudSfxDurationMs); } const url = poisonCloudEffectSoundUrl(); return new Promise((resolve) => { const a = new Audio(); const done = (ms: number): void => { cachedPoisonCloudSfxDurationMs = ms; a.removeAttribute('src'); resolve(ms); }; a.addEventListener('loadedmetadata', () => { const d = a.duration; done(Number.isFinite(d) && d > 0 ? Math.round(d * 1000) : DEFAULT_POISON_CLOUD_LIFE_MS); }); a.addEventListener('error', () => done(DEFAULT_POISON_CLOUD_LIFE_MS)); a.src = url; a.load(); }); } /** Длительность визуала = длительности файла (с разумными пределами). */ export async function getPoisonCloudEffectLifeMs(): Promise { const raw = await getPoisonCloudSfxDurationMs(); return Math.min(60_000, Math.max(600, raw)); } /** * Воспроизведение с подгонкой скорости: фактическое время звука по часам ≈ `lifeMs`, * чтобы совпасть с анимацией. */ export async function playPoisonCloudEffectSound(lifeMs: number): Promise { try { const rawMs = await getPoisonCloudSfxDurationMs(); const target = Math.max(200, lifeMs); const rate = Math.max(0.25, Math.min(4, rawMs / target)); const el = new Audio(poisonCloudEffectSoundUrl()); el.volume = POISON_CLOUD_SFX_VOLUME; el.playbackRate = rate; void el.play().catch(() => undefined); } catch { /* ignore */ } }