Transcriber¶
UI ยท v0.1.0
Record from the microphone and show live captions, for any server speaking the transcriber contract. Headless capture (resampled PCM16 in numbered chunks), a transcript store, and React recorder and caption components.
| Install | copit add @chanx-kit-ui/transcriber |
| Or with shadcn | npx shadcn add https://huynguyengl99.github.io/chanx-kit/r/transcriber.json |
| Contract | transcriber@1 |
| Works with | audio-stream-in, deepgram, elevenlabs, fake-voice, openai, pipecat-bridge |
| Requires UI kits | audio |
| Frameworks | react (the core is framework-free) |
| npm packages | @chanx-js/client@^0.1.2 |
| Source | ui/transcriber |
Record from the microphone and show live captions, for any server speaking
transcriber@1: fake-voice while you build,
deepgram (or any provider kit's stt part) in production. Switching
provider changes one server class, not this UI.
React¶
import { Recorder, TranscriptView, useTranscriber } from '@/chanx-kit/transcriber/react';
import '@/chanx-kit/transcriber/transcriber.css';
import { voice } from '@/generated';
export function Dictation({ session }: { session: string }) {
const { utterances, speaking, recording, level, error, status, start, stop } = useTranscriber(
voice,
voice.topics.transcriberTopic.with({ session }),
);
return (
<>
<Recorder recording={recording} level={level} error={error}
onStart={start} onStop={stop} disabled={status !== 'open'} />
<TranscriptView utterances={utterances} speaking={speaking} />
</>
);
}
Another tab, or captions on a shared screen, watches the same session read-only:
What the capture does¶
getUserMedia(echo cancellation, noise suppression and auto gain on), an AudioWorklet that downmixes to mono, resampling from the device rate to the rate the server'saudio_configasks for (24 kHz by default), PCM16, and fixed chunks (100 ms by default), numbered from 0 and sent as base64.- The worklet is inline JS loaded from a
BlobURL, so no bundler setup is needed. If your Content-Security-Policy forbidsblob:workers, serveCAPTURE_WORKLETas a file and passworkletUrl. start()must run from a user gesture (a click): browsers keep audio contexts suspended until one. Stopping sends the last partial chunk, thenaudio_end, so the provider flushes its final words.- A
transcriber_errorthat ends the stream (limit_reached,provider_unavailable,bad_format) stops recording and shows inerror;audio_gapdoes not.
Test on real devices
Microphone permissions, iOS Safari's audio sample rates and autoplay rules differ by platform. The capture is unit tested with a fake microphone, and each release should be tried on a real phone.
Without a framework¶
import { connectTranscriber, utteranceText } from '@/chanx-kit/transcriber';
const dictation = connectTranscriber(client, voice, voice.topics.transcriberTopic.with({ session }));
dictation.transcript.subscribe(() =>
render(dictation.transcript.getSnapshot().utterances.map(utteranceText)),
);
dictation.start();
button.onclick = () => dictation.record();
The pieces are exported separately too: createTranscript (the store),
createRecorder (microphone to messages), openMicrophone, and the pure helpers
Resampler, toPcm16, Chunker and toBase64.
Styling¶
transcriber.css is the default look, driven by the shared --chanx-* variables. The
components render class names and data-state, data-done and data-speaking
attributes only.