Preload media assets and refine process cursors

This commit is contained in:
2026-05-18 11:15:39 +02:00
parent 4f6300042c
commit 6e908037fb
4 changed files with 162 additions and 52 deletions
+58 -11
View File
@@ -39,6 +39,7 @@ class SentenceQueueModule extends BaseModule {
'getPreparedSentence',
'prefetchAhead',
'prepareSpeechMetadata',
'preloadAssetsForItem',
'normalizeTtsText',
'runTtsPreloadWithTimeout',
'cancelBlockingGeneration',
@@ -400,12 +401,7 @@ class SentenceQueueModule extends BaseModule {
try {
if (metadata.type && !['paragraph', 'heading'].includes(metadata.type)) {
if (metadata.type === 'music') {
const audioManager = this.getModule('audio-manager');
if (audioManager && typeof audioManager.playMusic === 'function') {
audioManager.getAssetUrl('music', metadata.filename);
}
}
await this.preloadAssetsForItem(metadata, { blocking: true, sentenceId: id });
return {
id,
@@ -425,7 +421,10 @@ class SentenceQueueModule extends BaseModule {
const audioManager = this.getModule('audio-manager');
if (audioManager && typeof audioManager.preloadMediaCues === 'function') {
await audioManager.preloadMediaCues(metadata.cueMarkers || []);
await this.preloadAssetsForItem({
type: 'paragraph',
cueMarkers: metadata.cueMarkers || []
}, { blocking: true, sentenceId: id });
}
const ttsData = await this.prepareSpeechMetadata(text, {
@@ -597,6 +596,44 @@ class SentenceQueueModule extends BaseModule {
}
}
async preloadAssetsForItem(item = {}, context = {}) {
const audioManager = this.getModule('audio-manager');
if (!audioManager) return;
const tasks = [];
const type = String(item.type || item.kind || '').toLowerCase();
if (['image', 'music', 'sfx', 'sound'].includes(type) && typeof audioManager.preloadStructuredBlock === 'function') {
tasks.push(audioManager.preloadStructuredBlock(item));
}
if (Array.isArray(item.cueMarkers) && item.cueMarkers.length > 0 && typeof audioManager.preloadMediaCues === 'function') {
tasks.push(audioManager.preloadMediaCues(item.cueMarkers));
}
const pending = tasks.filter(Boolean);
if (pending.length === 0) return;
const state = context.blocking ? 'waiting-generating' : 'playing-generating';
document.dispatchEvent(new CustomEvent('story:process-state', {
detail: {
state,
reason: 'asset-preload-start',
sentenceId: context.sentenceId || item.id || null,
assetType: type || 'cue'
}
}));
await Promise.all(pending);
document.dispatchEvent(new CustomEvent('story:process-state', {
detail: {
state: 'playing-ready',
reason: 'asset-preload-complete',
sentenceId: context.sentenceId || item.id || null,
assetType: type || 'cue'
}
}));
}
shouldPauseAfterSentence(sentence) {
if (sentence.kind !== 'paragraph' || this.shouldAutoplay()) {
return false;
@@ -684,16 +721,26 @@ class SentenceQueueModule extends BaseModule {
}));
console.log(`Process state: ${state}`, { reason: 'prefetch-start', sentenceId: nextItem.id, queueIndex: index });
const promise = (this.isSpeechItem(nextItem)
? this.prepareSpeechMetadata(nextItem.text || '', {
const promise = (async () => {
await this.preloadAssetsForItem(nextItem, {
sentenceId: nextItem.id,
blocking: false,
prefetch: true
});
if (!this.isSpeechItem(nextItem)) {
return null;
}
return this.prepareSpeechMetadata(nextItem.text || '', {
sentenceId: nextItem.id,
blockId: nextItem.blockId ?? null,
turnId: nextItem.turnId ?? null,
queueIndex: index,
prefetch: true,
blocking: false
})
: Promise.resolve(null))
});
})()
.then(() => {
console.log('SentenceQueue: Prefetched queued speech/media', { sentenceId: nextItem.id, queueIndex: index });
document.dispatchEvent(new CustomEvent('story:process-state', {