qfuxa commited on
Commit
50bbd26
·
1 Parent(s): 0553b75

throw errors if websocket connection fails

Browse files
Files changed (1) hide show
  1. src/live_transcription.html +226 -164
src/live_transcription.html CHANGED
@@ -1,178 +1,240 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Audio Transcription</title>
7
- <style>
8
- body {
9
- font-family: 'Inter', sans-serif;
10
- text-align: center;
11
- margin: 20px;
12
- }
13
- #recordButton {
14
- width: 80px;
15
- height: 80px;
16
- font-size: 36px;
17
- border: none;
18
- border-radius: 50%;
19
- background-color: white;
20
- cursor: pointer;
21
- box-shadow: 0 0px 10px rgba(0, 0, 0, 0.2);
22
- transition: background-color 0.3s ease, transform 0.2s ease;
23
- }
24
- #recordButton.recording {
25
- background-color: #ff4d4d;
26
- color: white;
27
- }
28
- #recordButton:active {
29
- transform: scale(0.95);
30
- }
31
- #transcriptions {
32
- margin-top: 20px;
33
- font-size: 18px;
34
- text-align: left;
35
- }
36
- .transcription {
37
- display: inline;
38
- color: black;
39
- }
40
- .buffer {
41
- display: inline;
42
- color: rgb(197, 197, 197);
43
- }
44
- .settings-container {
45
- display: flex;
46
- justify-content: center;
47
- align-items: center;
48
- gap: 15px;
49
- margin-top: 20px;
50
- }
51
- .settings {
52
- display: flex;
53
- flex-direction: column;
54
- align-items: flex-start;
55
- gap: 5px;
56
- }
57
- #chunkSelector, #websocketInput {
58
- font-size: 16px;
59
- padding: 5px;
60
- border-radius: 5px;
61
- border: 1px solid #ddd;
62
- background-color: #f9f9f9;
63
- }
64
- #websocketInput {
65
- width: 200px;
66
- }
67
- #chunkSelector:focus, #websocketInput:focus {
68
- outline: none;
69
- border-color: #007bff;
70
- }
71
- label {
72
- font-size: 14px;
73
- }
74
- </style>
 
 
75
  </head>
76
  <body>
77
- <div class="settings-container">
78
- <button id="recordButton">🎙️</button>
79
- <div class="settings">
80
- <div>
81
- <label for="chunkSelector">Chunk size (ms):</label>
82
- <select id="chunkSelector">
83
- <option value="500">500 ms</option>
84
- <option value="1000" selected>1000 ms</option>
85
- <option value="2000">2000 ms</option>
86
- <option value="3000">3000 ms</option>
87
- <option value="4000">4000 ms</option>
88
- <option value="5000">5000 ms</option>
89
- </select>
90
- </div>
91
- <div>
92
- <label for="websocketInput">WebSocket URL:</label>
93
- <input id="websocketInput" type="text" value="ws://localhost:8000/ws" />
94
- </div>
95
- </div>
96
  </div>
97
- <p id="status"></p>
98
-
99
- <div id="transcriptions"></div>
100
-
101
- <script>
102
- let isRecording = false, websocket, recorder, chunkDuration = 1000, websocketUrl = "ws://localhost:8000/ws";
103
-
104
- const statusText = document.getElementById("status");
105
- const recordButton = document.getElementById("recordButton");
106
- const chunkSelector = document.getElementById("chunkSelector");
107
- const websocketInput = document.getElementById("websocketInput");
108
- const transcriptionsDiv = document.getElementById("transcriptions");
109
-
110
- let fullTranscription = ""; // Store confirmed transcription
111
-
112
- // Update chunk duration based on the selector
113
- chunkSelector.addEventListener("change", () => {
114
- chunkDuration = parseInt(chunkSelector.value);
115
- });
116
-
117
- // Update WebSocket URL dynamically
118
- websocketInput.addEventListener("change", () => {
119
- websocketUrl = websocketInput.value;
120
- });
121
-
122
- function setupWebSocket() {
123
- websocket = new WebSocket(websocketUrl);
124
- websocket.onmessage = (event) => {
125
- const data = JSON.parse(event.data);
126
- const { transcription, buffer } = data;
127
-
128
- // Update confirmed transcription
129
- fullTranscription += transcription;
130
-
131
- // Update the transcription display
132
- transcriptionsDiv.innerHTML = `
133
- <span class="transcription">${fullTranscription}</span>
134
- <span class="buffer">${buffer}</span>
135
- `;
136
- };
137
-
138
- websocket.onerror = () => {
139
- statusText.textContent = "Error connecting to WebSocket";
140
- stopRecording(); // Stop recording if WebSocket fails
141
- };
142
- }
143
 
