start implementing frontend part of https://github.com/QuentinFuxa/WhisperLiveKit/pull/80
Browse files
whisperlivekit/web/live_transcription.html
CHANGED
@@ -38,7 +38,6 @@
|
|
38 |
transform: scale(0.95);
|
39 |
}
|
40 |
|
41 |
-
/* Shape inside the button */
|
42 |
.shape-container {
|
43 |
width: 25px;
|
44 |
height: 25px;
|
@@ -56,6 +55,10 @@
|
|
56 |
transition: all 0.3s ease;
|
57 |
}
|
58 |
|
|
|
|
|
|
|
|
|
59 |
#recordButton.recording .shape {
|
60 |
border-radius: 5px;
|
61 |
width: 25px;
|
@@ -304,6 +307,7 @@
|
|
304 |
let waveCanvas = document.getElementById("waveCanvas");
|
305 |
let waveCtx = waveCanvas.getContext("2d");
|
306 |
let animationFrame = null;
|
|
|
307 |
waveCanvas.width = 60 * (window.devicePixelRatio || 1);
|
308 |
waveCanvas.height = 30 * (window.devicePixelRatio || 1);
|
309 |
waveCtx.scale(window.devicePixelRatio || 1, window.devicePixelRatio || 1);
|
@@ -346,10 +350,16 @@
|
|
346 |
|
347 |
websocket.onclose = () => {
|
348 |
if (userClosing) {
|
349 |
-
statusText.textContent
|
|
|
|
|
|
|
350 |
} else {
|
351 |
statusText.textContent =
|
352 |
"Disconnected from the WebSocket server. (Check logs if model is loading.)";
|
|
|
|
|
|
|
353 |
}
|
354 |
userClosing = false;
|
355 |
};
|
@@ -363,6 +373,27 @@
|
|
363 |
websocket.onmessage = (event) => {
|
364 |
const data = JSON.parse(event.data);
|
365 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
366 |
const {
|
367 |
lines = [],
|
368 |
buffer_transcription = "",
|
@@ -494,8 +525,17 @@
|
|
494 |
}
|
495 |
}
|
496 |
|
497 |
-
function stopRecording() {
|
498 |
userClosing = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
499 |
if (recorder) {
|
500 |
recorder.stop();
|
501 |
recorder = null;
|
@@ -531,34 +571,67 @@
|
|
531 |
timerElement.textContent = "00:00";
|
532 |
startTime = null;
|
533 |
|
534 |
-
|
535 |
-
|
536 |
-
|
537 |
-
|
538 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
539 |
}
|
540 |
-
|
|
|
541 |
updateUI();
|
542 |
}
|
543 |
|
544 |
async function toggleRecording() {
|
545 |
if (!isRecording) {
|
546 |
-
|
|
|
|
|
|
|
|
|
547 |
try {
|
548 |
-
|
549 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
550 |
} catch (err) {
|
551 |
statusText.textContent = "Could not connect to WebSocket or access mic. Aborted.";
|
552 |
console.error(err);
|
553 |
}
|
554 |
} else {
|
|
|
555 |
stopRecording();
|
556 |
}
|
557 |
}
|
558 |
|
559 |
function updateUI() {
|
560 |
recordButton.classList.toggle("recording", isRecording);
|
561 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
562 |
}
|
563 |
|
564 |
recordButton.addEventListener("click", toggleRecording);
|
|
|
38 |
transform: scale(0.95);
|
39 |
}
|
40 |
|
|
|
41 |
.shape-container {
|
42 |
width: 25px;
|
43 |
height: 25px;
|
|
|
55 |
transition: all 0.3s ease;
|
56 |
}
|
57 |
|
58 |
+
#recordButton:disabled .shape {
|
59 |
+
background-color: #6e6d6d;
|
60 |
+
}
|
61 |
+
|
62 |
#recordButton.recording .shape {
|
63 |
border-radius: 5px;
|
64 |
width: 25px;
|
|
|
307 |
let waveCanvas = document.getElementById("waveCanvas");
|
308 |
let waveCtx = waveCanvas.getContext("2d");
|
309 |
let animationFrame = null;
|
310 |
+
let waitingForStop = false;
|
311 |
waveCanvas.width = 60 * (window.devicePixelRatio || 1);
|
312 |
waveCanvas.height = 30 * (window.devicePixelRatio || 1);
|
313 |
waveCtx.scale(window.devicePixelRatio || 1, window.devicePixelRatio || 1);
|
|
|
350 |
|
351 |
websocket.onclose = () => {
|
352 |
if (userClosing) {
|
353 |
+
if (!statusText.textContent.includes("Recording stopped. Processing final audio")) { // This is a bit of a hack. We should have a better way to handle this. eg. using a status code.
|
354 |
+
statusText.textContent = "Finished processing audio! Ready to record again.";
|
355 |
+
}
|
356 |
+
waitingForStop = false;
|
357 |
} else {
|
358 |
statusText.textContent =
|
359 |
"Disconnected from the WebSocket server. (Check logs if model is loading.)";
|
360 |
+
if (isRecording) {
|
361 |
+
stopRecording();
|
362 |
+
}
|
363 |
}
|
364 |
userClosing = false;
|
365 |
};
|
|
|
373 |
websocket.onmessage = (event) => {
|
374 |
const data = JSON.parse(event.data);
|
375 |
|
376 |
+
// Check for status messages
|
377 |
+
if (data.type === "ready_to_stop") {
|
378 |
+
console.log("Ready to stop, closing WebSocket");
|
379 |
+
|
380 |
+
// signal that we are not waiting for stop anymore
|
381 |
+
waitingForStop = false;
|
382 |
+
recordButton.disabled = false; // this should be elsewhere
|
383 |
+
console.log("Record button enabled");
|
384 |
+
|
385 |
+
//Now we can close the WebSocket
|
386 |
+
if (websocket) {
|
387 |
+
websocket.close();
|
388 |
+
websocket = null;
|
389 |
+
}
|
390 |
+
|
391 |
+
|
392 |
+
|
393 |
+
return;
|
394 |
+
}
|
395 |
+
|
396 |
+
// Handle normal transcription updates
|
397 |
const {
|
398 |
lines = [],
|
399 |
buffer_transcription = "",
|
|
|
525 |
}
|
526 |
}
|
527 |
|
528 |
+
async function stopRecording() {
|
529 |
userClosing = true;
|
530 |
+
waitingForStop = true;
|
531 |
+
|
532 |
+
if (websocket && websocket.readyState === WebSocket.OPEN) {
|
533 |
+
// Send empty audio buffer as stop signal
|
534 |
+
const emptyBlob = new Blob([], { type: 'audio/webm' });
|
535 |
+
websocket.send(emptyBlob);
|
536 |
+
statusText.textContent = "Recording stopped. Processing final audio...";
|
537 |
+
}
|
538 |
+
|
539 |
if (recorder) {
|
540 |
recorder.stop();
|
541 |
recorder = null;
|
|
|
571 |
timerElement.textContent = "00:00";
|
572 |
startTime = null;
|
573 |
|
574 |
+
if (websocket && websocket.readyState === WebSocket.OPEN) {
|
575 |
+
try {
|
576 |
+
await websocket.send(JSON.stringify({
|
577 |
+
type: "stop",
|
578 |
+
message: "User stopped recording"
|
579 |
+
}));
|
580 |
+
statusText.textContent = "Recording stopped. Processing final audio...";
|
581 |
+
} catch (e) {
|
582 |
+
console.error("Could not send stop message:", e);
|
583 |
+
statusText.textContent = "Recording stopped. Error during final audio processing.";
|
584 |
+
websocket.close();
|
585 |
+
websocket = null;
|
586 |
+
}
|
587 |
}
|
588 |
+
|
589 |
+
isRecording = false;
|
590 |
updateUI();
|
591 |
}
|
592 |
|
593 |
async function toggleRecording() {
|
594 |
if (!isRecording) {
|
595 |
+
if (waitingForStop) {
|
596 |
+
console.log("Waiting for stop, early return");
|
597 |
+
return; // Early return, UI is already updated
|
598 |
+
}
|
599 |
+
console.log("Connecting to WebSocket");
|
600 |
try {
|
601 |
+
// If we have an active WebSocket that's still processing, just restart audio capture
|
602 |
+
if (websocket && websocket.readyState === WebSocket.OPEN) {
|
603 |
+
await startRecording();
|
604 |
+
} else {
|
605 |
+
// If no active WebSocket or it's closed, create new one
|
606 |
+
await setupWebSocket();
|
607 |
+
await startRecording();
|
608 |
+
}
|
609 |
} catch (err) {
|
610 |
statusText.textContent = "Could not connect to WebSocket or access mic. Aborted.";
|
611 |
console.error(err);
|
612 |
}
|
613 |
} else {
|
614 |
+
console.log("Stopping recording");
|
615 |
stopRecording();
|
616 |
}
|
617 |
}
|
618 |
|
619 |
function updateUI() {
|
620 |
recordButton.classList.toggle("recording", isRecording);
|
621 |
+
|
622 |
+
if (waitingForStop) {
|
623 |
+
statusText.textContent = "Please wait for processing to complete...";
|
624 |
+
recordButton.disabled = true; // Optionally disable the button while waiting
|
625 |
+
console.log("Record button disabled");
|
626 |
+
} else if (isRecording) {
|
627 |
+
statusText.textContent = "Recording...";
|
628 |
+
recordButton.disabled = false;
|
629 |
+
console.log("Record button enabled");
|
630 |
+
} else {
|
631 |
+
statusText.textContent = "Click to start transcription";
|
632 |
+
recordButton.disabled = false;
|
633 |
+
console.log("Record button enabled");
|
634 |
+
}
|
635 |
}
|
636 |
|
637 |
recordButton.addEventListener("click", toggleRecording);
|