Core API
Use these APIs to build a custom framework integration or control audio capture
and text editing directly. Most React apps should use
@voiceinput/react instead.
The package provides a recording session, a browser audio source, and a text engine that inserts speech while preserving unrelated edits.
Install
Section titled “Install”npm install @voiceinput/corepnpm add @voiceinput/coreSession
Section titled “Session”createVoiceInputSession coordinates option validation, two-phase audio
startup, provider streaming, duration limits, normalized state, and cleanup.
import { createBrowserAudioSource, createVoiceInputSession,} from "@voiceinput/core";
const session = createVoiceInputSession({ provider, audioSource: createBrowserAudioSource(), language: "en-CA", vocabulary: ["VoiceInput"], endpointing: { silenceMs: 650 }, connectionTimeoutMs: 15_000, finalizationTimeoutMs: 15_000, stopWhenHidden: true, maxDurationMs: 5 * 60 * 1_000,});
const unsubscribe = session.subscribe((event) => { console.log(event.type);});
await session.start();await session.stop();unsubscribe();Actions are start, stop, cancel, and toggle. stop is graceful and
preserves visible dictated text; cancel aborts immediately. The default
maximum duration is five minutes, with one warning 30 seconds before cutoff.
Once microphone audio is acquired, provider connection and audio activation must
complete within connectionTimeoutMs (15 seconds by default). Expiry aborts the
full run, releases acquired audio, reports a retryable network-error, and
permits a fresh start().
Audio/provider shutdown has a finalizationTimeoutMs budget (15 seconds by
default), including the final audio flush. If finalization times out, the
session releases resources, promotes the last interim text, runs any configured
text transform under its separate transformTimeoutMs budget, returns to
idle, and emits stop with reason finalization-timeout. The snapshot
finalTranscript includes that preserved fallback; it is not a guarantee that
the provider finalized every phrase. Errors while flushing audio or finalizing
after Stop preserve text in the same way: the session completes the text engine,
reports an error event, keeps the error in its snapshot, and returns to idle
with the original stop reason. Errors during recording still terminate the run.
stopWhenHidden defaults to true: switching tabs or apps stops recording. Set
it to false for desktop workflows that need background dictation. Page
hide/freeze still stops the session, and browsers may interrupt microphone
capture regardless of this option.
The immutable snapshot exposes status, transcript, interimTranscript,
finalTranscript, and error. Status values are idle,
requesting-permission, connecting, listening, stopping, processing,
and error. Stop reasons are user, max-duration, replaced, max-length,
target-unavailable, backgrounded, and finalization-timeout. A text-limit
event reports a constrained insertion.
Final parts use the same boundary policy as field insertion: outer provider
whitespace is normalized, word boundaries are added when needed, punctuation is
kept adjacent, empty parts are ignored, and consecutive Han, Hiragana, and
Katakana parts are not separated. finalTranscript is cumulative; transcript
adds the current normalized interim part. Session final events expose the raw
provider part as text and the cumulative normalized value as transcript.
Audio source lifecycle
Section titled “Audio source lifecycle”VoiceAudioSource.prepare({ sampleRate, abortSignal, onAcquired }) returns a
PreparedVoiceAudioSource with a PCM16 stream plus start, stop, and
abort. Preparation can request permission. Once acquired, audio is buffered
while the provider connects; delivery to the provider starts after connection.
Custom sources must call the optional onAcquired() callback as soon as they
hold live audio resources; this starts the connection deadline even if later
preparation is still pending.
createBrowserAudioSource supplies the production browser implementation:
const audioSource = createBrowserAudioSource({ constraints: { echoCancellation: true }, frameDurationMs: 20, // Optional: use a self-hosted module under strict CSP. workletModuleUrl: "/voiceinput-worklet.js",});It captures mono audio through an AudioWorklet, resamples to the adapter’s
declared rate, emits Int16Array frames, resumes suspended Safari contexts, and
tears down tracks, nodes, and contexts on every terminal path.
The default worklet uses a temporary Blob URL. For a policy without blob:,
write VOICE_INPUT_AUDIO_WORKLET_SOURCE to a same-origin JavaScript asset at
build time and pass its URL as workletModuleUrl. See the
Content Security Policy guide
for the copy script and exact directives.
Use getBrowserVoiceInputSupport() for a capability report. It checks secure
context, media devices, getUserMedia, AudioContext, and AudioWorklet.
normalizeBrowserAudioError(error) converts browser failures into
VoiceInputError.
Text ownership engine
Section titled “Text ownership engine”createVoiceInputTextEngine inserts transcript text without taking ownership of
unrelated user content:
let value = textarea.value;
const engine = createVoiceInputTextEngine({ controlled: { getValue: () => value, onValueChange: (nextValue) => { value = nextValue; }, }, interimBehavior: "inline", transformTranscript: async (text) => text.trim(), transformTimeoutMs: 10_000,});
engine.setTarget(textarea);engine.captureSelection();engine.begin();engine.applyInterim("draft");engine.applyFinal("final text");const completion = engine.complete();await completion.result;Supported targets are <textarea> and <input> types text, search, url,
and tel. The engine tracks provisional, finalized, frozen, and transformed
spans. If a user edits or moves the caret, it freezes text it can no longer
prove ownership of and re-anchors later speech. Uncontrolled targets receive a
bubbling native input event.
For a controlled target, pass each committed application value to
reconcileControlledValue.
interimBehavior: "inline" inserts replaceable interim text. "expose" keeps
interim text out of the field while still reporting it in snapshots.
Public API
Section titled “Public API”Session and errors:
createVoiceInputSessiongetVoiceInputErrorMessageVoiceInputSession,CreateVoiceInputSessionOptionsVoiceInputSnapshot,VoiceInputStatus,VoiceInputSessionEventVoiceInputStopReasonVoiceAudioSource,VoiceAudioSourcePrepareOptionsPreparedVoiceAudioSourceVoiceInputError,VoiceInputErrorCode,VoiceInputErrorOptions
Browser audio:
VOICE_INPUT_AUDIO_WORKLET_SOURCEcreateBrowserAudioSource,CreateBrowserAudioSourceOptionsgetBrowserVoiceInputSupportBrowserVoiceInputSupport,BrowserVoiceInputCapabilitynormalizeBrowserAudioError
Text ownership:
createVoiceInputTextEngine,CreateVoiceInputTextEngineOptionsVoiceInputTextEngine,VoiceInputTextEngineSnapshotVoiceInputTextTargetVoiceInputTextSelectionVoiceInputTextSpan,VoiceInputTextSpanStateVoiceInputControlledTextBindingVoiceInputInterimBehaviorVoiceInputTransformTranscriptVoiceInputTextCompletion,VoiceInputTextLimitVoiceInputTextEngineEvent,VoiceInputTextWritableChange
Provider boundary
Section titled “Provider boundary”The session accepts any VoiceInputProviderV1. Provider-specific models,
tokens, and settings belong in adapter factories, not core options. See the
@voiceinput/provider guide
to implement an adapter.
The text engine exposes undo(), redo(), isWritable(), and subscribe().
Subscribers receive writable-change, text-limit, target-unavailable, and
reset events. See editing and undo for the
field behavior and
@voiceinput/provider for segment
identity and audio backpressure.