How to build an offline transcription app with Whisper NobodyWho published a tutorial for building an offline transcription app in React Native that runs the Whisper model locally at small size with int8 quantization, using the company's .transcribePcm method to process streamed audio buffers chunk by chunk. The app records 10+ minutes of English speech, strips non-verbal sounds such as coughs and laughs via a cleanText step, and lets users export the full transcript or individual chunks. The post compares the app's output against a YouTube transcript of an MKBHD iPhone Duo review, but the excerpt cuts off before presenting the app's full result. ← Back to blog https://www.nobodywho.ai/posts/ How to build an offline transcription app with Whisper At NobodyWho, we've spent the last months shipping new features, like Speech-to-Text or Voice Activity Detection, to complete our local AI stack, and now felt like a good time to run a few experiments with our software. I decided to build a simple transcription app with React Native that you can use fully offline once you have downloaded the whisper model small, int8 quantization . With this app, you can easily record 10+ minutes of English speech and save either the entire transcript or just the parts you are interested in. Let's take a closer look at the implementation and see how it compares to a cloud transcription service. Recording and transcription The interface is straightforward: a button to start the recording and a timer, with the first sentences appearing as soon as they are processed. Let's have a look at what happens when the user presses the recording button: 1. First the OS asks permission for microphone access. 2. Then the audio streaming starts and the stream of audio buffers arrives. 3. Each buffer is processed by transcribeChunk via onChunk , which calls NobodyWho's .transcribePcm method and returns the corresponding text. Since Whisper by default, returns non-verbal human sounds like coughs, laughs, yawning and so on, we remove them from the transcription see cleanText . 4. When the user presses the stop button, we stop the recording and wait for any queued chunks to be processed thanks to pendingRef . Here is what the simplified streaming process with buffers looks like in the app: const { stream } = useAudioStream { sampleRate: SAMPLE RATE, channels: 1, encoding: 'int16', onBuffer: buffer = { if chunkerRef.current { chunkerRef.current = new Chunker buffer.sampleRate, onChunk ; } chunkerRef.current.push toMonoInt16 buffer ; // .push also calls onChunk }, } ; const start = async = { await requestRecordingPermissionsAsync ; await stream.start ; }; const onChunk = chunk: AudioChunk = { const index = chunksRef.current.length; const live: LiveChunk = { start: chunk.start, end: chunk.end, text: null }; chunksRef.current = ...chunksRef.current, live ; const job = transcribeChunk chunk .then text = { chunksRef.current = chunksRef.current.map c, i = i === index ? { ...c, text } : c, ; setChunks chunksRef.current ; } ; pendingRef.current.push job ; }; const transcribeChunk = async chunk: AudioChunk : Promise