diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-09-25 17:43:33 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-09-25 17:43:33 +0000 |
| commit | 3f650131627f6737e01585b97519e03413119b44 (patch) | |
| tree | 04d3f82015887c35047a74d539afa1023e8eb929 /CLAUDE.md | |
| parent | 593ee7740de24aae22ddec1dc5a2e083fff06bd3 (diff) | |
Simple skip: manual offsets and chapter-marked breaks
The last of Phase 2's three tracks, and the part Peter asked for in place of the
audio fingerprinting, which stays deferred.
Two tiers. Manual per-show offsets start each fresh episode past the intro and
treat it as finished before the outro; resuming something part-heard still lands
where it was left, and an offset longer than the episode is dropped rather than
obeyed, because someone typing minutes where seconds were meant should not have
a show start at the end. The fields have been on SubscriptionEntity since phase
1 with nothing reading them.
Chapters are the exact tier: where a show publishes a Podcasting 2.0 chapter
file, the player lists it and taps seek. Breaks the publisher marked `toc:
false` can be skipped, opt-in per show. Nothing is guessed from chapter titles
-- a show discussing its own sponsorship deal has a chapter called "Sponsors"
that is the actual programme, and a skip feature that eats an episode is worse
than none. A break whose end cannot be worked out plays rather than throwing the
playhead somewhere arbitrary.
Two real bugs surfaced while verifying this on a device, both older than this
work:
A start position handed to `seekTo` after `prepare()` is silently dropped across
the MediaController-to-service boundary. The intro offset began at zero, and so,
all along, did every resume -- an episode left half-heard restarted from the
top. Nothing failed and nothing logged. It now goes to `setMediaItem`, verified
on a device both ways: a fresh episode starts at the offset, a stored position
resumes at 150s and plays on from there.
Finishing an episode did not stop playback when the queue was empty, because
until now the file had always ended anyway. With an outro offset the episode
finishes while audio is still playing, so it marked the episode played and then
played the credits regardless.
Schema v3 -> v4: the chapter file cached per episode, and the per-show opt-in.
The migration test now covers 1 -> 4 straight through and asserts the opt-in
arrives off -- defaulting it on would have started removing audio from every
show at once.
Per-show settings moved to their own view model. Drip, the archive fetch and
both skip settings are one concern used by one screen, and ListenViewModel had
become the entry point for the library, the queue, episode actions and these as
well. It also gets its own busy flag: fetching an archive and refreshing a feed
are different operations, and one spinner for both had the top bar claiming a
refresh was running when it was not.
Verified on a device against a real feed throughout: a 60-second intro offset
starts the episode at 60s, an outro offset finishes it early and stops the
audio, and a seeded chapter file with a five-second marked break plays 0-5s then
jumps to 45s with the chapter list rendering alongside.
No permission changes. 364 unit tests green, detekt and lint clean, coverage
65.30 -> 66.23.
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 | 33 |
1 files changed, 28 insertions, 5 deletions
@@ -104,8 +104,8 @@ claims three separate times across these repos. queue, play, variable speed, sleep timer, retention, unsubscribe, Android Auto with a browsable library and voice search. 207 unit tests plus instrumented tests covering the media browse tree. -**Phase 2 is in progress**, in three tracks. Peter deferred the audio-fingerprint skip detection -(the design plan's Tier 3); the simple skip work comes last. +**Phase 2 is complete**, in three tracks. Peter deferred the audio-fingerprint skip detection +(the design plan's Tier 3), so the simple skip work came last. - **Track 1, auto-queue rules — done.** Named, ordered, enable-able rules with conditions on show, title, duration, age and season, and actions to queue top/bottom, mark played, auto-download, or @@ -115,12 +115,23 @@ library and voice search. 207 unit tests plus instrumented tests covering the me `DripReleaser` pays it out), a released-set the show and Android Auto both list, and `FeedArchiveWalker` following RFC 5005 `prev-archive` pages to fetch a back catalogue. A drip release always enqueues and deliberately bypasses the rule engine. -- **Track 3, simple skip — not started.** Manual per-show offsets (`skipIntroMs`/`skipOutroMs` - exist on `SubscriptionEntity` and nothing reads them) and Podcasting 2.0 chapter JSON via - `EpisodeEntity.chaptersUrl`, which the parser already captures. +- **Track 3, simple skip — done.** Manual per-show intro/outro offsets through + `PlaybackProgress`, and Podcasting 2.0 chapters: `ChapterParser`, a chapter list in the player, + and opt-in skipping of breaks the publisher marked `toc: false`. Nothing is guessed from + chapter titles. + +**Tier 3 skip detection — audio fingerprinting of theme music — is still not built.** Peter +deferred it; the design plan puts it in Phase 3 and wants a desktop prototype against real +episodes before any Android code. ID3 `CHAP` frames are also not read: that means parsing ID3 +out of a possibly-partial MP3, and the JSON path covers the shows that mark chapters at all. **Known gaps in what is built** (deliberate, not forgotten): +- **Ad-chapter skipping trusts `toc: false` and nothing else.** No title matching: a show + discussing its own sponsorship deal has a chapter called "Sponsors" that is the actual + programme. It is also off by default per show, being the one part of the feature that can + remove audio nobody has heard. + - **Auto-downloads are not retried.** A rule's download that fails is left FAILED rather than requeued: retrying forever against a 404 would burn the battery, and the failed row is visible in the episode list. Retry is the manual Download button. @@ -241,6 +252,18 @@ library and voice search. 207 unit tests plus instrumented tests covering the me left off. `getForSubscription` deliberately still returns everything: feed reconciliation must see held-back episodes or it re-inserts them on every refresh. +- **A start position belongs to `setMediaItem`, not a `seekTo` after `prepare()`.** Seeking + afterwards is silently dropped across the MediaController-to-service boundary. This was found + wiring up the intro offset -- an episode set to start sixty seconds in began at zero -- and it + had been quietly breaking *every resume* since phase 1: an episode left half-heard restarted + from the top. Nothing failed, nothing logged. Verified fixed on a device: a fresh episode now + starts at the offset and a stored position resumes where it was left. + +- **Finishing an episode has to stop playback when nothing is queued.** `onFinished` used to + rely on the file having ended anyway. With an outro offset the episode is finished while audio + is still playing, so it marked the episode played and then played the credits regardless -- + the exact thing the offset exists to prevent. + - **XML comments cannot contain `--`.** The house prose style uses it constantly and it makes the manifest unparseable, with a merge error that doesn't name the line. |
