UNPKG

win-stream-audio

Version:

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

271 lines (232 loc) â€ĸ 9.98 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Simple Audio Source</title> <style> body { font-family: Arial, sans-serif; padding: 20px; background: #f0f0f0; } .container { max-width: 500px; margin: 0 auto; background: white; padding: 20px; border-radius: 10px; } button { background: #4CAF50; color: white; border: none; padding: 15px 20px; margin: 10px 0; border-radius: 5px; width: 100%; font-size: 16px; cursor: pointer; } button:disabled { background: #ccc; } .status { padding: 10px; margin: 10px 0; border-radius: 5px; background: #f8f9fa; border: 1px solid #ddd; } .debug { font-size: 12px; background: #e9ecef; padding: 10px; margin: 10px 0; border-radius: 5px; word-wrap: break-word; max-height: 200px; overflow-y: auto; } select { width: 100%; padding: 10px; margin: 10px 0; border-radius: 5px; border: 1px solid #ddd; } </style> </head> <body> <div class="container"> <h1>đŸŽĩ Simple Audio Source</h1> <div class="status" id="status">Ready</div> <div class="debug" id="debug">Debug: Page loaded</div> <select id="audioSource"> <option value="">Loading audio sources...</option> </select> <button id="startBtn">🎤 Start Raw Audio Streaming</button> <button id="stopBtn" disabled>âšī¸ Stop Streaming</button> </div> <script> // Get elements const startBtn = document.getElementById('startBtn'); const stopBtn = document.getElementById('stopBtn'); const statusDiv = document.getElementById('status'); const debugDiv = document.getElementById('debug'); const audioSourceSelect = document.getElementById('audioSource'); let websocket = null; let mediaStream = null; let audioContext = null; let processor = null; let source = null; // Debug logging function addDebugLog(message) { const timestamp = new Date().toLocaleTimeString(); const logEntry = `[${timestamp}] ${message}`; debugDiv.textContent += '\n' + logEntry; debugDiv.scrollTop = debugDiv.scrollHeight; } // Load audio sources async function loadAudioSources() { try { const devices = await navigator.mediaDevices.enumerateDevices(); const audioInputs = devices.filter(device => device.kind === 'audioinput'); audioSourceSelect.innerHTML = ''; audioInputs.forEach(device => { const option = document.createElement('option'); option.value = device.deviceId; option.textContent = device.label || `Audio Input ${device.deviceId.substring(0, 8)}`; audioSourceSelect.appendChild(option); }); addDebugLog(`Found ${audioInputs.length} audio sources`); } catch (error) { addDebugLog('Error loading audio sources: ' + error.message); } } // Start streaming startBtn.onclick = async function() { try { addDebugLog('🔘 Starting raw audio streaming...'); const selectedDeviceId = audioSourceSelect.value; if (!selectedDeviceId) { addDebugLog('❌ Please select an audio source first'); return; } // Get audio stream mediaStream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId: { exact: selectedDeviceId }, sampleRate: 44100, channelCount: 2, echoCancellation: false, noiseSuppression: false, autoGainControl: false } }); addDebugLog('✅ Got audio stream'); // Connect to WebSocket const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = protocol + '//' + window.location.host; websocket = new WebSocket(wsUrl); websocket.onopen = function() { addDebugLog('✅ WebSocket connected'); // Send role websocket.send(JSON.stringify({ type: 'role', role: 'source' })); // Set up Web Audio API for raw audio capture with optimized settings audioContext = new (window.AudioContext || window.webkitAudioContext)(); source = audioContext.createMediaStreamSource(mediaStream); processor = audioContext.createScriptProcessor(4096, 2, 2); // Back to 4096 for stability processor.onaudioprocess = function(event) { if (websocket && websocket.readyState === WebSocket.OPEN) { // Get raw audio data const leftChannel = event.inputBuffer.getChannelData(0); const rightChannel = event.inputBuffer.getChannelData(1); const length = leftChannel.length; // Create interleaved stereo data const audioData = new Float32Array(length * 2); for (let i = 0; i < length; i++) { audioData[i * 2] = leftChannel[i]; audioData[i * 2 + 1] = rightChannel[i]; } // Send metadata occasionally for format detection if (Math.random() < 0.05) { // Only 5% of the time to reduce overhead const metadata = { type: 'raw-audio', sampleRate: audioContext.sampleRate, channels: 2, length: length, timestamp: Date.now() }; try { websocket.send(JSON.stringify(metadata)); } catch (error) { // Ignore metadata send errors } } // Always send audio data try { websocket.send(audioData.buffer); } catch (error) { addDebugLog('❌ Send error: ' + error.message); } } }; source.connect(processor); processor.connect(audioContext.destination); addDebugLog('đŸŽĩ Raw audio streaming started!'); statusDiv.textContent = 'Streaming Raw Audio'; startBtn.disabled = true; stopBtn.disabled = false; }; websocket.onclose = function() { addDebugLog('❌ WebSocket closed'); stopStreaming(); }; websocket.onerror = function() { addDebugLog('❌ WebSocket error'); stopStreaming(); }; } catch (error) { addDebugLog('❌ Start error: ' + error.message); } }; // Stop streaming stopBtn.onclick = function() { stopStreaming(); }; function stopStreaming() { addDebugLog('âšī¸ Stopping streaming...'); if (processor) { processor.disconnect(); processor = null; } if (source) { source.disconnect(); source = null; } if (audioContext) { audioContext.close(); audioContext = null; } if (mediaStream) { mediaStream.getTracks().forEach(track => track.stop()); mediaStream = null; } if (websocket) { websocket.close(); websocket = null; } statusDiv.textContent = 'Stopped'; startBtn.disabled = false; stopBtn.disabled = true; addDebugLog('✅ Streaming stopped'); } // Load audio sources on page load loadAudioSources(); </script> </body> </html>