]> git.taranathan.com Git - audio-stream.git/commitdiff
functions, doesn't overwrite
authortaran nathan <moogoesmeow123@gmail.com>
Wed, 22 Jul 2026 20:48:31 +0000 (13:48 -0700)
committertaran nathan <moogoesmeow123@gmail.com>
Wed, 22 Jul 2026 20:48:31 +0000 (13:48 -0700)
script.js

index c1a19918eb0711851865dfbe9c875ee521995908..04e0dfbb091dd7f5f113e9219a8c98f8e999d8de 100644 (file)
--- a/script.js
+++ b/script.js
@@ -12,6 +12,7 @@ let streamProcessorNode;
 let streamMuteNode;
 let committedTranscription = "";
 let partialTranscription = "";
+let lastCommittedTurn = -1;
 
 const startButton = document.getElementById("btnStart");
 const stopButton = document.getElementById("btnStop");
@@ -32,7 +33,9 @@ function renderTranscription() {
     return;
   }
 
-  transcriptionElement.textContent = partial ? `${committed} ${partial}`.trim() : committed;
+  transcriptionElement.textContent = partial
+    ? `${committed} ${partial}`.trim()
+    : committed;
 }
 
 function cleanupCaptureResources(closeTranscriptionSocket = true) {
@@ -56,7 +59,10 @@ function cleanupCaptureResources(closeTranscriptionSocket = true) {
     audioContext.close();
   }
 
-  if (closeTranscriptionSocket && transcriptionSocket && transcriptionSocket.readyState === WebSocket.OPEN) {
+  if (
+    closeTranscriptionSocket && transcriptionSocket &&
+    transcriptionSocket.readyState === WebSocket.OPEN
+  ) {
     transcriptionSocket.close();
   }
 
@@ -102,43 +108,34 @@ function extractTranscriptFromRealtimeEvent(payload) {
   }
 
   if (typeof payload.error === "string") {
-    return {
-      kind: "error",
-      text: payload.error
-    };
+    return { kind: "error", text: payload.error };
   }
 
-  const typeValue = typeof payload.type === "string" ? payload.type.toLowerCase() : "";
-  const finalByType =
-    typeValue.includes("termination") ||
-    typeValue.includes("final") ||
-    typeValue.includes("completed");
-
-  const candidates = [
-    payload.text,
-    payload.transcript,
-    payload.delta,
-    payload.partial,
-    payload.final,
-    payload.turn?.text,
-    payload.turn?.transcript,
-    payload.turn?.final,
-    payload.result?.text,
-    payload.result?.transcript
-  ];
-
-  const transcriptText = candidates.find((value) => typeof value === "string" && value.trim());
-  if (!transcriptText) {
+  if (payload.type !== "Turn") {
+    return null;
+  }
+
+  const transcriptText = typeof payload.transcript === "string"
+    ? payload.transcript
+    : "";
+  if (!transcriptText.trim()) {
     return null;
   }
 
   return {
-    kind: finalByType || payload.is_final === true || payload.final === true ? "final" : "partial",
-    text: transcriptText
+    kind: payload.end_of_turn === true ? "final" : "partial",
+    text: transcriptText,
+    turnOrder: typeof payload.turn_order === "number"
+      ? payload.turn_order
+      : undefined,
   };
 }
 
-function downsampleToTargetSampleRate(float32Samples, inputSampleRate, targetSampleRate) {
+function downsampleToTargetSampleRate(
+  float32Samples,
+  inputSampleRate,
+  targetSampleRate,
+) {
   if (targetSampleRate >= inputSampleRate) {
     return float32Samples;
   }
@@ -154,7 +151,11 @@ function downsampleToTargetSampleRate(float32Samples, inputSampleRate, targetSam
     let total = 0;
     let count = 0;
 
-    for (let i = inputIndex; i < nextInputIndex && i < float32Samples.length; i += 1) {
+    for (
+      let i = inputIndex;
+      i < nextInputIndex && i < float32Samples.length;
+      i += 1
+    ) {
       total += float32Samples[i];
       count += 1;
     }
@@ -201,8 +202,11 @@ function connectTranscriptionSocket() {
       } catch {
         return;
       }
+      console.log(parsed);
+      console.log(JSON.stringify(parsed, null, 2));
 
       const transcriptEvent = extractTranscriptFromRealtimeEvent(parsed);
+      console.log(transcriptEvent);
       if (!transcriptEvent) {
         return;
       }
@@ -213,7 +217,10 @@ function connectTranscriptionSocket() {
       }
 
       if (transcriptEvent.kind === "final") {
-        appendCommittedTranscription(transcriptEvent.text);
+        if (transcriptEvent.turnOrder > lastCommittedTurn) {
+          lastCommittedTurn = transcriptEvent.turnOrder;
+          appendCommittedTranscription(transcriptEvent.text);
+        }
       } else {
         updatePartialTranscription(transcriptEvent.text);
       }
@@ -235,12 +242,15 @@ startButton.addEventListener("click", async () => {
   try {
     committedTranscription = "";
     partialTranscription = "";
+    lastCommittedTurn = -1;
     renderTranscription();
 
-    microphoneStream = await navigator.mediaDevices.getUserMedia({ audio: true });
+    microphoneStream = await navigator.mediaDevices.getUserMedia({
+      audio: true,
+    });
     systemStream = await navigator.mediaDevices.getDisplayMedia({
       video: true,
-      audio: true
+      audio: true,
     });
 
     console.log(systemStream.getTracks());
@@ -274,7 +284,10 @@ startButton.addEventListener("click", async () => {
     streamMuteNode.connect(audioContext.destination);
 
     streamProcessorNode.onaudioprocess = (audioProcessEvent) => {
-      if (!transcriptionSocket || transcriptionSocket.readyState !== WebSocket.OPEN) {
+      if (
+        !transcriptionSocket ||
+        transcriptionSocket.readyState !== WebSocket.OPEN
+      ) {
         return;
       }
 
@@ -282,7 +295,7 @@ startButton.addEventListener("click", async () => {
       const downsampled = downsampleToTargetSampleRate(
         channelSamples,
         audioContext.sampleRate,
-        TARGET_SAMPLE_RATE
+        TARGET_SAMPLE_RATE,
       );
       const pcm16Buffer = float32ToPcm16Buffer(downsampled);
       transcriptionSocket.send(pcm16Buffer);
@@ -292,9 +305,11 @@ startButton.addEventListener("click", async () => {
       "audio/webm;codecs=opus",
       "audio/webm",
       "audio/ogg;codecs=opus",
-      "audio/ogg"
+      "audio/ogg",
     ];
-    const selectedMimeType = preferredMimeTypes.find((type) => MediaRecorder.isTypeSupported(type));
+    const selectedMimeType = preferredMimeTypes.find((type) =>
+      MediaRecorder.isTypeSupported(type)
+    );
     console.log("selected mime type:", selectedMimeType);
     mediaRecorder = selectedMimeType
       ? new MediaRecorder(mixedAudioStream, { mimeType: selectedMimeType })
@@ -309,8 +324,10 @@ startButton.addEventListener("click", async () => {
     });
 
     mediaRecorder.addEventListener("stop", () => {
-      const audioBlob = new Blob(audioChunks, { type: mediaRecorder.mimeType || "audio/webm" });
-      console.log(audioBlob)
+      const audioBlob = new Blob(audioChunks, {
+        type: mediaRecorder.mimeType || "audio/webm",
+      });
+      console.log(audioBlob);
 
       if (recordedAudioUrl) {
         URL.revokeObjectURL(recordedAudioUrl);
@@ -352,7 +369,9 @@ stopButton.addEventListener("click", () => {
     return;
   }
 
-  if (transcriptionSocket && transcriptionSocket.readyState === WebSocket.OPEN) {
+  if (
+    transcriptionSocket && transcriptionSocket.readyState === WebSocket.OPEN
+  ) {
     transcriptionSocket.send(JSON.stringify({ type: "Terminate" }));
   }