144
- async function startRecording() {
145
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
146
- recorder = new MediaRecorder(stream, { mimeType: "audio/webm" });
147
- recorder.ondataavailable = (e) => websocket?.send(e.data);
148
- recorder.start(chunkDuration); // Use dynamic chunk duration
149
- isRecording = true;
150
- updateUI();
151
- }
152
 
153
- function stopRecording() {
154
- recorder?.stop();
155
- recorder = null;
156
- isRecording = false;
157
- websocket?.close();
158
- websocket = null;
159
- updateUI();
160
- }
161
 
162
- async function toggleRecording() {
163
- if (isRecording) stopRecording();
164
- else {
165
- setupWebSocket();
166
- await startRecording();
167
- }
168
- }
 
 
 
169
 
170
- function updateUI() {
171
- recordButton.classList.toggle("recording", isRecording);
172
- statusText.textContent = isRecording ? "Recording..." : "Click to start transcription";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  }
174
 
175
- recordButton.addEventListener("click", toggleRecording);
176
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  </body>
178
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
+ <meta charset="UTF-8"/>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>Audio Transcription</title>
7
+ <style>
8
+ body {
9
+ font-family: 'Inter', sans-serif;
10
+ text-align: center;
11
+ margin: 20px;
12
+ }
13
+ #recordButton {
14
+ width: 80px;
15
+ height: 80px;
16
+ font-size: 36px;
17
+ border: none;
18
+ border-radius: 50%;
19
+ background-color: white;
20
+ cursor: pointer;
21
+ box-shadow: 0 0px 10px rgba(0, 0, 0, 0.2);
22
+ transition: background-color 0.3s ease, transform 0.2s ease;
23
+ }
24
+ #recordButton.recording {
25
+ background-color: #ff4d4d;
26
+ color: white;
27
+ }
28
+ #recordButton:active {
29
+ transform: scale(0.95);
30
+ }
31
+ #transcriptions {
32
+ margin-top: 20px;
33
+ font-size: 18px;
34
+ text-align: left;
35
+ }
36
+ .transcription {
37
+ display: inline;
38
+ color: black;
39
+ }
40
+ .buffer {
41
+ display: inline;
42
+ color: rgb(197, 197, 197);
43
+ }
44
+ .settings-container {
45
+ display: flex;
46
+ justify-content: center;
47
+ align-items: center;
48
+ gap: 15px;
49
+ margin-top: 20px;
50
+ }
51
+ .settings {
52
+ display: flex;
53
+ flex-direction: column;
54
+ align-items: flex-start;
55
+ gap: 5px;
56
+ }
57
+ #chunkSelector,
58
+ #websocketInput {
59
+ font-size: 16px;
60
+ padding: 5px;
61
+ border-radius: 5px;
62
+ border: 1px solid #ddd;
63
+ background-color: #f9f9f9;
64
+ }
65
+ #websocketInput {
66
+ width: 200px;
67
+ }
68
+ #chunkSelector:focus,
69
+ #websocketInput:focus {
70
+ outline: none;
71
+ border-color: #007bff;
72
+ }
73
+ label {
74
+ font-size: 14px;
75
+ }
76
+ </style>
77
  </head>
78
  <body>
79
+ <div class="settings-container">
80
+ <button id="recordButton">🎙️</button>
81
+ <div class="settings">
82
+ <div>
83
+ <label for="chunkSelector">Chunk size (ms):</label>
84
+ <select id="chunkSelector">
85
+ <option value="500">500 ms</option>
86
+ <option value="1000" selected>1000 ms</option>
87
+ <option value="2000">2000 ms</option>
88
+ <option value="3000">3000 ms</option>
89
+ <option value="4000">4000 ms</option>
90
+ <option value="5000">5000 ms</option>
91
+ </select>
92
+ </div>
93
+ <div>
94
+ <label for="websocketInput">WebSocket URL:</label>
95
+ <input id="websocketInput" type="text" value="ws://localhost:8000/ws" />
96
+ </div>
 
97
  </div>
98
+ </div>
99
+ <p id="status"></p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
+ <div id="transcriptions"></div>
 
 
 
 
 
 
 
102
 
103
+ <script>
104
+ let isRecording = false,
105
+ websocket,
106
+ recorder,
107
+ chunkDuration = 1000,
108
+ websocketUrl = "ws://localhost:8000/ws";
 
 
109
 
