Latest state before reworking with cluade 4.6.
This commit is contained in:
+165
@@ -0,0 +1,165 @@
|
|||||||
|
# Client Architecture Refactoring TODO List
|
||||||
|
|
||||||
|
## Phase 1: Cleanup and Removal of Redundant Components
|
||||||
|
|
||||||
|
### High Priority Removals
|
||||||
|
|
||||||
|
1. **TTSPlayerModule - Complete Removal**
|
||||||
|
- [X] Mark entire module for removal (completely redundant with SentenceQueueModule)
|
||||||
|
- [ ] Identify all references to TTSPlayerModule across the codebase
|
||||||
|
- [ ] Plan migration path for any UI components directly using TTSPlayerModule
|
||||||
|
- [ ] Update any event listeners that expect TTSPlayerModule events
|
||||||
|
|
||||||
|
2. **UIDisplayHandlerModule Cleanup**
|
||||||
|
- [ ] Remove `pendingParagraphs` queue
|
||||||
|
- [ ] Remove `processNextParagraph()` method
|
||||||
|
- [ ] Refactor `displayText()` to be a thin wrapper for SentenceQueue
|
||||||
|
|
||||||
|
3. **KokoroTTSModule Cleanup**
|
||||||
|
- [ ] Remove `pendingGenerations` Map
|
||||||
|
- [ ] Refactor `generateSpeech()` to work directly with SentenceQueue
|
||||||
|
- [ ] Update iframe message handling to support the new architecture
|
||||||
|
|
||||||
|
4. **LayoutRendererModule Cleanup**
|
||||||
|
- [ ] Remove TTS triggering from `renderParagraph()` method
|
||||||
|
- [ ] Remove animation scheduling from rendering logic
|
||||||
|
- [ ] Separate layout rendering from animation timing
|
||||||
|
|
||||||
|
### Additional Obsolete Components
|
||||||
|
|
||||||
|
1. **TextBufferModule Redundancies**
|
||||||
|
- [ ] Remove sentence preparation logic that will move to SentenceQueue
|
||||||
|
- [ ] Remove any temporary sentence storage mechanisms
|
||||||
|
- [ ] Identify and mark for removal any text processing that duplicates SentenceQueue functionality
|
||||||
|
|
||||||
|
2. **AnimationQueueModule Overlaps**
|
||||||
|
- [ ] Identify animation scheduling that will be handled by SentenceQueue
|
||||||
|
- [ ] Remove redundant animation timing calculations
|
||||||
|
- [ ] Refactor to work as a service for SentenceQueue
|
||||||
|
|
||||||
|
3. **TTSFactoryModule Redundancies**
|
||||||
|
- [ ] Remove any temporary audio storage that duplicates SentenceQueue functionality
|
||||||
|
- [ ] Refactor speech generation methods to work with SentenceQueue
|
||||||
|
- [ ] Streamline caching to avoid duplication with SentenceQueue
|
||||||
|
|
||||||
|
4. **Event System Cleanup**
|
||||||
|
- [ ] Identify and remove redundant TTS-related events
|
||||||
|
- [ ] Remove text animation events that will be handled by SentenceQueue
|
||||||
|
- [ ] Consolidate playback state events
|
||||||
|
|
||||||
|
## Phase 2: Enhanced SentenceQueueModule Implementation
|
||||||
|
|
||||||
|
### Core Structure and Design
|
||||||
|
|
||||||
|
1. **Design the Sentence Object Structure**
|
||||||
|
- [ ] Define comprehensive sentence object with fields for:
|
||||||
|
- Unique ID
|
||||||
|
- Original text
|
||||||
|
- Processed text (hyphenated, typeset)
|
||||||
|
- Layout information (breaks, nodes, typography)
|
||||||
|
- Audio component (player, duration, data, type)
|
||||||
|
- Status tracking (pending, processing, ready, playing, complete)
|
||||||
|
|
||||||
|
2. **Implement Basic Queue Management**
|
||||||
|
- [ ] Create methods for adding sentences to the queue
|
||||||
|
- [ ] Implement queue processing logic that maintains order
|
||||||
|
- [ ] Add status tracking for each sentence in the queue
|
||||||
|
- [ ] Implement priority handling for urgent sentences
|
||||||
|
|
||||||
|
### Text Processing Integration
|
||||||
|
|
||||||
|
1. **Integrate with Paragraph Layout**
|
||||||
|
- [ ] Connect to ParagraphLayoutModule for text processing
|
||||||
|
- [ ] Implement hyphenation and typesetting in the queue
|
||||||
|
- [ ] Store layout information in the sentence object
|
||||||
|
- [ ] Ensure layout processing happens in parallel with audio
|
||||||
|
|
||||||
|
2. **Text Animation Preparation**
|
||||||
|
- [ ] Calculate animation timing based on text length and settings
|
||||||
|
- [ ] Prepare animation data for each word in the sentence
|
||||||
|
- [ ] Store animation timing in the sentence object
|
||||||
|
- [ ] Create animation player function for the sentence
|
||||||
|
|
||||||
|
### Audio Processing Integration
|
||||||
|
|
||||||
|
1. **TTS System Integration**
|
||||||
|
- [ ] Implement audio generation for Kokoro TTS
|
||||||
|
- [ ] Implement browser TTS handling with duration estimation
|
||||||
|
- [ ] Implement "none" TTS option with duration calculation
|
||||||
|
- [ ] Create consistent player interface for all TTS types
|
||||||
|
|
||||||
|
2. **Audio Data Management**
|
||||||
|
- [ ] Implement audio data storage in sentence objects
|
||||||
|
- [ ] Connect with TTSFactoryModule's IndexedDB for persistent caching
|
||||||
|
- [ ] Add audio preloading capabilities
|
||||||
|
- [ ] Implement audio resource cleanup
|
||||||
|
|
||||||
|
### Playback Coordination
|
||||||
|
|
||||||
|
1. **Synchronized Playback**
|
||||||
|
- [ ] Implement coordinated text animation and audio playback
|
||||||
|
- [ ] Create timing adjustment based on speed settings
|
||||||
|
- [ ] Add event handling for playback states (start, pause, resume, complete)
|
||||||
|
- [ ] Implement sentence transition handling
|
||||||
|
|
||||||
|
2. **User Interaction Handling**
|
||||||
|
- [ ] Add support for fast-forwarding text/audio
|
||||||
|
- [ ] Implement pause/resume functionality
|
||||||
|
- [ ] Handle user interruptions gracefully
|
||||||
|
- [ ] Support skipping to next sentence
|
||||||
|
|
||||||
|
## Phase 3: Module Interface Updates
|
||||||
|
|
||||||
|
1. **Update Module Interfaces**
|
||||||
|
- [ ] Create consistent interfaces for interacting with SentenceQueue
|
||||||
|
- [ ] Update event system to work with sentence objects
|
||||||
|
- [ ] Implement progress reporting for sentence processing
|
||||||
|
- [ ] Add debugging and monitoring capabilities
|
||||||
|
|
||||||
|
2. **Documentation and Examples**
|
||||||
|
- [ ] Document the new architecture and interfaces
|
||||||
|
- [ ] Create usage examples for common scenarios
|
||||||
|
- [ ] Update developer guidelines
|
||||||
|
- [ ] Add migration guide for existing code
|
||||||
|
|
||||||
|
## Phase 4: Testing and Validation
|
||||||
|
|
||||||
|
1. **Unit Testing**
|
||||||
|
- [ ] Create tests for SentenceQueue core functionality
|
||||||
|
- [ ] Test text processing integration
|
||||||
|
- [ ] Test audio processing integration
|
||||||
|
- [ ] Test playback coordination
|
||||||
|
|
||||||
|
2. **Integration Testing**
|
||||||
|
- [ ] Test interaction between SentenceQueue and other modules
|
||||||
|
- [ ] Validate timing and synchronization
|
||||||
|
- [ ] Test error handling and recovery
|
||||||
|
- [ ] Verify performance under load
|
||||||
|
|
||||||
|
3. **User Experience Testing**
|
||||||
|
- [ ] Validate text animation quality
|
||||||
|
- [ ] Test audio playback quality
|
||||||
|
- [ ] Verify synchronization from user perspective
|
||||||
|
- [ ] Test accessibility features
|
||||||
|
|
||||||
|
## Implementation Strategy
|
||||||
|
|
||||||
|
1. **Phased Rollout**
|
||||||
|
- [ ] Complete cleanup of redundant components
|
||||||
|
- [ ] Implement SentenceQueue core structure
|
||||||
|
- [ ] Add text processing integration
|
||||||
|
- [ ] Add audio processing integration
|
||||||
|
- [ ] Implement playback coordination
|
||||||
|
- [ ] Gradually replace existing components
|
||||||
|
|
||||||
|
2. **Backward Compatibility**
|
||||||
|
- [ ] Maintain support for existing interfaces during transition
|
||||||
|
- [ ] Implement adapter patterns where needed
|
||||||
|
- [ ] Add feature flags for enabling/disabling new architecture
|
||||||
|
- [ ] Create fallback mechanisms for error recovery
|
||||||
|
|
||||||
|
3. **Performance Optimization**
|
||||||
|
- [ ] Implement parallel processing where possible
|
||||||
|
- [ ] Optimize memory usage for sentence objects
|
||||||
|
- [ ] Add resource management for audio data
|
||||||
|
- [ ] Implement efficient queue processing algorithms
|
||||||
+1
-1
@@ -99,6 +99,7 @@ const ModuleLoader = (function() {
|
|||||||
{ id: 'localization', script: '/js/localization-module.js', weight: 12 },
|
{ id: 'localization', script: '/js/localization-module.js', weight: 12 },
|
||||||
{ id: 'text-processor', script: '/js/text-processor-module.js', weight: 15 },
|
{ id: 'text-processor', script: '/js/text-processor-module.js', weight: 15 },
|
||||||
{ id: 'paragraph-layout', script: '/js/paragraph-layout-module.js', weight: 17 },
|
{ id: 'paragraph-layout', script: '/js/paragraph-layout-module.js', weight: 17 },
|
||||||
|
{ id: 'sentence-queue', script: '/js/sentence-queue-module.js', weight: 12 },
|
||||||
{ id: 'layout-renderer', script: '/js/layout-renderer-module.js', weight: 13 }, // Add Layout Renderer module
|
{ id: 'layout-renderer', script: '/js/layout-renderer-module.js', weight: 13 }, // Add Layout Renderer module
|
||||||
{ id: 'animation-queue', script: '/js/animation-queue-module.js', weight: 12 },
|
{ id: 'animation-queue', script: '/js/animation-queue-module.js', weight: 12 },
|
||||||
|
|
||||||
@@ -109,7 +110,6 @@ const ModuleLoader = (function() {
|
|||||||
{ id: 'elevenlabs', script: '/js/elevenlabs-tts-module.js', weight: 12 },
|
{ id: 'elevenlabs', script: '/js/elevenlabs-tts-module.js', weight: 12 },
|
||||||
{ id: 'openai', script: '/js/openai-tts-module.js', weight: 12 },
|
{ id: 'openai', script: '/js/openai-tts-module.js', weight: 12 },
|
||||||
{ id: 'tts-factory', script: '/js/tts-factory-module.js', weight: 13 }, // TTSFactory must be loaded before TTSPlayer
|
{ id: 'tts-factory', script: '/js/tts-factory-module.js', weight: 13 }, // TTSFactory must be loaded before TTSPlayer
|
||||||
{ id: 'tts-player', script: '/js/tts-player-module.js', weight: 13 },
|
|
||||||
|
|
||||||
// UI and interaction modules
|
// UI and interaction modules
|
||||||
{ id: 'text-buffer', script: '/js/text-buffer-module.js', weight: 12 },
|
{ id: 'text-buffer', script: '/js/text-buffer-module.js', weight: 12 },
|
||||||
|
|||||||
@@ -0,0 +1,246 @@
|
|||||||
|
/**
|
||||||
|
* SentenceQueueModule
|
||||||
|
* Manages the preparation pipeline for sentences, including TTS generation
|
||||||
|
*/
|
||||||
|
import { BaseModule } from './base-module.js';
|
||||||
|
|
||||||
|
class SentenceQueueModule extends BaseModule {
|
||||||
|
constructor() {
|
||||||
|
super('sentence-queue', 'Sentence Queue');
|
||||||
|
|
||||||
|
// Dependencies
|
||||||
|
this.dependencies = ['text-buffer', 'tts-factory', 'tts-player'];
|
||||||
|
|
||||||
|
// Queue state
|
||||||
|
this.sentenceQueue = [];
|
||||||
|
this.isProcessing = false;
|
||||||
|
this.onSentenceReadyCallback = null;
|
||||||
|
|
||||||
|
// Bind methods
|
||||||
|
this.bindMethods([
|
||||||
|
'initialize',
|
||||||
|
'addSentence',
|
||||||
|
'processNextSentence',
|
||||||
|
'setOnSentenceReady',
|
||||||
|
'completeSentence'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the module
|
||||||
|
* @returns {Promise<boolean>} - Resolves with success status
|
||||||
|
*/
|
||||||
|
async initialize() {
|
||||||
|
try {
|
||||||
|
// Get dependencies
|
||||||
|
const textBuffer = this.getModule('text-buffer');
|
||||||
|
|
||||||
|
if (!textBuffer) {
|
||||||
|
console.error("SentenceQueue: TextBuffer dependency not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the text buffer to send sentences to this queue
|
||||||
|
textBuffer.setOnSentenceReady((sentence, callback) => {
|
||||||
|
this.addSentence(sentence, callback);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.reportProgress(100, "Sentence queue ready");
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error initializing Sentence Queue:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set callback for when a sentence is ready for display
|
||||||
|
* @param {Function} callback - Function to call with prepared sentence
|
||||||
|
*/
|
||||||
|
setOnSentenceReady(callback) {
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
this.onSentenceReadyCallback = callback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a sentence to the queue
|
||||||
|
* @param {string} sentence - Sentence to add
|
||||||
|
* @param {Function} callback - Callback to call when sentence is processed
|
||||||
|
*/
|
||||||
|
addSentence(sentence, callback) {
|
||||||
|
this.sentenceQueue.push({
|
||||||
|
text: sentence,
|
||||||
|
callback: callback
|
||||||
|
});
|
||||||
|
|
||||||
|
// Process the queue if not already processing
|
||||||
|
if (!this.isProcessing) {
|
||||||
|
this.processNextSentence();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the next sentence in the queue
|
||||||
|
*/
|
||||||
|
async processNextSentence() {
|
||||||
|
if (this.sentenceQueue.length === 0 || this.isProcessing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isProcessing = true;
|
||||||
|
const item = this.sentenceQueue[0]; // Don't remove yet
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get TTS Factory
|
||||||
|
const ttsFactory = this.getModule('tts-factory');
|
||||||
|
|
||||||
|
if (!ttsFactory) {
|
||||||
|
console.error("SentenceQueue: TTSFactory dependency not found");
|
||||||
|
this.completeSentence(item, { success: false, reason: 'no_tts_factory' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a speech metadata object
|
||||||
|
const speechMetadata = await this.prepareSpeechMetadata(item.text);
|
||||||
|
|
||||||
|
// If we have a callback for ready sentences, call it with the metadata
|
||||||
|
if (this.onSentenceReadyCallback) {
|
||||||
|
this.onSentenceReadyCallback(item.text, speechMetadata, () => {
|
||||||
|
// Remove from queue and process next
|
||||||
|
this.completeSentence(item, { success: true });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// No callback, just complete
|
||||||
|
this.completeSentence(item, { success: true });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error processing sentence:", error);
|
||||||
|
this.completeSentence(item, { success: false, reason: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare speech metadata for a sentence
|
||||||
|
* @param {string} text - Text to prepare speech for
|
||||||
|
* @returns {Promise<Object>} - Speech metadata object
|
||||||
|
*/
|
||||||
|
async prepareSpeechMetadata(text) {
|
||||||
|
const ttsFactory = this.getModule('tts-factory');
|
||||||
|
const ttsPlayer = this.getModule('tts-player');
|
||||||
|
|
||||||
|
if (!ttsFactory || !ttsPlayer) {
|
||||||
|
throw new Error("TTS dependencies not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if TTS is enabled
|
||||||
|
const isTtsEnabled = ttsPlayer.isEnabled();
|
||||||
|
|
||||||
|
// If TTS is disabled, estimate duration based on character count
|
||||||
|
if (!isTtsEnabled) {
|
||||||
|
return this.estimateSpeechDuration(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Preload the speech to get metadata
|
||||||
|
const result = await ttsFactory.preloadSpeech(text);
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
console.warn("SentenceQueue: Speech preload failed, using estimated duration");
|
||||||
|
return this.estimateSpeechDuration(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a speech metadata object
|
||||||
|
return {
|
||||||
|
text: text,
|
||||||
|
duration: result.duration || this.estimateSpeechDuration(text).duration,
|
||||||
|
handler: ttsFactory.getActiveHandler() ? ttsFactory.getActiveHandler().id : null,
|
||||||
|
play: async () => {
|
||||||
|
return ttsFactory.speak(text);
|
||||||
|
},
|
||||||
|
stop: () => {
|
||||||
|
return ttsFactory.stop();
|
||||||
|
},
|
||||||
|
isTtsEnabled: isTtsEnabled
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error preparing speech metadata:", error);
|
||||||
|
return this.estimateSpeechDuration(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimate speech duration based on character count
|
||||||
|
* @param {string} text - Text to estimate duration for
|
||||||
|
* @returns {Object} - Speech metadata object with estimated duration
|
||||||
|
*/
|
||||||
|
estimateSpeechDuration(text) {
|
||||||
|
// Average reading speed is about 14-15 characters per second
|
||||||
|
// We'll use a slightly slower rate for TTS
|
||||||
|
const charactersPerSecond = 12;
|
||||||
|
const ttsPlayer = this.getModule('tts-player');
|
||||||
|
|
||||||
|
// Get the current speed setting if available
|
||||||
|
let speedMultiplier = 1.0;
|
||||||
|
if (ttsPlayer) {
|
||||||
|
const ttsFactory = this.getModule('tts-factory');
|
||||||
|
if (ttsFactory) {
|
||||||
|
// Get the current speed setting (typically 0.5-2.0)
|
||||||
|
const speed = ttsFactory.speed || 1.0;
|
||||||
|
speedMultiplier = speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate estimated duration in milliseconds
|
||||||
|
const charCount = text.length;
|
||||||
|
const durationSeconds = charCount / (charactersPerSecond * speedMultiplier);
|
||||||
|
const durationMs = Math.max(durationSeconds * 1000, 500); // Minimum 500ms
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: text,
|
||||||
|
duration: durationMs,
|
||||||
|
handler: null,
|
||||||
|
play: async () => ({ success: false, reason: 'tts_disabled' }),
|
||||||
|
stop: () => true,
|
||||||
|
isTtsEnabled: false,
|
||||||
|
isEstimated: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete processing of a sentence
|
||||||
|
* @param {Object} item - Queue item
|
||||||
|
* @param {Object} result - Processing result
|
||||||
|
*/
|
||||||
|
completeSentence(item, result) {
|
||||||
|
// Remove from queue
|
||||||
|
this.sentenceQueue.shift();
|
||||||
|
|
||||||
|
// Call the original callback
|
||||||
|
if (item.callback) {
|
||||||
|
item.callback(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset processing flag
|
||||||
|
this.isProcessing = false;
|
||||||
|
|
||||||
|
// Process next sentence if any
|
||||||
|
if (this.sentenceQueue.length > 0) {
|
||||||
|
this.processNextSentence();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the singleton instance
|
||||||
|
const SentenceQueue = new SentenceQueueModule();
|
||||||
|
|
||||||
|
// Export the module
|
||||||
|
export { SentenceQueue };
|
||||||
|
|
||||||
|
// Register with the module registry
|
||||||
|
if (window.moduleRegistry) {
|
||||||
|
window.moduleRegistry.register(SentenceQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep a reference in window for loader system
|
||||||
|
window.SentenceQueue = SentenceQueue;
|
||||||
@@ -1,364 +0,0 @@
|
|||||||
/**
|
|
||||||
* TTS Player Module
|
|
||||||
* Manages TTS functionality and interacts with available TTS handlers
|
|
||||||
*/
|
|
||||||
import { BaseModule } from './base-module.js';
|
|
||||||
|
|
||||||
class TTSPlayerModule extends BaseModule {
|
|
||||||
constructor() {
|
|
||||||
super('tts-player', 'TTS Player');
|
|
||||||
|
|
||||||
// Module dependencies
|
|
||||||
this.dependencies = ['tts-factory'];
|
|
||||||
|
|
||||||
// TTS state
|
|
||||||
this.enabled = true;
|
|
||||||
this.currentSpeech = null;
|
|
||||||
this.pendingCallback = null;
|
|
||||||
|
|
||||||
// Preloading mechanism
|
|
||||||
this.preloadQueue = [];
|
|
||||||
this.preloadedAudio = new Map(); // Cache for preloaded TTS
|
|
||||||
this.isPreloading = false;
|
|
||||||
|
|
||||||
// Bind methods using parent's bindMethods utility
|
|
||||||
this.bindMethods([
|
|
||||||
'speak',
|
|
||||||
'preloadSpeech',
|
|
||||||
'processPreloadQueue',
|
|
||||||
'stop',
|
|
||||||
'enable',
|
|
||||||
'isEnabled',
|
|
||||||
'isSpeaking',
|
|
||||||
'setVoice',
|
|
||||||
'setSpeed',
|
|
||||||
'getVoices',
|
|
||||||
'toggle'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the module
|
|
||||||
* @returns {Promise<boolean>} - Resolves with success status
|
|
||||||
*/
|
|
||||||
async initialize() {
|
|
||||||
try {
|
|
||||||
this.reportProgress(20, "Initializing TTS Player");
|
|
||||||
|
|
||||||
// Get TTS Factory dependency
|
|
||||||
const ttsFactory = this.getModule('tts-factory');
|
|
||||||
if (!ttsFactory) {
|
|
||||||
console.error("TTS Player: TTS Factory dependency not found");
|
|
||||||
this.reportProgress(100, "TTS Player failed - missing dependencies");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check TTS availability from TTS Factory
|
|
||||||
this.enabled = ttsFactory.ttsAvailable && ttsFactory.getPreference('tts', 'enabled', false);
|
|
||||||
|
|
||||||
// Set up event listeners
|
|
||||||
this.addEventListener(document, 'tts:enabled', (event) => {
|
|
||||||
if (event.detail) {
|
|
||||||
this.enabled = event.detail.enabled;
|
|
||||||
console.log(`TTS Player: TTS ${this.enabled ? 'enabled' : 'disabled'}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listen for TTS availability changes
|
|
||||||
this.addEventListener(document, 'tts:availability', (event) => {
|
|
||||||
if (event.detail) {
|
|
||||||
const available = event.detail.available;
|
|
||||||
console.log(`TTS Player: TTS availability changed to ${available ? 'available' : 'unavailable'}`);
|
|
||||||
|
|
||||||
// If TTS becomes unavailable, disable it
|
|
||||||
if (!available) {
|
|
||||||
this.enabled = false;
|
|
||||||
// Notify UI that TTS is disabled
|
|
||||||
super.dispatchEvent('tts:stateChange', {
|
|
||||||
enabled: false,
|
|
||||||
available: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listen for TTS toggle events from UI - support both event names
|
|
||||||
this.addEventListener(document, 'tts:toggle', () => {
|
|
||||||
this.toggle();
|
|
||||||
// Dispatch state change event for UI to update
|
|
||||||
super.dispatchEvent('tts:stateChange', {
|
|
||||||
enabled: this.enabled,
|
|
||||||
available: ttsFactory.ttsAvailable
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Also listen for ui:tts:toggle events (from the main UI)
|
|
||||||
this.addEventListener(document, 'ui:tts:toggle', (event) => {
|
|
||||||
// If we have explicit enabled value, use it instead of toggling
|
|
||||||
if (event.detail && typeof event.detail.enabled === 'boolean') {
|
|
||||||
this.enabled = event.detail.enabled;
|
|
||||||
} else {
|
|
||||||
this.toggle();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch state change event for UI to update
|
|
||||||
super.dispatchEvent('tts:stateChange', {
|
|
||||||
enabled: this.enabled,
|
|
||||||
available: ttsFactory.ttsAvailable
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Request available TTS voices
|
|
||||||
this.reportProgress(60, "Checking for available TTS voices");
|
|
||||||
const voices = await ttsFactory.getVoices();
|
|
||||||
console.log(`TTS Player: ${voices.length} voices available`);
|
|
||||||
|
|
||||||
this.reportProgress(100, "TTS Player ready");
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error initializing TTS Player:", error);
|
|
||||||
this.reportProgress(100, "TTS Player initialization failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Preload speech for a sentence
|
|
||||||
* @param {string} text - Text to preload
|
|
||||||
*/
|
|
||||||
preloadSpeech(text) {
|
|
||||||
if (!text || !this.enabled) return;
|
|
||||||
|
|
||||||
// Skip if already preloaded or in queue
|
|
||||||
if (this.preloadedAudio.has(text) || this.preloadQueue.includes(text)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.preloadQueue.push(text);
|
|
||||||
|
|
||||||
// Start preloading if not already preloading and no active speech
|
|
||||||
if (!this.isPreloading && !this.currentSpeech) {
|
|
||||||
this.processPreloadQueue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process the preload queue
|
|
||||||
*/
|
|
||||||
processPreloadQueue() {
|
|
||||||
if (this.preloadQueue.length === 0 || this.isPreloading) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isPreloading = true;
|
|
||||||
const text = this.preloadQueue.shift();
|
|
||||||
|
|
||||||
// Skip if already preloaded
|
|
||||||
if (this.preloadedAudio.has(text)) {
|
|
||||||
this.isPreloading = false;
|
|
||||||
this.processPreloadQueue();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use TTS Factory to generate audio
|
|
||||||
const ttsFactory = this.getModule('tts-factory');
|
|
||||||
if (ttsFactory) {
|
|
||||||
console.log(`Preloading TTS for: "${text.substring(0, 30)}${text.length > 30 ? '...' : ''}"`);
|
|
||||||
|
|
||||||
ttsFactory.generateSpeech(text)
|
|
||||||
.then(audioData => {
|
|
||||||
if (audioData && audioData.success) {
|
|
||||||
this.preloadedAudio.set(text, audioData);
|
|
||||||
console.log(`TTS preloaded successfully for: "${text.substring(0, 30)}${text.length > 30 ? '...' : ''}"`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error("Error preloading TTS:", error);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
this.isPreloading = false;
|
|
||||||
|
|
||||||
// Continue processing queue
|
|
||||||
if (this.preloadQueue.length > 0) {
|
|
||||||
this.processPreloadQueue();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.isPreloading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Speak a sentence
|
|
||||||
* @param {string} text - Text to speak
|
|
||||||
* @param {Function} callback - Callback for when speech completes
|
|
||||||
* @returns {boolean} - Success status
|
|
||||||
*/
|
|
||||||
speak(text, callback = null) {
|
|
||||||
if (!this.enabled) {
|
|
||||||
if (callback) {
|
|
||||||
setTimeout(() => callback({ success: false, reason: 'disabled' }), 0);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.currentSpeech) {
|
|
||||||
// Stop current speech if any
|
|
||||||
this.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentSpeech = text;
|
|
||||||
this.pendingCallback = callback;
|
|
||||||
|
|
||||||
const ttsFactory = this.getModule('tts-factory');
|
|
||||||
if (ttsFactory) {
|
|
||||||
// Check if we have this preloaded
|
|
||||||
if (this.preloadedAudio.has(text)) {
|
|
||||||
const preloadedAudio = this.preloadedAudio.get(text);
|
|
||||||
this.preloadedAudio.delete(text); // Remove from cache after use
|
|
||||||
|
|
||||||
console.log(`Using preloaded TTS for: "${text.substring(0, 30)}${text.length > 30 ? '...' : ''}"`);
|
|
||||||
|
|
||||||
// Play the preloaded audio
|
|
||||||
ttsFactory.playAudio(preloadedAudio, (result) => {
|
|
||||||
// Store the completed result
|
|
||||||
this.currentSpeech = null;
|
|
||||||
|
|
||||||
// Call the callback if provided
|
|
||||||
if (this.pendingCallback) {
|
|
||||||
this.pendingCallback(result);
|
|
||||||
this.pendingCallback = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process next in preload queue if any
|
|
||||||
if (this.preloadQueue.length > 0 && !this.isPreloading) {
|
|
||||||
this.processPreloadQueue();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Start TTS with regular speech if not preloaded
|
|
||||||
ttsFactory.speak(text, (result) => {
|
|
||||||
// Store the completed result
|
|
||||||
this.currentSpeech = null;
|
|
||||||
|
|
||||||
// Call the callback if provided
|
|
||||||
if (this.pendingCallback) {
|
|
||||||
this.pendingCallback(result);
|
|
||||||
this.pendingCallback = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process next in preload queue if any
|
|
||||||
if (this.preloadQueue.length > 0 && !this.isPreloading) {
|
|
||||||
this.processPreloadQueue();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
console.error("TTS Player: TTSFactory module not found in registry");
|
|
||||||
if (callback) {
|
|
||||||
setTimeout(() => callback({ success: false, reason: 'no_tts_factory' }), 0);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop speaking
|
|
||||||
*/
|
|
||||||
stop() {
|
|
||||||
const ttsFactory = this.getModule('tts-factory');
|
|
||||||
if (ttsFactory) {
|
|
||||||
ttsFactory.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentSpeech = null;
|
|
||||||
this.pendingCallback = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle TTS enabled state
|
|
||||||
*/
|
|
||||||
toggle() {
|
|
||||||
this.enabled = !this.enabled;
|
|
||||||
this.enable(this.enabled);
|
|
||||||
return this.enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable or disable TTS
|
|
||||||
* @param {boolean} enabled - Whether TTS should be enabled
|
|
||||||
*/
|
|
||||||
enable(enabled) {
|
|
||||||
this.enabled = enabled;
|
|
||||||
console.log(`TTS Player: ${this.enabled ? 'Enabled' : 'Disabled'}`);
|
|
||||||
|
|
||||||
// Save preference if persistence manager is available
|
|
||||||
const persistenceManager = this.getModule('persistence-manager');
|
|
||||||
if (persistenceManager) {
|
|
||||||
persistenceManager.updatePreference('tts', 'enabled', this.enabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if TTS is enabled
|
|
||||||
* @returns {boolean} - Whether TTS is enabled
|
|
||||||
*/
|
|
||||||
isEnabled() {
|
|
||||||
return this.enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if TTS is currently speaking
|
|
||||||
* @returns {boolean} - Whether TTS is speaking
|
|
||||||
*/
|
|
||||||
isSpeaking() {
|
|
||||||
const ttsFactory = this.getModule('tts-factory');
|
|
||||||
if (ttsFactory) {
|
|
||||||
return ttsFactory.isSpeaking();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the voice to use
|
|
||||||
* @param {string} voice - Voice identifier
|
|
||||||
*/
|
|
||||||
setVoice(voice) {
|
|
||||||
const ttsFactory = this.getModule('tts-factory');
|
|
||||||
if (ttsFactory) {
|
|
||||||
ttsFactory.configure({ voice });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the speech rate/speed
|
|
||||||
* @param {number} speed - Speech rate (0.5-2.0)
|
|
||||||
*/
|
|
||||||
setSpeed(speed) {
|
|
||||||
const ttsFactory = this.getModule('tts-factory');
|
|
||||||
if (ttsFactory) {
|
|
||||||
ttsFactory.configure({ speed });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get available voices
|
|
||||||
* @returns {Promise<Array>} - Resolves with array of voice objects
|
|
||||||
*/
|
|
||||||
async getVoices() {
|
|
||||||
const ttsFactory = this.getModule('tts-factory');
|
|
||||||
if (ttsFactory) {
|
|
||||||
return ttsFactory.getVoices();
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the singleton instance
|
|
||||||
const TTSPlayer = new TTSPlayerModule();
|
|
||||||
|
|
||||||
// Export the module
|
|
||||||
export { TTSPlayer };
|
|
||||||
@@ -17,10 +17,6 @@ class UIDisplayHandlerModule extends BaseModule {
|
|||||||
this.pageRight = null;
|
this.pageRight = null;
|
||||||
this.paragraphContainer = null;
|
this.paragraphContainer = null;
|
||||||
|
|
||||||
// State
|
|
||||||
this.currentParagraphId = 0;
|
|
||||||
this.pendingParagraphs = [];
|
|
||||||
|
|
||||||
// Resources to preload
|
// Resources to preload
|
||||||
this.cssPath = '/css/style.css';
|
this.cssPath = '/css/style.css';
|
||||||
this.imagesToPreload = [
|
this.imagesToPreload = [
|
||||||
@@ -28,32 +24,13 @@ class UIDisplayHandlerModule extends BaseModule {
|
|||||||
'/images/brown-wooden-flooring.jpg'
|
'/images/brown-wooden-flooring.jpg'
|
||||||
];
|
];
|
||||||
|
|
||||||
// Configuration
|
|
||||||
this.updateConfig({
|
|
||||||
typography: {
|
|
||||||
fontFamily: "'EB Garamond', serif",
|
|
||||||
fontSize: '1.15rem',
|
|
||||||
lineHeight: 1.5,
|
|
||||||
maxWidth: 600
|
|
||||||
},
|
|
||||||
animation: {
|
|
||||||
speed: 0.05, // Speed multiplier
|
|
||||||
useTypingAnimation: true
|
|
||||||
},
|
|
||||||
display: {
|
|
||||||
showChoices: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Bind methods using parent's bindMethods utility
|
// Bind methods using parent's bindMethods utility
|
||||||
this.bindMethods([
|
this.bindMethods([
|
||||||
'initializeContainers',
|
'initializeContainers',
|
||||||
'displayText',
|
'displayText',
|
||||||
'showChoices',
|
|
||||||
'processNextParagraph',
|
|
||||||
'measureText',
|
'measureText',
|
||||||
'updateTypographySettings',
|
|
||||||
'loadCSS',
|
'loadCSS',
|
||||||
|
'showChoices',
|
||||||
'preloadImages'
|
'preloadImages'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -76,21 +53,6 @@ class UIDisplayHandlerModule extends BaseModule {
|
|||||||
this.layoutRenderer = this.getModule('layout-renderer');
|
this.layoutRenderer = this.getModule('layout-renderer');
|
||||||
this.animationQueue = this.getModule('animation-queue');
|
this.animationQueue = this.getModule('animation-queue');
|
||||||
|
|
||||||
if (!this.paragraphLayout) {
|
|
||||||
console.error("UIDisplayHandler: Missing paragraph-layout module");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.layoutRenderer) {
|
|
||||||
console.error("UIDisplayHandler: Missing layout-renderer module");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.animationQueue) {
|
|
||||||
console.error("UIDisplayHandler: Missing animation-queue module");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.reportProgress(50, "Initializing display containers");
|
this.reportProgress(50, "Initializing display containers");
|
||||||
|
|
||||||
// Initialize container elements
|
// Initialize container elements
|
||||||
@@ -98,15 +60,8 @@ class UIDisplayHandlerModule extends BaseModule {
|
|||||||
|
|
||||||
this.reportProgress(70, "Setting up typography");
|
this.reportProgress(70, "Setting up typography");
|
||||||
|
|
||||||
// Set up measure function for paragraph layout
|
|
||||||
const { fontSize, fontFamily } = this.config.typography;
|
|
||||||
this.paragraphLayout.updateFont(fontSize, fontFamily);
|
|
||||||
|
|
||||||
this.reportProgress(90, "Setting up event listeners");
|
this.reportProgress(90, "Setting up event listeners");
|
||||||
|
|
||||||
// Set up event listeners
|
|
||||||
this.setupEventListeners();
|
|
||||||
|
|
||||||
this.reportProgress(100, "UI Display Handler ready");
|
this.reportProgress(100, "UI Display Handler ready");
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -115,24 +70,6 @@ class UIDisplayHandlerModule extends BaseModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up event listeners
|
|
||||||
*/
|
|
||||||
setupEventListeners() {
|
|
||||||
// Listen for typography setting changes
|
|
||||||
this.addEventListener(document, 'ui:typography:update', (event) => {
|
|
||||||
if (event.detail) {
|
|
||||||
this.updateTypographySettings(event.detail);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listen for animation speed changes
|
|
||||||
this.addEventListener(document, 'ui:animation:speed', (event) => {
|
|
||||||
if (event.detail && typeof event.detail.speed === 'number') {
|
|
||||||
this.config.animation.speed = event.detail.speed;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load CSS file asynchronously and wait for it to be applied
|
* Load CSS file asynchronously and wait for it to be applied
|
||||||
@@ -141,16 +78,6 @@ class UIDisplayHandlerModule extends BaseModule {
|
|||||||
*/
|
*/
|
||||||
loadCSS(cssPath) {
|
loadCSS(cssPath) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// Check if the stylesheet is already loaded
|
|
||||||
const existingLinks = document.querySelectorAll('link[rel="stylesheet"]');
|
|
||||||
for (const link of existingLinks) {
|
|
||||||
if (link.href.includes(cssPath)) {
|
|
||||||
console.log(`UIDisplayHandler: CSS ${cssPath} already loaded`);
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create link element
|
// Create link element
|
||||||
const link = document.createElement('link');
|
const link = document.createElement('link');
|
||||||
link.rel = 'stylesheet';
|
link.rel = 'stylesheet';
|
||||||
@@ -159,11 +86,7 @@ class UIDisplayHandlerModule extends BaseModule {
|
|||||||
// Set up load and error handlers
|
// Set up load and error handlers
|
||||||
link.onload = () => {
|
link.onload = () => {
|
||||||
console.log(`UIDisplayHandler: CSS ${cssPath} loaded successfully`);
|
console.log(`UIDisplayHandler: CSS ${cssPath} loaded successfully`);
|
||||||
|
|
||||||
// Give a small delay for the CSS to be applied
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve();
|
resolve();
|
||||||
}, 50);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
link.onerror = (error) => {
|
link.onerror = (error) => {
|
||||||
@@ -347,49 +270,6 @@ class UIDisplayHandlerModule extends BaseModule {
|
|||||||
return this.context.measureText(text).width;
|
return this.context.measureText(text).width;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update typography settings
|
|
||||||
* @param {Object} settings - Typography settings
|
|
||||||
*/
|
|
||||||
updateTypographySettings(settings) {
|
|
||||||
let changed = false;
|
|
||||||
|
|
||||||
if (settings.fontSize && settings.fontSize !== this.config.typography.fontSize) {
|
|
||||||
this.config.typography.fontSize = settings.fontSize;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.fontFamily && settings.fontFamily !== this.config.typography.fontFamily) {
|
|
||||||
this.config.typography.fontFamily = settings.fontFamily;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.lineHeight && settings.lineHeight !== this.config.typography.lineHeight) {
|
|
||||||
this.config.typography.lineHeight = settings.lineHeight;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If font settings changed, update the paragraph layout
|
|
||||||
if (changed && this.paragraphLayout) {
|
|
||||||
// Use the existing updateFont method
|
|
||||||
this.paragraphLayout.updateFont(
|
|
||||||
this.config.typography.fontSize,
|
|
||||||
this.config.typography.fontFamily
|
|
||||||
);
|
|
||||||
|
|
||||||
// Also update our local canvas context
|
|
||||||
if (this.context) {
|
|
||||||
this.context.font = `${this.config.typography.fontSize} ${this.config.typography.fontFamily}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch event about typography changes
|
|
||||||
this.dispatchEvent('ui:font:change', {
|
|
||||||
fontSize: this.config.typography.fontSize,
|
|
||||||
fontFamily: this.config.typography.fontFamily,
|
|
||||||
lineHeight: this.config.typography.lineHeight
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display text in the UI
|
* Display text in the UI
|
||||||
@@ -421,83 +301,6 @@ class UIDisplayHandlerModule extends BaseModule {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Process the next paragraph in the queue
|
|
||||||
*/
|
|
||||||
processNextParagraph() {
|
|
||||||
if (this.pendingParagraphs.length === 0) return;
|
|
||||||
|
|
||||||
const paragraph = this.pendingParagraphs[0];
|
|
||||||
const { id, text, options, resolve } = paragraph;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Use the paragraph layout to calculate the optimal layout
|
|
||||||
const layout = this.paragraphLayout.calculateLayout(text, {
|
|
||||||
width: this.config.typography.maxWidth,
|
|
||||||
fontSize: options.fontSize || this.config.typography.fontSize,
|
|
||||||
fontFamily: options.fontFamily || this.config.typography.fontFamily,
|
|
||||||
lineHeight: options.lineHeight || this.config.typography.lineHeight
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!layout) {
|
|
||||||
console.error("UIDisplayHandler: Failed to calculate paragraph layout");
|
|
||||||
this.pendingParagraphs.shift(); // Remove this paragraph
|
|
||||||
resolve(null);
|
|
||||||
|
|
||||||
// Process next paragraph if any
|
|
||||||
if (this.pendingParagraphs.length > 0) {
|
|
||||||
this.processNextParagraph();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the original text in the layout for TTS
|
|
||||||
layout.originalText = text;
|
|
||||||
|
|
||||||
// Use the layout renderer to create the DOM elements
|
|
||||||
const paragraphElement = this.layoutRenderer.renderParagraph(layout, {
|
|
||||||
container: this.paragraphContainer,
|
|
||||||
id: id,
|
|
||||||
className: options.className || '',
|
|
||||||
style: options.style || {},
|
|
||||||
animateWords: this.config.animation.useTypingAnimation,
|
|
||||||
animationSpeed: this.config.animation.speed,
|
|
||||||
tts: options.speak !== false, // Enable TTS by default
|
|
||||||
onComplete: () => {
|
|
||||||
// Dispatch event when paragraph is complete
|
|
||||||
this.dispatchEvent('ui:paragraph:complete', { id });
|
|
||||||
|
|
||||||
// Remove this paragraph from the queue
|
|
||||||
this.pendingParagraphs.shift();
|
|
||||||
|
|
||||||
// Resolve the promise with the paragraph element
|
|
||||||
resolve(paragraphElement);
|
|
||||||
|
|
||||||
// Process next paragraph if any
|
|
||||||
if (this.pendingParagraphs.length > 0) {
|
|
||||||
this.processNextParagraph();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Scroll to the new paragraph
|
|
||||||
if (paragraphElement) {
|
|
||||||
paragraphElement.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("UIDisplayHandler: Error processing paragraph:", error);
|
|
||||||
|
|
||||||
// Remove this paragraph from the queue
|
|
||||||
this.pendingParagraphs.shift();
|
|
||||||
resolve(null);
|
|
||||||
|
|
||||||
// Process next paragraph if any
|
|
||||||
if (this.pendingParagraphs.length > 0) {
|
|
||||||
this.processNextParagraph();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show choices in the UI
|
* Show choices in the UI
|
||||||
* @param {Array<Object>} choices - Array of choice objects
|
* @param {Array<Object>} choices - Array of choice objects
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
# Client Architecture Refactoring TODO List
|
||||||
|
|
||||||
|
## Enhanced SentenceQueueModule Implementation
|
||||||
|
|
||||||
|
### Phase 1: Core Structure and Design
|
||||||
|
|
||||||
|
1. **Design the Sentence Object Structure**
|
||||||
|
- [ ] Define comprehensive sentence object with fields for:
|
||||||
|
- Unique ID
|
||||||
|
- Original text
|
||||||
|
- Processed text (hyphenated, typeset)
|
||||||
|
- Layout information (breaks, nodes, typography)
|
||||||
|
- Audio component (player, duration, data, type)
|
||||||
|
- Status tracking (pending, processing, ready, playing, complete)
|
||||||
|
|
||||||
|
2. **Implement Basic Queue Management**
|
||||||
|
- [ ] Create methods for adding sentences to the queue
|
||||||
|
- [ ] Implement queue processing logic that maintains order
|
||||||
|
- [ ] Add status tracking for each sentence in the queue
|
||||||
|
- [ ] Implement priority handling for urgent sentences
|
||||||
|
|
||||||
|
### Phase 2: Text Processing Integration
|
||||||
|
|
||||||
|
1. **Integrate with Paragraph Layout**
|
||||||
|
- [ ] Connect to ParagraphLayoutModule for text processing
|
||||||
|
- [ ] Implement hyphenation and typesetting in the queue
|
||||||
|
- [ ] Store layout information in the sentence object
|
||||||
|
- [ ] Ensure layout processing happens in parallel with audio
|
||||||
|
|
||||||
|
2. **Text Animation Preparation**
|
||||||
|
- [ ] Calculate animation timing based on text length and settings
|
||||||
|
- [ ] Prepare animation data for each word in the sentence
|
||||||
|
- [ ] Store animation timing in the sentence object
|
||||||
|
- [ ] Create animation player function for the sentence
|
||||||
|
|
||||||
|
### Phase 3: Audio Processing Integration
|
||||||
|
|
||||||
|
1. **TTS System Integration**
|
||||||
|
- [ ] Implement audio generation for Kokoro TTS
|
||||||
|
- [ ] Implement browser TTS handling with duration estimation
|
||||||
|
- [ ] Implement "none" TTS option with duration calculation
|
||||||
|
- [ ] Create consistent player interface for all TTS types
|
||||||
|
|
||||||
|
2. **Audio Data Management**
|
||||||
|
- [ ] Implement audio data storage in sentence objects
|
||||||
|
- [ ] Connect with TTSFactoryModule's IndexedDB for persistent caching
|
||||||
|
- [ ] Add audio preloading capabilities
|
||||||
|
- [ ] Implement audio resource cleanup
|
||||||
|
|
||||||
|
### Phase 4: Playback Coordination
|
||||||
|
|
||||||
|
1. **Synchronized Playback**
|
||||||
|
- [ ] Implement coordinated text animation and audio playback
|
||||||
|
- [ ] Create timing adjustment based on speed settings
|
||||||
|
- [ ] Add event handling for playback states (start, pause, resume, complete)
|
||||||
|
- [ ] Implement sentence transition handling
|
||||||
|
|
||||||
|
2. **User Interaction Handling**
|
||||||
|
- [ ] Add support for fast-forwarding text/audio
|
||||||
|
- [ ] Implement pause/resume functionality
|
||||||
|
- [ ] Handle user interruptions gracefully
|
||||||
|
- [ ] Support skipping to next sentence
|
||||||
|
|
||||||
|
## Component Consolidation
|
||||||
|
|
||||||
|
### Phase 1: Identify and Remove Redundancies
|
||||||
|
|
||||||
|
1. **TTSPlayerModule Refactoring**
|
||||||
|
- [ ] Remove preloadedAudio Map (replaced by sentence objects)
|
||||||
|
- [ ] Remove preloadQueue (replaced by SentenceQueue)
|
||||||
|
- [ ] Update speak method to use SentenceQueue
|
||||||
|
- [ ] Refactor to be a thin wrapper around SentenceQueue
|
||||||
|
|
||||||
|
2. **UIDisplayHandlerModule Refactoring**
|
||||||
|
- [ ] Remove pendingParagraphs queue (replaced by SentenceQueue)
|
||||||
|
- [ ] Update displayText to use SentenceQueue
|
||||||
|
- [ ] Modify processNextParagraph to work with sentence objects
|
||||||
|
- [ ] Update event handling to work with the new architecture
|
||||||
|
|
||||||
|
3. **KokoroTTSModule Refactoring**
|
||||||
|
- [ ] Replace pendingGenerations Map with SentenceQueue integration
|
||||||
|
- [ ] Update generateSpeech to work with sentence objects
|
||||||
|
- [ ] Modify iframe communication to support the new structure
|
||||||
|
- [ ] Ensure backward compatibility during transition
|
||||||
|
|
||||||
|
4. **TextBufferModule Refactoring**
|
||||||
|
- [ ] Move sentence preparation logic to SentenceQueue
|
||||||
|
- [ ] Update text handling to work with the new architecture
|
||||||
|
- [ ] Ensure proper integration with SentenceQueue
|
||||||
|
- [ ] Maintain high-level text management responsibilities
|
||||||
|
|
||||||
|
### Phase 2: Interface Updates
|
||||||
|
|
||||||
|
1. **Update Module Interfaces**
|
||||||
|
- [ ] Create consistent interfaces for interacting with SentenceQueue
|
||||||
|
- [ ] Update event system to work with sentence objects
|
||||||
|
- [ ] Implement progress reporting for sentence processing
|
||||||
|
- [ ] Add debugging and monitoring capabilities
|
||||||
|
|
||||||
|
2. **Documentation and Examples**
|
||||||
|
- [ ] Document the new architecture and interfaces
|
||||||
|
- [ ] Create usage examples for common scenarios
|
||||||
|
- [ ] Update developer guidelines
|
||||||
|
- [ ] Add migration guide for existing code
|
||||||
|
|
||||||
|
## Testing and Validation
|
||||||
|
|
||||||
|
1. **Unit Testing**
|
||||||
|
- [ ] Create tests for SentenceQueue core functionality
|
||||||
|
- [ ] Test text processing integration
|
||||||
|
- [ ] Test audio processing integration
|
||||||
|
- [ ] Test playback coordination
|
||||||
|
|
||||||
|
2. **Integration Testing**
|
||||||
|
- [ ] Test interaction between SentenceQueue and other modules
|
||||||
|
- [ ] Validate timing and synchronization
|
||||||
|
- [ ] Test error handling and recovery
|
||||||
|
- [ ] Verify performance under load
|
||||||
|
|
||||||
|
3. **User Experience Testing**
|
||||||
|
- [ ] Validate text animation quality
|
||||||
|
- [ ] Test audio playback quality
|
||||||
|
- [ ] Verify synchronization from user perspective
|
||||||
|
- [ ] Test accessibility features
|
||||||
|
|
||||||
|
## Implementation Strategy
|
||||||
|
|
||||||
|
1. **Phased Rollout**
|
||||||
|
- [ ] Implement SentenceQueue core structure
|
||||||
|
- [ ] Add text processing integration
|
||||||
|
- [ ] Add audio processing integration
|
||||||
|
- [ ] Implement playback coordination
|
||||||
|
- [ ] Gradually replace existing components
|
||||||
|
|
||||||
|
2. **Backward Compatibility**
|
||||||
|
- [ ] Maintain support for existing interfaces during transition
|
||||||
|
- [ ] Implement adapter patterns where needed
|
||||||
|
- [ ] Add feature flags for enabling/disabling new architecture
|
||||||
|
- [ ] Create fallback mechanisms for error recovery
|
||||||
|
|
||||||
|
3. **Performance Optimization**
|
||||||
|
- [ ] Implement parallel processing where possible
|
||||||
|
- [ ] Optimize memory usage for sentence objects
|
||||||
|
- [ ] Add resource management for audio data
|
||||||
|
- [ ] Implement efficient queue processing algorithms
|
||||||
Reference in New Issue
Block a user