UNPKG

win-stream-audio

Version:

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

551 lines (462 loc) 22.6 kB
/** * AudioPilot - UI Controls Module * Handles all user interface interactions and controls */ class UIControls { constructor(audioProcessor, connectionManager, logger) { this.audioProcessor = audioProcessor; this.connectionManager = connectionManager; this.logger = logger; // Get all UI elements this.elements = this.getUIElements(); // Control states this.screenLockActive = false; this.lowLatencyMode = false; this.internetMode = false; this.debugMode = false; // Screen wake system this.noSleep = null; this.wakeLock = null; this.initializeControls(); } getUIElements() { return { // Connection controls connectBtn: document.getElementById('connectBtn'), disconnectBtn: document.getElementById('disconnectBtn'), reconnectBtn: document.getElementById('reconnectBtn'), testBtn: document.getElementById('testBtn'), // System controls screenLockBtn: document.getElementById('screenLockBtn'), lowLatencyBtn: document.getElementById('lowLatencyBtn'), internetModeBtn: document.getElementById('internetModeBtn'), audioFilterBtn: document.getElementById('audioFilterBtn'), // Debug controls debugToggle: document.getElementById('debugToggle'), clearDebugBtn: document.getElementById('clearDebugBtn'), // Status indicators connectionLight: document.getElementById('connectionLight'), audioLight: document.getElementById('audioLight'), screenLight: document.getElementById('screenLight'), bufferLevel: document.getElementById('bufferLevel'), latencyDisplay: document.getElementById('latencyDisplay'), // Volume control volumeKnob: document.getElementById('volumeKnob'), volumeValue: document.getElementById('volumeValue'), // EQ controls bassSlider: document.getElementById('bassSlider'), midSlider: document.getElementById('midSlider'), trebleSlider: document.getElementById('trebleSlider'), bassValue: document.getElementById('bassValue'), midValue: document.getElementById('midValue'), trebleValue: document.getElementById('trebleValue'), // Pitch controls pitchKnob: document.getElementById('pitchKnob'), pitchValue: document.getElementById('pitchValue'), pitchReset: document.getElementById('pitchReset'), pitchMale: document.getElementById('pitchMale'), pitchFemale: document.getElementById('pitchFemale'), pitchChild: document.getElementById('pitchChild'), pitchRobot: document.getElementById('pitchRobot'), // Effect controls reverbBtn: document.getElementById('reverbBtn'), echoBtn: document.getElementById('echoBtn'), distortionBtn: document.getElementById('distortionBtn'), chorusBtn: document.getElementById('chorusBtn'), // Screen wake screenWakeVideo: document.getElementById('screenWakeVideo') }; } initializeControls() { this.setupConnectionControls(); this.setupSystemControls(); this.setupVolumeControl(); this.setupEQControls(); this.setupPitchControls(); this.setupEffectControls(); this.setupDebugControls(); this.setupScreenWake(); this.setupConnectionCallbacks(); // Set initial states this.elements.audioFilterBtn.classList.add('active'); this.elements.audioFilterBtn.textContent = '🎛️ Filter: ON'; this.logger.log('🎛️ UI Controls Initialized'); } setupConnectionControls() { this.elements.connectBtn.onclick = () => { this.connectionManager.connectToMissionControl() .then(() => { this.elements.connectBtn.disabled = true; this.elements.disconnectBtn.disabled = false; this.elements.reconnectBtn.disabled = true; }) .catch((error) => { this.logger.log('❌ Connection failed: ' + error.message); }); }; this.elements.disconnectBtn.onclick = () => { this.connectionManager.disconnectFromMissionControl(); // Stop screen lock if (this.screenLockActive) { this.toggleScreenLock(); } this.elements.connectBtn.disabled = false; this.elements.disconnectBtn.disabled = true; this.elements.reconnectBtn.disabled = false; }; this.elements.reconnectBtn.onclick = () => { this.connectionManager.reconnectToMissionControl() .then(() => { this.elements.connectBtn.disabled = true; this.elements.disconnectBtn.disabled = false; this.elements.reconnectBtn.disabled = true; }) .catch((error) => { this.logger.log('❌ Reconnection failed: ' + error.message); }); }; this.elements.testBtn.onclick = () => { if (this.audioProcessor.playTestTone()) { this.logger.log('✅ Audio Test Complete - 440Hz Tone'); } }; } setupSystemControls() { this.elements.screenLockBtn.onclick = () => this.toggleScreenLock(); this.elements.lowLatencyBtn.onclick = () => this.toggleLowLatency(); this.elements.internetModeBtn.onclick = () => this.toggleInternetMode(); this.elements.audioFilterBtn.onclick = () => this.toggleAudioFilter(); } setupVolumeControl() { const knob = this.elements.volumeKnob; let isDragging = false; let startAngle = 0; const updateKnobRotation = () => { const rotation = (this.audioProcessor.masterVolume * 270) - 135; knob.style.transform = `rotate(${rotation}deg)`; this.elements.volumeValue.textContent = `${Math.round(this.audioProcessor.masterVolume * 100)}%`; }; knob.addEventListener('mousedown', startDrag); knob.addEventListener('touchstart', startDrag); function startDrag(e) { isDragging = true; const rect = knob.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; const clientX = e.clientX || e.touches[0].clientX; const clientY = e.clientY || e.touches[0].clientY; startAngle = Math.atan2(clientY - centerY, clientX - centerX); document.addEventListener('mousemove', drag); document.addEventListener('mouseup', stopDrag); document.addEventListener('touchmove', drag); document.addEventListener('touchend', stopDrag); } const drag = (e) => { if (!isDragging) return; const rect = knob.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; const clientX = e.clientX || e.touches[0].clientX; const clientY = e.clientY || e.touches[0].clientY; const currentAngle = Math.atan2(clientY - centerY, clientX - centerX); const deltaAngle = currentAngle - startAngle; const newVolume = Math.max(0, Math.min(1, this.audioProcessor.masterVolume + (deltaAngle / (Math.PI * 1.5)))); this.audioProcessor.masterVolume = newVolume; updateKnobRotation(); startAngle = currentAngle; }; function stopDrag() { isDragging = false; document.removeEventListener('mousemove', drag); document.removeEventListener('mouseup', stopDrag); document.removeEventListener('touchmove', drag); document.removeEventListener('touchend', stopDrag); } updateKnobRotation(); } setupEQControls() { this.elements.bassSlider.oninput = () => { this.audioProcessor.eqSettings.bass = parseFloat(this.elements.bassSlider.value); this.elements.bassValue.textContent = `${this.audioProcessor.eqSettings.bass > 0 ? '+' : ''}${this.audioProcessor.eqSettings.bass}dB`; this.logger.log(`🎛️ Bass: ${this.audioProcessor.eqSettings.bass}dB`); }; this.elements.midSlider.oninput = () => { this.audioProcessor.eqSettings.mid = parseFloat(this.elements.midSlider.value); this.elements.midValue.textContent = `${this.audioProcessor.eqSettings.mid > 0 ? '+' : ''}${this.audioProcessor.eqSettings.mid}dB`; this.logger.log(`🎛️ Mid: ${this.audioProcessor.eqSettings.mid}dB`); }; this.elements.trebleSlider.oninput = () => { this.audioProcessor.eqSettings.treble = parseFloat(this.elements.trebleSlider.value); this.elements.trebleValue.textContent = `${this.audioProcessor.eqSettings.treble > 0 ? '+' : ''}${this.audioProcessor.eqSettings.treble}dB`; this.logger.log(`🎛️ Treble: ${this.audioProcessor.eqSettings.treble}dB`); }; } setupPitchControls() { const pitchKnob = this.elements.pitchKnob; let isDragging = false; let startAngle = 0; const updatePitchKnobRotation = () => { const rotation = (this.audioProcessor.pitchShift / 1200) * 270; pitchKnob.style.transform = `rotate(${rotation}deg)`; this.elements.pitchValue.textContent = `${this.audioProcessor.pitchShift > 0 ? '+' : ''}${this.audioProcessor.pitchShift} cents`; }; pitchKnob.addEventListener('mousedown', startDrag); pitchKnob.addEventListener('touchstart', startDrag); function startDrag(e) { isDragging = true; const rect = pitchKnob.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; const clientX = e.clientX || e.touches[0].clientX; const clientY = e.clientY || e.touches[0].clientY; startAngle = Math.atan2(clientY - centerY, clientX - centerX); document.addEventListener('mousemove', drag); document.addEventListener('mouseup', stopDrag); document.addEventListener('touchmove', drag); document.addEventListener('touchend', stopDrag); } const drag = (e) => { if (!isDragging) return; const rect = pitchKnob.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; const clientX = e.clientX || e.touches[0].clientX; const clientY = e.clientY || e.touches[0].clientY; const currentAngle = Math.atan2(clientY - centerY, clientX - centerX); const deltaAngle = currentAngle - startAngle; const newPitch = Math.max(-1200, Math.min(1200, this.audioProcessor.pitchShift + (deltaAngle * 200))); this.audioProcessor.pitchShift = Math.round(newPitch); updatePitchKnobRotation(); startAngle = currentAngle; this.logger.log(`🎵 Pitch: ${this.audioProcessor.pitchShift} cents`); }; function stopDrag() { isDragging = false; document.removeEventListener('mousemove', drag); document.removeEventListener('mouseup', stopDrag); document.removeEventListener('touchmove', drag); document.removeEventListener('touchend', stopDrag); } // Pitch preset buttons this.elements.pitchReset.onclick = () => this.setPitchPreset(0, 'Reset'); this.elements.pitchMale.onclick = () => this.setPitchPreset(-200, 'Male'); this.elements.pitchFemale.onclick = () => this.setPitchPreset(300, 'Female'); this.elements.pitchChild.onclick = () => this.setPitchPreset(600, 'Child'); this.elements.pitchRobot.onclick = () => this.setPitchPreset(-800, 'Robot'); updatePitchKnobRotation(); } setPitchPreset(cents, name) { this.audioProcessor.pitchShift = cents; const rotation = (cents / 1200) * 270; this.elements.pitchKnob.style.transform = `rotate(${rotation}deg)`; this.elements.pitchValue.textContent = `${cents > 0 ? '+' : ''}${cents} cents`; // Update button states document.querySelectorAll('.preset-btn').forEach(btn => btn.classList.remove('active')); event.target.classList.add('active'); this.logger.log(`🎵 Pitch Preset: ${name} (${cents} cents)`); } setupEffectControls() { this.elements.reverbBtn.onclick = () => this.toggleEffect('reverb', '🏛️ Reverb'); this.elements.echoBtn.onclick = () => this.toggleEffect('echo', '📢 Echo'); this.elements.distortionBtn.onclick = () => this.toggleEffect('distortion', '⚡ Distortion'); this.elements.chorusBtn.onclick = () => this.toggleEffect('chorus', '🌊 Chorus'); } toggleEffect(effectName, displayName) { this.audioProcessor.activeEffects[effectName] = !this.audioProcessor.activeEffects[effectName]; const button = event.target; if (this.audioProcessor.activeEffects[effectName]) { button.classList.add('active'); this.logger.log(`🎭 ${displayName} Enabled`); } else { button.classList.remove('active'); this.logger.log(`🎭 ${displayName} Disabled`); } } setupDebugControls() { this.elements.debugToggle.onclick = () => { this.debugMode = !this.debugMode; if (this.debugMode) { this.elements.debugToggle.classList.add('active'); this.elements.debugToggle.textContent = '📋 Debug: ON'; this.logger.enableHistory(); this.logger.log('🔍 Debug Mode Activated'); } else { this.elements.debugToggle.classList.remove('active'); this.elements.debugToggle.textContent = '📋 Debug Log'; this.logger.disableHistory(); this.logger.log('🔍 Debug Mode Deactivated'); } }; this.elements.clearDebugBtn.onclick = () => { this.logger.clear(); this.logger.log('🗑️ Mission Log Cleared'); }; } setupScreenWake() { this.createTransparentVideo(); if (typeof NoSleep !== 'undefined') { this.noSleep = new NoSleep(); this.logger.log('😴 NoSleep.js System Ready'); } } createTransparentVideo() { const video = this.elements.screenWakeVideo; const canvas = document.createElement('canvas'); canvas.width = 1; canvas.height = 1; const ctx = canvas.getContext('2d'); let frame = 0; function animateTransparent() { ctx.clearRect(0, 0, 1, 1); ctx.fillStyle = `rgba(0, 0, 0, ${0.01 + (Math.sin(frame * 0.1) * 0.005)})`; ctx.fillRect(0, 0, 1, 1); frame++; requestAnimationFrame(animateTransparent); } const stream = canvas.captureStream(30); video.srcObject = stream; animateTransparent(); this.logger.log('📹 Transparent Video System Ready'); } setupConnectionCallbacks() { this.connectionManager.onConnectionChange = (connected) => { if (connected) { this.elements.connectionLight.classList.add('connected'); } else { this.elements.connectionLight.classList.remove('connected'); this.elements.audioLight.classList.remove('connected'); } }; this.connectionManager.onAudioStateChange = (active) => { if (active) { this.elements.audioLight.classList.add('connected'); } else { this.elements.audioLight.classList.remove('connected'); } }; this.connectionManager.onBufferUpdate = (current, max) => { this.elements.bufferLevel.textContent = `${current}/${max}`; }; } // System control methods toggleScreenLock() { this.screenLockActive = !this.screenLockActive; if (this.screenLockActive) { this.elements.screenLockBtn.classList.add('active'); this.elements.screenLockBtn.textContent = '📱 Screen: LOCKED'; this.elements.screenLight.classList.add('connected'); this.activateScreenLock(); this.logger.log('🔒 Screen Lock Activated'); } else { this.elements.screenLockBtn.classList.remove('active'); this.elements.screenLockBtn.textContent = '📱 Screen Lock'; this.elements.screenLight.classList.remove('connected'); this.deactivateScreenLock(); this.logger.log('🔓 Screen Lock Deactivated'); } } activateScreenLock() { // NoSleep.js if (this.noSleep) { try { this.noSleep.enable(); this.logger.log('😴 NoSleep System Engaged'); } catch (error) { this.logger.log('❌ NoSleep Failed: ' + error.message); } } // Wake Lock API if ('wakeLock' in navigator) { navigator.wakeLock.request('screen').then(lock => { this.wakeLock = lock; this.logger.log('🔒 Wake Lock Acquired'); lock.addEventListener('release', () => { this.logger.log('🔓 Wake Lock Released - Reacquiring...'); if (this.screenLockActive) { setTimeout(() => { if (this.screenLockActive) this.activateScreenLock(); }, 1000); } }); }).catch(error => { this.logger.log('❌ Wake Lock Failed: ' + error.message); }); } // Play transparent video this.elements.screenWakeVideo.play().catch(error => { this.logger.log('❌ Screen Wake Video Failed: ' + error.message); }); // Periodic interaction simulation window.screenKeepAlive = setInterval(() => { document.dispatchEvent(new Event('touchstart')); document.dispatchEvent(new Event('touchend')); }, 15000); } deactivateScreenLock() { if (this.noSleep) { try { this.noSleep.disable(); } catch (error) { this.logger.log('❌ NoSleep Disable Failed: ' + error.message); } } if (this.wakeLock) { this.wakeLock.release(); this.wakeLock = null; } this.elements.screenWakeVideo.pause(); if (window.screenKeepAlive) { clearInterval(window.screenKeepAlive); window.screenKeepAlive = null; } } toggleLowLatency() { if (this.internetMode) { this.logger.log('❌ Cannot Enable Low Latency in Internet Mode'); return; } this.lowLatencyMode = !this.lowLatencyMode; if (this.lowLatencyMode) { this.elements.lowLatencyBtn.classList.add('active'); this.elements.lowLatencyBtn.textContent = '⚡ Low Latency: ON'; this.connectionManager.setBufferSize(2); this.logger.log('⚡ Low Latency Mode Engaged - Buffer: 2'); } else { this.elements.lowLatencyBtn.classList.remove('active'); this.elements.lowLatencyBtn.textContent = '⚡ Low Latency'; this.connectionManager.setBufferSize(this.internetMode ? 10 : 5); this.logger.log('🔄 Normal Latency Mode - Buffer: ' + (this.internetMode ? 10 : 5)); } } toggleInternetMode() { this.internetMode = !this.internetMode; if (this.internetMode) { this.elements.internetModeBtn.classList.add('active'); this.elements.internetModeBtn.textContent = '🌐 Internet: ON'; this.connectionManager.setBufferSize(10); this.lowLatencyMode = false; this.elements.lowLatencyBtn.classList.remove('active'); this.elements.lowLatencyBtn.textContent = '⚡ Low Latency'; this.logger.log('🌐 Internet Mode Engaged - Buffer: 10'); } else { this.elements.internetModeBtn.classList.remove('active'); this.elements.internetModeBtn.textContent = '🌐 Internet Mode'; this.connectionManager.setBufferSize(5); this.logger.log('🏠 Local Network Mode - Buffer: 5'); } } toggleAudioFilter() { this.audioProcessor.audioFilterMode = !this.audioProcessor.audioFilterMode; if (this.audioProcessor.audioFilterMode) { this.elements.audioFilterBtn.classList.add('active'); this.elements.audioFilterBtn.textContent = '🎛️ Filter: ON'; this.logger.log('🎛️ Audio Filter Enabled - Noise Reduction Active'); } else { this.elements.audioFilterBtn.classList.remove('active'); this.elements.audioFilterBtn.textContent = '🎛️ Audio Filter'; this.logger.log('🎛️ Audio Filter Disabled - Raw Audio Mode'); } } } // Export for use in other modules window.UIControls = UIControls;