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') {
|
||||
|
||||
Reference in New Issue
Block a user