Newer
Older
rpg / packages / engine / src / audio / decode.ts
/**
 * Кэш декодированных аудио-буферов: реестр URL + decodeAudioData.
 * Параллельные вызовы decode по одному ключу делят один in-flight промис —
 * двойной fetch+decode при старте сцены исключён.
 */
export class BufferCache {
    private urls = new Map<string, string>();
    private buffers = new Map<string, AudioBuffer>();
    private pending = new Map<string, Promise<AudioBuffer | null>>();

    /** Зарегистрировать URL ключа (без загрузки — файл читается в decode). */
    register(key: string, url: string): void {
        this.urls.set(key, url);
    }

    /** Уже декодированный буфер (без загрузки); не декодирован — null. */
    cached(key: string): AudioBuffer | null {
        return this.buffers.get(key) ?? null;
    }

    /** Декодировать (fetch + decodeAudioData); сбой — null с warn в консоль. */
    async decode(key: string, ctx: AudioContext): Promise<AudioBuffer | null> {
        const cached = this.buffers.get(key);
        if (cached) return cached;
        let flight = this.pending.get(key);
        if (!flight) {
            const url = this.urls.get(key);
            if (!url) return null;
            flight = this.fetchDecode(key, url, ctx).finally(() => this.pending.delete(key));
            this.pending.set(key, flight);
        }
        return flight;
    }

    private async fetchDecode(key: string, url: string, ctx: AudioContext): Promise<AudioBuffer | null> {
        try {
            const res = await fetch(url);
            const buf = await ctx.decodeAudioData(await res.arrayBuffer());
            this.buffers.set(key, buf);
            return buf;
        } catch (err) {
            console.warn(`[audio] не удалось декодировать ${key}:`, err);
            return null;
        }
    }
}