forked from phenom/healer-man
Update Healer Man server
This commit is contained in:
1 parent
39458d5de6
commit
6cc1b9217f
203 files changed
+133929
-31271
No files matched your search
+43
-6
@@ -11,6 +11,8 @@ import {
|
||||
} from "./combatMediaPack";
|
||||
|
||||
const MAX_COMBAT_VOICES = 32;
|
||||
const MAX_CACHED_AUDIO_BUFFERS = 64;
|
||||
const MAX_CACHED_AUDIO_BYTES = 32 * 1024 * 1024;
|
||||
|
||||
interface ActiveVoice {
|
||||
readonly group: CombatActorGroup;
|
||||
@@ -34,6 +36,23 @@ const activeVoices: ActiveVoice[] = [];
|
||||
const activeCastLoops = new Map<string, ActiveCastLoop>();
|
||||
const activeMasterVoices = new Set<() => void>();
|
||||
const decodedBuffers = new Map<string, Promise<AudioBuffer | null>>();
|
||||
const decodedBufferBytes = new Map<string, number>();
|
||||
let cachedAudioBytes = 0;
|
||||
|
||||
function removeCachedAudio(url: string): void {
|
||||
decodedBuffers.delete(url);
|
||||
cachedAudioBytes -= decodedBufferBytes.get(url) ?? 0;
|
||||
decodedBufferBytes.delete(url);
|
||||
}
|
||||
|
||||
function trimCachedAudio(): void {
|
||||
while (decodedBuffers.size > MAX_CACHED_AUDIO_BUFFERS || cachedAudioBytes > MAX_CACHED_AUDIO_BYTES) {
|
||||
const oldest = decodedBuffers.keys().next().value;
|
||||
if (oldest === undefined) break;
|
||||
// Active sources retain their own buffer; eviction never stops playback.
|
||||
removeCachedAudio(oldest);
|
||||
}
|
||||
}
|
||||
|
||||
export function masterAudioOutputEnabled(masterVolume: number): boolean {
|
||||
return Number.isFinite(masterVolume) && masterVolume > 0;
|
||||
@@ -222,13 +241,31 @@ function soundsForEvent(event: CombatPresentationEvent): readonly CombatEffectSo
|
||||
async function decodeAudio(url: string): Promise<AudioBuffer | null> {
|
||||
const audioContext = ensureContext();
|
||||
if (!audioContext) return null;
|
||||
if (!decodedBuffers.has(url)) {
|
||||
decodedBuffers.set(url, fetchOptionalMediaAsset(url)
|
||||
.then((response) => response.ok ? response.arrayBuffer() : Promise.reject(new Error(String(response.status))))
|
||||
.then((data) => audioContext.decodeAudioData(data))
|
||||
.catch(() => null));
|
||||
const cached = decodedBuffers.get(url);
|
||||
if (cached) {
|
||||
decodedBuffers.delete(url);
|
||||
decodedBuffers.set(url, cached);
|
||||
return cached;
|
||||
}
|
||||
return decodedBuffers.get(url)!;
|
||||
const request = fetchOptionalMediaAsset(url)
|
||||
.then((response) => response.ok ? response.arrayBuffer() : Promise.reject(new Error(String(response.status))))
|
||||
.then((data) => audioContext.decodeAudioData(data))
|
||||
.then((buffer) => {
|
||||
if (decodedBuffers.get(url) === request) {
|
||||
const bytes = buffer.length * buffer.numberOfChannels * Float32Array.BYTES_PER_ELEMENT;
|
||||
decodedBufferBytes.set(url, Number.isFinite(bytes) ? Math.max(0, bytes) : 0);
|
||||
cachedAudioBytes += decodedBufferBytes.get(url)!;
|
||||
trimCachedAudio();
|
||||
}
|
||||
return buffer;
|
||||
})
|
||||
.catch(() => {
|
||||
if (decodedBuffers.get(url) === request) removeCachedAudio(url);
|
||||
return null;
|
||||
});
|
||||
decodedBuffers.set(url, request);
|
||||
trimCachedAudio();
|
||||
return request;
|
||||
}
|
||||
|
||||
export async function playMasterAudioCue(
|
||||
|
||||
Reference in new issue
Block a user