Split everything up into dynamically loaded modules.

This commit is contained in:
2025-04-04 00:00:43 +00:00
parent 2f7cda4b6d
commit aa29a6fd93
32 changed files with 8768 additions and 3935 deletions
+238 -106
View File
@@ -1,137 +1,269 @@
/**
* TTS Player Module
* Manages text-to-speech playback integration with animation queue.
* TTS Player Module for AI Interactive Fiction
* Handles Text-to-Speech functionality with resource-aware loading and progress reporting
*/
export class TtsPlayer {
/**
* Create a new TtsPlayer
* @param {Object} config - Configuration options
* @param {string} config.apiKey - API key for TTS service (if applicable)
* @param {Object} config.animationQueue - AnimationQueue instance for synchronization
*/
constructor(config = {}) {
this.config = config;
this.animationQueue = config.animationQueue;
this.ttsHandler = null;
this.enabled = false; // Start with TTS disabled by default
this.currentAudio = null;
import { BaseModule, ModuleEvent } from './base-module.js';
import { moduleRegistry } from './module-registry.js';
// Bind methods to ensure 'this' context
this.speak = this.speak.bind(this);
this.stop = this.stop.bind(this);
class TTSPlayerModule extends BaseModule {
constructor() {
super('tts', 'Text-to-Speech');
this.ttsFactory = null;
this.isInitialized = false;
this.kokoroLoadingPromise = null;
this.kokoroLoadingStarted = false;
}
/**
* Set the TTS handler
* @param {Object} ttsHandler - The TTS handler instance
* Load module dependencies
* @returns {Promise} - Resolves when dependencies are loaded
*/
setTtsHandler(ttsHandler) {
if (!ttsHandler) {
console.warn("TtsPlayer: No valid TTS handler provided.");
return;
}
console.log("TtsPlayer: Handler set to", ttsHandler.constructor.name);
this.ttsHandler = ttsHandler;
// Make sure the window.ttsHandler is also set for global access
if (!window.ttsHandler) {
window.ttsHandler = ttsHandler;
async loadDependencies() {
try {
// Import the TTS Factory module
const { ttsFactory } = await import('./tts-factory.js');
this.ttsFactory = ttsFactory;
this.reportProgress(20, "TTS Factory loaded");
// Set up event listeners
window.addEventListener('tts-ready', this.handleTTSReadyEvent.bind(this));
// Create a Promise that resolves when Kokoro is loaded
this.kokoroLoadingPromise = new Promise(resolve => {
// Listen for when Kokoro starts loading
window.addEventListener('kokoro-loading-started', () => {
this.kokoroLoadingStarted = true;
this.reportProgress(50, "Loading Kokoro TTS");
});
// Listen for when Kokoro completes loading
window.addEventListener('kokoro-loading-complete', (event) => {
// Check if loading was successful from the event details
if (event.detail && event.detail.success === false) {
this.reportProgress(95, "Kokoro TTS failed to load - using fallback");
console.warn("Kokoro failed to load:", event.detail?.error || "unknown error");
} else {
this.reportProgress(95, "Kokoro TTS loaded");
}
resolve();
});
});
return true;
} catch (error) {
console.error("Error loading TTS dependencies:", error);
return false;
}
}
/**
* Enable or disable TTS
* @param {boolean} enabled - Whether TTS should be enabled
* Initialize the module
* @returns {Promise<boolean>} - Resolves with success status
*/
setEnabled(enabled = true) {
this.enabled = enabled;
console.log(`TtsPlayer: TTS ${enabled ? 'enabled' : 'disabled'}`);
// If disabling while audio is playing, stop it
if (!enabled && this.currentAudio) {
this.stop();
}
// Also set the handler's state if available
if (this.ttsHandler && typeof this.ttsHandler.setEnabled === 'function') {
this.ttsHandler.setEnabled(enabled);
} else if (window.ttsHandler && typeof window.ttsHandler.setEnabled === 'function') {
window.ttsHandler.setEnabled(enabled);
async initialize() {
try {
// Initialize TTS Factory
await this.ttsFactory.constructor.initializeInterface((percent, message) => {
// Scale to 20-90% of our progress range
const scaledPercent = 20 + (percent * 0.7);
this.reportProgress(scaledPercent, message);
});
// IMPORTANT: Always wait for Kokoro's loading promise to resolve
this.reportProgress(90, "Waiting for Kokoro TTS to complete loading");
// Wait for the Kokoro loading promise to complete with a timeout
try {
// Add a timeout to prevent waiting forever
const timeoutPromise = new Promise(resolve => setTimeout(() => {
console.log("TTS Player: Kokoro loading timed out, continuing without Kokoro");
resolve(false);
}, 10000)); // 10 second timeout
// Race between normal completion and timeout
await Promise.race([this.kokoroLoadingPromise, timeoutPromise]);
this.reportProgress(95, "Kokoro TTS loading completed or timed out");
} catch (err) {
console.warn("TTS Player: Error waiting for Kokoro:", err);
this.reportProgress(95, "Error waiting for Kokoro, continuing anyway");
}
this.isInitialized = true;
// Final status check
const ttsInfo = this.ttsFactory.getActiveTTSInfo();
if (ttsInfo.available) {
this.reportProgress(100, `TTS Player initialized using ${ttsInfo.name}`);
return true;
} else {
this.reportProgress(100, "TTS initialization complete but no voices available");
return true; // Still consider this a success, just with no voices
}
} catch (error) {
console.error("Error initializing TTS Player:", error);
this.reportProgress(100, "TTS initialization failed, continuing without TTS");
this.isInitialized = true; // Mark as initialized anyway to not block other modules
return true; // Return true to not block the application
}
}
/**
* Toggle TTS state
* @returns {boolean} The new enabled state
* Handle TTS ready event from the factory
* @param {CustomEvent} event - The TTS ready event
*/
handleTTSReadyEvent(event) {
const { available, type } = event.detail;
if (available && type) {
this.reportProgress(95, `TTS system ready: ${type}`);
} else {
this.reportProgress(95, "No TTS system available");
}
}
// Public API methods
/**
* Get information about the active TTS system
* @returns {Object} - TTS system info
*/
getTTSInfo() {
if (!this.ttsFactory) return { available: false, type: 'none', name: 'None' };
return this.ttsFactory.getActiveTTSInfo();
}
/**
* Toggle TTS functionality on/off
* @returns {boolean} - New TTS enabled state
*/
toggle() {
this.setEnabled(!this.enabled);
return this.enabled;
if (!this.ttsFactory) return false;
return this.ttsFactory.toggle();
}
/**
* Check if TTS is enabled
* @returns {boolean} Whether TTS is enabled
* Speak text using the active TTS system
* @param {string} text - Text to speak
* @param {Function} callback - Called when speech completes
*/
isEnabled() {
return this.enabled;
}
/**
* Speak text
* @param {string} text - The text to speak
*/
speak(text) {
if (!this.enabled || !text) return;
// Stop any current speech
this.stop();
console.log(`TtsPlayer: Speaking - "${text.substring(0, 50)}${text.length > 50 ? '...' : ''}"`);
// Try to use our handler first
if (this.ttsHandler && typeof this.ttsHandler.speak === 'function') {
this.ttsHandler.speak(text);
}
// Fall back to window.ttsHandler if available
else if (window.ttsHandler && typeof window.ttsHandler.speak === 'function') {
window.ttsHandler.speak(text);
}
else {
console.warn("TtsPlayer: No TTS handler available to speak text");
speak(text, callback) {
if (!this.ttsFactory) {
console.warn("TTS Factory not available for speak");
if (callback) callback("TTS not available");
return;
}
console.log(`TTS Player speaking: "${text}"`);
this.ttsFactory.speak(text, (result) => {
console.log("TTS Player speak complete", result);
if (callback) callback(result);
});
}
/**
* Stop current speech
* Stop any ongoing speech
*/
stop() {
// Try to use our handler first
if (this.ttsHandler && typeof this.ttsHandler.stop === 'function') {
this.ttsHandler.stop();
}
// Fall back to window.ttsHandler if available
else if (window.ttsHandler && typeof window.ttsHandler.stop === 'function') {
window.ttsHandler.stop();
if (this.ttsFactory) {
this.ttsFactory.stop();
}
}
/**
* Fast forward current speech (may skip or speed up)
* Set voice options for the active TTS system
* @param {Object} options - Voice options
*/
fastForward() {
// Try to use our handler first
if (this.ttsHandler && typeof this.ttsHandler.fastForward === 'function') {
this.ttsHandler.fastForward();
setVoiceOptions(options) {
if (this.ttsFactory) {
this.ttsFactory.setVoiceOptions(options);
}
// Fall back to window.ttsHandler if available
else if (window.ttsHandler && typeof window.ttsHandler.fastForward === 'function') {
window.ttsHandler.fastForward();
}
// If no fastForward method, just stop the speech
else {
this.stop();
}
/**
* Set speech rate/speed
* @param {number} speed - Speech rate (0.5-2.0)
*/
setSpeed(speed) {
this.setVoiceOptions({ rate: speed });
}
/**
* Set the volume for speech
* @param {number} volume - Volume level (0.0-1.0)
*/
setVolume(volume) {
this.setVoiceOptions({ volume: volume });
}
/**
* Set the voice for speech
* @param {string} voice - Voice identifier
*/
setVoice(voice) {
this.setVoiceOptions({ voice: voice });
}
/**
* Switch to a specific TTS system
* @param {string} type - The TTS system to use ('kokoro', 'browser', or 'api')
* @returns {boolean} - Success status
*/
switchTTS(type) {
if (!this.ttsFactory) return false;
const result = this.ttsFactory.switchTTS(type);
// If the switch was successful, refresh the voice list
if (result) {
// Notify listeners that the TTS system changed
window.dispatchEvent(new CustomEvent('tts-system-changed', {
detail: {
type,
info: this.getTTSInfo()
}
}));
}
return result;
}
/**
* Get available TTS systems
* @returns {Array<string>} - Array of available TTS system IDs
*/
getAvailableSystems() {
if (!this.ttsFactory) return [];
const handlers = this.ttsFactory.getAvailableHandlers();
return Object.keys(handlers);
}
/**
* Get available voices for the active TTS system
* @returns {Promise<Array>} - Array of voice objects
*/
async getVoices() {
if (!this.ttsFactory) return [];
return this.ttsFactory.getVoices();
}
/**
* Is TTS enabled currently
* @returns {boolean} - Whether TTS is enabled
*/
isEnabled() {
if (!this.ttsFactory) return false;
return this.ttsFactory.isEnabled();
}
}
// Create the singleton instance
const TTSPlayer = new TTSPlayerModule();
// Register with the module registry
moduleRegistry.register(TTSPlayer);
// Export the module
export { TTSPlayer };
// Keep a reference in window for loader system
window.TTSPlayer = TTSPlayer;