95 lines
3.7 KiB
HTML
95 lines
3.7 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head><title>Voice Test</title></head>
|
|
<body style="background:#1a1a2e;color:#eee;font-family:monospace;padding:20px">
|
|
<h2>Voice Chat Debug</h2>
|
|
<div id="log" style="white-space:pre;overflow:auto;max-height:80vh;border:1px solid #444;padding:10px;font-size:12px"></div>
|
|
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>
|
|
<script>
|
|
const VOICE_SERVER = 'https://voicegrech.mkn8n.ru';
|
|
const logEl = document.getElementById('log');
|
|
function log(msg) { logEl.textContent += new Date().toISOString().substr(11,8) + ' ' + msg + '\n'; logEl.scrollTop = logEl.scrollHeight; }
|
|
|
|
log('Starting voice test...');
|
|
|
|
(async () => {
|
|
try {
|
|
log('Requesting microphone...');
|
|
const stream = await navigator.mediaDevices.getUserMedia({audio: {echoCancellation:true,noiseSuppression:true,autoGainControl:true}});
|
|
log('Got mic stream: ' + stream.getTracks().map(t => t.label + ' ' + t.readyState).join(', '));
|
|
|
|
const audioCtx = new AudioContext({sampleRate: 16000});
|
|
log('AudioContext: state=' + audioCtx.state + ' sampleRate=' + audioCtx.sampleRate);
|
|
if (audioCtx.state === 'suspended') { await audioCtx.resume(); log('AudioContext resumed: ' + audioCtx.state); }
|
|
|
|
const source = audioCtx.createMediaStreamSource(stream);
|
|
const analyser = audioCtx.createAnalyser();
|
|
analyser.fftSize = 2048;
|
|
source.connect(analyser);
|
|
|
|
// Check mic levels
|
|
const dataArr = new Float32Array(analyser.fftSize);
|
|
let checkCount = 0;
|
|
const checkInterval = setInterval(() => {
|
|
analyser.getFloatTimeDomainData(dataArr);
|
|
let sum = 0;
|
|
for (let i = 0; i < dataArr.length; i++) sum += dataArr[i] * dataArr[i];
|
|
const rms = Math.sqrt(sum / dataArr.length);
|
|
checkCount++;
|
|
if (checkCount <= 20 || checkCount % 50 === 0) log('Mic RMS: ' + rms.toFixed(6) + (rms > 0.008 ? ' SPEAKING' : ''));
|
|
}, 200);
|
|
|
|
log('Connecting to voice server...');
|
|
const socket = io(VOICE_SERVER, {transports: ['websocket']});
|
|
|
|
socket.on('connect', () => {
|
|
log('SOCKET CONNECTED: ' + socket.id);
|
|
socket.emit('voice_join', {world_id: 'test', x: 0, y: 0, name: 'Tester', mode: 'world', codec: 'pcm'});
|
|
log('voice_join sent');
|
|
});
|
|
|
|
socket.on('connect_error', (e) => {
|
|
log('SOCKET ERROR: ' + e.message);
|
|
});
|
|
|
|
socket.on('voice_in', (payload) => {
|
|
log('RX voice_in from ' + (payload.meta?.from||'?').substring(0,8) + ' codec:' + payload.codec + ' size:' + (payload.data?.byteLength || payload.data?.length || '?'));
|
|
});
|
|
|
|
// Capture and send
|
|
const processor = audioCtx.createScriptProcessor(320, 1, 1);
|
|
source.connect(processor);
|
|
processor.connect(audioCtx.destination);
|
|
|
|
let seq = 0;
|
|
let sendCount = 0;
|
|
processor.onaudioprocess = (e) => {
|
|
if (!socket.connected) return;
|
|
const float32 = e.inputBuffer.getChannelData(0);
|
|
let rms = 0;
|
|
for (let i = 0; i < float32.length; i++) rms += float32[i] * float32[i];
|
|
rms = Math.sqrt(rms / float32.length);
|
|
|
|
if (rms < 0.005) return; // Skip silence
|
|
|
|
const int16 = new Int16Array(float32.length);
|
|
for (let i = 0; i < float32.length; i++) {
|
|
const s = Math.max(-1, Math.min(1, float32[i]));
|
|
int16[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
|
|
}
|
|
|
|
socket.emit('voice_data', {codec: 'pcm', data: int16.buffer, seq: seq++, speaking: true});
|
|
sendCount++;
|
|
if (sendCount <= 5 || sendCount % 50 === 0) log('TX frame #' + sendCount + ' rms:' + rms.toFixed(4) + ' size:' + int16.buffer.byteLength);
|
|
};
|
|
|
|
log('Voice capture active. Speak into mic!');
|
|
|
|
} catch(e) {
|
|
log('ERROR: ' + e.message);
|
|
}
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|