I find reading hard, so I built a text-to-speech reader for Android — here's how
I've always found reading hard. Long documents slide off my attention, and I lose my place constantly. What I really wanted was something that would read to me and show me the words as it went — so my eyes and ears stayed in sync. Nothing did exactly that, so I built it. It's called ReadAloud , it's on Google Play, and this post is the "why" and the interesting bits of the "how." The moment it became real The first person I showed a rough build to was my Sister, Praise . She'd come to town to officiate a Women's Premier League match at Auntie Aku Astro Turf Park, and I pulled out my phone between everything else. She watched a paragraph read itself aloud with each word lighting up and got genuinely excited — that was the push I needed. She became tester #1. My colleague Reggie became tester #2. Between them they found the rough edges I'd stopped seeing, and the app settled into something stable. What it is A text-to-speech reader for PDFs, EPUB, DOCX, plain text and web articles . It reads aloud in natural voices, highlights each word as it speaks , and auto-scrolls to follow along. There's offline listening, English/French/Spanish, speed-reading (RSVP), a vocabulary builder, and reading stats. The stack: Kotlin, Jetpack Compose + Material 3, MVVM + Clean Architecture, Hilt, Room, DataStore, WorkManager , minSdk 26 . Now the parts that were actually interesting to build. 1. Word-by-word highlighting This is the whole product, so it had to be right. On-device voices are easy — Android's TextToSpeech gives you onRangeStart (API 26+), which fires per spoken range: override fun onRangeStart ( utteranceId : String , start : Int , end : Int , frame : Int ) { // highlight the substring [start, end) in the reader _currentRange . value = start to end } The catch: the natural cloud voices people actually want don't emit onRangeStart . So for cloud synthesis I wrap each word in an SSML <mark> and ask Google Cloud TTS to return timepoints : <speak><mark name= "w0" /> Every <mar