Refactored modules and updated loader.
This commit is contained in:
@@ -8,6 +8,9 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
|
||||
constructor(id, name) {
|
||||
super(id, name);
|
||||
|
||||
// Declare proper dependencies according to architecture principles
|
||||
this.dependencies = ['persistence-manager', 'localization'];
|
||||
|
||||
// Basic voice options
|
||||
this.voiceOptions = {
|
||||
speed: 1.0,
|
||||
@@ -86,8 +89,13 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
|
||||
// Check if we have an API key
|
||||
this.isReady = !!this.apiKey;
|
||||
|
||||
// Always mark as available for UI configuration purposes
|
||||
// (even if not ready due to missing API key)
|
||||
if (!this.isReady) {
|
||||
console.error(`${this.name}: Missing API key, initialization failed`);
|
||||
this.reportProgress(100, `${this.name} initialization failed - missing API key`);
|
||||
return false; // Properly report failure when API key is missing
|
||||
}
|
||||
|
||||
// Only mark as complete if we have an API key
|
||||
this.reportProgress(100, `${this.name} initialization complete`);
|
||||
|
||||
return true;
|
||||
@@ -111,6 +119,7 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
|
||||
const localization = this.getModule('localization');
|
||||
|
||||
if (!persistenceManager || !localization) {
|
||||
console.error(`${this.name}: Required dependencies not found`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -120,16 +129,13 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
|
||||
// Get current locale
|
||||
const currentLocale = localization.getLocale();
|
||||
|
||||
// If we have a preferred voice and available voices, use it
|
||||
if (preferredVoiceId && this.voices && this.voices.length > 0) {
|
||||
const voice = this.voices.find(v => v.id === preferredVoiceId);
|
||||
if (voice) {
|
||||
this.voiceOptions.voice = voice;
|
||||
return true;
|
||||
}
|
||||
// If we have a preferred voice ID, use it
|
||||
if (preferredVoiceId && this.voices.some(v => v.id === preferredVoiceId)) {
|
||||
this.voiceOptions.voice = this.voices.find(v => v.id === preferredVoiceId);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise select a voice based on locale
|
||||
// Otherwise, select voice based on locale
|
||||
if (currentLocale) {
|
||||
return this.selectVoiceForLocale(currentLocale);
|
||||
}
|
||||
@@ -163,7 +169,7 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
|
||||
* @returns {boolean} - Success status
|
||||
*/
|
||||
selectDefaultVoice() {
|
||||
if (this.voices && this.voices.length > 0) {
|
||||
if (this.voices.length > 0) {
|
||||
this.voiceOptions.voice = this.voices[0];
|
||||
return true;
|
||||
}
|
||||
@@ -188,50 +194,42 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
|
||||
*/
|
||||
speakPreloaded(preloadData, callback = null) {
|
||||
if (!preloadData || !preloadData.audioData) {
|
||||
if (callback) {
|
||||
callback({ success: false, reason: 'invalid_data' });
|
||||
}
|
||||
console.error(`${this.name}: Invalid preloaded data`);
|
||||
if (callback) callback({ success: false, reason: 'invalid_data' });
|
||||
return false;
|
||||
}
|
||||
|
||||
// Stop any ongoing speech
|
||||
this.stop();
|
||||
|
||||
// Create audio blob
|
||||
// Create an audio element to play the audio
|
||||
const audioBlob = new Blob([preloadData.audioData], { type: 'audio/mp3' });
|
||||
const audioUrl = URL.createObjectURL(audioBlob);
|
||||
|
||||
// Create audio element
|
||||
const audio = new Audio(audioUrl);
|
||||
|
||||
// Set up state
|
||||
this.isSpeaking = true;
|
||||
this.currentAudio = audio;
|
||||
|
||||
// Set up event handlers
|
||||
audio.onended = () => {
|
||||
this.isSpeaking = false;
|
||||
if (callback) {
|
||||
callback({ success: true });
|
||||
}
|
||||
this.currentAudio = null;
|
||||
URL.revokeObjectURL(audioUrl);
|
||||
|
||||
if (callback) callback({ success: true });
|
||||
};
|
||||
|
||||
audio.onerror = (error) => {
|
||||
console.error(`${this.name}: Audio playback error:`, error);
|
||||
this.isSpeaking = false;
|
||||
if (callback) {
|
||||
callback({ success: false, reason: 'playback_error', error });
|
||||
}
|
||||
this.currentAudio = null;
|
||||
URL.revokeObjectURL(audioUrl);
|
||||
|
||||
if (callback) callback({ success: false, reason: 'playback_error', error });
|
||||
};
|
||||
|
||||
// Start playback
|
||||
this.currentAudio = audio;
|
||||
this.isSpeaking = true;
|
||||
|
||||
// Handle play error
|
||||
// Play the audio
|
||||
audio.play().catch(error => {
|
||||
this.isSpeaking = false;
|
||||
if (callback) {
|
||||
callback({ success: false, reason: 'playback_error', error });
|
||||
}
|
||||
URL.revokeObjectURL(audioUrl);
|
||||
console.error(`${this.name}: Failed to play audio:`, error);
|
||||
if (callback) callback({ success: false, reason: 'playback_error', error });
|
||||
});
|
||||
|
||||
return true;
|
||||
@@ -244,17 +242,21 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
|
||||
stop() {
|
||||
if (this.currentAudio) {
|
||||
try {
|
||||
// Stop current audio
|
||||
this.currentAudio.pause();
|
||||
this.currentAudio.currentTime = 0;
|
||||
this.currentAudio = null;
|
||||
|
||||
// Clean up
|
||||
this.isSpeaking = false;
|
||||
this.currentAudio = null;
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`${this.name}: Error stopping speech:`, error);
|
||||
console.error(`${this.name}: Error stopping audio:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return true; // Already stopped
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -351,6 +353,7 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
|
||||
handleApiKeyChanged(event) {
|
||||
if (event && event.detail && event.detail.provider === this.id) {
|
||||
const newKey = event.detail.key || '';
|
||||
const oldKey = this.apiKey;
|
||||
|
||||
// Security check - never use a URL as an API key
|
||||
if (newKey && newKey.startsWith('http')) {
|
||||
@@ -368,7 +371,33 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
|
||||
}
|
||||
|
||||
// Update ready state
|
||||
const wasReady = this.isReady;
|
||||
this.isReady = !!this.apiKey;
|
||||
|
||||
// If state changed (now ready/not-ready), notify the TTS factory
|
||||
if (wasReady !== this.isReady) {
|
||||
console.log(`${this.name}: TTS ready state changed to ${this.isReady ? 'ready' : 'not ready'} after API key change`);
|
||||
|
||||
// Find and notify the TTS factory
|
||||
const ttsFactory = this.getModule('tts-factory');
|
||||
if (ttsFactory) {
|
||||
// If we have a key now (and didn't before), try initializing voices
|
||||
if (this.isReady && !wasReady) {
|
||||
// Reload voices with the new API key
|
||||
this.loadVoices().then(() => {
|
||||
// Then set up voice from preferences
|
||||
this.setupVoiceFromPreferences().then(() => {
|
||||
console.log(`${this.name}: Successfully initialized with new API key`);
|
||||
// Notify the factory of our readiness change
|
||||
ttsFactory.updateTTSAvailability();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Just update the availability
|
||||
ttsFactory.updateTTSAvailability();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,6 +407,7 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
|
||||
*/
|
||||
handleApiUrlChanged(event) {
|
||||
if (event && event.detail && event.detail.provider === this.id) {
|
||||
const oldUrl = this.apiBaseUrl;
|
||||
const newUrl = event.detail.url || this.getDefaultApiBaseUrl();
|
||||
|
||||
// Update API URL
|
||||
@@ -388,6 +418,25 @@ export class ApiTTSModuleBase extends TTSHandlerModule {
|
||||
if (persistenceManager) {
|
||||
persistenceManager.updatePreference('tts', `${this.id}_api_url`, newUrl);
|
||||
}
|
||||
|
||||
// Only reinitialize if the URL actually changed and we have an API key
|
||||
if (oldUrl !== newUrl && this.isReady) {
|
||||
console.log(`${this.name}: API URL changed, reinitializing`);
|
||||
|
||||
// Reload voices with the new API URL if we're ready
|
||||
this.loadVoices().then(() => {
|
||||
// Then set up voice from preferences
|
||||
this.setupVoiceFromPreferences().then(() => {
|
||||
console.log(`${this.name}: Successfully reinitialized with new API URL`);
|
||||
|
||||
// Notify the TTS factory
|
||||
const ttsFactory = this.getModule('tts-factory');
|
||||
if (ttsFactory) {
|
||||
ttsFactory.updateTTSAvailability();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user