Read YouTube subtitles one word at a time — so 2x actually works.
Your eyes stop moving. The words move instead — and the red letter never leaves that column.
Reading is slow mostly because the eye jumps. RSVP removes the jumps by holding the text still and moving the words, and pins the one letter your brain uses to recognise a word to the same spot every time.
The pace comes from YouTube's own playback rate: set the video to 2x and the reader tracks it exactly, because it reads the video clock rather than a timer of its own. Pausing, seeking and speed changes all just work.
|
Six ways to read RSVP, RSVP + Bionic, Bionic, Static, Highlighter, Focus line. Nine profiles, ten palettes, and a custom one. |
A study mode Chapters, timestamped notes, an optional AI assistant, and export to one Markdown file. |
Nothing phones home No analytics, no account, no server. The assistant is opt-in and uses your own API key. |
Status: early. Built and unit-tested, but not yet run against a broad range of real videos. See Known risks.
All six reading the same sentence, at the same instant:
| Mode | What it does |
|---|---|
| RSVP | One word, pinned at the focal point, with the pivot letter picked out. The original. |
| RSVP + Bionic | The same pinned word, with the neighbours' leading letters bolded so you can read ahead. |
| Bionic | A line at once, leading letters emboldened. The accent on them can be turned off. |
| Plain | A line with nothing marked at all — no pivot, no bold, no accent. The quietest of them. |
| Highlighter | A block behind the current word, the way captions look on Shorts. Holds up at speed. |
| Focus line | RSVP's rhythm without its coloured letter — one word, plainly centred. |
A separate axis, and the one worth knowing about. It decides whether the line slides — advancing a word at a time, so something on screen changes roughly three hundred times a minute — or holds still, showing a whole line and swapping once at a sentence or clause boundary, around twenty times a minute.
That is a setting rather than a mode because it is a genuinely independent question. "How the window moves" and "how the current word is marked" were conflated before, which meant Bionic could only ever slide. Split apart, Bionic held still is just a combination — a printed-looking line with its leading letters bolded, and nothing sliding underneath you.
Read by Bionic and Plain. The one-word modes have no line to hold, and Highlighter's block has to sit on the word being spoken, so those four always slide and the control dims itself.
Static also builds its own lines, since YouTube's cue boundaries do not survive
parsing — the transcript is one flat timeline of words, which is what every
sliding mode needs. They are rebuilt from the punctuation instead: at sentences,
at clauses once a line is worth ending, and at a hard cap so an unpunctuated
auto-transcript cannot run off the card. See
src/content/lines.ts.
Switch profile mid-video from the toolbar — the right settings depend on what is on screen.
Lyric — gold italic on a dark band |
Black Box — white on solid black |
| Profile | What it is for |
|---|---|
| Default | One word at a fixed point, dark, a little context either side. |
| Peripheral | Pinned word with the preview bolded ahead of it, and nothing behind. |
| Serif Flow | A whole line at once, serif in black on white. Reads like print. |
| Serif Static | The same page, held still. Bionic without anything sliding. |
| Clarity Cold | Atkinson at full weight on cold light blue, with a blue pivot. |
| Obsidian | A violet block on lilac, over near-black. |
| Black Box | White on solid black, a line at a time. Nothing moves. |
| Lyric | Gold italic on a solid dark band, small and low in the frame. |
Ten palettes including a Custom one with four colour pickers, plus a slider for how much of the video shows through the card — down to none at all. On top of any of them: slant, letter case, weight, and an outline around every glyph. The outline matters most — a dark edge is what makes text legible over an arbitrary moving picture.
Editing any control leaves the change live and marks the profile modified — built-ins are never overwritten, so Default stays exactly as shipped whatever you do to it. Save as… keeps your version under its own name.
Two honest limits. No italic faces are bundled, so the slant is the browser slanting the upright one — fine on the serifs, less so on the monospaces. And not every family ships every weight (Geist stops at 600), above which the browser thickens it itself and reads thinner than a real bold. Both are because
src/reader-core/fonts.cssis byte-identical to the desktop app, so adding faces means changing both repositories.
A study view for the video you are already watching. Press Shift+R, or use the toolbar popup.
- Chapters, on the left. YouTube's own chapter list when the video has one. When it does not, the transcript is segmented and the sections are named by a model — asked for once, then saved.
- Notes, under the video. Start typing anywhere and the note box takes it. Each note keeps the timestamp it was written at; clicking one seeks there.
- An assistant, on the right. Summarises the transcript when you open the view, and answers questions about it afterwards. Requires your own API key — see Privacy and data.
- Export to a single Markdown file — summary, notes, the questions you asked, and optionally the whole transcript. Point it at an Obsidian vault or pick the folder each time.
- A library of past sessions, with watch time, coverage and rewatch counts. Tracking is a setting, and turning it off stops the recording rather than merely hiding it.
The reader keeps running over the video the whole time. Read Mode changes the page around the video, not the video.
TextReadsFast transcribes your computer's audio live, which means guessing when each word was spoken and pacing against a backlog that can never quite be trusted.
Here, YouTube hands over the entire transcript with timings attached before
playback starts. So there is no transcription, no backlog, no pacing to
estimate — the current word is a lookup against video.currentTime. Seeking,
pausing and playback rate all work without a line of code, because the video
clock already accounts for them.
The two share their reading engine — the pivot maths and alignment — so a word
lands identically in both. See src/reader-core/.
Not yet on the Chrome Web Store. To run it now:
pnpm install
pnpm run buildThen open chrome://extensions, enable Developer mode, choose Load
unpacked, and select dist/ — not the repository root. The root has no
manifest precisely so it cannot be loaded by mistake; dist/ is the extension.
Chrome and Edge; Firefox is a later port, since its MV3 differs enough to be its own task.
pnpm run watch rebuilds on change — reload the extension to pick changes up.
Skilled silent reading runs at roughly 200-300 words per minute, and that ceiling is not set by how fast the brain recognises words. It is set by the mechanics of looking.
Your eyes do not glide along a line. They move in jerks — saccades — and see almost nothing during the jump itself, a phenomenon called saccadic suppression. Vision happens only in the still moments between them, the fixations, which last around 200-250 milliseconds each. A saccade in reading advances about 7-9 characters and takes 20-50ms, and roughly 10-15% of them go backwards — regressions, where you re-read something that did not land the first time.
So the cost of reading a line is not the recognition. It is the fixate, jump, re-acquire, occasionally jump back cycle, repeated for every few characters. Only a narrow window around each fixation is sharp enough to read at all: the fovea covers about 1-2° of visual angle, which is a handful of letters. Everything else on the line is peripheral blur that your brain is quietly guessing at.
This is the best-established finding in reading research — Keith Rayner's reviews of eye movements in reading are the standard reference — and it is the one thing an interface can actually do something about.
Rapid Serial Visual Presentation presents words one at a time at a single screen position. The text moves; the eye does not. No saccades, no re-acquisition, no line-ends to find, no place to lose.
Mary Potter's work in the 1970s-80s established the striking part: people can extract the gist of a stream at rates far above normal reading — on the order of 10+ words per second — because word recognition itself was never the bottleneck.
Removing saccades is only half of it. Where a word is presented matters too.
Word recognition is fastest when the eye lands slightly left of the word's centre — a finding from Kevin O'Regan and colleagues, usually called the Optimal Viewing Position. Land there and the whole word falls inside the sharp foveal window at once. Land on the first or last letter and recognition measurably slows, because part of the word is out in the blur.
Normal reading spends effort targeting that position on every word. RSVP can simply guarantee it: shift each word so its optimal letter sits on the same fixed column, every time. Your eye finds it once and never has to move again.
That letter is the red one. In this extension it is called the Optimal Recognition Point, and holding it still is the entire mechanism:
·
But un̲til a few
·
The two small marks are a fixation anchor — something for the eye to hold onto so it does not drift toward the middle of longer words.
The 1000-words-per-minute claims made for RSVP reading apps did not survive scrutiny, and it is worth being clear about why. Three real losses:
- No regressions. Normal readers jump back when a sentence does not resolve, and that repair is a load-bearing part of comprehension. A word stream does not let you. When comprehension breaks, it stays broken.
- No parafoveal preview. While fixating one word you are already extracting information from the next, which saves real time per word. Showing one word alone throws that benefit away — which is part of why RSVP is not simply free speed. This is the loss the RSVP + Bionic mode exists to soften: a bolded word-opening stays legible further into the periphery than a plain one, so the neighbours can be taken in without moving fixation off the pivot.
- No wrap-up time. Readers slow down at clause and sentence boundaries to integrate what they have just read. A constant-rate stream gives no room for it, so complex or unfamiliar material suffers most.
The honest summary: RSVP reliably beats normal reading for gist at high speed, and reliably loses to it for careful study of difficult text.
Here is the argument for putting RSVP over YouTube rather than over a book.
Conversational speech runs at roughly 150 words per minute. Play a video at 2x and you are at ~300 wpm — the top of ordinary silent reading. At 3x you are near 450 wpm, past what reading a subtitle line can keep up with. That is precisely the range where removing saccades stops being a parlour trick and starts being the only way to follow along.
And the two big RSVP costs are much smaller here than they are for prose:
- You are not relying on the text alone. The audio is running in parallel. Speech and text are processed through partly different channels, so the stream is reinforcing something you are already receiving rather than carrying the whole message by itself.
- You can regress after all. It is a video: scrub back a few seconds. The chapter list and timestamped notes in Read Mode exist partly for this — they are the repair mechanism RSVP normally denies you.
- The pace is not arbitrary. Most RSVP apps ask you to pick a words-per-
minute number, which is a guess. Here the rate is set by the speaker, and the
reader is a lookup against
video.currentTime— so pausing, seeking and changing speed are handled by the video clock rather than estimated.
Bionic-style bolding of each word's leading letters is included because a fair number of people find it genuinely more comfortable, and comfort matters over a long session.
But the evidence that it makes you read faster is weak — independent attempts to replicate the claimed speed benefit have largely not found one. It is offered as a preference, not as a performance feature, and the accent colour on the bold letters can be turned off entirely if you want it quieter still.
The same applies to the other line modes. Static and Highlighter make no speed claim at all. They exist because a fixed focal point is demanding, and sometimes you want a calm subtitle you can glance at instead.
Static is the deliberate opposite of everything above. Every other mode advances one word per word, so something on screen changes roughly three hundred times a minute. Static shows a whole line, holds it still, and swaps once at a sentence or clause boundary — around twenty times a minute. It makes no claim to speed at all; it is there for when you want to be able to look away.
Because YouTube's own cue boundaries do not survive parsing — the transcript is
flattened into one timeline of words, which is what every sliding mode needs —
the lines are rebuilt from the punctuation, breaking at sentences, at clauses
once a line is worth ending, and at a hard cap so an unpunctuated
auto-transcript cannot run off the card. See
src/content/lines.ts.
Click the toolbar icon → All settings…, or find Options on
chrome://extensions. There is a live preview running the real overlay, so
every control can be judged by looking at it.
Defaults match the desktop app so the two feel like one product.
The reader itself never sends anything anywhere. Caption tracks are read
from YouTube, which your browser is fetching regardless, and settings live in
chrome.storage.sync. There is no analytics, no telemetry, and no account.
The Read Mode assistant is the one exception, and it is opt-in. It does nothing at all until you add your own API key. Once you have:
- Opening Read Mode sends the video's transcript and title to the provider you configured — OpenRouter by default — to generate the summary. Questions you type are sent with it.
- Your key is stored in
chrome.storage.local, which is not synced to Google's servers or to your other devices, and is never given to any content script. Only the extension's own service worker can read it, andpnpm run check-boundariesfails the build if that stops being true. - Network access to the provider is an optional permission, requested the first time you save a key rather than granted at install.
- Notes, sessions and stats are held in the extension's own IndexedDB, on this device only.
Delete the key and the extension goes back to sending nothing. Full detail in PRIVACY.md.
YouTube's caption plumbing is not a public API. It changes, and this extension reads it three ways so that one change does not break everything:
- Fetch the caption track directly, from the URL in the page's player response. Works without the viewer turning captions on — when it works.
- Capture what the player fetches for itself. A page-context script hooks
fetchandXMLHttpRequestfor/api/timedtext. Always correctly signed, but only fires once captions are switched on. - Failing both, the extension does nothing at all.
Route 1 is already unreliable. Requesting a caption track without the proof-of-origin token the player attaches returns HTTP 200 with an empty body — verified against a live video — rather than an error. Anything trusting the status code would conclude it had captions and then render nothing, which is why success here is judged on whether words actually came out.
Word-level timing depends on the track. Auto-generated captions carry a per-word offset, which is exactly what this needs. Manually uploaded ones usually give a whole phrase per cue, so those are interpolated across the cue by character count — good enough to read along with, but not as tight.
Videos with no captions do nothing. That is deliberate: there is no transcription here, so there is nothing to fall back to.
A model can be wrong. Summaries and generated chapter names are a model's reading of the transcript, not a transcript of the video. Treat them as notes, not as a record of what was said.
pnpm run typecheck
pnpm test # reader, captions, chapters, charts, read mode
pnpm run check-drift # reader-core vs the desktop app
pnpm run check-boundaries # the API key cannot reach a content script
pnpm run check # all of the above
pnpm run shoot # screenshot both harnesses, assert their geometrysrc/
reader-core/ copied from the desktop app — do not edit here
content/ caption parsing, the overlay, the video-clock loop
page/ runs in the page context to reach YouTube's own objects
readmode/ the study view: chapters, notes, assistant, export
background/ the service worker: the API key, the provider, the library
library/ saved sessions and study stats
options/ settings page
popup/ toolbar profile switcher
dev/ screenshot harnesses, not shipped
profiles.ts the built-in profiles and the saved-profile store
Two boundaries are enforced by scripts rather than by convention:
- The API key never leaves the service worker.
check-boundariesfails the build if anything undersrc/content/orsrc/page/references it. A content script runs in YouTube's page and must not be trusted with it. src/reader-core/is byte-identical to the desktop app.check-driftcompares them and fails on any difference. It cannot prevent divergence, only make it visible — if the two disagree about where a pivot goes, the same word reads differently in the app than in the browser.
pnpm run shoot renders both harnesses in headless Chromium and asserts their
geometry. It is what catches layout regressions that no unit test can see —
including whether the Highlighter block changes a word's width and makes the
whole line twitch.
Pushing a version tag builds a Chrome-Web-Store-ready zip and attaches it to a draft release:
git tag v0.1.1 && git push origin v0.1.1The zip has manifest.json at its root and forward slashes throughout, and the
workflow asserts both before attaching it — the two things a store upload is
usually rejected for.
pnpm run package builds the same zip locally.
Found a vulnerability? Please report it privately — see SECURITY.md.
MIT — see LICENSE. Bundled fonts are SIL Open Font License 1.1; see public/fonts/LICENSES.md.
Not affiliated with, endorsed by, or sponsored by YouTube or Google. See DISCLAIMER.md.




