Spaces:
Sleeping
Sleeping
const messageHistory = document.getElementById('chatHistory') | |
let stream; | |
let ws | |
let silenceDetectorNode; | |
let silenceCount = 0 | |
let recording = false; | |
let mediaRecorder; | |
let continuousRecorder | |
let audioContext; | |
let source; | |
let currentAudioChunks = []; | |
let allAudioChunks = []; | |
const uuid = generateUUID() | |
const startRecordingButton = document.getElementById('startRecording') | |
const loadingModal = document.getElementById('loadingModal'); | |
function makeLoading() { | |
loadingModal.style.visibility = 'visible'; | |
} | |
function stopLoading() { | |
loadingModal.style.visibility = 'hidden'; | |
} | |
function showMessage(message_text) { | |
const message = document.getElementById('message'); | |
message.innerText = message_text | |
message.style.display = 'block'; | |
setTimeout(function () { | |
message.style.display = 'none'; | |
}, 2000); | |
} | |
function createMessage(type, message) { | |
const newMessage = document.createElement('div') | |
newMessage.className = 'message rounded-4 bg-white mb-4 mx-4 py-2 px-3 border' | |
newMessage.innerHTML = ` | |
<h5>${type}</h5> | |
${message} | |
` | |
messageHistory.appendChild(newMessage) | |
} | |
function playResponse(data) { | |
const response = JSON.parse(data) | |
createMessage('User', response['user_query']) | |
stopLoading() | |
console.log(response) | |
const audioSrc = `data:audio/mp3;base64,${response['voice_response']}`; | |
const audio = new Audio(audioSrc) | |
audio.play() | |
audio.onended = () => { | |
recording = true | |
createMessage('Liza', response['ai_response']) | |
showMessage('You can speak!') | |
startMediaRecorder() | |
} | |
} | |
startRecordingButton.addEventListener('click', async () => { | |
if (!recording) { | |
if (mediaRecorder && mediaRecorder.state !== 'inactive') { | |
mediaRecorder.stop(); | |
} | |
ws = new WebSocket(`ws://localhost:8000/ws/${uuid}`); | |
ws.onclose = (event) => { | |
if (mediaRecorder && mediaRecorder.state !== 'inactive') { | |
mediaRecorder.stop(); | |
} | |
} | |
ws.onerror = (error) => { | |
alert('Something was wrong. Try again later.') | |
console.log(error) | |
window.location.reload() | |
}; | |
ws.onmessage = (event) => { | |
const response = event.data | |
playResponse(response) | |
} | |
startRecordingButton.innerHTML = 'Stop call'; | |
try { | |
stream = await navigator.mediaDevices.getUserMedia({audio: true, video: false}); | |
audioContext = new AudioContext(); | |
await audioContext.audioWorklet.addModule('../../../static/js/audio-processor.js'); | |
silenceDetectorNode = new AudioWorkletNode(audioContext, 'silence-detector-processor'); | |
silenceDetectorNode.port.onmessage = (event) => { | |
if (event.data.type === 'silence') { | |
if (currentAudioChunks.length > 0) { | |
if (silenceCount === 0) { | |
silenceCount += 1; | |
stopRecorder(); | |
} | |
} | |
} else if (event.data.type === 'sound') { | |
silenceCount = 0; | |
} | |
}; | |
source = audioContext.createMediaStreamSource(stream); | |
mediaRecorder = new MediaRecorder(stream); | |
mediaRecorder.start(1000); | |
mediaRecorder.ondataavailable = event => { | |
currentAudioChunks.push(event.data); | |
}; | |
source.connect(silenceDetectorNode).connect(audioContext.destination); | |
continuousRecorder = new MediaRecorder(stream); | |
continuousRecorder.start(); | |
continuousRecorder.ondataavailable = event => { | |
allAudioChunks.push(event.data); | |
}; | |
recording = true; | |
} catch (error) { | |
console.error('Access to microphone denied:', error); | |
} | |
} else { | |
await stopRecording(); | |
} | |
}); | |
async function stopRecording() { | |
// startRecordingButton.innerHTML = 'Start recording'; | |
// recording = false; | |
// mediaRecorder.stop(); | |
// continuousRecorder.stop(); | |
// silenceDetectorNode.disconnect(); | |
// source.disconnect(); | |
// audioContext.close(); | |
// currentAudioChunks = []; | |
window.location.reload() | |
} | |
function sendAudioToServer(audioBlob) { | |
return new Promise((resolve, reject) => { | |
console.log("Sending audio to server...", audioBlob); | |
const reader = new FileReader(); | |
reader.readAsDataURL(audioBlob); | |
reader.onloadend = () => { | |
let base64String = reader.result; | |
base64String = base64String.split(',')[1]; | |
const dataWS = {'audio': base64String}; | |
ws.send(JSON.stringify(dataWS)); | |
makeLoading() | |
resolve(); | |
}; | |
reader.onerror = reject; | |
}); | |
} | |
async function stopRecorder() { | |
if (mediaRecorder && mediaRecorder.state !== 'inactive') { | |
mediaRecorder.stop(); | |
} | |
await sendAudioToServer(new Blob(currentAudioChunks, {type: 'audio/wav'})); | |
currentAudioChunks = []; | |
} | |
async function startMediaRecorder() { | |
mediaRecorder = new MediaRecorder(stream); | |
mediaRecorder.start(1000); | |
mediaRecorder.ondataavailable = event => { | |
currentAudioChunks.push(event.data); | |
} | |
} | |
function generateUUID() { | |
const arr = new Uint8Array(16) | |
window.crypto.getRandomValues(arr) | |
arr[6] = (arr[6] & 0x0f) | 0x40 | |
arr[8] = (arr[8] & 0x3f) | 0x80 | |
return ([...arr].map((b, i) => | |
(i === 4 || i === 6 || i === 8 || i === 10 ? "-" : "") + b.toString(16).padStart(2, "0") | |
).join("")) | |
} | |