UNPKG

expo-edge-speech

Version:

Text-to-speech library for Expo using Microsoft Edge TTS service

444 lines (443 loc) 16.7 kB
"use strict"; /** * Provides centralized state management using all services and complete type definitions. * Coordinates state between Network, Storage, Voice, and Audio services while maintaining * expo-speech API compatibility and implementing thread-safe state updates. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.StateManager = exports.ApplicationState = void 0; const types_1 = require("../types"); const constants_1 = require("../constants"); // Added import const audioService_1 = require("../services/audioService"); const commonUtils_1 = require("../utils/commonUtils"); // ============================================================================= // State Management Types and Interfaces // ============================================================================= /** * Application state enumeration */ var ApplicationState; (function (ApplicationState) { ApplicationState["Idle"] = "idle"; ApplicationState["Initializing"] = "initializing"; ApplicationState["Ready"] = "ready"; ApplicationState["Synthesizing"] = "synthesizing"; ApplicationState["Playing"] = "playing"; ApplicationState["Paused"] = "paused"; ApplicationState["Error"] = "error"; ApplicationState["Cleaning"] = "cleaning"; })(ApplicationState || (exports.ApplicationState = ApplicationState = {})); // ============================================================================= // State Management Service // ============================================================================= /** * Centralized state management service for expo-edge-speech * Coordinates state between all services and provides thread-safe state updates */ class StateManager { // Service instances storageService; networkService; voiceService; audioService; // State tracking applicationState = ApplicationState.Idle; configuration; activeSessions = new Map(); stateChangeListeners = new Set(); // Thread safety stateUpdateQueue = []; isProcessingStateUpdates = false; constructor(storageService, networkService, voiceService, audioService, initialConfiguration) { this.storageService = storageService; this.networkService = networkService; this.voiceService = voiceService; this.audioService = audioService; // Initialize default configuration this.configuration = this.createDefaultConfiguration(initialConfiguration); // Set up service integration this.initializeServiceIntegration(); } // ============================================================================= // Configuration Management // ============================================================================= /** * Create default configuration using complete constants */ createDefaultConfiguration(overrides) { const defaultConfig = { audioFormat: { format: "audio-24khz-48kbitrate-mono-mp3", sampleRate: 24000, bitRate: 48000, channels: 1, }, connectionPooling: { maxConnections: constants_1.EDGE_TTS_CONFIG.connectionPoolSize, connectionTimeout: constants_1.EDGE_TTS_CONFIG.connectionTimeout, reuseConnections: false, // Assuming 'false' is the desired default, EDGE_TTS_CONFIG doesn't specify this }, wordBoundary: { enabled: true, offsetCompensation: 8750000, // 8,750,000 ticks padding }, }; return { ...defaultConfig, ...overrides }; } /** * Get current configuration */ getConfiguration() { return { ...this.configuration }; } /** * Update configuration with state change notification */ updateConfiguration(updates) { return this.enqueueStateUpdate(() => { const previousConfig = { ...this.configuration }; this.configuration = { ...this.configuration, ...updates }; this.notifyStateChange({ type: "configuration", previousState: previousConfig, newState: this.configuration, timestamp: new Date(), context: { updates }, }); }); } // ============================================================================= // Application State Management // ============================================================================= /** * Get current application state */ getApplicationState() { return this.applicationState; } /** * Update application state with notifications */ updateApplicationState(newState) { return this.enqueueStateUpdate(() => { if (this.applicationState !== newState) { const previousState = this.applicationState; this.applicationState = newState; this.notifyStateChange({ type: "application", previousState, newState, timestamp: new Date(), }); } }); } // ============================================================================= // Speech Synthesis Session Management // ============================================================================= /** * Create new synthesis session using service integration */ async createSynthesisSession(text, options) { const sessionId = (0, commonUtils_1.generateSessionId)(); const connectionId = (0, commonUtils_1.generateConnectionId)(); const session = { id: sessionId, connectionId, text, options, state: ApplicationState.Initializing, createdAt: new Date(), lastActivity: new Date(), }; // Note: Connection buffer creation is handled by ConnectionManager // to avoid duplicate buffer creation issues await this.enqueueStateUpdate(() => { this.activeSessions.set(sessionId, session); this.notifyStateChange({ type: "synthesis", previousState: null, newState: session, timestamp: new Date(), context: { action: "created" }, }); }); return session; } /** * Update synthesis session state */ async updateSynthesisSession(sessionId, updates) { await this.enqueueStateUpdate(() => { const session = this.activeSessions.get(sessionId); if (session) { const previousSession = { ...session }; const updatedSession = { ...session, ...updates, lastActivity: new Date(), }; this.activeSessions.set(sessionId, updatedSession); this.notifyStateChange({ type: "synthesis", previousState: previousSession, newState: updatedSession, timestamp: new Date(), context: { sessionId, updates }, }); } }); } /** * Remove synthesis session and cleanup */ async removeSynthesisSession(sessionId) { await this.enqueueStateUpdate(() => { const session = this.activeSessions.get(sessionId); if (session) { // Cleanup connection via storage service this.storageService.cleanupConnection(session.connectionId); this.activeSessions.delete(sessionId); this.notifyStateChange({ type: "synthesis", previousState: session, newState: null, timestamp: new Date(), context: { action: "removed", sessionId }, }); } }); } /** * Get active synthesis sessions */ getActiveSessions() { return Array.from(this.activeSessions.values()); } /** * Get specific synthesis session */ getSynthesisSession(sessionId) { return this.activeSessions.get(sessionId); } // ============================================================================= // Service Coordination // ============================================================================= /** * Initialize service integration with state tracking */ initializeServiceIntegration() { // Storage service connection state tracking (if available) if (this.storageService.onConnectionStateChange) { this.storageService.onConnectionStateChange((connectionId, state) => { this.handleConnectionStateChange(connectionId, state); }); } // Network service connection tracking (if available) if (this.networkService.onConnectionStateChange) { this.networkService.onConnectionStateChange((connectionId, state) => { this.handleConnectionStateChange(connectionId, state); }); } // Audio service playback state tracking (if available) if (this.audioService.onPlaybackStateChange) { this.audioService.onPlaybackStateChange((state, context) => { this.handleAudioStateChange(state, context); }); } } /** * Handle connection state changes from services */ handleConnectionStateChange(connectionId, state) { this.enqueueStateUpdate(() => { // Find sessions using this connection const affectedSessions = Array.from(this.activeSessions.values()).filter((session) => session.connectionId === connectionId); affectedSessions.forEach((session) => { let newSessionState; switch (state) { case types_1.ConnectionState.Connecting: newSessionState = ApplicationState.Initializing; break; case types_1.ConnectionState.Connected: newSessionState = ApplicationState.Ready; break; case types_1.ConnectionState.Synthesizing: newSessionState = ApplicationState.Synthesizing; break; case types_1.ConnectionState.Error: newSessionState = ApplicationState.Error; break; default: newSessionState = ApplicationState.Idle; } this.updateSynthesisSession(session.id, { state: newSessionState }); }); this.notifyStateChange({ type: "connection", previousState: null, newState: state, timestamp: new Date(), context: { connectionId, affectedSessions: affectedSessions.length }, }); }); } /** * Handle audio state changes from audio service */ handleAudioStateChange(state, context) { this.enqueueStateUpdate(() => { let newAppState; switch (state) { case audioService_1.AudioPlaybackState.Playing: newAppState = ApplicationState.Playing; break; case audioService_1.AudioPlaybackState.Paused: newAppState = ApplicationState.Paused; break; case audioService_1.AudioPlaybackState.Error: newAppState = ApplicationState.Error; break; case audioService_1.AudioPlaybackState.Completed: case audioService_1.AudioPlaybackState.Stopped: newAppState = ApplicationState.Idle; break; default: newAppState = this.applicationState; } if (newAppState !== this.applicationState) { this.updateApplicationState(newAppState); } this.notifyStateChange({ type: "audio", previousState: null, newState: state, timestamp: new Date(), context, }); }); } // ============================================================================= // Thread-Safe State Updates // ============================================================================= /** * Enqueue state update for thread-safe processing */ async enqueueStateUpdate(updateFn) { return new Promise((resolve, reject) => { this.stateUpdateQueue.push(() => { try { updateFn(); resolve(); } catch (error) { reject(error); } }); this.processStateUpdateQueue(); }); } /** * Process queued state updates */ async processStateUpdateQueue() { if (this.isProcessingStateUpdates || this.stateUpdateQueue.length === 0) { return; } this.isProcessingStateUpdates = true; try { while (this.stateUpdateQueue.length > 0) { const updateFn = this.stateUpdateQueue.shift(); if (updateFn) { updateFn(); } } } finally { this.isProcessingStateUpdates = false; } } // ============================================================================= // Event Listeners and Notifications // ============================================================================= /** * Add state change listener */ addStateChangeListener(listener) { this.stateChangeListeners.add(listener); } /** * Remove state change listener */ removeStateChangeListener(listener) { this.stateChangeListeners.delete(listener); } /** * Notify all listeners of state change */ notifyStateChange(event) { this.stateChangeListeners.forEach((listener) => { try { listener(event); } catch (error) { console.error("Error in state change listener:", error); } }); } // ============================================================================= // Utility Methods // ============================================================================= /** * Get comprehensive state summary */ getStateSummary() { return { application: this.applicationState, configuration: this.getConfiguration(), activeSessions: this.activeSessions.size, connections: this.storageService.getActiveConnectionCount(), memoryUsage: this.storageService.getMemoryStats?.() || {}, }; } /** * Initialize state manager */ async initialize() { await this.updateApplicationState(ApplicationState.Initializing); try { // Initialize all services await this.storageService.initialize?.(); await this.voiceService.initialize?.(); await this.audioService.initialize?.(); await this.updateApplicationState(ApplicationState.Ready); } catch (error) { await this.updateApplicationState(ApplicationState.Error); throw error; } } /** * Cleanup state manager and all services */ async cleanup() { await this.updateApplicationState(ApplicationState.Cleaning); try { // Cleanup all active sessions const sessionIds = Array.from(this.activeSessions.keys()); await Promise.all(sessionIds.map((sessionId) => this.removeSynthesisSession(sessionId))); // Cleanup services await this.storageService.cleanup?.(); await this.networkService.cleanup?.(); await this.audioService.cleanup?.(); // Clear listeners this.stateChangeListeners.clear(); await this.updateApplicationState(ApplicationState.Idle); } catch (error) { await this.updateApplicationState(ApplicationState.Error); throw error; } } } exports.StateManager = StateManager;