File size: 4,196 Bytes
1ff9fd2 f56b852 1ff9fd2 f56b852 1ff9fd2 f56b852 1ff9fd2 f56b852 1ff9fd2 f56b852 1ff9fd2 f56b852 1ff9fd2 f56b852 1ff9fd2 f56b852 1ff9fd2 f56b852 1ff9fd2 |
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 |
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) {
document.getElementById("statusMessage").innerText =
"❌ This browser does NOT support speech recognition. Use Chrome Desktop.";
throw new Error("SpeechRecognition unsupported");
}
let recognition = null;
let isRecording = false;
let lastFinalTimestamp = null;
let pauseTimer = null;
const languageSelect = document.getElementById("languageSelect");
const toggleButton = document.getElementById("toggleButton");
const transcriptionOutput = document.getElementById("transcriptionOutput");
const statusMessage = document.getElementById("statusMessage");
const clearButton = document.getElementById("clearButton");
const downloadButton = document.getElementById("downloadButton");
function highlightDisfluencies(text) {
return text.replace(
/\b(um+|uh+|er+|ah+|eh+|oh+|hmm+|mmm+|like)\b/gi,
(m) => `<span class="disfluency">${m}</span>`
);
}
function addPauseMarker() {
transcriptionOutput.innerHTML += " <span class='pause'>…</span> ";
transcriptionOutput.scrollTop = transcriptionOutput.scrollHeight;
}
function initRecognition(lang) {
recognition = new SpeechRecognition();
recognition.lang = lang;
recognition.continuous = true;
recognition.interimResults = true;
recognition.onstart = () => {
statusMessage.innerText = `🎙️ Listening… (${lang})`;
};
recognition.onerror = (e) => {
statusMessage.innerText = `⚠️ Error: ${e.error}`;
if (e.error === "not-allowed") {
statusMessage.innerText =
"❌ Microphone blocked. Click the 🔒 icon → Allow microphone.";
}
};
recognition.onend = () => {
if (isRecording) recognition.start();
};
recognition.onresult = (event) => {
let interimText = "";
let finalText = "";
for (let i = event.resultIndex; i < event.results.length; i++) {
const text = event.results[i][0].transcript.trim();
if (event.results[i].isFinal) {
finalText += text + " ";
} else {
interimText += text + " ";
}
}
// ---- INTERIM (live) ----
if (interimText) {
const base = transcriptionOutput.innerHTML.replace(/<span class="interim">.*?<\/span>/, "");
transcriptionOutput.innerHTML = base +
`<span class="interim">${highlightDisfluencies(interimText)}</span>`;
transcriptionOutput.scrollTop = transcriptionOutput.scrollHeight;
clearTimeout(pauseTimer);
pauseTimer = setTimeout(() => addPauseMarker(), 2000);
}
// ---- FINAL ----
if (finalText) {
let cleaned = transcriptionOutput.innerHTML.replace(/<span class="interim">.*?<\/span>/, "");
const now = Date.now();
if (lastFinalTimestamp && now - lastFinalTimestamp > 3000) {
cleaned += "<br><span class='pause'>…</span> ";
}
transcriptionOutput.innerHTML =
cleaned + highlightDisfluencies(finalText);
transcriptionOutput.scrollTop = transcriptionOutput.scrollHeight;
lastFinalTimestamp = now;
}
};
}
toggleButton.addEventListener("click", () => {
if (isRecording) {
isRecording = false;
recognition.stop();
toggleButton.textContent = "🎤 Start Listening";
statusMessage.innerText = "Stopped.";
} else {
initRecognition(languageSelect.value);
recognition.start();
isRecording = true;
toggleButton.textContent = "⏹ Stop Listening";
}
});
clearButton.addEventListener("click", () => {
transcriptionOutput.innerHTML = "";
statusMessage.innerText = "Cleared.";
});
downloadButton.addEventListener("click", () => {
const text = transcriptionOutput.innerText;
const blob = new Blob([text], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "transcription.txt";
a.click();
});
|