Preload media assets and refine process cursors
This commit is contained in:
@@ -9,6 +9,8 @@ class AudioManagerModule extends BaseModule {
|
||||
super('audio-manager', 'Audio Manager');
|
||||
this.sounds = new Map();
|
||||
this.sfxCache = new Map();
|
||||
this.musicCache = new Map();
|
||||
this.imageCache = new Map();
|
||||
this.currentAudio = null;
|
||||
this.currentAudioRole = null;
|
||||
this.currentLoop = null;
|
||||
@@ -486,29 +488,87 @@ class AudioManagerModule extends BaseModule {
|
||||
|
||||
async preloadSfx(filename) {
|
||||
const url = this.getAssetUrl('sounds', filename);
|
||||
if (this.sfxCache.has(url)) {
|
||||
return this.sfxCache.get(url);
|
||||
}
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
const audio = new Audio(url);
|
||||
audio.preload = 'auto';
|
||||
this.setMediaVolume(audio, this.getSfxVolume());
|
||||
audio.addEventListener('canplaythrough', () => resolve(audio), { once: true });
|
||||
audio.addEventListener('error', () => reject(new Error(`Failed to preload sound effect: ${url}`)), { once: true });
|
||||
audio.load();
|
||||
});
|
||||
|
||||
if (this.sfxCache.has(url)) return this.sfxCache.get(url);
|
||||
const promise = this.preloadAudioUrl(url, 'sound effect')
|
||||
.then(audio => {
|
||||
this.setMediaVolume(audio, this.getSfxVolume());
|
||||
return audio;
|
||||
});
|
||||
this.sfxCache.set(url, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
async preloadMusic(filename) {
|
||||
const url = this.getAssetUrl('music', filename);
|
||||
if (this.musicCache.has(url)) return this.musicCache.get(url);
|
||||
const promise = this.preloadAudioUrl(url, 'music track')
|
||||
.then(audio => {
|
||||
this.setMediaVolume(audio, this.getMusicVolume());
|
||||
return audio;
|
||||
});
|
||||
this.musicCache.set(url, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
preloadAudioUrl(url, label = 'audio') {
|
||||
return new Promise((resolve, reject) => {
|
||||
const audio = new Audio(url);
|
||||
let settled = false;
|
||||
const finish = (result, error = null) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
audio.removeEventListener('canplaythrough', onReady);
|
||||
audio.removeEventListener('loadeddata', onReady);
|
||||
audio.removeEventListener('error', onError);
|
||||
if (error) reject(error);
|
||||
else resolve(result);
|
||||
};
|
||||
const onReady = () => finish(audio);
|
||||
const onError = () => finish(null, new Error(`Failed to preload ${label}: ${url}`));
|
||||
audio.preload = 'auto';
|
||||
audio.addEventListener('canplaythrough', onReady, { once: true });
|
||||
audio.addEventListener('loadeddata', onReady, { once: true });
|
||||
audio.addEventListener('error', onError, { once: true });
|
||||
audio.load();
|
||||
});
|
||||
}
|
||||
|
||||
async preloadImage(filename) {
|
||||
const url = this.getAssetUrl('images', filename);
|
||||
if (this.imageCache.has(url)) return this.imageCache.get(url);
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
const image = new Image();
|
||||
image.decoding = 'async';
|
||||
image.onload = () => {
|
||||
if (typeof image.decode === 'function') {
|
||||
image.decode().catch(() => null).then(() => resolve(image));
|
||||
} else {
|
||||
resolve(image);
|
||||
}
|
||||
};
|
||||
image.onerror = () => reject(new Error(`Failed to preload image: ${url}`));
|
||||
image.src = url;
|
||||
});
|
||||
this.imageCache.set(url, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
async preloadStructuredBlock(block = {}) {
|
||||
const type = String(block.type || block.kind || '').toLowerCase();
|
||||
const filename = block.filename || block.metadata?.filename;
|
||||
if (!filename) return null;
|
||||
if (type === 'image') return this.preloadImage(filename);
|
||||
if (type === 'music') return this.preloadMusic(filename);
|
||||
if (type === 'sfx' || type === 'sound') return this.preloadSfx(filename);
|
||||
return null;
|
||||
}
|
||||
|
||||
async preloadMediaCues(cues = []) {
|
||||
const tasks = cues
|
||||
.filter(cue => cue && cue.type === 'sfx' && cue.filename)
|
||||
.map(cue => this.preloadSfx(cue.filename).catch(error => {
|
||||
console.warn('AudioManager: SFX preload failed:', error);
|
||||
return null;
|
||||
.filter(cue => cue && cue.filename)
|
||||
.map(cue => this.preloadStructuredBlock(cue).catch(error => {
|
||||
console.warn('AudioManager: Media cue preload failed:', error);
|
||||
throw error;
|
||||
}));
|
||||
|
||||
await Promise.all(tasks);
|
||||
|
||||
Reference in New Issue
Block a user