Repository navigation
save the listening position from the playback clock (PP-4964) - #233
Merged
Merged
Conversation
The automatic position save rode `.positionUpdated` from DefaultAudiobookManager's main-runloop `Timer.publish`, re-throttled on `RunLoop.main`. iOS coalesces and suspends that kind of timer during long screen-locked playback, which is why the lock-screen heartbeat was already moved onto `player.positionPublisher`. The save now subscribes to the same playback-clock stream, with its own 15-second monotonic rate limit. Movement is now measured from the last WRITTEN position. The drift filter was given `currentLocation`, which `positionPublisher` keeps within a tick of the live playhead (since #132), so it compared a timer sample against a position at most one timer interval newer. At 1x that gap stays under the 2-second threshold. Subscribing the old comparison to the playback clock would have made the gap ~0 and saved nothing, so the two changes go together. 15 seconds, not the 5 the old throttle named: each write reaches Palace's TPPBookRegistry.setLocation, which rewrites the whole registry file twice (backup and primary) and posts a registry-changed notification the shelf, holds and detail views observe. The host's RemotePositionWriter already coalesces to one POST per 15 seconds, so a faster local cadence would buy nothing on the server either. PP-5268 makes the local write cheap. Measured on a simulator, develop + ios-core #1526, Lyrasis Reads, an unencrypted BiblioBoard audiobook (OpenAccessPlayer), trace switch on, at the interim 5-second interval: before: 0 automatic saves in ~9 min of continuous playback (the only write was the chapter-completion save). Killing the app and relaunching resumed ~9 minutes behind the playhead. after: 46 saves in 3m55s. Kill and relaunch resumed within 5 seconds of the playhead. At 15 seconds the worst case after a kill is 15 seconds of listening. The first tick after load seeds the baseline instead of writing, so a transient position emitted while the player settles cannot replace the restored place. Save suppression still applies, and a suppressed tick does not advance the baseline. Lifecycle saves (resign active, background, termination, seek, chapter end, transport) are unchanged and remain the backstop. Verified by reintroduction at 15 seconds: deleting the `autosaveFromPlaybackClock()` call fails test_lockedListen_savesFromThePlaybackClockAlone; moving the baseline write above the suppression guard fails test_saveSuppression_whenItLapses_theNextDueTickWrites. palace_mutate --diff-only: 2/2 killed, 0 errored. Full toolkit suite: 307 tests, 0 failures, 1 skipped (pre-existing). **Scope:** AudiobookPlaybackModel only. DefaultAudiobookManager's timer still publishes `.positionUpdated` for the lock screen and the slider; only the save stopped listening to it. **Not done:** a locked-screen measurement on physical hardware. The simulator's lock key left the app `active` (no resign/background notifications fired), so it cannot reproduce iOS timer coalescing and says nothing about the locked half of the diagnosis. The foreground defect above is independent of locking. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
mauricecarrier7
force-pushed
the
fix/PP-4964-position-save-clock
branch
from
September 29, 2026 14:42
018ae3e to
c46c5dd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Jira: PP-4964 · follow-up PP-5268
What changes
The automatic listening-position save in
AudiobookPlaybackModelnow subscribes toplayer.positionPublisher, the stream every player feeds from its playback clock, with its own 15-second monotonic rate limit. It previously rode.positionUpdatedfromDefaultAudiobookManager's main-runloopTimer.publish, re-throttled onRunLoop.main. That is the kind of timer iOS coalesces during long screen-locked playback, and the reason the lock-screen heartbeat already moved topositionPublisher.Drift is now measured from the last written position. The filter was given
currentLocation, whichpositionPublisherhas kept within a tick of the live playhead since #132, so it compared a timer sample against a position at most one timer interval newer. At 1× that gap stays under the 2-second threshold. This part is needed for the move to work at all: on the playback-clock stream the old comparison would see a gap of ~0 and never save.Unchanged: lifecycle saves (resign active, background, termination, seek, chapter end, transport), save suppression, and the manager's timer, which still drives the lock screen and slider.
Why 15 seconds and not 5
Each write lands in Palace's
TPPBookRegistry.setLocation, which rewrites the whole registry file twice (backup and primary) and posts a registry-changed notification that the shelf, holds and detail views observe. The remote side is already coalesced byRemotePositionWriterto one POST per 15 s per book, so a faster local cadence buys nothing on the server. At 15 s, each local write lines up with at most one POST, and the worst case after a kill is 15 s of listening. PP-5268 makes the local write cheap.Measured (simulator, develop + ios-core #1526, Lyrasis Reads, unencrypted BiblioBoard title on
OpenAccessPlayer, PP-4963 trace on, interim 5-second interval)Verification (at 15 s)
AudiobookPlaybackModelAutosaveTests: the decision table (no baseline / inside interval / moved / not moved / suppressed / suppression lapsed) plus wiring, driven throughPlayerMock._emitPositionwith the manager's timer unable to produce a position. The wiring test pins the cadence at 11–12 writes per 180 s.autosaveFromPlaybackClock()call failstest_lockedListen_savesFromThePlaybackClockAlone; moving the baseline write above the suppression guard failstest_saveSuppression_whenItLapses_theNextDueTickWrites.palace_mutate.py --diff-only: 2/2 killed, 0 errored.harness test --project audiobooktoolkit: 307 tests, 0 failures, 1 skipped (pre-existing).Not done
active, so it cannot reproduce iOS timer coalescing. The foreground defect above doesn't depend on locking.🤖 Generated with Claude Code