diff --git a/game.js b/game.js
index b73196c..a3e37ce 100644
--- a/game.js
+++ b/game.js
@@ -1245,39 +1245,42 @@
let voiceSocket = null;
let voiceStream = null;
let audioCtx = null;
- let voiceWorklet = null;
+ let voiceProcessor = null;
let voiceActive = false;
+ let voiceMode = 'near'; // 'near' or 'world'
+ let voiceDebugCount = 0;
const VOICE_SERVER = 'https://voicegrech.mkn8n.ru';
- // AudioWorklet processor as Blob URL — no separate file needed
- const workletCode = `
- class VoiceProcessor extends AudioWorkletProcessor {
- process(inputs) {
- const ch = inputs[0];
- if (ch && ch[0]) {
- const pcm = ch[0];
- const int16 = new Int16Array(pcm.length);
- for (let i = 0; i < pcm.length; i++) {
- const s = Math.max(-1, Math.min(1, pcm[i]));
- int16[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
- }
- this.port.postMessage(int16.buffer, [int16.buffer]);
- }
- return true;
- }
- }
- registerProcessor('voice-pcm', VoiceProcessor);
- `;
- const workletBlob = new Blob([workletCode], { type: 'application/javascript' });
- const workletUrl = URL.createObjectURL(workletBlob);
-
// Кнопка микрофона
const voiceBtn = document.createElement('div');
voiceBtn.innerHTML = '🎤/';
voiceBtn.title = 'Голосовой чат (выкл)';
- voiceBtn.style.cssText = 'position:absolute;top:74px;right:130px;width:52px;height:52px;border-radius:12px;background:#555;z-index:200;display:flex;align-items:center;justify-content:center;font-size:24px;cursor:pointer;border:2px solid rgba(255,255,255,0.9);box-shadow:0 4px 0 rgba(0,0,0,0.5);pointer-events:auto;';
+ voiceBtn.style.cssText = 'position:absolute;top:74px;right:170px;width:52px;height:52px;border-radius:12px;background:#555;z-index:200;display:flex;align-items:center;justify-content:center;font-size:24px;cursor:pointer;border:2px solid rgba(255,255,255,0.9);box-shadow:0 4px 0 rgba(0,0,0,0.5);pointer-events:auto;';
document.querySelector('.ui').appendChild(voiceBtn);
+ // Кнопка режима голоса (близко / весь мир)
+ const voiceModeBtn = document.createElement('div');
+ voiceModeBtn.innerHTML = '📢';
+ voiceModeBtn.title = 'Режим: рядом (600px)';
+ voiceModeBtn.style.cssText = 'position:absolute;top:74px;right:112px;width:48px;height:52px;border-radius:12px;background:#3498db;z-index:200;display:flex;align-items:center;justify-content:center;font-size:20px;cursor:pointer;border:2px solid rgba(255,255,255,0.7);box-shadow:0 4px 0 rgba(0,0,0,0.5);pointer-events:auto;color:#fff;font-weight:bold;';
+ document.querySelector('.ui').appendChild(voiceModeBtn);
+ voiceModeBtn.onclick = () => {
+ if (voiceMode === 'near') {
+ voiceMode = 'world';
+ voiceModeBtn.innerHTML = '🌍';
+ voiceModeBtn.title = 'Режим: весь мир';
+ voiceModeBtn.style.background = '#e67e22';
+ } else {
+ voiceMode = 'near';
+ voiceModeBtn.innerHTML = '📢';
+ voiceModeBtn.title = 'Режим: рядом (600px)';
+ voiceModeBtn.style.background = '#3498db';
+ }
+ if (voiceSocket && voiceSocket.connected) {
+ voiceSocket.emit('voice_mode', { mode: voiceMode });
+ }
+ };
+
// Индикатор говорящего
const speakingIndicator = document.createElement('div');
speakingIndicator.style.cssText = 'position:absolute;top:130px;right:10px;z-index:200;display:none;background:rgba(0,0,0,0.7);color:#2ecc71;padding:4px 8px;border-radius:6px;font-size:12px;';
@@ -1291,11 +1294,11 @@
voiceActive = false;
voiceBtn.innerHTML = '🎤/';
voiceBtn.style.background = '#555';
- if (voiceWorklet) { voiceWorklet.disconnect(); voiceWorklet = null; }
if (voiceStream) {
voiceStream.getTracks().forEach(t => t.stop());
voiceStream = null;
}
+ if (voiceProcessor) { voiceProcessor.disconnect(); voiceProcessor = null; }
if (audioCtx) { audioCtx.close(); audioCtx = null; }
if (voiceSocket) { voiceSocket.disconnect(); voiceSocket = null; }
return;
@@ -1306,26 +1309,53 @@
voiceStream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true } });
audioCtx = new AudioContext({ sampleRate: 24000 });
if (audioCtx.state === 'suspended') await audioCtx.resume();
+ console.log('[voice] AudioContext state:', audioCtx.state, 'sampleRate:', audioCtx.sampleRate);
- await audioCtx.audioWorklet.addModule(workletUrl);
const source = audioCtx.createMediaStreamSource(voiceStream);
- voiceWorklet = new AudioWorkletNode(audioCtx, 'voice-pcm');
- voiceWorklet.port.onmessage = (e) => {
- if (!voiceActive || !voiceSocket || !voiceSocket.connected) return;
- voiceSocket.emit('voice_data', e.data);
+ voiceProcessor = audioCtx.createScriptProcessor(2048, 1, 1);
+ console.log('[voice] ScriptProcessor created, bufferSize=2048');
+
+ voiceProcessor.onaudioprocess = (e) => {
+ if (!voiceActive) return;
+ voiceDebugCount++;
+ if (voiceDebugCount <= 5) {
+ const pcm = e.inputBuffer.getChannelData(0);
+ console.log('[voice] onaudioprocess #' + voiceDebugCount, 'samples:', pcm.length, 'max:', Math.max(...pcm.map(Math.abs)).toFixed(4), 'socket:', !!voiceSocket, 'connected:', voiceSocket?.connected);
+ }
+ if (!voiceSocket || !voiceSocket.connected) return;
+ const pcm = e.inputBuffer.getChannelData(0);
+ const int16 = new Int16Array(pcm.length);
+ for (let i = 0; i < pcm.length; i++) {
+ const s = Math.max(-1, Math.min(1, pcm[i]));
+ int16[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
+ }
+ voiceSocket.emit('voice_data', int16.buffer);
};
- source.connect(voiceWorklet);
+
+ // Chain: source → processor → gain(0) → destination
+ // ScriptProcessor MUST reach destination to fire onaudioprocess
+ const silentGain = audioCtx.createGain();
+ silentGain.gain.value = 0;
+ source.connect(voiceProcessor);
+ voiceProcessor.connect(silentGain);
+ silentGain.connect(audioCtx.destination);
+ console.log('[voice] Audio chain: source → processor → silentGain(0) → destination');
// Подключаемся к голосовому серверу
voiceSocket = io(VOICE_SERVER, { transports: ['websocket'] });
voiceSocket.on('connect', () => {
- voiceSocket.emit('voice_join', { world_id: worldId, x: player.x, y: player.y, name: playerName || 'Игрок' });
+ console.log('[voice] Socket connected, id:', voiceSocket.id);
+ voiceSocket.emit('voice_join', { world_id: worldId, x: player.x, y: player.y, name: playerName || 'Игрок', mode: voiceMode });
+ });
+ voiceSocket.on('connect_error', (err) => {
+ console.error('[voice] Socket connect error:', err.message);
});
voiceSocket.on('voice_in', (payload) => {
// Воспроизводим входящий голос — raw PCM int16
const { data, meta, volume } = payload;
if (!audioCtx || audioCtx.state === 'closed') return;
+ console.log('[voice] voice_in from', meta.name, 'volume:', volume, 'bytes:', data.byteLength);
const int16 = new Int16Array(data);
const float32 = new Float32Array(int16.length);
@@ -1351,8 +1381,9 @@
voiceActive = true;
voiceBtn.textContent = '🎤';
voiceBtn.style.background = '#2ecc71';
+ console.log('[voice] Voice chat ACTIVE');
} catch(e) {
- console.error('Voice error:', e);
+ console.error('[voice] Error:', e);
voiceBtn.style.background = '#e74c3c';
}
};
diff --git a/index.html b/index.html
index 01c2bce..cffd717 100644
--- a/index.html
+++ b/index.html
@@ -92,6 +92,6 @@
-
+