Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <title>Whisper.cpp Live STT</title> | |
| <style> | |
| body { | |
| font-family: Arial, sans-serif; | |
| background: #0f172a; | |
| color: #e5e7eb; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| padding: 30px; | |
| } | |
| h1 { | |
| margin-bottom: 10px; | |
| } | |
| .controls { | |
| margin: 20px 0; | |
| } | |
| button { | |
| margin: 0 10px; | |
| padding: 10px 20px; | |
| border-radius: 8px; | |
| border: none; | |
| cursor: pointer; | |
| font-size: 16px; | |
| } | |
| #startBtn { | |
| background: #22c55e; | |
| color: #022c22; | |
| } | |
| #stopBtn { | |
| background: #ef4444; | |
| color: #fee2e2; | |
| } | |
| #status { | |
| margin-top: 10px; | |
| font-size: 14px; | |
| opacity: 0.8; | |
| } | |
| #output { | |
| margin-top: 20px; | |
| width: 100%; | |
| max-width: 700px; | |
| min-height: 200px; | |
| border-radius: 10px; | |
| padding: 15px; | |
| background: #020617; | |
| box-shadow: 0 0 0 1px #1f2937 inset; | |
| white-space: pre-wrap; | |
| overflow-y: auto; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>Whisper.cpp Live Speech-to-Text</h1> | |
| <p>Click start, allow mic access, and speak. Partial transcripts will appear below.</p> | |
| <div class="controls"> | |
| <button id="startBtn">🎤 Start</button> | |
| <button id="stopBtn" disabled>⛔ Stop</button> | |
| </div> | |
| <div id="status">Status: idle</div> | |
| <pre id="output"></pre> | |
| <script> | |
| let socket = null; | |
| let mediaRecorder = null; | |
| let isRecording = false; | |
| const statusEl = document.getElementById("status"); | |
| const outputEl = document.getElementById("output"); | |
| const startBtn = document.getElementById("startBtn"); | |
| const stopBtn = document.getElementById("stopBtn"); | |
| function logStatus(text) { | |
| statusEl.textContent = "Status: " + text; | |
| console.log("[STATUS]", text); | |
| } | |
| function appendText(text) { | |
| outputEl.textContent += text + "\n"; | |
| outputEl.scrollTop = outputEl.scrollHeight; | |
| } | |
| function createWebSocket() { | |
| // Build ws/wss URL from current location (works local + HF Space) | |
| const wsUrl = (location.origin.replace(/^http/, "ws") + "/ws/transcribe_stream"); | |
| console.log("Connecting to", wsUrl); | |
| socket = new WebSocket(wsUrl); | |
| socket.onopen = () => { | |
| logStatus("WebSocket connected, mic ready"); | |
| }; | |
| socket.onmessage = (event) => { | |
| const msg = event.data; | |
| appendText(msg); | |
| }; | |
| socket.onerror = (err) => { | |
| console.error("WebSocket error:", err); | |
| logStatus("WebSocket error"); | |
| }; | |
| socket.onclose = () => { | |
| logStatus("WebSocket closed"); | |
| }; | |
| } | |
| async function startRecording() { | |
| if (isRecording) return; | |
| // Make sure WebSocket exists and open | |
| if (!socket || socket.readyState !== WebSocket.OPEN) { | |
| createWebSocket(); | |
| // Wait briefly for connect (simple version) | |
| await new Promise((resolve) => setTimeout(resolve, 500)); | |
| } | |
| try { | |
| const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); | |
| const options = { mimeType: "audio/webm;codecs=opus" }; | |
| if (!MediaRecorder.isTypeSupported(options.mimeType)) { | |
| alert("WebM/Opus not supported in this browser."); | |
| return; | |
| } | |
| mediaRecorder = new MediaRecorder(stream, options); | |
| mediaRecorder.onstart = () => { | |
| isRecording = true; | |
| startBtn.disabled = true; | |
| stopBtn.disabled = false; | |
| logStatus("Recording..."); | |
| }; | |
| mediaRecorder.ondataavailable = (event) => { | |
| if (event.data && event.data.size > 0 && socket && socket.readyState === WebSocket.OPEN) { | |
| event.data.arrayBuffer().then((buffer) => { | |
| socket.send(buffer); // send binary audio chunk | |
| }); | |
| } | |
| }; | |
| mediaRecorder.onstop = () => { | |
| isRecording = false; | |
| startBtn.disabled = false; | |
| stopBtn.disabled = true; | |
| logStatus("Recording stopped"); | |
| }; | |
| // send chunks every 500ms | |
| mediaRecorder.start(500); | |
| } catch (err) { | |
| console.error("Error starting microphone:", err); | |
| logStatus("Mic permission denied or error"); | |
| } | |
| } | |
| function stopRecording() { | |
| if (!isRecording || !mediaRecorder) return; | |
| mediaRecorder.stop(); | |
| if (socket && socket.readyState === WebSocket.OPEN) { | |
| // Tell server we are done | |
| socket.send("__END__"); | |
| } | |
| } | |
| startBtn.addEventListener("click", startRecording); | |
| stopBtn.addEventListener("click", stopRecording); | |
| </script> | |
| </body> | |
| </html> | |