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.
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.