129 lines
5.1 KiB
HTML
129 lines
5.1 KiB
HTML
<html>
|
|
<head>
|
|
<script>
|
|
microOn = false;
|
|
function changeMicro() {
|
|
microOn = !microOn;
|
|
const micOn = document.getElementById("micOn");
|
|
const micOff = document.getElementById("micOff");
|
|
if (microOn) {
|
|
micOn.hidden = false;
|
|
micOff.hidden = true;
|
|
} else {
|
|
micOn.hidden = true;
|
|
micOff.hidden = false;
|
|
}
|
|
}
|
|
async function getMicrophoneStream() {
|
|
try {
|
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
audio: true,
|
|
video: false
|
|
});
|
|
console.log("Microphone access granted, stream object:", stream);
|
|
return stream;
|
|
} catch (err) {
|
|
console.error(`Error accessing microphone: ${err}`);
|
|
// Handle cases where the user denies permission or no mic is available
|
|
}
|
|
}
|
|
async function connect() {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const callId = urlParams.get('call_id');
|
|
const socket = new WebSocket("/connect?call_id=" + callId);
|
|
socket.addEventListener("open", async () => {
|
|
console.log("WebSocket открыт");
|
|
|
|
// Запрашиваем доступ к микрофону
|
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
|
|
// Создаём AudioContext
|
|
const audioContext = new AudioContext();
|
|
|
|
// Создаём источник из микрофона
|
|
const source = audioContext.createMediaStreamSource(stream);
|
|
|
|
// ScriptProcessorNode является устаревшим, но всё ещё работает
|
|
const processor = audioContext.createScriptProcessor(4096, 1, 1);
|
|
|
|
source.connect(processor);
|
|
processor.connect(audioContext.destination); // необязательно, но нужно для запуска
|
|
|
|
processor.onaudioprocess = (audioEvent) => {
|
|
const input = audioEvent.inputBuffer.getChannelData(0); // Float32Array
|
|
|
|
// Конвертируем в 16-bit PCM (часто требуется серверам)
|
|
const pcm = floatTo16BitPCM(input);
|
|
|
|
// Отправляем через WebSocket
|
|
if (socket.readyState === WebSocket.OPEN) {
|
|
socket.send(pcm);
|
|
}
|
|
};
|
|
});
|
|
socket.binaryType = "arraybuffer";
|
|
|
|
socket.onmessage = (msg) => {
|
|
const int16 = new Int16Array(msg.data);
|
|
playPCM(int16);
|
|
};
|
|
|
|
// Функция конвертации Float32 → Int16
|
|
function floatTo16BitPCM(float32Array) {
|
|
const buffer = new ArrayBuffer(float32Array.length * 2);
|
|
const view = new DataView(buffer);
|
|
|
|
for (let i = 0; i < float32Array.length; i++) {
|
|
let s = Math.max(-1, Math.min(1, float32Array[i]));
|
|
view.setInt16(i * 2, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
const audioContext = new AudioContext();
|
|
|
|
// Очередь буферов, чтобы звук шел плавно
|
|
const playQueue = [];
|
|
|
|
// Функция для воспроизведения PCM
|
|
function playPCM(int16Array) {
|
|
const float32 = new Float32Array(int16Array.length);
|
|
|
|
for (let i = 0; i < int16Array.length; i++) {
|
|
float32[i] = int16Array[i] / 0x7fff;
|
|
}
|
|
|
|
const audioBuffer = audioContext.createBuffer(1, float32.length, audioContext.sampleRate);
|
|
audioBuffer.getChannelData(0).set(float32);
|
|
|
|
playQueue.push(audioBuffer);
|
|
|
|
if (playQueue.length === 1) {
|
|
playNext();
|
|
}
|
|
}
|
|
|
|
function playNext() {
|
|
if (playQueue.length === 0) return;
|
|
|
|
const buffer = playQueue[0];
|
|
const source = audioContext.createBufferSource();
|
|
source.buffer = buffer;
|
|
source.connect(audioContext.destination);
|
|
|
|
source.onended = () => {
|
|
playQueue.shift();
|
|
playNext();
|
|
};
|
|
|
|
source.start();
|
|
}
|
|
}
|
|
connect();
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<p id="micOff">Call active</p>
|
|
</body>
|
|
</html> |