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

WebGPU is used when available (Chrome, Edge, and other browsers that support it). If no usable GPU adapter is found, the library automatically falls back to WebAssembly, which works everywhere but is slower.

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?)

OptionDefaultDescription
modelwhisper-tinyWhisper model size
languageautoBCP-47 code, e.g. en
quantizationhybridfp32, 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.

ModelSizeNotes
moonshine-tiny~55 MBEnglish only · fastest
moonshine-base~98 MBEnglish only
parakeet-ctc-0.6b~612 MBEnglish only · NVIDIA Parakeet CTC · lowercase, no punctuation, no timestamps
parakeet-tdt-0.6b-v3~1.26 GBWebGPU 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 MBMultilingual · fastest Whisper · default
whisper-base~206 MBMultilingual
whisper-small~586 MBMultilingual · better accuracy
whisper-tiny_timestamped~120 MBWord-level timestamps
whisper-base_timestamped~206 MBWord-level timestamps
whisper-small_timestamped~586 MBWord-level timestamps
lite-whisper-large-v3-turbo-fast~1.59 GBLarge · speed-optimized
lite-whisper-large-v3-turbo~1.84 GBLarge · balanced
lite-whisper-large-v3-turbo-acc~2.02 GBLarge · accuracy-optimized
whisper-large-v3-turbo~2.88 GBBest turbo accuracy
whisper-large-v3-turbo_timestamped~2.88 GBTurbo + word timestamps
distil-whisper-small~538 MBEnglish 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.

BrowserWebGPUWebCodecs
Chrome113+94+
Firefox141+130+
Safari18+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