summaryrefslogtreecommitdiff
path: root/CLAUDE.md
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-09-25 17:43:33 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-09-25 17:43:33 +0000
commit3f650131627f6737e01585b97519e03413119b44 (patch)
tree04d3f82015887c35047a74d539afa1023e8eb929 /CLAUDE.md
parent593ee7740de24aae22ddec1dc5a2e083fff06bd3 (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.md33
1 files changed, 28 insertions, 5 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index ab38a4b..65655be 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -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.