128 lines
4.6 KiB
HTML
128 lines
4.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>FRN Websocket</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<style>
|
|
body {
|
|
background-color: #121212;
|
|
color: #ffffff;
|
|
}
|
|
.vu-meter {
|
|
width: 100%;
|
|
height: 20px;
|
|
background-color: #444;
|
|
position: relative;
|
|
}
|
|
.vu-meter-fill {
|
|
height: 100%;
|
|
background-color: #0f0;
|
|
width: 0%;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="container py-5">
|
|
<h1 class="mb-4">FRN WebSocket</h1>
|
|
<button id="connectButton" class="btn btn-primary mb-3">Connect to Audio Stream</button>
|
|
<p id="status" class="mb-3">Status: Disconnected</p>
|
|
<div class="vu-meter mb-3">
|
|
<div id="vuMeterFill" class="vu-meter-fill"></div>
|
|
</div>
|
|
|
|
<script>
|
|
const connectButton = document.getElementById('connectButton');
|
|
const statusElement = document.getElementById('status');
|
|
const vuMeterFill = document.getElementById('vuMeterFill');
|
|
let audioContext;
|
|
let websocket;
|
|
let analyser;
|
|
let dataArray;
|
|
let isPlayingAudio = false;
|
|
|
|
connectButton.addEventListener('click', () => {
|
|
if (websocket && websocket.readyState === WebSocket.OPEN) {
|
|
websocket.close();
|
|
connectButton.textContent = 'Connect to Audio Stream';
|
|
statusElement.textContent = 'Status: Disconnected';
|
|
isPlayingAudio = false;
|
|
} else {
|
|
startWebSocketConnection();
|
|
}
|
|
});
|
|
|
|
function startWebSocketConnection() {
|
|
websocket = new WebSocket('ws://192.168.1.50/ws/');
|
|
websocket.binaryType = 'arraybuffer';
|
|
|
|
websocket.onopen = () => {
|
|
connectButton.textContent = 'Disconnect from Audio Stream';
|
|
statusElement.textContent = 'Status: Connected';
|
|
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
analyser = audioContext.createAnalyser();
|
|
analyser.fftSize = 256;
|
|
const bufferLength = analyser.frequencyBinCount;
|
|
dataArray = new Uint8Array(bufferLength);
|
|
};
|
|
|
|
websocket.onmessage = (event) => {
|
|
playAudioChunk(event.data);
|
|
};
|
|
|
|
websocket.onclose = () => {
|
|
connectButton.textContent = 'Connect to Audio Stream';
|
|
statusElement.textContent = 'Status: Disconnected';
|
|
isPlayingAudio = false;
|
|
vuMeterFill.style.width = '0%';
|
|
};
|
|
|
|
websocket.onerror = (error) => {
|
|
console.error('WebSocket error:', error);
|
|
};
|
|
}
|
|
|
|
function playAudioChunk(arrayBuffer) {
|
|
try {
|
|
const audioBuffer = audioContext.createBuffer(1, arrayBuffer.byteLength / 2, audioContext.sampleRate);
|
|
const audioData = new Float32Array(arrayBuffer.byteLength / 2);
|
|
const dataView = new DataView(arrayBuffer);
|
|
for (let i = 0; i < audioData.length; i++) {
|
|
audioData[i] = dataView.getInt16(i * 2, true) / 32768;
|
|
}
|
|
audioBuffer.copyToChannel(audioData, 0);
|
|
|
|
const source = audioContext.createBufferSource();
|
|
source.buffer = audioBuffer;
|
|
source.connect(analyser);
|
|
analyser.connect(audioContext.destination);
|
|
source.start(0);
|
|
isPlayingAudio = true;
|
|
visualizeAudio();
|
|
|
|
source.onended = () => {
|
|
isPlayingAudio = false;
|
|
vuMeterFill.style.width = '0%';
|
|
};
|
|
} catch (error) {
|
|
console.error('Error processing audio data:', error);
|
|
}
|
|
}
|
|
|
|
function visualizeAudio() {
|
|
if (isPlayingAudio) {
|
|
analyser.getByteFrequencyData(dataArray);
|
|
let sum = 0;
|
|
for (let i = 0; i < dataArray.length; i++) {
|
|
sum += dataArray[i];
|
|
}
|
|
const average = sum / dataArray.length;
|
|
const percentage = (average / 256) * 100;
|
|
vuMeterFill.style.width = percentage + '%';
|
|
requestAnimationFrame(visualizeAudio);
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|