Bugfixes for server-based microphone decoding..
This commit is contained in:
+67
-21
@@ -685,34 +685,68 @@ export function App() {
|
||||
if (options.task) wsUrl.searchParams.set("task", options.task);
|
||||
|
||||
const ws = new WebSocket(wsUrl);
|
||||
|
||||
|
||||
// Ends the session locally: stops the mic (releasing the hardware and
|
||||
// the browser's recording indicator — nothing else holds this
|
||||
// MediaStream) and clears the refs so a stale onclose/onerror firing
|
||||
// later is a no-op (guarded by `liveWsRef.current === ws` below).
|
||||
const endSession = (message: string | null) => {
|
||||
liveWsRef.current = null;
|
||||
const session = liveRef.current;
|
||||
liveRef.current = null;
|
||||
void session?.mic.stop().catch(() => {});
|
||||
setLiveCommitted("");
|
||||
setLiveTentative("");
|
||||
setLiveStatus("idle");
|
||||
if (message) setError(message);
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
let data: { type: string; [key: string]: unknown };
|
||||
try {
|
||||
data = JSON.parse(event.data);
|
||||
} catch {
|
||||
// Not a frame we understand — ignore rather than crash the handler
|
||||
// and strand the session in "listening"/"stopping" forever.
|
||||
return;
|
||||
}
|
||||
if (data.type === "update") {
|
||||
setLiveCommitted(data.update.committedText);
|
||||
setLiveTentative(data.update.tentativeText);
|
||||
setLivePending(data.update.pendingFinalPasses ?? 0);
|
||||
if (data.update.error) setError(data.update.error);
|
||||
const update = data.update as {
|
||||
committedText: string;
|
||||
tentativeText: string;
|
||||
pendingFinalPasses?: number;
|
||||
error?: string;
|
||||
};
|
||||
setLiveCommitted(update.committedText);
|
||||
setLiveTentative(update.tentativeText);
|
||||
setLivePending(update.pendingFinalPasses ?? 0);
|
||||
if (update.error) setError(update.error);
|
||||
} else if (data.type === "result") {
|
||||
setResult(data.result);
|
||||
setLiveCommitted("");
|
||||
setLiveTentative("");
|
||||
setLiveStatus("idle");
|
||||
liveWsRef.current = null;
|
||||
liveRef.current = null;
|
||||
setResult(data.result as TranscriptResult);
|
||||
endSession(null);
|
||||
} else if (data.type === "error") {
|
||||
setError(data.error);
|
||||
setLiveStatus("idle");
|
||||
liveWsRef.current = null;
|
||||
liveRef.current = null;
|
||||
endSession(typeof data.error === "string" ? data.error : "Live transcription failed.");
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = () => {
|
||||
setError("WebSocket connection error.");
|
||||
setLiveStatus("idle");
|
||||
liveWsRef.current = null;
|
||||
liveRef.current = null;
|
||||
// A genuine connection failure also fires "close" right after, which
|
||||
// does the actual cleanup — this only needs to supply the message
|
||||
// (onclose can't distinguish a clean vs. abrupt closure on its own).
|
||||
if (liveWsRef.current === ws) {
|
||||
setError(
|
||||
"Lost connection to the server during live transcription. The microphone has been stopped.",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
// Fires after a normal finish too (onmessage already nulled the ref
|
||||
// there), so this only acts on an *unexpected* disconnect — otherwise
|
||||
// stop() would hang forever waiting for a "result" that never comes,
|
||||
// and the mic would stay open with no code left holding a reference
|
||||
// to it.
|
||||
if (liveWsRef.current === ws) endSession(null);
|
||||
};
|
||||
|
||||
ws.onopen = async () => {
|
||||
@@ -796,7 +830,19 @@ export function App() {
|
||||
await session.mic.stop();
|
||||
|
||||
if (liveWsRef.current) {
|
||||
liveWsRef.current.send(JSON.stringify({ type: "stop" }));
|
||||
if (liveWsRef.current.readyState === WebSocket.OPEN) {
|
||||
liveWsRef.current.send(JSON.stringify({ type: "stop" }));
|
||||
} else {
|
||||
// The connection is already closing/closed — send() would silently
|
||||
// drop the "stop" frame (WebSocket.send() does not throw for a
|
||||
// non-OPEN socket), and nothing would ever answer with a "result",
|
||||
// leaving the UI stuck on "Finishing up…" forever. Its close/error
|
||||
// handler already stops the mic; just clear the ref and settle.
|
||||
liveWsRef.current = null;
|
||||
liveRef.current = null;
|
||||
setLiveStatus("idle");
|
||||
setError("Live transcription lost its connection to the server before it could finish.");
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const final = await session.transcriber!.stop();
|
||||
|
||||
+9
-1
@@ -65,7 +65,15 @@ export const liveWebSocket = {
|
||||
|
||||
message(ws: ServerWebSocket<LiveSessionData>, message: string | Buffer | Uint8Array) {
|
||||
if (typeof message === "string") {
|
||||
const parsed = JSON.parse(message);
|
||||
let parsed: any;
|
||||
try {
|
||||
parsed = JSON.parse(message);
|
||||
} catch {
|
||||
// Not a frame we understand — ignore rather than crash the handler
|
||||
// (which would otherwise take the whole connection down for every
|
||||
// client sharing this Bun.serve process, not just this session).
|
||||
return;
|
||||
}
|
||||
if (parsed.type === "stop") {
|
||||
const transcriber = (ws as any).transcriber as LiveTranscriber;
|
||||
transcriber.stop().then((result) => {
|
||||
|
||||
Reference in New Issue
Block a user