diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-10-28 08:41:34 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-10-28 08:41:34 +0000 |
| commit | 562742d040d75bdb2875e7411622dc3ec952ed24 (patch) | |
| tree | 70806fd657804c8376c757f1a0949d1966860003 /server/internal | |
| parent | 73cad06e99357e8b3cf48f7eaf890db8ff30db6b (diff) | |
The app reads an episode's words, and follows them while they play
The ⋮ menu gains Transcript, which opens the episode's lines with the one being
spoken in bold and the list following the playhead. Tapping a line seeks to it,
which is the thing anybody actually wants a transcript for -- finding the bit
you half-heard.
It is also why the server refuses a publisher's transcript for an ad-inserted
copy. Tap a line in a transcript of the ad-free master and the audio starts
minutes from where the words are.
The words arrive with the audio rather than when the screen opens. The point of
downloading is that the episode works with no network, and a transcript fetched
on opening is blank on a plane or in a car park, which is where this app is used.
Tens of kilobytes beside tens of megabytes.
One row per cue, not a JSON column. Android reads every Room row through a
CursorWindow that refuses a row over 2MB *on the way out* -- the insert succeeds
and the next read throws -- which is exactly how the oversized pendingClipPcm
crashed the app on every show's second fingerprinted episode. A three-hour
episode is thousands of cues, so one column holding them all is that bug with a
different name. Both tables cascade from the episode. Schema 17, migrated and
checked on a device, including that deleting an episode takes its words.
Three states again, kept apart: words to read, a recorded reason there are none
-- the server looked at the publisher's transcript and would not use it for this
copy -- and nobody having looked. The menu offers the entry for the first two
and the screen says something different for each.
TranscriptSyncing holds the highlighting, apart from the screen and tested,
because the way this goes wrong is quiet: a highlight on the line before or
after the one being spoken looks right at a glance and is useless to read along
with. It holds the last line through a gap rather than flicking off during
silence or music, and highlights nothing before the first line, since an episode
opening on theme music has not said anything yet.
Verified end to end against a real feed before committing, on a throwaway data
directory: Wards Way Podcast mirrored, both episodes' transcripts taken, and the
API served 748 cues whose last one ends 466ms before the measured file does.
Transcripts live on their own view model rather than two more functions on
ListenViewModel, which was already at the size the linter allows -- same call as
splitting Screens.kt, and they have a different lifetime anyway.
The coverage gate caught the replace transaction having no direct test, which
was fair: its clear-then-insert ordering is the thing that would leave a reader
looking at an empty list under a header counting lines.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
Diffstat (limited to 'server/internal')
0 files changed, 0 insertions, 0 deletions
