mirror of
https://github.com/dscripka/openWakeWord.git
synced 2026-08-27 18:17:20 -04:00
196 lines
No EOL
5.9 KiB
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> |