172 lines
6.0 KiB
JavaScript
172 lines
6.0 KiB
JavaScript
/**
|
|
* TTS Factory for AI Interactive Fiction
|
|
* Attempts to use Kokoro TTS first, then falls back to browser TTS if needed
|
|
*/
|
|
import { kokoroHandler } from './kokoro-handler.js';
|
|
import { browserTtsHandler } from './tts-handler.js';
|
|
|
|
export class TTSFactory {
|
|
constructor() {
|
|
this.activeTTSHandler = null;
|
|
this.initializationAttempted = false;
|
|
this.usingKokoro = false;
|
|
this.initializationPromise = null; // Promise for the factory initialization
|
|
|
|
// Initialize on DOM ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', () => this.initialize());
|
|
} else {
|
|
// Use requestAnimationFrame to ensure scripts are parsed
|
|
requestAnimationFrame(() => this.initialize());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize available TTS handlers
|
|
*/
|
|
async initialize() {
|
|
// Prevent multiple initializations
|
|
if (this.initializationAttempted) return this.initializationPromise;
|
|
this.initializationAttempted = true;
|
|
|
|
console.log('Initializing TTS Factory...');
|
|
|
|
this.initializationPromise = new Promise(async (resolve) => {
|
|
let kokoroInitialized = false;
|
|
// Try to initialize Kokoro first (preferred option)
|
|
try {
|
|
console.log('Attempting to initialize Kokoro TTS...');
|
|
|
|
// --- Increase Timeout for Kokoro Initialization ---
|
|
// Wait for KokoroHandler's internal initialization promise
|
|
// Use Promise.race to add a longer timeout specifically for Kokoro init
|
|
const kokoroTimeoutPromise = new Promise((_, reject) =>
|
|
setTimeout(() => reject(new Error('Kokoro initialization timed out in factory')), 60000) // 60 seconds timeout
|
|
);
|
|
|
|
try {
|
|
kokoroInitialized = await Promise.race([
|
|
kokoroHandler.initializationPromise,
|
|
kokoroTimeoutPromise
|
|
]);
|
|
} catch (timeoutError) {
|
|
console.error(timeoutError.message); // Log the timeout error
|
|
kokoroInitialized = false;
|
|
}
|
|
// --- End Increase Timeout ---
|
|
|
|
if (kokoroInitialized) {
|
|
console.log('Kokoro Handler reported successful initialization.');
|
|
} else {
|
|
console.warn('Kokoro Handler reported failed or timed out initialization.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error initializing Kokoro Handler:', error);
|
|
kokoroInitialized = false; // Ensure it's marked as failed
|
|
}
|
|
|
|
// Decide which handler to use based on Kokoro's success
|
|
this.selectActiveHandler(kokoroInitialized);
|
|
resolve(); // Resolve the factory's promise
|
|
});
|
|
|
|
return this.initializationPromise;
|
|
}
|
|
|
|
/**
|
|
* Select which TTS handler to use
|
|
* @param {boolean} kokoroInitialized - Whether Kokoro initialization succeeded
|
|
*/
|
|
selectActiveHandler(kokoroInitialized) {
|
|
// First choice: Kokoro if it's available and initialized successfully
|
|
if (kokoroInitialized && kokoroHandler.kokoroReady) {
|
|
console.log('Using Kokoro TTS as primary TTS system');
|
|
this.activeTTSHandler = kokoroHandler;
|
|
this.usingKokoro = true;
|
|
}
|
|
// Fallback to browser TTS if available
|
|
else if (browserTtsHandler) {
|
|
console.log('Falling back to browser TTS.');
|
|
this.activeTTSHandler = browserTtsHandler;
|
|
this.usingKokoro = false;
|
|
}
|
|
// No TTS available
|
|
else {
|
|
console.error('No TTS system available.');
|
|
this.activeTTSHandler = null;
|
|
this.usingKokoro = false;
|
|
}
|
|
|
|
// Expose the active handler as the global ttsHandler for compatibility
|
|
window.ttsHandler = this.activeTTSHandler;
|
|
|
|
// Log the active TTS system
|
|
if (this.usingKokoro) {
|
|
console.log('TTS Factory initialized with Kokoro TTS');
|
|
} else if (this.activeTTSHandler) {
|
|
console.log('TTS Factory initialized with browser TTS');
|
|
} else {
|
|
console.log('TTS Factory initialized with no available TTS');
|
|
}
|
|
|
|
// Dispatch an event to notify the UI that TTS is ready (or not)
|
|
const ttsReadyEvent = new CustomEvent('tts-ready', {
|
|
detail: {
|
|
available: !!this.activeTTSHandler,
|
|
type: this.usingKokoro ? 'kokoro' : (this.activeTTSHandler ? 'browser' : 'none'),
|
|
handler: this.activeTTSHandler
|
|
}
|
|
});
|
|
window.dispatchEvent(ttsReadyEvent);
|
|
}
|
|
|
|
/**
|
|
* Get info about the active TTS system
|
|
*/
|
|
getActiveTTSInfo() {
|
|
if (!this.activeTTSHandler) {
|
|
return { available: false, type: 'none', name: 'None' };
|
|
}
|
|
|
|
return {
|
|
available: true,
|
|
type: this.usingKokoro ? 'kokoro' : 'browser',
|
|
name: this.usingKokoro ? 'Kokoro TTS' : 'Browser TTS'
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Force switching to a specific TTS system
|
|
* @param {string} type - Either 'kokoro' or 'browser'
|
|
*/
|
|
switchTTS(type) {
|
|
if (type === 'kokoro' && kokoroHandler && kokoroHandler.kokoroReady) {
|
|
this.activeTTSHandler = kokoroHandler;
|
|
this.usingKokoro = true;
|
|
window.ttsHandler = this.activeTTSHandler;
|
|
console.log('Switched to Kokoro TTS');
|
|
// Dispatch event on switch
|
|
const ttsReadyEvent = new CustomEvent('tts-ready', { detail: { available: true, type: 'kokoro', handler: this.activeTTSHandler } });
|
|
window.dispatchEvent(ttsReadyEvent);
|
|
return true;
|
|
} else if (type === 'browser' && browserTtsHandler) {
|
|
this.activeTTSHandler = browserTtsHandler;
|
|
this.usingKokoro = false;
|
|
window.ttsHandler = this.activeTTSHandler;
|
|
console.log('Switched to browser TTS');
|
|
// Dispatch event on switch
|
|
const ttsReadyEvent = new CustomEvent('tts-ready', { detail: { available: true, type: 'browser', handler: this.activeTTSHandler } });
|
|
window.dispatchEvent(ttsReadyEvent);
|
|
return true;
|
|
}
|
|
|
|
console.error(`Failed to switch to ${type} TTS - not available`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Create and export singleton instance
|
|
export const ttsFactory = new TTSFactory();
|
|
|
|
// Keep a reference in window for compatibility with existing code
|
|
window.ttsFactory = ttsFactory; |