Case Study Live

A PDF reader where nothing leaves your device

A local-first PDF text-to-speech tool for legal, medical, and confidential documents where uploading text to a cloud TTS service is a non-starter. Extracts text in the browser, synthesizes speech on-device, and highlights the sentence being read in sync with playback.

The problem

Cloud TTS services require uploading document text to third-party servers — unacceptable for legal, medical, or confidential files. Privacy-preserving alternatives existed but required technical setup non-technical users couldn't navigate, or dumped audio synthesis entirely on a slow local machine with no way to hide the latency.

What I built

A local-first PDF reader that extracts text in the browser using PDF.js, streams it to an on-device Kokoro ONNX TTS model served by a FastAPI backend, and plays audio with sentence-level highlighting synchronized to playback. An adaptive prefetch engine queues audio ahead based on current playback pace, backed by a 150 MB IndexedDB audio cache that persists across sessions, all packaged in a single Docker container.

1
Extract text in-browser
PDF.js extracts document text client-side — the raw PDF never leaves the browser tab for this step.
2
Synthesize on-device
Extracted text streams to a Kokoro ONNX TTS model served by a local FastAPI backend — no text is sent to any external service.
3
Prefetch adaptively
The prefetch engine monitors playback pace and queues upcoming audio ahead of time, eliminating stalls between sentences.
4
Cache for reuse
Synthesized audio persists in a 150 MB IndexedDB cache with LRU eviction, so re-reading a document skips synthesis entirely.
5
Play with synced highlighting
Playback highlights the current sentence in the extracted text, keeping the reading position visually anchored.

Engineering decisions

Why Kokoro ONNX instead of a cloud TTS API?

The entire premise of the tool is that document text never leaves the user's device. A cloud TTS API would defeat that by design, regardless of the provider's privacy policy. Kokoro ONNX runs locally and produces quality high enough to be a genuine alternative to hosted options, not just a degraded fallback.

Why build an adaptive prefetch engine instead of synthesizing on demand?

On-device synthesis has real latency, and synthesizing strictly one sentence ahead causes audible stalls the moment playback catches up. The prefetch engine tracks actual playback pace and adjusts how far ahead it queues audio, which removes stalls without over-synthesizing content the user might stop before reaching.

Why also support browser TTS as an option?

Kokoro ONNX needs the FastAPI backend running, which is a heavier setup than some users want for a quick read. Browser TTS requires zero setup and works everywhere, trading voice quality for immediacy — offering both means the tool works for a casual read and a serious one without forcing a single tradeoff on every user.

Why a 150 MB IndexedDB cache specifically?

Re-synthesizing the same document every session wastes time and defeats the point of offering fast playback. 150 MB comfortably holds several full-length documents' worth of audio while staying well under typical browser storage-quota limits, with LRU eviction so the cache doesn't grow unbounded across many different documents.

Operational details

Zero data egress

No document text or audio is sent to any external service — PDF processing and speech synthesis are both fully local to the deployment.

Sub-second cached playback

Once audio is cached, playback starts in under a second, since synthesis is skipped entirely on repeat listens.

Single-command deployment

The full stack — FastAPI backend, Kokoro ONNX model, frontend — ships in one Docker container for single-command deployment.

Sentence-level sync

Highlighting tracks playback at the sentence level, keeping the visual reading position accurate rather than jumping in large blocks.

Python FastAPI Kokoro ONNX JavaScript PDF.js IndexedDB Docker Nginx

See the rest of the production work.

Back to all projects