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
JavaScript
/**
* 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;