Realtime Overview
Define live input, choose Socket.IO or HTTP, reconcile result state, and recover conservatively.
Realtime means the audio is still arriving when transcription or diarization begins. A streamed response doesn't make a complete uploaded file realtime.
1. Choose by source lifecycle
| Source state when processing starts | Choose | Contract |
|---|---|---|
| A long or large complete meeting, podcast, or archive | Batch REST | Upload once, receive jobId, and poll a job |
| One complete latency-sensitive conversational unit | Fast transcription through Socket.IO audio_file or HTTP POST /realtime/http/stt | Send the whole unit; finish on is_final: true |
| Audio still arriving from a microphone, call, or live source | Realtime ASR or diarization | Send framed PCM as it arrives; reconcile provisional and final state |
TTS can stream generated output, but it isn't the live audio-input lifecycle defined here. See the concrete transport guides for TTS behavior.
2. Choose the live transport
| Concern | Socket.IO with SDK 0.18.0 | Direct framed HTTP |
|---|---|---|
| Released wrapper | JavaScript and Python | None |
| Configuration | API_URL and API_KEY; SDK defaults to /socket.io | API_URL and API_KEY; no Socket.IO path |
| Live ASR input | SDK stream sends audio_stream | One POST /realtime/http/stt-stream per framed chunk |
| Live diarization input | SDK stream sends diarization_stream | One POST /realtime/http/diarization-stream per framed chunk |
| Results | transcription_result and diarization_result events | application/x-ndjson response records |
| Framing owner | SDK creates and routes frames by UUID | App creates every 18-byte header and reuses the UUID |
| Cleanup owner | Close the stream, then disconnect the client | Send a final frame, finish or stop reads, and close response bodies |
Use the SDK transport when the runtime supports Socket.IO. Use direct HTTP when the trusted runtime can't use Socket.IO and can implement the exact framing and response contract itself.
Both live transports require x-api-key and PCM16 little-endian, 16 kHz, mono
audio. Start them from a trusted backend. Each input frame contains 16 raw UUID
bytes, a flags byte, a language byte, then PCM. The concrete transport guides
below show the exact layout.
3. Create one state record per stream
Before sending audio, create a fresh UUID and one app-owned state record for that stream. Track:
- the stream
idand an app-assigned arrival counter; - one replaceable provisional transcription;
- committed final-event words in observed arrival order;
- whether speech-final and stream-final signals arrived;
- closed and active diarization segments; and
- the first structured error plus the overall deadline.
Keep this state independent from the socket or HTTP reader. A transport close must not erase committed text, and a late provisional response must not replace newer or finalized state.
4. Reconcile ASR arrival order and finality
The response contains seq, but the current public Realtime contract does not
define ordering or uniqueness semantics for it. Assign a local arrival number,
check both final flags on every response, and apply these mutations:
| Signal | Meaning | State action |
|---|---|---|
| Result event arrives | New observed state | Record a local arrival number; keep the server seq only as diagnostic data |
is_final: false and is_speech_final: false | Provisional text | Replace the current provisional display |
is_speech_final: true | The model detected an end-of-speech boundary | Commit that event's words in arrival order, then clear the provisional value it supersedes while the stream may continue |
is_final: true | Final result for the transcription stream | Commit that event once, clear superseded provisional text, and mark the stream result terminal |
When both final flags are true on one response, commit it once and record both
facts. Never infer either flag from a quiet interval, a closed connection, or a
completed close() call.
Build SRT or WebVTT only from finalized word timing. RealtimeSubtitles
deduplicates by id:seq, so it can collapse distinct final events while the
server supplies non-distinct seq values. For this contract, collect final
words in observed arrival order and render them with Subtitles.
5. Reconcile diarization as an evolving timeline
For each diarization_result or HTTP diarization record:
- Preserve and deduplicate
final_segments; these closed segments don't change. - Replace the prior
active_segmentscollection with the latest one; these segments can evolve or become final. - Treat
is_final: trueas the last response for that diarization stream. - Reconcile the latest speaker timeline with finalized word timing using an explicit overlap rule.
Speaker labels represent relative turns in this stream, not real-world identity.
6. End with separate deadlines and explicit cleanup
Use separate finite deadlines for connection, the whole session, each send or read, and the final-result wait. Then end the stream in this order:
- Stop the audio producer so it can't enqueue more PCM.
- Send exactly one documented final frame.
- Wait only until the final-result deadline for the relevant final signal.
- Record whether termination was complete, timed out, or failed.
- Release the transport in a
finallyblock or async context.
For realtime ASR in SDK 0.18.0, JavaScript
stream.close(timeoutSeconds) and Python
stream.close(timeout_seconds=...) send the final frame and wait for
protocol-level is_final, a routed error, or the supplied timeout. They return
when the wait expires instead of raising a timeout error. That return isn't
proof that is_final arrived; inspect the state recorded by the callbacks.
is_speech_final is only an utterance boundary and does not release the close
wait. The diarization helper likewise returns its best-known timeline if the
final wait expires.
After stream-level termination, JavaScript must still call disconnect() in
finally. Python must still exit the client async context. For direct HTTP,
cancel an expired request, and close its response reader.
7. Treat disconnect outcome as ambiguous
The public contracts define neither session resumption nor idempotent audio frame replay. They don't guarantee what server-side state survives a dropped Socket.IO connection or interrupted HTTP request. Recover across a new stream boundary:
- Stop feeding the old stream and close its transport.
- Preserve committed results, discard unresolved provisional text, and mark the uncertain audio interval.
- Retry only if the structured error and app policy allow it and the overall deadline remains. Bound backoff, jitter, attempts, and elapsed time.
- Reconnect with a fresh UUID and send a new start frame.
- Keep the new stream's results separate until the app explicitly joins the two committed timelines.
Don't reuse the old UUID or replay frames under an assumption of server-side deduplication. If the app retained audio from the uncertain interval, process it through an explicit recovery path instead of silently splicing it into the new live stream. See Errors and Rate Limits.
8. Continue with a transport, reference, and recipe
Choose one transport, run its smallest representative live stream, verify the right final signal, and exercise timeout and disconnect paths before production.
Socket.IO transport
Use SDK 0.18.0 events, binary frames, result routing, and explicit disconnects.
Framed HTTP transport
Implement one binary request per live chunk and parse bounded NDJSON responses.
Realtime event reference
Inspect exact ASR, diarization, frame, finality, and error schemas.
Tested live recipe
Stream PCM with both released SDKs, reconcile state, terminate, and export WebVTT.