v0.5.2: custom start times survive a track change

Playing a track with an iTunes start time (e.g. "Pretty Girls is a
Motherfucker", 0:40) began at 0:00 whenever another track was already
loaded. QMediaPlayer.setSource() synchronously emits two status changes
before returning: a LoadedMedia still reporting the *outgoing* source,
then LoadingMedia for the new one. The stale first event consumed the
one-shot _pending_start_ms armed in Round 22 and seeked the dying
pipeline, so the new track's real LoadedMedia ~5 ms later found nothing
armed. Only the first track after launch worked.

The armed seek now carries the URL it belongs to and is consumed only
when source() matches. tests/test_round32.py models the real Qt event
sequence, which Round 22's plain MagicMock never emitted; the
test_round8/test_round22 stubs now report the new source by the time its
LoadedMedia arrives, as Qt does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 09:28:50 -04:00
co-authored by Claude Opus 5
parent bcc17a7542
commit 01996d5fe2
6 changed files with 227 additions and 7 deletions
+27
View File
@@ -1,5 +1,32 @@
## Done
### Round 32 (2026-08-14) — Custom start times survive a track change (v0.5.2)
trav reported "Pretty Girls is a Motherfucker" starting at 0:00 despite its
imported 0:40 start time. Import and storage were fine (70 tracks carry a start
time, 72 a stop time); the seek was being thrown away. `QMediaPlayer.setSource()`
synchronously emits *two* status changes before it returns — a `LoadedMedia`
still reporting the **outgoing** source, then `LoadingMedia` for the new one —
and the stale first event consumed the one-shot `_pending_start_ms` armed by
Round 22, leaving nothing for the new track's real `LoadedMedia` ~5 ms later.
So it misfired on every track change; only the first track after launch worked,
which is why Round 22 looked green.
- [x] The armed seek is tagged with the URL it belongs to
(`_pending_start_url`), and `_on_media_status` consumes it only when
`source()` matches — a status change fired for other media can't eat it.
- [x] `LocalSink.load` restructured so the pending seek is armed only on the
new-source branch; Round 22's same-source replay path still seeks directly
and arms nothing.
- [x] `tests/test_round32.py` models the real Qt event sequence (a `setSource`
side effect that fires the stale `LoadedMedia` before switching `source()`),
which Round 22's plain `MagicMock` never did. All three behavioral tests fail
on the old code. `test_round8` / `test_round22` stubs updated to report the
new source by the time its `LoadedMedia` arrives, as real Qt does.
- [x] Verified against the real files headless: switching from a playing track
to the Psychic Vagina mp3 with `start_ms=40000` now lands at 40.1 s and
climbs; a track with no start time still starts at 0.
### Round 31 (2026-08-13) — One cast control, not two (v0.5.1)
The cast glyph appeared both under the volume slider and inside the visualizer,