Add timeline owner for WebGL book playback

This commit is contained in:
2026-06-10 02:00:57 +02:00
parent b41340151d
commit 10bf23b10b
6 changed files with 447 additions and 74 deletions
+15 -69
View File
@@ -11,7 +11,7 @@ class UIDisplayHandlerModule extends BaseModule {
super('ui-display-handler', 'UI Display Handler');
// Module dependencies
this.dependencies = ['layout-renderer', 'webgl-book-scene', 'playback-coordinator', 'game-config', 'localization', 'story-history', 'sentence-queue', 'persistence-manager', 'markup-parser', 'book-pagination', 'book-texture-renderer'];
this.dependencies = ['layout-renderer', 'webgl-book-scene', 'playback-coordinator', 'book-playback-timeline', 'game-config', 'localization', 'story-history', 'sentence-queue', 'persistence-manager', 'markup-parser', 'book-pagination', 'book-texture-renderer'];
// DOM elements
this.container = null;
@@ -69,6 +69,7 @@ class UIDisplayHandlerModule extends BaseModule {
'applyTranslations',
'renderSentence',
'isWebGLMode',
'playWebGLBookSentence',
'prepareWebGLBookReveal',
'waitForWebGLPageFlip',
'renderStoryBlock',
@@ -985,9 +986,7 @@ class UIDisplayHandlerModule extends BaseModule {
try {
if (useWebGLBookReveal) {
await this.prepareWebGLBookReveal(sentence);
if (!isCurrent()) return null;
await this.playbackCoordinator.play(sentence);
await this.playWebGLBookSentence(sentence);
if (!isCurrent()) return null;
if (sentence.blockId != null) this.markBlockRendered(sentence.blockId);
this.dispatchDeferredTagsForBlock(sentence);
@@ -1055,73 +1054,20 @@ class UIDisplayHandlerModule extends BaseModule {
|| document.body?.classList?.contains('webgl-mode');
}
async playWebGLBookSentence(sentence) {
const timeline = this.getModule('book-playback-timeline');
if (!timeline || typeof timeline.playSentence !== 'function') {
throw new Error('WebGL book playback timeline is required for 3D sentence playback');
}
return timeline.playSentence(sentence);
}
async prepareWebGLBookReveal(sentence) {
const bookPagination = this.getModule('book-pagination');
const bookTextureRenderer = this.getModule('book-texture-renderer');
if (!bookPagination || !bookTextureRenderer || sentence.blockId == null) return;
const sentenceQueue = this.getModule('sentence-queue');
if (!Array.isArray(sentence.animation?.wordTimings) || sentence.animation.wordTimings.length === 0) {
const words = String(sentence.layoutText || sentence.text || '').match(/\S+/g) || [];
sentence.animation = sentenceQueue?.calculateAnimationTiming?.(words, sentence.tts?.duration || 0, sentence.cueMarkers || [])
|| { wordTimings: [], cueTimings: [], totalDuration: 0 };
}
let preparedSpread = null;
if (typeof bookPagination.preparePendingBlock === 'function') {
const currentSpreadIndex = Math.max(0, Number(bookPagination.currentSpreadIndex || 0));
const previewSpread = sentence.webglBookPresentation?.spread || await bookPagination.preparePendingBlock(sentence, {
activate: false,
publish: false,
includeUnrenderedHistory: true
});
const previewRevealDetail = {
id: sentence.id,
blockId: sentence.blockId,
wordTimings: sentence.animation?.wordTimings || [],
cueTimings: sentence.animation?.cueTimings || [],
totalDuration: sentence.animation?.totalDuration || 0,
spread: previewSpread,
phase: 'prepare'
};
if (previewSpread && typeof bookTextureRenderer.prepareRevealBlock === 'function') {
bookTextureRenderer.prepareRevealBlock(previewRevealDetail, { phase: 'prepare' });
}
if (Number(previewSpread?.index || 0) > currentSpreadIndex) {
const flipped = await this.waitForWebGLPageFlip({
direction: 1,
reason: 'pending-block-overflow',
targetSpread: previewSpread.index
});
if (!flipped) {
throw new Error(`WebGL book page flip did not start for prepared spread ${previewSpread.index}`);
}
}
preparedSpread = await bookPagination.preparePendingBlock(sentence, {
includeUnrenderedHistory: true
});
} else {
document.dispatchEvent(new CustomEvent('book-pagination:prepare-block', {
detail: {
block: sentence
}
}));
}
const revealDetail = {
id: sentence.id,
blockId: sentence.blockId,
wordTimings: sentence.animation?.wordTimings || [],
cueTimings: sentence.animation?.cueTimings || [],
totalDuration: sentence.animation?.totalDuration || 0,
spread: preparedSpread
};
if (typeof bookTextureRenderer.prepareRevealBlock === 'function') {
bookTextureRenderer.prepareRevealBlock(revealDetail);
} else {
document.dispatchEvent(new CustomEvent('book-texture:prepare-reveal-block', {
detail: revealDetail
}));
const timeline = this.getModule('book-playback-timeline');
if (!timeline || typeof timeline.prepareSentence !== 'function') {
throw new Error('WebGL book playback timeline is required for 3D reveal preparation');
}
return timeline.prepareSentence(sentence, { immediate: true });
}
waitForWebGLPageFlip(detail = {}) {