Quentin Fuxa 2024-12-25
throw errors if websocket connection fails
@6d3d6ca132bfdfddf4910e1b6f950d01203a2165
src/live_transcription.html
--- src/live_transcription.html
+++ src/live_transcription.html
@@ -1,178 +1,240 @@
 <!DOCTYPE html>
 <html lang="en">
 <head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>Audio Transcription</title>
-    <style>
-        body {
-            font-family: 'Inter', sans-serif;
-            text-align: center;
-            margin: 20px;
-        }
-        #recordButton {
-            width: 80px;
-            height: 80px;
-            font-size: 36px;
-            border: none;
-            border-radius: 50%;
-            background-color: white;
-            cursor: pointer;
-            box-shadow: 0 0px 10px rgba(0, 0, 0, 0.2);
-            transition: background-color 0.3s ease, transform 0.2s ease;
-        }
-        #recordButton.recording {
-            background-color: #ff4d4d;
-            color: white;
-        }
-        #recordButton:active {
-            transform: scale(0.95);
-        }
-        #transcriptions {
-            margin-top: 20px;
-            font-size: 18px;
-            text-align: left;
-        }
-        .transcription {
-            display: inline;
-            color: black;
-        }
-        .buffer {
-            display: inline;
-            color: rgb(197, 197, 197);
-        }
-        .settings-container {
-            display: flex;
-            justify-content: center;
-            align-items: center;
-            gap: 15px;
-            margin-top: 20px;
-        }
-        .settings {
-            display: flex;
-            flex-direction: column;
-            align-items: flex-start;
-            gap: 5px;
-        }
-        #chunkSelector, #websocketInput {
-            font-size: 16px;
-            padding: 5px;
-            border-radius: 5px;
-            border: 1px solid #ddd;
-            background-color: #f9f9f9;
-        }
-        #websocketInput {
-            width: 200px;
-        }
-        #chunkSelector:focus, #websocketInput:focus {
-            outline: none;
-            border-color: #007bff;
-        }
-        label {
-            font-size: 14px;
-        }
-    </style>
+  <meta charset="UTF-8"/>
+  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+  <title>Audio Transcription</title>
+  <style>
+    body {
+      font-family: 'Inter', sans-serif;
+      text-align: center;
+      margin: 20px;
+    }
+    #recordButton {
+      width: 80px;
+      height: 80px;
+      font-size: 36px;
+      border: none;
+      border-radius: 50%;
+      background-color: white;
+      cursor: pointer;
+      box-shadow: 0 0px 10px rgba(0, 0, 0, 0.2);
+      transition: background-color 0.3s ease, transform 0.2s ease;
+    }
+    #recordButton.recording {
+      background-color: #ff4d4d;
+      color: white;
+    }
+    #recordButton:active {
+      transform: scale(0.95);
+    }
+    #transcriptions {
+      margin-top: 20px;
+      font-size: 18px;
+      text-align: left;
+    }
+    .transcription {
+      display: inline;
+      color: black;
+    }
+    .buffer {
+      display: inline;
+      color: rgb(197, 197, 197);
+    }
+    .settings-container {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      gap: 15px;
+      margin-top: 20px;
+    }
+    .settings {
+      display: flex;
+      flex-direction: column;
+      align-items: flex-start;
+      gap: 5px;
+    }
+    #chunkSelector,
+    #websocketInput {
+      font-size: 16px;
+      padding: 5px;
+      border-radius: 5px;
+      border: 1px solid #ddd;
+      background-color: #f9f9f9;
+    }
+    #websocketInput {
+      width: 200px;
+    }
+    #chunkSelector:focus,
+    #websocketInput:focus {
+      outline: none;
+      border-color: #007bff;
+    }
+    label {
+      font-size: 14px;
+    }
+  </style>
 </head>
 <body>
