/** * 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} - 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} - 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 };