The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗A text-to-speech reader that highlights each word as it's spoken, with a soft band marking the sentence around it.
Single HTML file, no build step, no backend. Open index.html in a browser and
it works.
Grab a build from the Releases page — desktop is that same
HTML file zipped with the README and license; Android is an installable APK
built from android/.
Or install it as an app if it's served over HTTPS (or you're running it
locally): Chrome and Edge show an "Install" icon in the address bar once the
page has loaded, since it ships a manifest and a service worker. Installed,
it opens in its own window with its own icon and keeps working offline —
no download, no build. This needs actual hosting, not the file:// path;
see the note in Releasing below.
git clone https://github.com/akhilswarop/WordBeat.git
cd WordBeat
start index.html # Windows; macOS: open index.html
Or serve it:
python -m http.server 8000
| Action | How |
|---|---|
| Play / pause | Centre button, or Space |
| Jump to a word | Click any word |
| Skip a sentence | The << and >> buttons |
| Scrub | Click anywhere on the progress bar |
| Change speed | The dial — drag it, scroll it, tap it to cycle, or focus it and use arrow keys (0.75x to 5x) |
| Open a file | Folder icon, or drag a .txt / .md / .pdf / .epub onto the window |
| Switch chapters | Chapter dropdown in the toolbar (EPUB only, when a book has more than one chapter) |
| Paste text | Clipboard icon |
| Edit text | Pencil icon |
| Text size | Aa, cycles four sizes |
| Clean markup | The list icon, on by default — strips Markdown so symbols aren't read aloud |
| Follow along | The eye icon, keeps the spoken word in view |
Everything hinges on one abstraction: a timing track mapping character offsets in the source text to moments in playback. Every TTS engine is a producer of that track; the renderer is its only consumer.
Source text
-> Tokenizer words + exact char offsets
-> Chunker sentence-sized utterances, each carrying its absolute offset
-> TTS Engine emits absolute char positions as it speaks
-> Renderer binary-searches the position back to a word, paints it
That seam is the whole design. Swapping Web Speech for Amazon Polly speech
marks, Azure wordBoundary events, or a forced aligner means writing a new
producer — the UI never changes.
charIndex is utterance-relative. Each chunk stores its absolute
charOffset; adding them is what keeps highlighting aligned across a long
document. Getting this wrong is the classic "highlight drifts halfway
through" bug.speechSynthesis.cancel() fires onend in some browsers, which would
otherwise advance the queue.onboundary. If no boundary arrives shortly after a chunk starts, the
highlight advances on an estimated speaking rate instead, and a badge on the
player says so — it's a guess and shouldn't be mistaken for real timing.<meta charset="utf-8"> is load-bearing. Served without a charset
header, the browser decodes the file as latin-1, which turns a ligature
character class into an out-of-order range — a SyntaxError that kills the
entire script. Non-ASCII inside the script is written as escapes for the
same reason..txt is read directly, no network needed.
.md is rendered with marked, loaded from a CDN on
first use, rather than a hand-rolled parser — CommonMark and GFM (tables,
task lists, nested lists, autolinks, strikethrough) have enough edge cases
that a library already tested against them beats re-discovering those edge
cases one bug report at a time. Its HTML output is parsed into a DOM and run
through the same sanitiser as an EPUB chapter (never trusted via innerHTML)
before word-spans are threaded through it for highlighting, so headings,
emphasis, lists, tables, and images survive into the reader instead of being
flattened to text.
.pdf uses pdf.js, loaded from a CDN on
first use only. PDFs store glyphs at
coordinates rather than sentences, so fragments are reassembled into lines by
baseline, lines into paragraphs by vertical gap, hyphenated words rejoined, and
running heads, footers, and page numbers dropped by detecting repetition across
pages.
Scanned PDFs contain no text layer and will report that they need OCR.
This is the beginning of the README. Read the rest at the original. Read the full README ↗