UNPKG

win-stream-audio

Version:

🎧 Stream Windows system audio to Android devices over WiFi with professional audio controls, EQ, pitch shifting, and effects

278 lines (229 loc) 9.62 kB
/** * AudioPilot - Connection Manager Module * Handles WebSocket connections and data flow */ class ConnectionManager { constructor(audioProcessor, logger) { this.audioProcessor = audioProcessor; this.logger = logger; this.websocket = null; this.heartbeatInterval = null; this.isConnecting = false; this.manualDisconnect = false; this.detectedFormat = 'audio/webm'; this.bufferSize = 5; // Connection state callbacks this.onConnectionChange = null; this.onAudioStateChange = null; this.onBufferUpdate = null; } // Connect to mission control connectToMissionControl() { if (this.isConnecting) return Promise.reject('Already connecting'); return new Promise((resolve, reject) => { this.isConnecting = true; this.manualDisconnect = false; this.logger.log('🔗 Establishing Link to Mission Control...'); try { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = protocol + '//' + window.location.host; this.websocket = new WebSocket(wsUrl); this.websocket.onopen = () => { this.isConnecting = false; this.logger.log('✅ Link Established - AudioPilot Online'); // Reset audio state this.audioProcessor.audioBuffer = []; this.audioProcessor.audioQueue = []; this.audioProcessor.isPlaying = false; this.audioProcessor.consecutiveErrors = 0; this.detectedFormat = 'audio/webm'; // Send role identification this.websocket.send(JSON.stringify({ type: 'role', role: 'receiver' })); // Set up message handlers this.websocket.onmessage = (event) => this.handleMissionData(event); this.websocket.onclose = () => this.handleLinkLoss(); this.websocket.onerror = () => this.handleLinkError(); // Start heartbeat this.startHeartbeat(); // Initialize audio context this.audioProcessor.initializeAudioContext(); // Notify connection change if (this.onConnectionChange) { this.onConnectionChange(true); } resolve(); }; this.websocket.onclose = () => this.handleLinkLoss(); this.websocket.onerror = (error) => { this.isConnecting = false; reject(error); }; } catch (error) { this.isConnecting = false; this.logger.log('❌ Link Establishment Failed: ' + error.message); reject(error); } }); } // Disconnect from mission control disconnectFromMissionControl() { this.logger.log('🔌 Terminating Mission Control Link...'); this.manualDisconnect = true; // Stop heartbeat if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } // Close connection if (this.websocket) { this.websocket.onclose = null; this.websocket.onerror = null; this.websocket.onmessage = null; this.websocket.close(); this.websocket = null; } // Notify connection change if (this.onConnectionChange) { this.onConnectionChange(false); } this.logger.log('✅ Link Terminated - AudioPilot Offline'); } // Reconnect to mission control reconnectToMissionControl() { if (this.isConnecting) return Promise.reject('Already connecting'); this.logger.log('🔄 Manual Reconnect Initiated'); this.manualDisconnect = false; return this.connectToMissionControl(); } // Handle incoming mission data handleMissionData(event) { if (typeof event.data === 'string') { try { const message = JSON.parse(event.data); if (message.type === 'raw-audio') { this.detectedFormat = 'raw-audio'; if (this.onAudioStateChange) { this.onAudioStateChange(true); } this.logger.log(`🎵 Audio Format: ${message.sampleRate}Hz, ${message.channels}ch`); } } catch (e) { // Ignore parsing errors } } else { // Handle audio data this.audioProcessor.audioBuffer.push(event.data); if (this.onBufferUpdate) { this.onBufferUpdate(this.audioProcessor.audioBuffer.length, this.bufferSize); } // Start playing when buffer is full if (this.audioProcessor.audioBuffer.length >= this.bufferSize && !this.audioProcessor.isPlaying) { this.logger.log(`🎵 Buffer Full - Initiating Audio Playback`); this.playBufferedAudio(); } else if (this.audioProcessor.isPlaying && this.audioProcessor.audioBuffer.length >= this.bufferSize) { this.queueNextBatch(); } } } // Handle link loss handleLinkLoss() { if (this.websocket === null) return; // Manual disconnect this.isConnecting = false; this.logger.log('❌ Mission Control Link Lost'); // Notify connection change if (this.onConnectionChange) { this.onConnectionChange(false); } if (this.onAudioStateChange) { this.onAudioStateChange(false); } // Clear audio state this.audioProcessor.audioBuffer = []; this.audioProcessor.audioQueue = []; this.audioProcessor.isPlaying = false; if (this.onBufferUpdate) { this.onBufferUpdate(0, 0); } // Stop heartbeat if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } // Auto-reconnect if not manual disconnect if (!this.manualDisconnect) { this.logger.log('🔄 Auto-Reconnect in 3 seconds...'); setTimeout(() => { if (!this.isConnecting && !this.manualDisconnect) { this.connectToMissionControl().catch(() => { // Retry failed, will try again on next cycle }); } }, 3000); } } // Handle link error handleLinkError() { this.isConnecting = false; this.logger.log('❌ Mission Control Link Error'); } // Start heartbeat startHeartbeat() { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); } this.heartbeatInterval = setInterval(() => { if (this.websocket && this.websocket.readyState === WebSocket.OPEN) { this.websocket.send(JSON.stringify({ type: 'ping' })); } }, 5000); } // Play buffered audio playBufferedAudio() { if (this.audioProcessor.isPlaying) return; try { this.audioProcessor.isPlaying = true; if (this.detectedFormat === 'raw-audio') { this.audioProcessor.playRawAudio(this.audioProcessor.audioBuffer, () => { this.playNext(); }); } else { this.logger.log('❌ Unsupported Audio Format'); this.audioProcessor.isPlaying = false; } this.audioProcessor.audioBuffer = []; } catch (error) { this.logger.log('❌ Audio Playback Error: ' + error.message); this.audioProcessor.isPlaying = false; } } // Play next audio batch playNext() { this.audioProcessor.isPlaying = false; if (this.audioProcessor.audioQueue.length > 0) { const nextBatch = this.audioProcessor.audioQueue.shift(); this.audioProcessor.audioBuffer = nextBatch; this.playBufferedAudio(); } } // Queue next batch queueNextBatch() { if (this.audioProcessor.audioBuffer.length >= this.bufferSize) { this.audioProcessor.audioQueue.push([...this.audioProcessor.audioBuffer]); this.audioProcessor.audioBuffer = []; } } // Get connection status isConnected() { return this.websocket && this.websocket.readyState === WebSocket.OPEN; } // Set buffer size setBufferSize(size) { this.bufferSize = size; this.logger.log(`🔧 Buffer Size: ${size}`); } } // Export for use in other modules window.ConnectionManager = ConnectionManager;