diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-09-28 08:32:15 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-09-28 08:32:15 +0000 |
| commit | c496ce8d8fb9d91d41cd544800a1f7fabb5d3ca1 (patch) | |
| tree | 4a4a64b3a3b1bc866cecc1670c1a7759cc428326 /CLAUDE.md | |
| parent | 2d50a67edc5e8c5ace610aa672dc0a92dde347c5 (diff) | |
Correct durations from the file, and make the notification open the app
Two from Peter, one of which turns out to matter more than it looks.
**The list said 22m, the player said 29:34.** The feed is lying, not the
parser: Radiolab publishes Grass Apes as `00:22:05` and the file ExoPlayer
opens is 29:34. Checked against the live feed rather than guessed. The seven
and a half minutes between them are dynamically inserted advertising -- the
feed declares the ad-free content length and the CDN stitches in the rest --
so the error is large, systematic, and worst on exactly the shows the
midroll detector exists for.
The player is the first thing in the app that sees the truth, so
PositionWriter now writes it back, through a new narrow PlaybackStateDao.
Once per episode rather than once per tick: this runs on the position timer
for the length of an episode, and a lookup on every tick is precisely the
per-episode work that does not scale. A two-second disagreement is left
alone, being a container arguing with an encoder rather than a lie, and an
unprepared player reporting nothing never wipes what the feed said.
PlaybackStateDao is a split by meaning, not by count -- it owns the two
things playback learns and writes back, the only writes in the app whose
loss is unrecoverable. It also keeps EpisodeDao under detekt's interface
limit, which the new query had pushed it over; the config's own comment
argues against splitting a DAO to satisfy a count, so this splits it for a
reason that would stand on its own.
**Tapping the notification opens the app**, via setSessionActivity. It had
none, so the transport buttons worked and the body did nothing, which reads
as breakage rather than omission.
Verified on the emulator against the real feed: Grass Apes stored as
1325000ms on subscribe, 1770553ms after twenty-five seconds of playback.
The notification now carries `contentIntent=PendingIntent{... startActivity}`
in dumpsys -- I confirmed the intent is attached and targets this app's
activity, but did not simulate the physical tap. That same dump corroborates
what I told Peter about the notification being Auto's doing and not ours:
importance=2, sound=null, vibrate=null, category=transport.
ExoPlayhead joins the coverage exclusions. It is four one-line getters over
ExoPlayer that happen to live in PlaybackService.kt, so it read as covered
by that entry while actually being measured -- it is the seam that makes
PositionWriter testable, not logic. 540 unit tests, detekt and lint clean.
Coverage 92.73% -> 92.96%.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
Diffstat (limited to 'CLAUDE.md')
| -rw-r--r-- | CLAUDE.md | 15 |
1 files changed, 15 insertions, 0 deletions
@@ -210,6 +210,21 @@ by default and correct; it is simply waiting for a publisher who marks breaks. ## Domain notes worth keeping +- **A feed's declared duration is often wrong, and not by rounding.** Radiolab publishes Grass + Apes as `00:22:05`; the file ExoPlayer opens is 29:34. The seven and a half minutes between + them are dynamically inserted ads -- the feed states the ad-free content length and the CDN + stitches in the rest -- so the error is large, systematic, and worst on exactly the shows the + midroll detector cares about. `PositionWriter` replaces the stored value with the player's + once per episode, on the position tick, via the narrow `PlaybackStateDao`. Once per episode, + not once per tick: a lookup on every tick for the length of an episode is the per-episode + work the feed-scale note below warns about. + +- **The media notification opens the app**, via `setSessionActivity` on the session. Without it + the transport buttons work and tapping the body does nothing, which reads as breakage rather + than omission. Worth knowing for diagnosis: `dumpsys notification --noredact` shows this app's + notification as `importance=2`, `sound=null`, `vibrate=null`, `category=transport` -- it is a + control surface, and it is silent. + - **Playback position is the crown jewel of the data model.** It must survive process death. Save on a timer, on pause, and on lifecycle transitions — not just on stop. - **Never assume audio.** An enclosure carries a MIME type. Video was cut from scope, but the model |