-    <div class="settings-container">
-        <button id="recordButton">🎙�</button>
-        <div class="settings">
-            <div>
-                <label for="chunkSelector">Chunk size (ms):</label>
-                <select id="chunkSelector">
-                    <option value="500">500 ms</option>
-                    <option value="1000" selected>1000 ms</option>
-                    <option value="2000">2000 ms</option>
-                    <option value="3000">3000 ms</option>
-                    <option value="4000">4000 ms</option>
-                    <option value="5000">5000 ms</option>
-                </select>
-            </div>
-            <div>
-                <label for="websocketInput">WebSocket URL:</label>
-                <input id="websocketInput" type="text" value="ws://localhost:8000/ws" />
-            </div>
-        </div>
+  <div class="settings-container">
+    <button id="recordButton">🎙�</button>
+    <div class="settings">
+      <div>
+        <label for="chunkSelector">Chunk size (ms):</label>
+        <select id="chunkSelector">
+          <option value="500">500 ms</option>
+          <option value="1000" selected>1000 ms</option>
+          <option value="2000">2000 ms</option>
+          <option value="3000">3000 ms</option>
+          <option value="4000">4000 ms</option>
+          <option value="5000">5000 ms</option>
+        </select>
+      </div>
+      <div>
+        <label for="websocketInput">WebSocket URL:</label>
+        <input id="websocketInput" type="text" value="ws://localhost:8000/ws" />
+      </div>
     </div>
-    <p id="status"></p>
+  </div>
+  <p id="status"></p>
 
-    <div id="transcriptions"></div>
+  <div id="transcriptions"></div>
 
-    <script>
-        let isRecording = false, websocket, recorder, chunkDuration = 1000, websocketUrl = "ws://localhost:8000/ws";
+  <script>
+    let isRecording = false,
+      websocket,
+      recorder,
+      chunkDuration = 1000,
+      websocketUrl = "ws://localhost:8000/ws";
 
-        const statusText = document.getElementById("status");
-        const recordButton = document.getElementById("recordButton");
-        const chunkSelector = document.getElementById("chunkSelector");
-        const websocketInput = document.getElementById("websocketInput");
-        const transcriptionsDiv = document.getElementById("transcriptions");
+    // Tracks whether the user voluntarily closed the WebSocket
+    let userClosing = false;
 
-        let fullTranscription = ""; // Store confirmed transcription
+    const statusText = document.getElementById("status");
+    const recordButton = document.getElementById("recordButton");
+    const chunkSelector = document.getElementById("chunkSelector");
+    const websocketInput = document.getElementById("websocketInput");
+    const transcriptionsDiv = document.getElementById("transcriptions");
 
-        // Update chunk duration based on the selector
-        chunkSelector.addEventListener("change", () => {
-            chunkDuration = parseInt(chunkSelector.value);
-        });
+    let fullTranscription = ""; // Store confirmed transcription
 
-        // Update WebSocket URL dynamically
-        websocketInput.addEventListener("change", () => {
-            websocketUrl = websocketInput.value;
-        });
+    // Update chunk duration based on the selector
+    chunkSelector.addEventListener("change", () => {
+      chunkDuration = parseInt(chunkSelector.value);
+    });
 
-        function setupWebSocket() {
-            websocket = new WebSocket(websocketUrl);
-            websocket.onmessage = (event) => {
-                const data = JSON.parse(event.data);
-                const { transcription, buffer } = data;
+    // Update WebSocket URL dynamically, with some basic checks
+    websocketInput.addEventListener("change", () => {
+      const urlValue = websocketInput.value.trim();
 
-                // Update confirmed transcription
-                fullTranscription += transcription;
+      // Quick check to see if it starts with ws:// or wss://
+      if (!urlValue.startsWith("ws://") && !urlValue.startsWith("wss://")) {
+        statusText.textContent =
+          "Invalid WebSocket URL. It should start with ws:// or wss://";
+        return;
+      }
+      websocketUrl = urlValue;
+      statusText.textContent = "WebSocket URL updated. Ready to connect.";
+    });
 
-                // Update the transcription display
-                transcriptionsDiv.innerHTML = `
-                    <span class="transcription">${fullTranscription}</span>
-                    <span class="buffer">${buffer}</span>
-                `;
-            };
+    function setupWebSocket() {
+      try {
+        websocket = new WebSocket(websocketUrl);
+      } catch (error) {
+        statusText.textContent =
+          "Invalid WebSocket URL. Please check the URL and try again.";
+        throw error;
+      }
 
-            websocket.onerror = () => {
-                statusText.textContent = "Error connecting to WebSocket";
-                stopRecording(); // Stop recording if WebSocket fails
-            };
+      websocket.onopen = () => {
+        statusText.textContent = "Connected to server";
+      };
+
+      websocket.onclose = (event) => {
+        if (userClosing) {
+          statusText.textContent = "WebSocket closed by user.";
+        } else {
+          statusText.textContent = "Disconnected from server (unexpected).";
+        }
+        userClosing = false;
+      };
+
+      websocket.onerror = () => {
+        statusText.textContent = "Error connecting to WebSocket";
+        stopRecording();
+      };
+
+      websocket.onmessage = (event) => {
+        const data = JSON.parse(event.data);
+        const { transcription, buffer } = data;
+
+        // Update confirmed transcription
+        fullTranscription += transcription;
+
+        // Update the transcription display
+        transcriptionsDiv.innerHTML = `
+          <span class="transcription">${fullTranscription}</span>
+          <span class="buffer">${buffer}</span>
+        `;
+      };
+    }
+
+    async function startRecording() {
+      try {
+        const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+        recorder = new MediaRecorder(stream, { mimeType: "audio/webm" });
+        recorder.ondataavailable = (e) => {
+          if (websocket && websocket.readyState === WebSocket.OPEN) {
+            websocket.send(e.data);
+          }
+        };
+        recorder.start(chunkDuration);
+        isRecording = true;
+        updateUI();
+      } catch (err) {
+        statusText.textContent =
+          "Error accessing microphone. Please allow microphone access.";
+      }
+    }
+
+    function stopRecording() {
+      userClosing = true;
+
+      recorder?.stop();
+      recorder = null;
+      isRecording = false;
+
+      websocket?.close();
+      websocket = null;
+
+      updateUI();
+    }
+
+    async function toggleRecording() {
+      if (!isRecording) {
+        fullTranscription = "";
+        transcriptionsDiv.innerHTML = "";
+
+        try {
+          setupWebSocket();
+        } catch (err) {
+          return;
         }
 
-        async function startRecording() {
-            const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
-            recorder = new MediaRecorder(stream, { mimeType: "audio/webm" });
-            recorder.ondataavailable = (e) => websocket?.send(e.data);
-            recorder.start(chunkDuration); // Use dynamic chunk duration
-            isRecording = true;
-            updateUI();
-        }
+        await startRecording();
+      } else {
+        stopRecording();
+      }
+    }
 
-        function stopRecording() {
-            recorder?.stop();
-            recorder = null;
-            isRecording = false;
-            websocket?.close();
-            websocket = null;
-            updateUI();
-        }
+    function updateUI() {
+      recordButton.classList.toggle("recording", isRecording);
+      statusText.textContent = isRecording
+        ? "Recording..."
+        : "Click to start transcription";
+    }
 
-        async function toggleRecording() {
-            if (isRecording) stopRecording();
-            else {
-                setupWebSocket();
-                await startRecording();
-            }
-        }
-
-        function updateUI() {
-            recordButton.classList.toggle("recording", isRecording);
-            statusText.textContent = isRecording ? "Recording..." : "Click to start transcription";
-        }
-
-        recordButton.addEventListener("click", toggleRecording);
-    </script>
+    recordButton.addEventListener("click", toggleRecording);
+  </script>
 </body>
 </html>
(파일 끝에 줄바꿈 문자 없음)
Add a comment
List