openwakeword/examples/web/streaming_client.html

196 lines
No EOL
5.9 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Websocket Microphone Streaming</title>
<style>
body {
text-align: center;
font-family: 'Roboto', sans-serif;
}
#startButton {
padding: 15px 30px;
font-size: 18px;
background-color: #03A9F4;
border: none;
border-radius: 4px;
color: white;
cursor: pointer;
outline: none;
transition: background-color 0.3s;
}
#startButton.listening {
background-color: #4CAF50;
}
table {
margin: 20px auto;
border-collapse: collapse;
width: 60%;
}
th, td {
border: 1px solid #E0E0E0;
padding: 10px;
text-align: left;
}
th {
background-color: #F5F5F5;
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.detected-animation {
animation: fadeOut 2s forwards;
}
</style>
</head>
<body>
<h1>Streaming Audio to openWakeWord Using Websockets</h1>
<button id="startButton">Start Listening</button>
<table>
<tr>
<th>Wakeword</th>
<th>Detected</th>
</tr>
<tr>
<td></td>
<td></td>
</tr>
</table>
<script>
// Create websocket connection
const ws = new WebSocket('ws://localhost:9000/ws');
// When the websocket connection is open
ws.onopen = function() {
console.log('WebSocket connection is open');
};
// Get responses from websocket and display information
ws.onmessage = (event) => {
console.log(event.data);
const model_payload = JSON.parse(event.data);
if ("loaded_models" in model_payload) {
// Add loaded models to the rows of the first column in the table, inserting rows as needed
const table = document.querySelector('table');
const rows = table.querySelectorAll('tr');
for (let i = 1; i < model_payload.loaded_models.length + 1; i++) {
if (i < rows.length) {
const row = rows[i];
const cell = row.querySelectorAll('td')[0];
cell.textContent = model_payload.loaded_models[i - 1];
} else {
// Insert extra rows if needed, both column 1 and 2
const row = table.insertRow();
const cell1 = row.insertCell();
const cell2 = row.insertCell();
cell1.textContent = model_payload.loaded_models[i - 1];
cell2.textContent = '';
}
}
}
if ("activations" in model_payload) {
// Add detected wakeword to the rows of the second column in the table
const table = document.querySelector('table');
const rows = table.querySelectorAll('tr');
for (let i = 1; i < rows.length; i++) {
// Check for the model name in the first column and add "Detected!" to the second column if they match
if (model_payload.activations.includes(rows[i].querySelectorAll('td')[0].textContent)) {
const cell = rows[i].querySelectorAll('td')[1];
cell.textContent = "Detected!";
cell.classList.add('detected-animation'); // animate fade out
// Remove the CSS class after the fade out animation ends to reset the state
cell.addEventListener('animationend', () => {
cell.textContent = '';
cell.classList.remove('detected-animation');
}, { once: true });
}
}
}
};
// Create microphone capture stream
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia;
let audioStream;
let audioContext;
let recorder;
let volume;
let sampleRate;
if (navigator.getUserMedia) {
navigator.getUserMedia({audio: true}, function(stream) {
audioStream = stream;
// creates the an instance of audioContext
const context = window.AudioContext || window.webkitAudioContext;
audioContext = new context();
// retrieve the current sample rate of microphone the browser is using and send to Python server
sampleRate = audioContext.sampleRate;
// creates a gain node
volume = audioContext.createGain();
// creates an audio node from the microphone incoming stream
const audioInput = audioContext.createMediaStreamSource(audioStream);
// connect the stream to the gain node
audioInput.connect(volume);
const bufferSize = 4096;
recorder = (audioContext.createScriptProcessor ||
audioContext.createJavaScriptNode).call(audioContext,
bufferSize,
1,
1);
recorder.onaudioprocess = function(event) {
const samples = event.inputBuffer.getChannelData(0);
const PCM16iSamples = samples.map(sample => {
let val = Math.floor(32767 * sample);
return Math.min(32767, Math.max(-32768, val));
});
// Push audio to websocket
const int16Array = new Int16Array(PCM16iSamples);
const blob = new Blob([int16Array], { type: 'application/octet-stream' });
ws.send(blob);
};
}, function(error) {
alert('Error capturing audio.');
});
} else {
alert('getUserMedia not supported in this browser.');
}
// start recording
const startButton = document.getElementById('startButton');
startButton.addEventListener('click', function() {
if (!startButton.classList.contains('listening')) {
volume.connect(recorder);
recorder.connect(audioContext.destination);
ws.send(sampleRate);
startButton.classList.add('listening');
startButton.textContent = 'Listening...';
}
});
</script>
</body>
</html>