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);
|
if (options.task) wsUrl.searchParams.set("task", options.task);
|
||||||
|
|
||||||
const ws = new WebSocket(wsUrl);
|
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) => {
|
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") {
|
if (data.type === "update") {
|
||||||
setLiveCommitted(data.update.committedText);
|
const update = data.update as {
|
||||||
setLiveTentative(data.update.tentativeText);
|
committedText: string;
|
||||||
setLivePending(data.update.pendingFinalPasses ?? 0);
|
tentativeText: string;
|
||||||
if (data.update.error) setError(data.update.error);
|
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") {
|
} else if (data.type === "result") {
|
||||||
setResult(data.result);
|
setResult(data.result as TranscriptResult);
|
||||||
setLiveCommitted("");
|
endSession(null);
|
||||||
setLiveTentative("");
|
|
||||||
setLiveStatus("idle");
|
|
||||||
liveWsRef.current = null;
|
|
||||||
liveRef.current = null;
|
|
||||||
} else if (data.type === "error") {
|
} else if (data.type === "error") {
|
||||||
setError(data.error);
|
endSession(typeof data.error === "string" ? data.error : "Live transcription failed.");
|
||||||
setLiveStatus("idle");
|
|
||||||
liveWsRef.current = null;
|
|
||||||
liveRef.current = null;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onerror = () => {
|
ws.onerror = () => {
|
||||||
setError("WebSocket connection error.");
|
// A genuine connection failure also fires "close" right after, which
|
||||||
setLiveStatus("idle");
|
// does the actual cleanup — this only needs to supply the message
|
||||||
liveWsRef.current = null;
|
// (onclose can't distinguish a clean vs. abrupt closure on its own).
|
||||||
liveRef.current = null;
|
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 () => {
|
ws.onopen = async () => {
|
||||||
@@ -796,7 +830,19 @@ export function App() {
|
|||||||
await session.mic.stop();
|
await session.mic.stop();
|
||||||
|
|
||||||
if (liveWsRef.current) {
|
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 {
|
} else {
|
||||||
try {
|
try {
|
||||||
const final = await session.transcriber!.stop();
|
const final = await session.transcriber!.stop();
|
||||||
|
|||||||
+9
-1
@@ -65,7 +65,15 @@ export const liveWebSocket = {
|
|||||||
|
|
||||||
message(ws: ServerWebSocket<LiveSessionData>, message: string | Buffer | Uint8Array) {
|
message(ws: ServerWebSocket<LiveSessionData>, message: string | Buffer | Uint8Array) {
|
||||||
if (typeof message === "string") {
|
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") {
|
if (parsed.type === "stop") {
|
||||||
const transcriber = (ws as any).transcriber as LiveTranscriber;
|
const transcriber = (ws as any).transcriber as LiveTranscriber;
|
||||||
transcriber.stop().then((result) => {
|
transcriber.stop().then((result) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user