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);
|