Fix WebGL timeline startup ordering
This commit is contained in:
@@ -8,12 +8,11 @@ import { BaseModule } from './base-module.js';
|
||||
class BookPlaybackTimelineModule extends BaseModule {
|
||||
constructor() {
|
||||
super('book-playback-timeline', 'Book Playback Timeline');
|
||||
this.dependencies = ['book-pagination', 'book-texture-renderer', 'webgl-page-cache', 'playback-coordinator', 'sentence-queue'];
|
||||
this.dependencies = ['book-pagination', 'book-texture-renderer', 'webgl-page-cache', 'playback-coordinator'];
|
||||
this.pagination = null;
|
||||
this.textureRenderer = null;
|
||||
this.pageCache = null;
|
||||
this.playbackCoordinator = null;
|
||||
this.sentenceQueue = null;
|
||||
this.activeSegment = null;
|
||||
this.preparedSegments = new Map();
|
||||
this.timelineDiagnostics = [];
|
||||
@@ -26,6 +25,7 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
'prepareSentence',
|
||||
'activatePreparedSegment',
|
||||
'ensureAnimationTimings',
|
||||
'calculateAnimationTiming',
|
||||
'createPreparedSegment',
|
||||
'createRevealDetail',
|
||||
'applyTexturePlan',
|
||||
@@ -55,7 +55,6 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
this.textureRenderer = this.getModule('book-texture-renderer');
|
||||
this.pageCache = this.getModule('webgl-page-cache');
|
||||
this.playbackCoordinator = this.getModule('playback-coordinator');
|
||||
this.sentenceQueue = this.getModule('sentence-queue');
|
||||
this.addEventListener(document, 'webgl-book:page-reveal-start', (event) => {
|
||||
this.markBenchmark('reveal-start', {
|
||||
blockId: event.detail?.blockId ?? null
|
||||
@@ -210,8 +209,50 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
ensureAnimationTimings(sentence = {}) {
|
||||
if (Array.isArray(sentence.animation?.wordTimings) && sentence.animation.wordTimings.length > 0) return;
|
||||
const words = String(sentence.layoutText || sentence.text || '').match(/\S+/g) || [];
|
||||
sentence.animation = this.sentenceQueue?.calculateAnimationTiming?.(words, sentence.tts?.duration || 0, sentence.cueMarkers || [])
|
||||
|| { wordTimings: [], cueTimings: [], totalDuration: 0 };
|
||||
sentence.animation = this.calculateAnimationTiming(words, sentence.tts?.duration || 0, sentence.cueMarkers || []);
|
||||
}
|
||||
|
||||
calculateAnimationTiming(words = [], totalDuration = 0, cueMarkers = []) {
|
||||
if (!Array.isArray(words) || words.length === 0) {
|
||||
return {
|
||||
wordTimings: [],
|
||||
cueTimings: [],
|
||||
totalDuration: 0
|
||||
};
|
||||
}
|
||||
const totalChars = words.reduce((sum, word) => sum + String(word || '').length, 0);
|
||||
if (totalChars === 0) {
|
||||
return {
|
||||
wordTimings: words.map(word => ({ word, delay: 0, duration: 0 })),
|
||||
cueTimings: [],
|
||||
totalDuration: 0
|
||||
};
|
||||
}
|
||||
const msPerChar = Number(totalDuration || 0) / totalChars;
|
||||
let currentDelay = 0;
|
||||
const wordTimings = words.map(word => {
|
||||
const duration = String(word || '').length * msPerChar;
|
||||
const timing = {
|
||||
word,
|
||||
delay: currentDelay,
|
||||
duration
|
||||
};
|
||||
currentDelay += duration;
|
||||
return timing;
|
||||
});
|
||||
const cueTimings = (cueMarkers || []).map(cue => {
|
||||
const wordIndex = Math.max(0, Math.min(cue.wordIndex || 0, wordTimings.length - 1));
|
||||
const timing = wordTimings[wordIndex] || { delay: currentDelay };
|
||||
return {
|
||||
...cue,
|
||||
delay: timing.delay
|
||||
};
|
||||
});
|
||||
return {
|
||||
wordTimings,
|
||||
cueTimings,
|
||||
totalDuration: Math.round(currentDelay)
|
||||
};
|
||||
}
|
||||
|
||||
createRevealDetail(sentence = {}, spread = null, phase = 'activate') {
|
||||
|
||||
@@ -13,7 +13,7 @@ class SentenceQueueModule extends BaseModule {
|
||||
super('sentence-queue', 'Sentence Queue');
|
||||
|
||||
// Dependencies
|
||||
this.dependencies = ['text-buffer', 'tts-factory', 'paragraph-layout', 'audio-manager', 'persistence-manager'];
|
||||
this.dependencies = ['text-buffer', 'tts-factory', 'paragraph-layout', 'audio-manager', 'persistence-manager', 'book-playback-timeline'];
|
||||
|
||||
// Queue state
|
||||
this.sentenceQueue = [];
|
||||
|
||||
@@ -24,6 +24,7 @@ class TextProcessorModule extends BaseModule {
|
||||
'hyphenate',
|
||||
'setLocale',
|
||||
'loadHyphenopolyLoader',
|
||||
'ensureHyphenopolySeedElements',
|
||||
'normalizeHyphenationLocale',
|
||||
'applyLocaleTypography',
|
||||
'getTypographyLocale',
|
||||
@@ -162,6 +163,7 @@ class TextProcessorModule extends BaseModule {
|
||||
this.hyphenatorReady = false;
|
||||
|
||||
await this.loadHyphenopolyLoader();
|
||||
this.ensureHyphenopolySeedElements(locale);
|
||||
|
||||
window.Hyphenopoly.config({
|
||||
require: {
|
||||
@@ -203,6 +205,35 @@ class TextProcessorModule extends BaseModule {
|
||||
}
|
||||
}
|
||||
|
||||
ensureHyphenopolySeedElements(locale = 'en-us') {
|
||||
const normalizedLocale = this.normalizeHyphenationLocale(locale);
|
||||
let container = document.getElementById('hyphenopoly_seed_elements');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.id = 'hyphenopoly_seed_elements';
|
||||
container.setAttribute('aria-hidden', 'true');
|
||||
Object.assign(container.style, {
|
||||
position: 'absolute',
|
||||
width: '1px',
|
||||
height: '1px',
|
||||
overflow: 'hidden',
|
||||
opacity: '0',
|
||||
pointerEvents: 'none',
|
||||
left: '-9999px',
|
||||
top: '-9999px'
|
||||
});
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
container.innerHTML = '';
|
||||
['hyphenate', 'hyphenatePipe'].forEach((className) => {
|
||||
const seed = document.createElement('span');
|
||||
seed.className = className;
|
||||
seed.lang = normalizedLocale;
|
||||
seed.textContent = normalizedLocale.startsWith('de') ? 'Silbentrennung' : 'hyphenation';
|
||||
container.appendChild(seed);
|
||||
});
|
||||
}
|
||||
|
||||
loadHyphenopolyLoader() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (window.Hyphenopoly && typeof window.Hyphenopoly.config === 'function') {
|
||||
|
||||
@@ -213,6 +213,7 @@ const checks = [
|
||||
['webgl visible spread state ignores future prepared publishes before flip', /spreadUpdate:deferred-future-unrendered/.test(source) && /incomingSpreadIndex > Math\.max\(0, Number\(bookPaginationState\.spreadIndex/.test(source) && /this\.drawSpread\(this\.currentSpread, \['left', 'right'\], \{ phase: 'prepare' \}\)/.test(textureRendererSource)],
|
||||
['3D overflow reveal preloads target spread before forced page flip', /createRevealDetail\(sentence, previewSpread, 'prepare'\)/.test(bookPlaybackTimelineSource) && /this\.textureRenderer\.prepareRevealBlock\(revealDetail, \{[\s\S]*phase: 'prepare'[\s\S]*publishEvent: false/.test(bookPlaybackTimelineSource) && /this\.prewarmSegmentTextures\(segment\)/.test(bookPlaybackTimelineSource) && /this\.assertSegmentReady\(segment, 'prepare'\)/.test(bookPlaybackTimelineSource) && /reason: 'timeline-preplay-spread-transition'/.test(bookPlaybackTimelineSource)],
|
||||
['book playback timeline is loaded through module infrastructure', /book-playback-timeline-module\.js/.test(loaderSource) && /super\('book-playback-timeline'/.test(bookPlaybackTimelineSource) && /reportProgress\(100, 'Book playback timeline ready'\)/.test(bookPlaybackTimelineSource)],
|
||||
['book playback timeline initializes before sentence queue without a dependency cycle', /this\.dependencies = \[[^\]]*'book-playback-timeline'[^\]]*\]/.test(sentenceQueueSource) && !/this\.dependencies = \[[^\]]*'sentence-queue'[^\]]*\]/.test(bookPlaybackTimelineSource) && /calculateAnimationTiming\(words = \[\]/.test(bookPlaybackTimelineSource)],
|
||||
['3D display playback is owned by book playback timeline', /book-playback-timeline/.test(uiDisplayHandlerSource) && /playWebGLBookSentence/.test(uiDisplayHandlerSource) && /timeline\.playSentence\(sentence\)/.test(uiDisplayHandlerSource) && /if \(useWebGLBookReveal\) \{[\s\S]*await this\.playWebGLBookSentence\(sentence\);[\s\S]*this\.markBlockRendered\(sentence\.blockId\);[\s\S]*return null;/.test(uiDisplayHandlerSource)],
|
||||
['sentence queue lookahead prepares 3D book timeline segments', /book-playback-timeline/.test(sentenceQueueSource) && /bookPlaybackTimeline\.prepareSentence\(sentence/.test(sentenceQueueSource) && /timelineSegment: segment/.test(sentenceQueueSource)],
|
||||
['book playback timeline prewarms texture window before prepared playback and flips', /prewarmSegmentTextures/.test(bookPlaybackTimelineSource) && /pageCache\.prewarmNavigationWindow/.test(bookPlaybackTimelineSource) && /this\.prewarmSegmentTextures\(segment\)/.test(bookPlaybackTimelineSource) && /await this\.pageCache\?\.prewarmNavigationWindow/.test(bookPlaybackTimelineSource)],
|
||||
|
||||
Reference in New Issue
Block a user