Files
ai.interactive.fiction/public/js/tts-handler.js

163 lines
4.4 KiB
JavaScript

/**
* TTS Handler Base Class
* Abstract base class defining the interface for all TTS handlers
*/
export class TTSHandler {
constructor() {
this.voiceOptions = {};
this.isReady = false;
// Set up event dispatcher
this.eventTarget = document.createElement('div');
// Module state tracking - conform to BaseModule interface
this.state = 'PENDING';
}
/**
* Get handler ID
* @returns {string} - Handler identifier
*/
getId() {
throw new Error('getId() must be implemented by subclass');
}
/**
* Initialize the TTS handler
* @param {Function} progressCallback - Optional progress callback
* @returns {Promise<boolean>} - Resolves with success status
*/
async initialize(progressCallback = null) {
throw new Error('initialize() must be implemented by subclass');
}
/**
* Check if this TTS handler is available
* @returns {boolean} - True if handler is ready to use
*/
isAvailable() {
return this.isReady;
}
/**
* Check if voice is currently speaking
* @returns {boolean} - True if speaking
*/
isSpeaking() {
return false; // Default implementation
}
/**
* Speak text using this handler
* @param {string} text - The text to speak
* @param {Function} callback - Optional callback when speech completes
*/
speak(text, callback = null) {
throw new Error('speak() must be implemented by subclass');
}
/**
* Stop speech
*/
stop() {
throw new Error('stop() must be implemented by subclass');
}
/**
* Set voice options
* @param {Object} options - Voice options
*/
setVoiceOptions(options = {}) {
// Default implementation merges options
this.voiceOptions = { ...this.voiceOptions, ...options };
}
/**
* Get available voices
* @returns {Promise<Array>} - Resolves with array of voice objects
*/
async getVoices() {
return [];
}
/**
* Get the current module state
* @returns {string} - Current state
*/
getState() {
return this.state;
}
/**
* Change the module state
* @param {string} newState - The new state
*/
changeState(newState) {
this.state = newState;
// Dispatch state change event
this.dispatchEvent('state:changed', {
state: newState
});
}
/**
* Dispatch a custom event
* @param {string} eventName - Name of the event
* @param {Object} detail - Event details
*/
dispatchEvent(eventName, detail = {}) {
const event = new CustomEvent(eventName, {
detail: { handlerId: this.getId(), ...detail },
bubbles: true
});
this.eventTarget.dispatchEvent(event);
}
/**
* Add event listener
* @param {string} eventName - Name of the event
* @param {Function} callback - Event handler function
*/
addEventListener(eventName, callback) {
this.eventTarget.addEventListener(eventName, callback);
}
/**
* Remove event listener
* @param {string} eventName - Name of the event
* @param {Function} callback - Event handler function
*/
removeEventListener(eventName, callback) {
this.eventTarget.removeEventListener(eventName, callback);
}
/**
* Bind methods to this instance
* @param {Array<string>} methodNames - Array of method names to bind
*/
bindMethods(methodNames) {
if (!Array.isArray(methodNames)) return;
methodNames.forEach(methodName => {
if (typeof this[methodName] === 'function') {
this[methodName] = this[methodName].bind(this);
}
});
}
/**
* Get a unique identifier for the current voice configuration
* Used for caching purposes
* @returns {string} - Unique identifier for current voice
*/
getCurrentVoiceIdentifier() {
// Default implementation uses voice ID and rate/speed
const voiceId = this.voiceOptions.voice || 'default';
const rate = this.voiceOptions.rate || this.voiceOptions.speed || 1.0;
// Return a string that uniquely identifies this voice configuration
return `${voiceId}_${rate}`;
}
}