Skip to content

fix: correctly store playback positions to the file being played#79

Draft
itsjoshpark wants to merge 7 commits into
mainfrom
fix/playback-position-attribution
Draft

fix: correctly store playback positions to the file being played#79
itsjoshpark wants to merge 7 commits into
mainfrom
fix/playback-position-attribution

Conversation

@itsjoshpark

@itsjoshpark itsjoshpark commented Jul 20, 2026

Copy link
Copy Markdown
Owner

No description provided.

itsjoshpark and others added 4 commits July 19, 2026 18:47
Opening a file while another was loaded could write the incoming file's
resume position under the outgoing file's URL, so reopening the first
file jumped to a time it was never left at.

`fileURL` was only assigned in the `.readyToPlay` sink, delivered
asynchronously, while the periodic time observer began reporting the new
item's time as soon as it was swapped in. Resetting the save throttle to
zero on open guaranteed the first tick crossed the save interval whenever
the resume target exceeded it, making the misattribution reliable rather
than rare. Adopt the new file's identity and clear its time before
swapping the item in, suppress saves for the duration of the swap, and
seed the throttle with the position playback actually starts from.

Also fix two ways stale positions accumulated:

- `applicationWillTerminate` re-saved the end position of a file watched
  to completion, undoing the play-to-end observer's cleanup. Move the
  end-of-playback check into `persistCurrentPlaybackPosition` so every
  caller inherits it.
- Recent entries dropped at launch because their bookmark no longer
  resolved, or trimmed off the end of the list, left their positions
  behind forever. Reconcile against the recents list on load and write
  the pruned bookmark list back.

Extract the resume thresholds into `ResumePolicy` and add a test target
covering them, the position store, and the orphan cleanup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Recents and positions were one logical record split across two stores,
joined by a URL string that is not a stable identity. PlaybackPositionStore
keyed on url.absoluteString, RecentDocumentsStore matched on URL equality
of a bookmark-resolved URL, and PlayEngine guarded with
recentURLs.contains(fileURL) — three places that had to agree with nothing
forcing them to. Every removal path had to remember to clear the matching
position by hand, and each of the three bugs fixed in 0c6b5b6 came out of
that seam.

Collapse both stores into one RecentDocument keyed by a UUID, with the
position as a field. Orphaned positions are now impossible to express:
removing a record takes its position with it, so retainOnly, all three
clearPosition call sites, and the init-time reconciliation are gone.

This also fixes the remaining identity bug by construction. FileOpening
recorded the URL the caller supplied while PlayEngine held the
bookmark-resolved one, so positions silently stopped saving whenever they
differed. There is now one URL, resolved once.

Opening becomes two-phase. prepareToOpen resolves without touching the
list; confirmOpened commits only once playback has started. An unplayable
file therefore never reaches NSDocumentController, which has no API to
retract a single entry. Security-scoped access becomes a ScopedAccess
whose lifetime is the grant, released at the commit point rather than at
the top of openFile — so a failed open no longer revokes access to the
file still playing.

Bookmarks are now resolved lazily at open rather than at launch. Eager
resolution could not distinguish a deleted file from one on an unmounted
volume and erased both, taking their positions with it. A missing file
stays listed until opened, then routes to the existing removal alert.

Also fixed along the way:

- FileCommands read the store statically, registering no observation, so
  the Open Recent menu could miss changes.
- The spatial audio demo called PlayEngine directly and never presented
  the player window. Remote URLs now have an explicit openRemoteAndPresent
  path instead of relying on bookmark creation throwing.

Existing recents and positions are dropped; the legacy defaults keys are
removed on launch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@itsjoshpark itsjoshpark changed the title Fix/playback position attribution fix: correctly store playback positions to the file being played Jul 20, 2026
itsjoshpark and others added 3 commits July 19, 2026 20:23
Adds a Test step to both workflows and documents the command in AGENTS.md.

Kept separate from Build and Analyze rather than folded into one xcodebuild
invocation: derived data persists across steps in a job, so the test step
compiles only the test bundle (12 files vs 42) and costs a few seconds,
while a failure still points at the right phase.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The separate Test step was justified on failure attribution, but xcodebuild
already prints a marker per action, so a broken test reports BUILD
SUCCEEDED / ANALYZE SUCCEEDED / TEST FAILED from a single invocation. The
step split only added a red X in the Actions UI, paid for with four
near-identical YAML blocks across two workflows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant