Queue WebGL book reveal masks

This commit is contained in:
2026-06-07 13:52:07 +02:00
parent 7fc083fb58
commit 9434950826
31 changed files with 383 additions and 73 deletions
+63 -1
View File
@@ -20,6 +20,8 @@ class PlaybackCoordinatorModule extends BaseModule {
'play',
'calculateWordTimings',
'animateWords',
'isWebGLPlaybackMode',
'scheduleWebGLReveal',
'waitForAudioStart',
'completeSentenceVisual',
'accelerateActiveWordAnimations',
@@ -213,7 +215,7 @@ class PlaybackCoordinatorModule extends BaseModule {
* @returns {Promise<void>} - Resolves when animation completes
*/
async animateWords(sentence) {
if (!sentence.element || !sentence.animation || !sentence.animation.wordTimings) {
if (!sentence.animation || !sentence.animation.wordTimings) {
console.error('PlaybackCoordinator: Missing animation data');
return Promise.resolve();
}
@@ -224,6 +226,15 @@ class PlaybackCoordinatorModule extends BaseModule {
return Promise.resolve();
}
if (this.isWebGLPlaybackMode()) {
return this.scheduleWebGLReveal(sentence, animQueue);
}
if (!sentence.element) {
console.error('PlaybackCoordinator: Missing DOM element for 2D animation');
return Promise.resolve();
}
const wordElements = sentence.element.querySelectorAll('.word');
let wordTimings = sentence.animation.wordTimings;
let cueTimings = sentence.animation.cueTimings || [];
@@ -302,6 +313,57 @@ class PlaybackCoordinatorModule extends BaseModule {
});
}
isWebGLPlaybackMode() {
return document.body?.dataset?.webglUiMode === '3d'
|| document.body?.classList?.contains('webgl-mode');
}
scheduleWebGLReveal(sentence, animQueue) {
let wordTimings = Array.isArray(sentence.animation?.wordTimings)
? sentence.animation.wordTimings
: [];
let cueTimings = Array.isArray(sentence.animation?.cueTimings)
? sentence.animation.cueTimings
: [];
if (wordTimings.length === 0) {
const words = String(sentence.text || '').match(/\S+/g) || [];
const calculated = this.calculateWordTimings(words, sentence.tts?.duration || sentence.animation?.totalDuration || 0);
wordTimings = calculated.wordTimings;
cueTimings = [];
}
document.dispatchEvent(new CustomEvent('book-texture:reveal-block', {
detail: {
id: sentence.id,
blockId: sentence.blockId ?? sentence.metadata?.blockId ?? null,
wordTimings,
cueTimings,
totalDuration: sentence.animation?.totalDuration || 0
}
}));
return new Promise((resolve) => {
const totalDuration = wordTimings.length > 0
? Math.max(...wordTimings.map(timing => Number(timing.delay || 0) + Number(timing.duration || 0)))
: 0;
cueTimings.forEach(cue => {
animQueue.schedule(() => {
document.dispatchEvent(new CustomEvent('story:media-cue', {
detail: {
sentenceId: sentence.id,
...cue
}
}));
}, cue.delay || 0);
});
animQueue.schedule(() => {
resolve();
}, totalDuration + 100);
});
}
/**
* Calculate word-level timing to match total TTS duration
* This is a utility method that can be called by SentenceQueue during preparation