Documentation
Run OpenAI Whisper entirely in the browser — no server, no API keys. This guide covers install, required headers, and everyday usage.
Introduction
browser-whisper decodes audio with WebCodecs
(via mediabunny) and runs inference with WebGPU
(via Transformers.js). If WebGPU or WebCodecs is unavailable, the library
falls back to WASM and AudioContext automatically.
Audio moves between two Web Workers over a MessageChannel so the
main thread stays responsive. Results stream back as an async iterator, one
segment at a time.
Requirements
Optional Linux tip: Chrome on Linux may need WebGPU enabled manually, e.g. --enable-unsafe-webgpu --enable-features=Vulkan.
Your app must also send COOP/COEP headers on every page that loads the library (see below). Without them, SharedArrayBuffer is unavailable and model loading will fail.
Install
npm install browser-whisper
# or
bun add browser-whisper
No peer dependencies — Transformers.js and mediabunny are bundled into the worker at build time.
Server headers
Add these headers on any route that uses browser-whisper:
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
Vite
// vite.config.ts
export default defineConfig({
server: {
headers: {
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin',
},
},
preview: {
headers: {
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin',
},
},
})
Next.js
Set the same headers in next.config.js (or middleware) for routes
that load the library. Import browser-whisper only on the client — it uses
Web Workers and cannot run during SSR.
// next.config.js
const nextConfig = {
async headers() {
return [
{
source: '/:path*',
headers: [
{ key: 'Cross-Origin-Embedder-Policy', value: 'require-corp' },
{ key: 'Cross-Origin-Opener-Policy', value: 'same-origin' },
],
},
]
},
}
module.exports = nextConfig
See the Next.js example for a full app setup.
Quick start
import { BrowserWhisper } from 'browser-whisper'
const whisper = new BrowserWhisper({ model: 'whisper-base' })
const file = document.querySelector('input[type=file]').files[0]
for await (const { text, start, end } of whisper.transcribe(file)) {
console.log(`[${start.toFixed(1)}s] ${text}`)
}
Collect all segments
const segments = await whisper.transcribe(file).collect()
console.log(segments.map(s => s.text).join(' '))
Pre-download a model
await whisper.downloadModel('whisper-small')
Weights are cached in the browser (OPFS) after the first download.
With callbacks
whisper.transcribe(file, {
language: 'en',
onSegment: (seg) => appendToUI(seg),
onProgress: ({ stage, progress }) => {
console.log(stage, Math.round(progress * 100) + '%')
},
})
API
new BrowserWhisper(options?)
| Option | Default | Description |
|---|---|---|
model | whisper-tiny | Whisper model size |
language | auto | BCP-47 code, e.g. en |
quantization | hybrid | fp32, fp16, q8, q4, or hybrid |
whisper.transcribe(file, options?)
Returns a TranscribeStream (async iterable). Override
model, language, or quantization per call.
Use onSegment and onProgress for callbacks.
whisper.transcribePCM(samples)
Transcribe a mono Float32Array at 16 kHz (e.g. from a VAD pipeline).
Skips file decoding.
whisper.downloadModel(model)
Download and cache a model without transcribing. Accepts a model name or options
with onProgress and an AbortSignal.
await whisper.downloadModel({
model: 'whisper-small',
onProgress: ({ progress }) => updateBar(progress),
signal: controller.signal,
})
BrowserWhisper.clearCache() / deleteModel(model)
Remove cached model weights from the browser.
await BrowserWhisper.clearCache() // every model
await BrowserWhisper.deleteModel('whisper-tiny') // one model
stream.cancel() and whisper.dispose()
TranscribeStream.cancel() and TranscribePCMStream.cancel()
stop an in-progress transcription and end the async iterator. After
cancel(), you can start another transcription on the same instance.
It begins once the chunk currently being transcribed finishes (that result is
discarded), and the loaded model is reused. whisper.dispose()
terminates the ASR worker; call it when the instance will not be used again,
such as when a component unmounts.
const stream = whisper.transcribe(file)
cancelButton.addEventListener('click', () => stream.cancel())
for await (const { text } of stream) {
console.log(text)
}
whisper.dispose()
TranscriptSegment
{ text: string, start: number, end: number }
start and end are in seconds from the beginning of the file.
Full API details are in the README on GitHub.
Models
Download sizes use default hybrid quantization (encoder fp32 + decoder q4; Moonshine: q4; Parakeet: q8), as listed on the Hugging Face onnx-community repos.
| Model | Size | Notes |
|---|---|---|
moonshine-tiny | ~55 MB | English only · fastest |
moonshine-base | ~98 MB | English only |
parakeet-ctc-0.6b | ~612 MB | English only · NVIDIA Parakeet CTC · lowercase, no punctuation, no timestamps |
parakeet-tdt-0.6b-v3 | ~1.26 GB | WebGPU only · NVIDIA Parakeet TDT v3 (CC-BY-4.0) · 25 European languages · punctuation + word timestamps · fp16 needs shader-f16, otherwise use quantization: 'q8' |
whisper-tiny | ~120 MB | Multilingual · fastest Whisper · default |
whisper-base | ~206 MB | Multilingual |
whisper-small | ~586 MB | Multilingual · better accuracy |
whisper-tiny_timestamped | ~120 MB | Word-level timestamps |
whisper-base_timestamped | ~206 MB | Word-level timestamps |
whisper-small_timestamped | ~586 MB | Word-level timestamps |
lite-whisper-large-v3-turbo-fast | ~1.59 GB | Large · speed-optimized |
lite-whisper-large-v3-turbo | ~1.84 GB | Large · balanced |
lite-whisper-large-v3-turbo-acc | ~2.02 GB | Large · accuracy-optimized |
whisper-large-v3-turbo | ~2.88 GB | Best turbo accuracy |
whisper-large-v3-turbo_timestamped | ~2.88 GB | Turbo + word timestamps |
distil-whisper-small | ~538 MB | English only · Distil |
Model credits: Parakeet TDT 0.6B v3 by NVIDIA, licensed under CC BY 4.0. Changes: converted to ONNX and quantized by istupakov/ysdede; runs with parakeet.js (MIT). Parakeet CTC 0.6B by NVIDIA, licensed under CC BY 4.0. Changes: converted to ONNX and quantized by onnx-community. Whisper by OpenAI (MIT). Moonshine by Useful Sensors (MIT). Distil-Whisper by Hugging Face (MIT). Lite-Whisper by efficient-speech (Apache-2.0). ONNX exports by onnx-community. All third-party notices.
Models are fetched from Hugging Face (onnx-community) on first run,
then cached locally.
Browser support
WebGPU and WebCodecs are preferred; fallbacks run automatically when needed.
| Browser | WebGPU | WebCodecs |
|---|---|---|
| Chrome | 113+ | 94+ |
| Firefox | 141+ | 130+ |
| Safari | 18+ | 16.4+ |
First run needs network access for WASM (~1 MB from CDN) and model weights. After caching, transcription works offline.
Try the live demo · All demos · Vite example · Next.js example