Fix TTS module initialization and dependency issues. Update module IDs for consistency, improve circular dependency detection, and fix UI Controller event handling.

This commit is contained in:
2025-04-04 19:15:28 +00:00
parent 02c7b9ef28
commit 49a5af252c
33 changed files with 7227 additions and 4060 deletions
+364 -518
View File
@@ -1,555 +1,401 @@
/**
* TTS Factory for AI Interactive Fiction
* Manages different TTS implementations with a common interface
* TTS Factory Module
* Creates and manages TTS handler instances
*/
class TTSFactory {
constructor() {
this.ttsHandler = null;
this.handlers = {};
this.initializationAttempted = false;
this.initializationPromise = null;
this.ttsEnabled = true;
this.progressCallback = null;
this.persistenceManager = null;
}
import { BaseModule } from './base-module.js';
import { moduleRegistry } from './module-registry.js';
import { BrowserTTSHandler } from './browser-tts-handler.js';
import { ApiTTSHandler } from './api-tts-handler.js';
import { KokoroHandler } from './kokoro-handler.js';
/**
* Initialize the TTS Factory - Static method for the module loader
* @param {Function} reportProgress - Function to report loading progress to the loader
* @returns {Promise} - Resolves when TTS is initialized
*/
static async initializeInterface(reportProgress = null) {
console.log('TTS Factory: Initializing interface');
// Create singleton instance if needed
if (!window.ttsFactory) {
window.ttsFactory = new TTSFactory();
}
// Initialize TTS with the progress callback
window.ttsFactory.progressCallback = reportProgress;
try {
// Start initialization process
await window.ttsFactory.initialize();
return true;
} catch (error) {
console.error('Error initializing TTS Factory:', error);
return false;
}
}
/**
* Initialize the TTS Factory
* This will load and initialize all available TTS handlers
* @returns {Promise} - Resolves when initialization is complete
*/
async initialize() {
if (this.initializationPromise) {
return this.initializationPromise;
}
this.initializationPromise = new Promise(async (resolve) => {
this.initializationAttempted = true;
const reportProgress = (percent, message) => {
console.log(`TTS progress: ${percent}% - ${message}`);
if (this.progressCallback && typeof this.progressCallback === 'function') {
this.progressCallback(percent, message);
}
};
try {
// Report starting initialization
reportProgress(10, 'Loading TTS modules');
class TTSFactoryModule extends BaseModule {
/**
* Create a new TTS factory
*/
constructor() {
super('tts-factory', 'TTS Factory');
// Get persistence manager if available
if (window.PersistenceManager) {
this.persistenceManager = window.PersistenceManager;
reportProgress(15, 'Persistence manager found, loading preferences');
// Load preferences to determine TTS enabled state and preferred provider
const prefs = this.persistenceManager.getAllPreferences();
if (prefs && prefs.tts) {
this.ttsEnabled = prefs.tts.enabled;
console.log(`TTS Factory: Setting initial TTS enabled state to ${this.ttsEnabled ? 'enabled' : 'disabled'} from preferences`);
}
}
// Available TTS handlers
this.handlers = {};
// Import needed modules dynamically
const [{ BrowserTTSHandler }, { KokoroHandler }, { ApiTTSHandler }] = await Promise.all([
import('./browser-tts-handler.js'),
import('./kokoro-handler.js'),
import('./api-tts-handler.js')
// Current active handler
this.activeHandler = null;
// Handler initialization status
this.initStatus = {
browser: false,
api: false,
kokoro: false
};
// TTS availability flag
this.ttsAvailable = false;
// Bind methods
this.bindMethods([
'registerHandler',
'initializeHandler',
'getHandler',
'setActiveHandler',
'getActiveHandler',
'getAvailableHandlers',
'speak',
'stop',
'pause',
'resume',
'getVoices',
'getPreference'
]);
reportProgress(20, 'TTS modules loaded');
// Create handlers
const browserHandler = new BrowserTTSHandler();
const kokoroHandler = new KokoroHandler();
const apiHandler = new ApiTTSHandler();
// Store handlers
this.handlers = {
browser: browserHandler,
kokoro: kokoroHandler,
api: apiHandler
};
// Get preferred TTS mode from options
const preferredTTSMode = this.getPreferredTTSMode();
// Initialize the preferred handler first
if (preferredTTSMode === 'browser') {
// User prefers browser TTS
await this.initializeBrowserTTS(browserHandler, reportProgress);
} else if (preferredTTSMode === 'api') {
// User prefers API TTS
await this.initializeApiTTS(apiHandler, reportProgress);
// Fallback to browser TTS if API fails
if (!apiHandler.isAvailable()) {
await this.initializeBrowserTTS(browserHandler, reportProgress);
}
} else {
// Default flow: prefer Kokoro, with browser as immediate fallback
// Initialize browser TTS immediately for a responsive experience
await this.initializeBrowserTTS(browserHandler, reportProgress);
// Then schedule Kokoro loading in the background
reportProgress(75, 'Scheduling Kokoro TTS initialization');
this.scheduleKokoroInitialization(kokoroHandler, reportProgress).then((kokoroAvailable) => {
if (kokoroAvailable) {
// Switch to Kokoro as it's the best option and set as preferred
this.ttsHandler = kokoroHandler;
this.setPreferredTTSMode('kokoro');
this.dispatchTTSReadyEvent(true, 'kokoro', kokoroHandler);
reportProgress(100, 'Kokoro TTS ready');
// Apply voice settings from preferences if available
this.applyVoiceSettingsFromPreferences();
} else if (!this.getPreferredTTSMode()) {
// If Kokoro failed and no preference was previously set,
// set browser as preferred mode
this.setPreferredTTSMode('browser');
// Add dependencies
this.dependencies = ['persistence-manager', 'localization'];
}
/**
* Initialize the module
* @returns {Promise<boolean>} - Resolves with success status
*/
async initialize() {
try {
this.reportProgress(10, "Initializing TTS factory");
// Get dependencies
const persistenceManager = this.getModule('persistence-manager');
const localization = this.getModule('localization');
if (!persistenceManager || !localization) {
console.error("TTS Factory: Required dependencies not found");
this.reportProgress(100, "TTS factory failed - missing dependencies");
return false;
}
});
}
// Apply voice settings from preferences for initial handler
this.applyVoiceSettingsFromPreferences();
// Resolve initialization even though Kokoro is still loading in background
reportProgress(80, 'TTS interface ready' +
(preferredTTSMode !== 'kokoro' ? '' : ' (Kokoro loading in background)'));
resolve(true);
} catch (error) {
console.error('Error initializing TTS Factory:', error);
// If we have any handler working, consider initialization successful
if (this.ttsHandler) {
reportProgress(100, `Using ${this.ttsHandler.getId()} TTS (fallback)`);
resolve(true);
} else {
this.dispatchTTSReadyEvent(false);
reportProgress(100, 'TTS initialization failed');
resolve(false);
}
}
});
return this.initializationPromise;
}
/**
* Apply stored voice settings from preferences
* @private
*/
applyVoiceSettingsFromPreferences() {
if (!this.ttsHandler || !this.persistenceManager) return;
const prefs = this.persistenceManager.getAllPreferences();
if (prefs && prefs.tts) {
if (prefs.tts.voice) {
console.log(`TTS Factory: Setting voice to ${prefs.tts.voice} from preferences`);
// Check if setVoice exists, otherwise try setting through voiceOptions
if (typeof this.ttsHandler.setVoice === 'function') {
this.ttsHandler.setVoice(prefs.tts.voice);
} else if (typeof this.ttsHandler.setVoiceOptions === 'function') {
this.ttsHandler.setVoiceOptions({ voice: prefs.tts.voice });
}
}
if (prefs.tts.rate !== undefined) {
console.log(`TTS Factory: Setting speech rate to ${prefs.tts.rate} from preferences`);
// Check if setSpeed exists, otherwise try setting through voiceOptions
if (typeof this.ttsHandler.setSpeed === 'function') {
this.ttsHandler.setSpeed(prefs.tts.rate);
} else if (typeof this.ttsHandler.setVoiceOptions === 'function') {
this.ttsHandler.setVoiceOptions({ rate: prefs.tts.rate });
}
}
if (prefs.tts.volume !== undefined && typeof this.ttsHandler.setVolume === 'function') {
console.log(`TTS Factory: Setting volume to ${prefs.tts.volume} from preferences`);
this.ttsHandler.setVolume(prefs.tts.volume);
}
}
}
/**
* Initialize browser TTS
* @param {BrowserTTSHandler} handler - The browser TTS handler
* @param {Function} reportProgress - Progress reporting function
* @returns {Promise<boolean>} - Resolves with availability status
*/
async initializeBrowserTTS(handler, reportProgress) {
reportProgress(30, 'Initializing browser TTS');
const browserAvailable = await handler.initialize();
if (browserAvailable) {
this.ttsHandler = handler;
this.dispatchTTSReadyEvent(true, 'browser', handler);
reportProgress(40, 'Browser TTS ready');
} else {
reportProgress(40, 'Browser TTS not available');
}
return browserAvailable;
}
/**
* Initialize API TTS
* @param {ApiTTSHandler} handler - The API TTS handler
* @param {Function} reportProgress - Progress reporting function
* @returns {Promise<boolean>} - Resolves with availability status
*/
async initializeApiTTS(handler, reportProgress) {
reportProgress(50, 'Initializing API TTS');
const apiAvailable = await handler.initialize();
if (apiAvailable) {
this.ttsHandler = handler;
this.dispatchTTSReadyEvent(true, 'api', handler);
reportProgress(70, 'API TTS ready');
}
return apiAvailable;
}
/**
* Get preferred TTS mode from storage
* @returns {string|null} - Preferred TTS mode or null if not set
*/
getPreferredTTSMode() {
// First check persistent settings if available
if (this.persistenceManager) {
const prefs = this.persistenceManager.getAllPreferences();
if (prefs && prefs.tts && prefs.tts.provider) {
console.log(`TTS Factory: Using preferred TTS mode '${prefs.tts.provider}' from persistence manager`);
return prefs.tts.provider;
}
}
// Fallback to localStorage if persistence manager is not available
try {
const savedMode = localStorage.getItem('preferred-tts-mode');
if (savedMode) {
console.log(`TTS Factory: Using preferred TTS mode '${savedMode}' from localStorage`);
return savedMode;
}
} catch (e) {
console.warn('Could not read TTS preference from localStorage');
}
// Default to Kokoro if no preference is found
return "kokoro";
}
/**
* Set preferred TTS mode in storage
* @param {string} mode - The TTS mode to save as preferred
*/
setPreferredTTSMode(mode) {
// Update in persistence manager if available
if (this.persistenceManager) {
this.persistenceManager.updatePreference('tts', 'provider', mode);
console.log(`TTS Factory: Saved preferred TTS mode '${mode}' to persistence manager`);
}
// Also save to localStorage as backup
try {
localStorage.setItem('preferred-tts-mode', mode);
} catch (e) {
console.warn('Could not save TTS preference to localStorage');
}
}
/**
* Schedule Kokoro initialization during idle time
* @param {Object} kokoroHandler - The Kokoro handler instance
* @param {Function} reportProgress - Progress reporting function
* @returns {Promise<boolean>} - Resolves with success status
*/
scheduleKokoroInitialization(kokoroHandler, reportProgress) {
// Immediately dispatch the loading started event so tts-player can catch it
window.dispatchEvent(new CustomEvent('kokoro-loading-started'));
return new Promise((resolve) => {
// Create the initialization function
const startKokoroInit = async () => {
try {
// Initialize Kokoro with progress callback
const kokoroAvailable = await kokoroHandler.initialize((percent, message) => {
// Scale progress to 80-95% range for the TTS module's overall progress
const scaledProgress = 80 + Math.floor(percent * 0.15);
reportProgress(scaledProgress, message || `Loading Kokoro TTS: ${percent}%`);
});
// Register available handlers
this.registerHandler('browser', new BrowserTTSHandler());
this.registerHandler('api', new ApiTTSHandler());
this.registerHandler('kokoro', new KokoroHandler());
this.reportProgress(30, "Registered TTS handlers");
// Get user preferences
const ttsEnabled = this.getPreference('tts', 'enabled', false);
const preferredProvider = this.getPreference('tts', 'provider', 'browser');
// Initialize handlers based on preferences
let initSuccess = false;
if (ttsEnabled) {
// Try to initialize preferred handler first
this.reportProgress(50, `Initializing preferred TTS handler: ${preferredProvider}`);
initSuccess = await this.initializeHandler(preferredProvider);
// Mark completion
if (kokoroAvailable) {
reportProgress(95, "Kokoro TTS initialized successfully");
if (initSuccess) {
this.setActiveHandler(preferredProvider);
} else {
reportProgress(95, "Kokoro TTS unavailable - using fallback");
// If preferred handler failed, try alternatives based on priority: Kokoro -> Browser -> None
console.warn(`Failed to initialize preferred TTS handler: ${preferredProvider}, trying alternatives`);
// Try Kokoro TTS as fallback if not already tried
if (preferredProvider !== 'kokoro') {
this.reportProgress(60, "Trying Kokoro TTS as fallback");
initSuccess = await this.initializeHandler('kokoro');
if (initSuccess) {
this.setActiveHandler('kokoro');
// Update preference to Kokoro since it worked
this.getModule('persistence-manager').updatePreference('tts', 'provider', 'kokoro');
}
}
// If Kokoro TTS failed, try Browser TTS
if (!initSuccess && preferredProvider !== 'browser') {
this.reportProgress(70, "Trying Browser TTS as fallback");
initSuccess = await this.initializeHandler('browser');
if (initSuccess) {
this.setActiveHandler('browser');
// Update preference to browser since it worked
this.getModule('persistence-manager').updatePreference('tts', 'provider', 'browser');
}
}
// Note: API TTS is not used as a fallback as it requires manual configuration
}
} else {
// Even if TTS is disabled, initialize handlers in the background
// so they're ready if the user enables TTS later
this.reportProgress(50, "TTS disabled, initializing handlers in background");
// Always dispatch event to indicate completion status
window.dispatchEvent(new CustomEvent('kokoro-loading-complete', {
detail: { success: kokoroAvailable }
}));
// Initialize Kokoro and Browser handlers in parallel (not API as it requires configuration)
const initPromises = [
this.initializeHandler('kokoro'),
this.initializeHandler('browser')
];
resolve(kokoroAvailable);
} catch (error) {
console.error('Error initializing Kokoro:', error);
reportProgress(95, 'Kokoro TTS failed to initialize - using fallback');
// Wait for all handlers to initialize
await Promise.allSettled(initPromises);
// Dispatch completion event with error information
window.dispatchEvent(new CustomEvent('kokoro-loading-complete', {
detail: { success: false, error: error.message }
}));
resolve(false);
// Check if any handler initialized successfully
initSuccess = this.initStatus.kokoro || this.initStatus.browser;
}
};
// Add timeout protection with a reasonable timeout (30 seconds for resource-intensive operations)
const timeoutId = setTimeout(() => {
reportProgress(95, 'Kokoro initialization timed out - using fallback');
window.dispatchEvent(new CustomEvent('kokoro-loading-complete', {
detail: { success: false, error: "Timeout" }
// Set TTS availability flag and dispatch event
this.ttsAvailable = initSuccess;
// Dispatch event to notify UI about TTS availability
document.dispatchEvent(new CustomEvent('tts:availability', {
detail: { available: this.ttsAvailable }
}));
resolve(false);
}, 30000); // Increased timeout to 30 seconds since model loading is resource intensive
// Use requestIdleCallback to start initialization during idle time
if (window.requestIdleCallback) {
reportProgress(75, 'Scheduling Kokoro TTS for background loading');
window.requestIdleCallback(() => {
startKokoroInit().then(() => clearTimeout(timeoutId));
}, { timeout: 10000 });
} else {
reportProgress(75, 'Background loading not available, loading Kokoro normally');
this.reportProgress(100, initSuccess ? "TTS factory ready" : "TTS factory ready (no handlers available)");
// Use a microtask to avoid blocking the UI thread
Promise.resolve().then(() => startKokoroInit().then(() => clearTimeout(timeoutId)));
// Always return true since TTS is optional for the application
return true;
} catch (error) {
console.error("Error initializing TTS factory:", error);
this.reportProgress(100, "TTS factory failed");
// Set TTS availability to false and dispatch event
this.ttsAvailable = false;
document.dispatchEvent(new CustomEvent('tts:availability', {
detail: { available: false }
}));
// Still return true since TTS is optional
return true;
}
});
}
/**
* Dispatch a custom event when TTS is ready
* @param {boolean} available - Whether TTS is available
* @param {string} type - The type of TTS
* @param {Object} handler - The TTS handler object
*/
dispatchTTSReadyEvent(available, type = null, handler = null) {
const event = new CustomEvent('tts-ready', {
detail: {
available,
type,
handler,
enabled: this.ttsEnabled
}
});
window.dispatchEvent(event);
}
/**
* Get information about the active TTS system
* @returns {Object} - TTS system info
*/
getActiveTTSInfo() {
if (!this.ttsHandler) {
return { available: false, type: 'none', name: 'None' };
}
const id = this.ttsHandler.getId();
const name = {
'browser': 'Browser TTS',
'kokoro': 'Kokoro Neural TTS',
'api': 'ElevenLabs API TTS'
}[id] || 'Unknown TTS';
return {
available: true,
type: id,
name: name
};
}
/**
* Switch to a specific TTS handler
* @param {string} type - The handler ID to use
* @returns {boolean} - Success status
*/
switchTTS(type) {
if (!this.handlers[type] || !this.handlers[type].isAvailable()) {
return false;
}
this.ttsHandler = this.handlers[type];
this.dispatchTTSReadyEvent(true, type, this.ttsHandler);
// Update preferred TTS mode
this.setPreferredTTSMode(type);
return true;
}
/**
* Speak text using the active TTS handler
* @param {string} text - Text to speak
* @param {Function} callback - Called when speech completes
* @returns {boolean} - True if speech started successfully
*/
speak(text, callback = null) {
if (!this.ttsEnabled || !this.ttsHandler) {
console.warn("TTSFactory: No active TTS handler available or TTS disabled");
if (callback) callback("No TTS handler");
return false;
/**
* Register a TTS handler
* @param {string} id - Handler ID
* @param {Object} handler - TTS handler instance
*/
registerHandler(id, handler) {
if (!id || !handler) return;
this.handlers[id] = handler;
}
const handlerType = this.ttsHandler.getId();
console.log(`TTSFactory: Using ${handlerType} handler to speak "${text}"`);
try {
this.ttsHandler.speak(text, (result) => {
console.log(`TTSFactory: Speech completed using ${handlerType}`, result);
if (callback) callback(result);
});
return true;
} catch (error) {
console.error('Error speaking:', error);
if (callback) callback(error);
return false;
}
}
/**
* Stop any ongoing speech
*/
stop() {
if (this.ttsHandler) {
this.ttsHandler.stop();
}
}
/**
* Set voice options for the active handler
* @param {Object} options - Voice options
*/
setVoiceOptions(options = {}) {
if (this.ttsHandler && typeof this.ttsHandler.setVoiceOptions === 'function') {
this.ttsHandler.setVoiceOptions(options);
// Save settings to persistence manager if available
if (this.persistenceManager) {
if (options.voice !== undefined) {
this.persistenceManager.updatePreference('tts', 'voice', options.voice, false);
/**
* Initialize a specific TTS handler
* @param {string} id - Handler ID
* @returns {Promise<boolean>} - Success status
*/
async initializeHandler(id) {
if (!id || !this.handlers[id]) {
console.error(`TTS Factory: Handler '${id}' not found`);
return false;
}
if (options.rate !== undefined) {
this.persistenceManager.updatePreference('tts', 'rate', options.rate, false);
try {
this.reportProgress(0, `Initializing ${id} TTS handler`);
// Initialize the handler
const success = await this.handlers[id].initialize(
(progress, message) => {
this.reportProgress(progress, message);
}
);
// Update initialization status
this.initStatus[id] = success;
if (success) {
console.log(`TTS Factory: Successfully initialized ${id} TTS handler`);
} else {
console.error(`TTS Factory: Failed to initialize ${id} TTS handler`);
}
return success;
} catch (error) {
console.error(`TTS Factory: Error initializing ${id} TTS handler:`, error);
this.initStatus[id] = false;
return false;
}
if (options.volume !== undefined) {
this.persistenceManager.updatePreference('tts', 'volume', options.volume, false);
}
/**
* Get a TTS handler by ID
* @param {string} id - Handler ID
* @returns {Object|null} - TTS handler instance or null if not found
*/
getHandler(id) {
if (!id || !this.handlers[id]) return null;
return this.handlers[id];
}
/**
* Set the active TTS handler
* @param {string} id - Handler ID
* @returns {boolean} - Success status
*/
setActiveHandler(id) {
if (!id || !this.handlers[id] || !this.initStatus[id]) {
console.warn(`Cannot set active handler to ${id}: handler not found or not initialized`);
return false;
}
// Save all changes at once
this.persistenceManager.savePreferences();
}
}
}
/**
* Toggle TTS on/off
* @returns {boolean} - New TTS enabled state
*/
toggle() {
this.ttsEnabled = !this.ttsEnabled;
console.log(`TTS Factory: Toggling TTS to ${this.ttsEnabled ? 'enabled' : 'disabled'}`);
if (!this.ttsEnabled && this.ttsHandler) {
this.ttsHandler.stop();
// Stop current handler if active
if (this.activeHandler) {
this.handlers[this.activeHandler].stop();
}
// Set new active handler
this.activeHandler = id;
// Update preference
this.getModule('persistence-manager').updatePreference('tts', 'provider', id);
// Dispatch event
this.dispatchEvent('tts-handler-changed', {
handler: id
});
return true;
}
// Save the new state to preferences if persistence manager is available
if (this.persistenceManager) {
this.persistenceManager.updatePreference('tts', 'enabled', this.ttsEnabled);
console.log(`TTS Factory: Saved enabled state (${this.ttsEnabled}) to persistence manager`);
/**
* Get the active TTS handler
* @returns {Object|null} - Active TTS handler instance or null if none active
*/
getActiveHandler() {
if (!this.activeHandler) return null;
return this.handlers[this.activeHandler];
}
return this.ttsEnabled;
}
/**
* Check if TTS is enabled
* @returns {boolean} - Current TTS enabled state
*/
isEnabled() {
return this.ttsEnabled;
}
/**
* Get available handlers
* @returns {Object} - Map of available handlers
*/
getAvailableHandlers() {
const available = {};
Object.entries(this.handlers).forEach(([id, handler]) => {
if (handler.isAvailable()) {
available[id] = handler;
}
});
return available;
}
/**
* Get available voices from active handler
* @returns {Promise<Array>} - Array of available voices
*/
async getVoices() {
if (!this.ttsHandler || typeof this.ttsHandler.getVoices !== 'function') {
return [];
/**
* Get all available TTS handlers
* @returns {Object} - Map of handler IDs to initialization status
*/
getAvailableHandlers() {
const available = {};
for (const id in this.handlers) {
available[id] = this.initStatus[id];
}
return available;
}
try {
return await this.ttsHandler.getVoices();
} catch (error) {
console.error('Error getting voices:', error);
return [];
/**
* Speak text using the active TTS handler
* @param {string} text - Text to speak
* @param {Object} options - TTS options
* @returns {Promise<boolean>} - Success status
*/
async speak(text, options = {}) {
if (!this.activeHandler) {
console.warn("No active TTS handler");
return false;
}
try {
return await this.handlers[this.activeHandler].speak(text, options);
} catch (error) {
console.error("Error speaking text:", error);
return false;
}
}
/**
* Stop speaking
* @returns {boolean} - Success status
*/
stop() {
if (!this.activeHandler) return false;
try {
return this.handlers[this.activeHandler].stop();
} catch (error) {
console.error("Error stopping TTS:", error);
return false;
}
}
/**
* Pause speaking
* @returns {boolean} - Success status
*/
pause() {
if (!this.activeHandler) return false;
try {
return this.handlers[this.activeHandler].pause();
} catch (error) {
console.error("Error pausing TTS:", error);
return false;
}
}
/**
* Resume speaking
* @returns {boolean} - Success status
*/
resume() {
if (!this.activeHandler) return false;
try {
return this.handlers[this.activeHandler].resume();
} catch (error) {
console.error("Error resuming TTS:", error);
return false;
}
}
/**
* Get available voices for the active TTS handler
* @returns {Array} - Array of voice objects
*/
getVoices() {
if (!this.activeHandler) return [];
try {
return this.handlers[this.activeHandler].getVoices();
} catch (error) {
console.error("Error getting voices:", error);
return [];
}
}
/**
* Get a preference from the persistence manager
* @param {string} category - Preference category
* @param {string} key - Preference key
* @param {*} defaultValue - Default value if preference doesn't exist
* @returns {*} - Preference value
*/
getPreference(category, key, defaultValue) {
const persistenceManager = this.getModule('persistence-manager');
if (persistenceManager) {
return persistenceManager.getPreference(category, key, defaultValue);
}
return defaultValue;
}
/**
* Clean up when module is disposed
*/
dispose() {
// Stop any active TTS
if (this.activeHandler) {
this.handlers[this.activeHandler].stop();
}
// Dispose all handlers
for (const id in this.handlers) {
if (this.handlers[id].dispose) {
this.handlers[id].dispose();
}
}
// Clear handlers
this.handlers = {};
this.activeHandler = null;
}
}
}
// Create singleton instance
const ttsFactory = new TTSFactory();
// Create the singleton instance
const TTSFactory = new TTSFactoryModule();
// Export the factory
export { ttsFactory };
// Register with the module registry
moduleRegistry.register(TTSFactory);
// Keep global reference
window.ttsFactory = ttsFactory;
// Export the module
export { TTSFactory };