Spaces:
Sleeping
Sleeping
File size: 7,978 Bytes
d6fcf92 9cbfee0 d6fcf92 c8c7d92 d6fcf92 9cbfee0 2e0855f c8c7d92 9cbfee0 d6fcf92 c8c7d92 2e0855f d6fcf92 f0821d0 9cbfee0 f0821d0 d6fcf92 9cbfee0 d6fcf92 2e0855f c8c7d92 9cbfee0 d6fcf92 2e0855f c8c7d92 d6fcf92 f0821d0 d6fcf92 f0821d0 d6fcf92 f0821d0 d6fcf92 f0821d0 d6fcf92 2e0855f c8c7d92 9cbfee0 d6fcf92 2e0855f d6fcf92 2e0855f d6fcf92 c8c7d92 d6fcf92 2e0855f c8c7d92 d6fcf92 2e0855f d6fcf92 9cbfee0 d6fcf92 c8c7d92 d6fcf92 2e0855f 9cbfee0 d6fcf92 9cbfee0 d6fcf92 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Real-Time Voice Translator</title>
<style>
body { font-family: sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; margin: 0; background-color: #f0f0f0; }
#controls { margin-bottom: 20px; }
button { font-size: 1.2em; padding: 10px 20px; cursor: pointer; }
#status { font-size: 1.1em; color: #333; }
</style>
</head>
<body>
<h1>Real-Time Voice Translator</h1>
<div id="controls">
<button id="startButton">Start Translation</button>
<button id="stopButton" disabled>Stop Translation</button>
</div>
<p id="status">Status: Not connected</p>
<div id="log"></div>
<script>
const startButton = document.getElementById('startButton');
const stopButton = document.getElementById('stopButton');
const status = document.getElementById('status');
let socket;
let mediaRecorder;
let audioContext;
let audioQueue = [];
let isPlaying = false;
const connectWebSocket = () => {
const proto = window.location.protocol === "https:" ? "wss:" : "ws:";
const wsUri = `${proto}//${window.location.host}/ws`;
console.log("[CLIENT] Attempting to connect to WebSocket:", wsUri);
status.textContent = `Status: Connecting to ${wsUri}...`;
socket = new WebSocket(wsUri);
socket.onopen = () => {
status.textContent = 'Status: Connected. Ready to start.';
console.log("[CLIENT] WebSocket connection opened. Enabling start button.");
startButton.disabled = false;
};
socket.onmessage = (event) => {
if (event.data instanceof Blob) {
const reader = new FileReader();
reader.onload = function() {
// The server sends raw PCM; we need to wrap it in a WAV header
const pcmData = new Int16Array(this.result);
const wavBlob = createWavBlob(pcmData, 1, 16000);
if (audioContext) {
audioQueue.push(wavBlob);
if (!isPlaying) playNextInQueue();
}
};
reader.readAsArrayBuffer(event.data);
} else {
// Handle text messages from server (e.g., for logging)
const logElement = document.createElement('p');
logElement.textContent = event.data;
document.getElementById('log').prepend(logElement);
}
};
socket.onclose = () => {
console.log("[CLIENT] WebSocket connection closed.");
status.textContent = 'Status: Disconnected. Please refresh the page.';
startButton.disabled = false; // Allow user to try starting again
stopButton.disabled = true;
};
socket.onerror = (error) => {
console.error("[CLIENT] WebSocket Error:", error);
status.textContent = 'Status: Connection error. Check console for details.';
};
};
const playNextInQueue = async () => {
if (audioQueue.length > 0) {
isPlaying = true;
const blob = audioQueue.shift();
try {
const arrayBuffer = await blob.arrayBuffer();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioContext.destination);
source.onended = playNextInQueue; // Chain the next playback
source.start();
} catch (e) {
console.error("Error decoding or playing audio:", e);
isPlaying = false;
playNextInQueue(); // Try the next one
}
} else {
isPlaying = false;
}
};
// Helper function to create a WAV blob from raw PCM data
const createWavBlob = (pcmData, numChannels, sampleRate) => {
const header = new ArrayBuffer(44);
const view = new DataView(header);
const pcmLength = pcmData.length * 2; // 16-bit samples
// RIFF header
view.setUint32(0, 0x52494646, false); // "RIFF"
view.setUint32(4, 36 + pcmLength, true);
view.setUint32(8, 0x57415645, false); // "WAVE"
// "fmt " sub-chunk
view.setUint32(12, 0x666d7420, false); // "fmt "
view.setUint32(16, 16, true); // Sub-chunk size
view.setUint16(20, 1, true); // Audio format (1 for PCM)
view.setUint16(22, numChannels, true);
view.setUint32(24, sampleRate, true);
view.setUint32(28, sampleRate * numChannels * 2, true); // Byte rate
view.setUint16(32, numChannels * 2, true); // Block align
view.setUint16(34, 16, true); // Bits per sample
view.setUint32(36, 0x64617461, false); // "data"
view.setUint32(40, pcmLength, true);
return new Blob([header, pcmData], { type: 'audio/wav' });
};
startButton.onclick = async () => {
console.log("[CLIENT] Start button clicked.");
// AudioContext must be created or resumed by a user gesture.
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 16000 });
} else if (audioContext.state === 'suspended') {
await audioContext.resume();
}
console.log("[CLIENT] Requesting microphone access...");
navigator.mediaDevices.getUserMedia({ audio: { sampleRate: 16000, channelCount: 1 } })
.then(stream => {
mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm; codecs=opus' });
mediaRecorder.ondataavailable = event => {
if (event.data.size > 0 && socket.readyState === WebSocket.OPEN) {
socket.send(event.data);
}
};
mediaRecorder.start(250); // Send data every 250ms
console.log("[CLIENT] Microphone access granted. MediaRecorder started.");
startButton.disabled = true;
stopButton.disabled = false;
status.textContent = 'Status: Translating...';
})
.catch(err => {
console.error('[CLIENT] Error getting user media:', err);
status.textContent = 'Error: Could not access microphone.';
});
};
stopButton.onclick = () => {
console.log("[CLIENT] Stop button clicked.");
if (mediaRecorder) {
mediaRecorder.stop();
}
// Don't close the socket, just stop sending data.
// The user might want to start and stop multiple times in one session.
startButton.disabled = false;
stopButton.disabled = true;
status.textContent = 'Status: Stopped. Press Start to translate again.';
};
window.onload = () => {
console.log("[CLIENT] Page loaded. Initializing...");
startButton.disabled = true;
stopButton.disabled = true;
connectWebSocket(); // Connect automatically on page load
};
</script>
</body>
</html>
|