A free, offline text-to-speech reader for Android and iOS, built with Kotlin Multiplatform and Compose Multiplatform.
Open-source alternative to Speechify — no subscriptions, no internet required, no data leaves your device.
TextLector.demo.mp4
- Import — PDF, TXT, EPUB, FB2, URL, or paste text manually
- Offline TTS — three engines: system voices, Piper (neural, ~63 MB), Supertonic (highest quality, ~380 MB)
- Paragraph highlighting — current paragraph highlighted in sync with playback
- Reading progress — app remembers where you stopped in every document
- Library — manage documents, mark favorites, sort by date
- Adjustable playback — speed control (0.5x–2.0x), voice gender, language, font size
- Switch engines at runtime — no app restart required
TextLector uses a three-tier TTS system managed by a single SwitchableTtsEngine interface.
Tier 1 — System TTS (default)
Android TextToSpeech and iOS AVSpeechSynthesizer — available immediately, no downloads.
Tier 2 — Neural TTS via Piper / sherpa-onnx sherpa-onnx bundles a pre-compiled ONNX Runtime and eSpeak-NG. Piper VITS models (~63 MB each) are downloaded on demand from HuggingFace and stored locally.
| Voice | Language | Gender |
|---|---|---|
| Ruslan | Russian | Male |
| Irina | Russian | Female |
| Ryan | English | Male |
| Lessac | English | Female |
Tier 3 — Neural TTS via Supertonic supertonic-kmp — a KMP wrapper around Supertonic v3 by Supertone Inc. Flow-matching neural synthesis with 10 bundled voice presets (M1–M5, F1–F5) for Russian and English. One-time model download (~380 MB), then fully offline.
Compared to Piper: higher naturalness, but slower generation on mobile CPU. Tune inferenceSteps (2–12) to trade quality for speed.
Both ONNX-based engines use a shared TtsQueue that prefetches the next paragraph in the background while the current one plays.
Known limitation: sherpa-onnx and supertonic-kmp both bundle libonnxruntime.so. Resolved via jniLibs.pickFirsts in build.gradle.kts combined with the static-link sherpa-onnx AAR which embeds ORT statically, eliminating the symbol conflict.
| Layer | Technology |
|---|---|
| UI | Compose Multiplatform |
| Architecture | MVI + ViewModel (commonMain) |
| DI | Koin Multiplatform |
| Navigation | Navigation Compose CMP (Nav2.8, typesafe routes) |
| Database | SQLDelight 2.x |
| Preferences | multiplatform-settings |
| File I/O | okio |
| HTTP | Ktor Client 3.x |
| HTML parsing | Ksoup (Jsoup KMP port) |
| Neural TTS | sherpa-onnx (Piper VITS) + supertonic-kmp (Supertonic v3) |
| PDF (Android) | PdfBox-Android |
| PDF (iOS) | PDFKit + Vision OCR fallback |
composeApp/
├── commonMain/ # shared UI, ViewModels, domain, data
│ ├── domain/ # models, repository interfaces, use cases
│ ├── data/ # SQLDelight, repository implementations
│ ├── ui/ # Compose screens and components
│ └── platform/ # expect declarations (TTS, FileReader, etc.)
├── androidMain/ # Android actuals + sherpa-onnx engine
├── iosMain/ # iOS actuals
└── jvmMain/ # Desktop (planned)
iosApp/
├── TTS/ # IosSherpaEngine, SherpaOnnxTtsBridge
├── PDF/ # PdfTextExtractor, IosPdfPageExtractor
└── Utils/ # IosFileDownloader, IosTarExtractor
- Android Studio Meerkat or later
- Xcode 15+
- JDK 17+
- Kotlin 2.0+
./gradlew :composeApp:assembleDebugOpen iosApp/iosApp.xcodeproj in Xcode and run on a simulator or device.
The sherpa-onnx XCFramework is included via CocoaPods / local framework — no additional setup required.
./gradlew :composeApp:compileKotlinAndroid
./gradlew :composeApp:iosArm64MainKlibrary- PDF, TXT, EPUB, FB2, manual text import
- URL import
- Native TTS (Android + iOS)
- Neural TTS via Piper / sherpa-onnx (Android + iOS)
- Camera / OCR import
- Neural TTS via Supertonic — flow-matching, RU + EN
- Background playback (MediaSession / AVAudioSession)
- JVM / Desktop support


