diff --git a/warzone/crates/warzone-server/src/routes/web.rs b/warzone/crates/warzone-server/src/routes/web.rs index e5bce69..41a479e 100644 --- a/warzone/crates/warzone-server/src/routes/web.rs +++ b/warzone/crates/warzone-server/src/routes/web.rs @@ -2068,6 +2068,97 @@ async function doSend() { return; } if (text === '/call') { startCall(); return; } + if (text === '/testcall' || text === '/testtone') { + addSys('Audio test: playing 440Hz tone for 3 seconds...'); + try { + const ctx = new AudioContext({ sampleRate: 48000 }); + const osc = ctx.createOscillator(); + osc.type = 'sine'; + osc.frequency.value = 440; + osc.connect(ctx.destination); + osc.start(); + setTimeout(() => { + osc.frequency.value = 880; + addSys('Audio test: 880Hz...'); + }, 1000); + setTimeout(() => { + osc.frequency.value = 660; + addSys('Audio test: 660Hz...'); + }, 2000); + setTimeout(() => { + osc.stop(); + ctx.close(); + addSys('Audio test: done. If you heard 3 tones, speaker works.'); + }, 3000); + } catch(e) { addSys('Audio test failed: ' + e.message); } + return; + } + if (text === '/testecho') { + addSys('Echo test: speak into mic, you should hear yourself...'); + addSys('Type /stopecho to stop.'); + try { + const ctx = new AudioContext({ sampleRate: 48000 }); + const stream = await navigator.mediaDevices.getUserMedia({ + audio: { sampleRate: 48000, channelCount: 1, echoCancellation: false, noiseSuppression: false } + }); + const source = ctx.createMediaStreamSource(stream); + // Direct mic → speaker loopback (with small delay to avoid feedback) + const delay = ctx.createDelay(0.15); + delay.delayTime.value = 0.1; + source.connect(delay); + delay.connect(ctx.destination); + addSys('Echo active \u2014 mic \u2192 speaker (100ms delay)'); + window._echoTest = { ctx, stream, source, delay }; + } catch(e) { addSys('Echo test failed: ' + e.message); } + return; + } + if (text === '/stopecho') { + if (window._echoTest) { + window._echoTest.stream.getTracks().forEach(t => t.stop()); + window._echoTest.source.disconnect(); + window._echoTest.delay.disconnect(); + window._echoTest.ctx.close(); + window._echoTest = null; + addSys('Echo test stopped.'); + } else { addSys('No echo test running.'); } + return; + } + if (text === '/testmic') { + addSys('Mic test: recording 3 seconds, then playback...'); + try { + const ctx = new AudioContext({ sampleRate: 48000 }); + const stream = await navigator.mediaDevices.getUserMedia({ + audio: { sampleRate: 48000, channelCount: 1, echoCancellation: true, noiseSuppression: true } + }); + const source = ctx.createMediaStreamSource(stream); + const processor = ctx.createScriptProcessor(4096, 1, 1); + const recorded = []; + processor.onaudioprocess = (e) => { + recorded.push(new Float32Array(e.inputBuffer.getChannelData(0))); + }; + source.connect(processor); + processor.connect(ctx.destination); + addSys('Recording... speak now'); + setTimeout(() => { + processor.disconnect(); + source.disconnect(); + stream.getTracks().forEach(t => t.stop()); + // Concatenate and play back + const total = recorded.reduce((s, a) => s + a.length, 0); + const buffer = ctx.createBuffer(1, total, 48000); + const channel = buffer.getChannelData(0); + let offset = 0; + for (const chunk of recorded) { channel.set(chunk, offset); offset += chunk.length; } + addSys('Playing back ' + (total / 48000).toFixed(1) + 's of audio...'); + const playSource = ctx.createBufferSource(); + playSource.buffer = buffer; + playSource.connect(ctx.destination); + playSource.start(); + playSource.onended = () => { ctx.close(); addSys('Mic test done. If you heard yourself, mic + speaker work.'); }; + }, 3000); + } catch(e) { addSys('Mic test failed: ' + e.message); } + return; + } if (text === '/hangup' || text === '/end') { hangupCall(); return; } if (text === '/accept') { acceptCall(); return; } if (text === '/reject') { rejectCall(); return; }