110
+ // Tracks whether the user voluntarily closed the WebSocket
111
+ let userClosing = false;
112
+
113
+ const statusText = document.getElementById("status");
114
+ const recordButton = document.getElementById("recordButton");
115
+ const chunkSelector = document.getElementById("chunkSelector");
116
+ const websocketInput = document.getElementById("websocketInput");
117
+ const transcriptionsDiv = document.getElementById("transcriptions");
118
+
119
+ let fullTranscription = ""; // Store confirmed transcription
120
 
121
+ // Update chunk duration based on the selector
122
+ chunkSelector.addEventListener("change", () => {
123
+ chunkDuration = parseInt(chunkSelector.value);
124
+ });
125
+
126
+ // Update WebSocket URL dynamically, with some basic checks
127
+ websocketInput.addEventListener("change", () => {
128
+ const urlValue = websocketInput.value.trim();
129
+
130
+ // Quick check to see if it starts with ws:// or wss://
131
+ if (!urlValue.startsWith("ws://") && !urlValue.startsWith("wss://")) {
132
+ statusText.textContent =
133
+ "Invalid WebSocket URL. It should start with ws:// or wss://";
134
+ return;
135
+ }
136
+ websocketUrl = urlValue;
137
+ statusText.textContent = "WebSocket URL updated. Ready to connect.";
138
+ });
139
+
140
+ function setupWebSocket() {
141
+ try {
142
+ websocket = new WebSocket(websocketUrl);
143
+ } catch (error) {
144
+ statusText.textContent =
145
+ "Invalid WebSocket URL. Please check the URL and try again.";
146
+ throw error;
147
+ }
148
+
149
+ websocket.onopen = () => {
150
+ statusText.textContent = "Connected to server";
151
+ };
152
+
153
+ websocket.onclose = (event) => {
154
+ if (userClosing) {
155
+ statusText.textContent = "WebSocket closed by user.";
156
+ } else {
157
+ statusText.textContent = "Disconnected from server (unexpected).";
158
+ }
159
+ userClosing = false;
160
+ };
161
+
162
+ websocket.onerror = () => {
163
+ statusText.textContent = "Error connecting to WebSocket";
164
+ stopRecording();
165
+ };
166
+
167
+ websocket.onmessage = (event) => {
168
+ const data = JSON.parse(event.data);
169
+ const { transcription, buffer } = data;
170
+
171
+ // Update confirmed transcription
172
+ fullTranscription += transcription;
173
+
174
+ // Update the transcription display
175
+ transcriptionsDiv.innerHTML = `
176
+ <span class="transcription">${fullTranscription}</span>
177
+ <span class="buffer">${buffer}</span>
178
+ `;
179
+ };
180
+ }
181
+
182
+ async function startRecording() {
183
+ try {
184
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
185
+ recorder = new MediaRecorder(stream, { mimeType: "audio/webm" });
186
+ recorder.ondataavailable = (e) => {
187
+ if (websocket && websocket.readyState === WebSocket.OPEN) {
188
+ websocket.send(e.data);
189
+ }
190
+ };
191
+ recorder.start(chunkDuration);
192
+ isRecording = true;
193
+ updateUI();
194
+ } catch (err) {
195
+ statusText.textContent =
196
+ "Error accessing microphone. Please allow microphone access.";
197
+ }
198
+ }
199
+
200
+ function stopRecording() {
201
+ userClosing = true;
202
+
203
+ recorder?.stop();
204
+ recorder = null;
205
+ isRecording = false;
206
+
207
+ websocket?.close();
208
+ websocket = null;
209
+
210
+ updateUI();
211
+ }
212
+
213
+ async function toggleRecording() {
214
+ if (!isRecording) {
215
+ fullTranscription = "";
216
+ transcriptionsDiv.innerHTML = "";
217
+
218
+ try {
219
+ setupWebSocket();
220
+ } catch (err) {
221
+ return;
222
  }
223
 
224
+ await startRecording();
225
+ } else {
226
+ stopRecording();
227
+ }
228
+ }
229
+
230
+ function updateUI() {
231
+ recordButton.classList.toggle("recording", isRecording);
232
+ statusText.textContent = isRecording
233
+ ? "Recording..."
234
+ : "Click to start transcription";
235
+ }
236
+
237
+ recordButton.addEventListener("click", toggleRecording);
238
+ </script>
239
  </body>
240
  </html>