Repository navigation
A finished book reopens at the end, and Play starts it over - #114
Conversation
A finished book used to start over at 0:00 the moment you opened it again. That reset came in with v0.95 so the player would not hit its end and finish the book a second time on load. Now a finished book, or one stopped in its last second, reopens parked at its end and paused. The scrubber, the chapter list, the mini player, the notification and Android Auto all show the end of the last chapter. Only the screen shows the end. The player waits paused at 0:00, so it can never reach its end on load, fire another finish, add listening time or send progress to the server. That also makes Play start the book over from anywhere: the app, the mini player, the widget, a headset or the car. The restart clears the finished flag through the usual position saves, as a restart always has. A seek back from the end (scrubber, chapter tap, skip back) resumes the book there and plays, the way a seek on a book that just finished does, and its save marks the book unfinished by the usual rule. Skip forward or next chapter from the end leaves it parked. The load now saves a parked book as still finished instead of unfinished, and a load with no server session open no longer counts the missing session as a 0:00 position that hid the book's end. A restarted book still resumes its saved spot (#106), and an end position held in memory still never beats a saved mid book one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014XfNSbF1fe9aiDwGMCNLmj
… parked book alone A Play tap on either widget for a book that was not loaded used to just load it. A finished book now loads parked at its end, so that tap played nothing. Widget Play, like the mini player's reload, now plays a book that loaded parked, which starts it over from 0:00. The sleep timer rewinds when it runs out. It did that even on a paused book, and on a parked book the rewind resumed it and saved it as unfinished 15 seconds before the end. It now rewinds only a book that is playing. A controller seek that moves a parked book off its end now re-arms its stop write, a failed load clears the parked state, and a load judges the end against the shorter of the server's length and the player's tracks, so a short track total cannot prepare the player at its end and finish the book again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014XfNSbF1fe9aiDwGMCNLmj
…nish A parked book that just got Play on a stream is still parked while it buffers. A sleep timer that ran out in that moment rewound it, which resumed it and saved it as unfinished 15 seconds before the end. The timer now rewinds only a book that is playing. The load judges the end against the server's length before the tracks are known. A book whose tracks run short and that finished at their end parked correctly but was saved unfinished. Saving the same position as the saved row now keeps that row's finished flag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014XfNSbF1fe9aiDwGMCNLmj
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 33bbde838c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| val handler = seekHandler | ||
| // Parked, the player sits on its first item, so this position is | ||
| // also the book's. | ||
| if (parkedAtEnd && handler != null) handler(positionMs) else super.seekTo(positionMs) |
There was a problem hiding this comment.
Apply parked-seek protection to the indexed seek overload
For a parked book without chapters, a controller calling seekTo(mediaItemIndex, positionMs) still goes directly to the underlying player: the indexed overload only delegates here when currentChapter != null. Seeking to the end through that overload therefore bypasses parkedSeek, clears the parked state in onSeekApplied, and can trigger STATE_ENDED and another completion write. Route indexed seeks through the parked-book guard too, translating track-relative positions when necessary; protecting only the single-argument overload leaves this controller path unprotected.
Useful? React with 👍 / 👎.
| if (parkedAtEnd) { | ||
| // Skip forward or next chapter from the end stays parked, so the | ||
| // player never seeks into its end and finishes the book again. | ||
| if (parkedSeek(position, _duration.value) == ParkedSeek.STAY_PARKED) return |
There was a problem hiding this comment.
Use the same end threshold for parking and parked seeks
When the track total exceeds the server duration by more than one second, loading judges completion against bookEndLength, but this guard uses _duration, which publishLoadedBook sets to the track total. If the final chapter ends at the server duration, the notification/Android Auto Next action passes that chapter end through ChapterAwareForwardingPlayer.seekToNext; this guard treats it as an earlier position, un-parks the book, and starts playing the remaining tail instead of doing nothing. Use the same effective end length here as during placement so a target already considered finished cannot resume a parked book.
Useful? React with 👍 / 👎.
A controller can seek by track and position. For a parked book without chapters that seek went straight to the player, which could land in the last track's end and finish the book again. It now goes through the book's own seek, with the track position turned into a book position, so the parked rules apply. A parked book judged its seeks against the player's track total, while parking judged the end against the shorter of that and the server's length. With tracks more than a second longer than the server says, Next to the last chapter's end counted as an earlier spot, so the book left its end and played the tail. Seeks now use the same end the book parked at. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014XfNSbF1fe9aiDwGMCNLmj
|
@codex review |
|
Codex Review: Didn't find any major issues. Delightful! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
After rebasing onto #114: a book parked at its end has the player at 0:00, so Previous reads the shown end position there instead of the player. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014XfNSe18pmZggK4TswCohg
… start (#115) * Previous Chapter restarts the chapter you're in, unless you're at its start The player screen's Previous Chapter button always jumped to the chapter before. It now restarts the current chapter when you are more than 3 seconds in, and only goes back one inside that window. It also stays enabled in chapter 1 so you can restart it. The rule lives in one place (previousChapterTarget) and the MediaSession (Android Auto, notification, lock screen) uses it too, so the two cannot drift apart again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014XfNSe18pmZggK4TswCohg * Previous reads the live position, and every Previous runs one code path Codex review: the button read the 500 ms position poll, which freezes on pause, so right at the 3 second line it could pick the wrong chapter. It now reads the player directly and works out the chapter from that. The notification, media keys and Android Auto now call the same PlaybackManager method as the button (books without chapters keep the plain player Previous). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014XfNSe18pmZggK4TswCohg * Previous keeps the retained position when the player is empty Codex review: after a failed load of another book the player has no media items and reads 0, which would send the shown book to chapter 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014XfNSe18pmZggK4TswCohg * A parked finished book's Previous restarts its last chapter After rebasing onto #114: a book parked at its end has the player at 0:00, so Previous reads the shown end position there instead of the player. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014XfNSe18pmZggK4TswCohg --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
What changes for the listener
A finished book reopens at the end. Before, coming back to a finished book started it over at 0:00 and played. Now it opens paused at the end (scrubber full, last chapter selected), with its finished flag, progress and listening time untouched. Press Play to hear it from the top. Drag the scrubber back, tap a chapter or skip back to play from there. Skip forward and next chapter do nothing at the end. A book stopped within its last second counts as finished, same as one played out.
Play from anywhere (app, mini player, both widgets, notification, headset, Android Auto) starts a parked book over in one tap. Book Detail's Continue opens it parked, which is the "come back to it" case.
A sleep timer that ends while you're paused leaves your place alone. It used to rewind 15 seconds even on a paused book, which on a finished book would quietly mark it unfinished.
How it works
Preparing the player at the end of its audio fires an immediate end and re-finishes the book (why ee82a42 reset finished books to 0:00). So the player sits paused at 0:00 while the screen shows the end ("parked"). Every play path goes through
startPlaybackWork, which clears parked and drops the shown position to 0:00 before any poll, save or sync runs. A parked book writes nothing on stop or switch. Finished is judged against the shorter of the server length and the player's track total.Known behavior
Evidence
Hold merge: Jeff is testing this build on his phone first.
🤖 Generated with Claude Code
https://claude.ai/code/session_014XfNSbF1fe9aiDwGMCNLmj