OHMOHM Studio

Streaming

Render the transcript first, then update fields when extraction completes.

View as Markdown

For latency-sensitive UI, use ohm.audio.extractStream(...) instead of the one-shot audio.extract. The server emits Server-Sent Events:

  1. transcript — STT done, you can show it on screen
  2. data — extraction done, you can populate the form
  3. done — total latency in ms
  4. error — only if something fails

The SDK parses the SSE wire and yields native StreamChunk objects.

Web

Recorder.tsx
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

PathTime-to-first-paintTime-to-data
audio.extract (one-shot)4-6 s (no UI update)4-6 s
audio.extractStream1-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 });
  // ...
}