Streaming
Render the transcript first, then update fields when extraction completes.
For latency-sensitive UI, use ohm.audio.extractStream(...) instead of the
one-shot audio.extract. The server emits Server-Sent Events:
transcript— STT done, you can show it on screendata— extraction done, you can populate the formdone— total latency in mserror— only if something fails
The SDK parses the SSE wire and yields native StreamChunk objects.
Web
import { OHM, Recorder } from "@ohm_studio/sdk";
const ohm = new OHM(process.env.NEXT_PUBLIC_OHM_TEST_KEY!);
const rec = new Recorder();
await rec.start();
const blob = await rec.stop();
const stream = ohm.audio.extractStream({ apiSlug: "opd-clinic", file: blob });
for await (const chunk of stream) {
switch (chunk.type) {
case "transcript":
setTranscript(chunk.transcript); // show transcript ASAP
break;
case "data":
setStructured(chunk.data); // populate form fields
break;
case "done":
console.log(`finished in ${chunk.latencyMs}ms`);
break;
case "error":
throw new Error(chunk.message);
}
}React Native (Expo)
import { OHM, ExpoRecorder } from "@ohm_studio/sdk-react-native";
import { Audio } from "expo-av";
const ohm = new OHM({ apiKey: TEST_KEY, acknowledgeBundledKey: true });
const file = await new ExpoRecorder(Audio).stop();
for await (const chunk of ohm.audio.extractStream({ apiSlug: "opd", file })) {
if (chunk.type === "transcript") setTranscript(chunk.transcript);
if (chunk.type === "data") setData(chunk.data);
}Why streaming wins for UX
| Path | Time-to-first-paint | Time-to-data |
|---|---|---|
audio.extract (one-shot) | 4-6 s (no UI update) | 4-6 s |
audio.extractStream | 1-2 s (transcript) | 4-6 s |
A 60-second consult feels like 1-2 seconds of latency to the doctor because the transcript appears before extraction finishes.
Streaming costs the same as one-shot — same transcription, same extraction. The only thing changing is the wire format.
Cancellation
Break out of the for await loop to stop consuming. The server-side
extraction continues in the background but the client stops processing.
const stream = ohm.audio.extractStream({ apiSlug, file });
for await (const chunk of stream) {
if (userCancelled) break;
// ...
}Error handling
try {
for await (const chunk of stream) {
if (chunk.type === "error") throw new Error(chunk.message);
// ...
}
} catch (err) {
// network error, malformed event, or thrown chunk.error
showToast("Streaming failed — falling back to one-shot");
const r = await ohm.audio.extract({ apiSlug, file });
// ...
}