Skip to content

Read a resumed start off the ready item held paused (#502) - #513

Merged
ramesh130 merged 1 commit into
mainfrom
issue-502-ios-resume-window
Oct 13, 2026
Merged

ramesh130 merged 1 commit into
mainfrom
issue-502-ios-resume-window

Conversation

@ramesh130

Copy link
Copy Markdown
Owner

Closes #502

Cause

SuperPlayerPlaybackTests read every start with firstPositionPlaying: play(), wait for the first 50 ms poll that finds timeControlStatus == .playing, then read currentTime(), held to expected - 0.05 … expected + 1.0. That upper window is a duration: whatever the host spends between the player starting and the read is added to the position. A loaded ios/check run saw the resume 1.04 s late. Five of the six tests in the class share the helper and therefore the race. The bare-URL test shares it too, through the same helper.

Reproduction (before the fix)

I added try await Task.sleep(for: .milliseconds(1_100)) between the .playing wait and the currentTime() read in firstPositionPlaying. This makes the read lose the race on purpose, without adding host load. All five sharing tests then failed every time, for example:

  • first seen playing at 3.622 s, expected 2.530 s (testOneFewerThanTheBoundIsStillRemembered)
  • first seen playing at 1.082 s, expected 0.0 s
  • first seen playing at 5.083 s, expected 4.0 s (testAnAtPositionStartsThere)

Fix

The new startedPosition(_:) waits for the item to reach .readyToPlay (the readiness reading ADR-0021's #401 addendum admits). It asserts the player is still .paused, then reads currentTime(). Nothing moves the position at that point, however slow the host is. Only then does it play(), wait for .playing, and assert that the player went on from there (playing >= start - frameTolerance). There is no later bound.

assertStarted is now XCTAssertEqual(position, expected, accuracy: frameTolerance), so the window is ±0.05 s where it used to be −0.05/+1.0. startTolerance is gone from this class.

The same 1.1 s sleeps were then put before both reads in the fixed helper, and the whole class still passed.

Decisions argued

Review summary

  • Standards axis:
    • Two // REPRO sleeps were still present at review time (mid-experiment); they are not in the commit.
    • Dangling references to startTolerance in PlayerPoolPlaybackTests: fixed.
    • The addendum and docs/testing.md overclaimed: they read as if every suite now used the held reading. Both are narrowed.
    • The helper's doc did not separate the two failure modes (a jump backwards fails the order assertion, never playing fails the wait) or state the paused precondition: fixed.
    • Soft finding, not acted on: the duplicated firstPositionPlaying and assertStarted copies across suites. They predate this change, and folding them together would touch Flake (iOS): PlaybackSessionPlaybackTests reads a stale Now Playing elapsed time (12.0 vs 4.0) #504's file.
  • Spec axis:
    • No scope creep, and no assertion weakened: the start window tightened, and a .paused check was added.
    • A forward jump after play() is no longer bounded. This is noted in the helper's doc: it would be AVFoundation's doing, since SuperPlayer's seek is zero-tolerance and lands before the item is ready.
    • The bare-URL replace-while-playing case would have been lost to a pre-replace pause; it was kept, as described above.
    • AC3 (the known-flake list): no tracked file holds that list. It lives in the batch brief, which already says this test is no longer on it.

Verification

  • xcodebuild test -test-iterations 20 -only-testing:SuperPlayerTestkitTests/SuperPlayerPlaybackTests/testOneFewerThanTheBoundIsStillRemembered on the sp-502 simulator: 20 of 20 passed.
  • The whole SuperPlayerPlaybackTests class passed, both plainly and with the 1.1 s sleeps before both reads.
  • SP_SIMULATOR=sp-502 ios/check passed.
  • No device run: this is a test-only change, and the simulator is the environment the issue is about.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MJAhQmFgdRRAKUaMHibQLe

…ow enough to miss it

SuperPlayerPlaybackTests read every start at the first poll that found the
player playing and held it to a one-second window, which is a duration: a
loaded host saw testOneFewerThanTheBoundIsStillRemembered 1.04 s late (#502).
The start is now read once the item is ready and before play(), held to an
AAC frame either side, and the player is then seen to play on from there.
ADR-0021 rule 1 gains an addendum admitting the reading beside the old one,
and the sibling suites that keep the old reading argue their window in place
of the reference to the constant this removes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MJAhQmFgdRRAKUaMHibQLe
@ramesh130
ramesh130 merged commit f406ec0 into main Oct 13, 2026
3 checks passed
@ramesh130
ramesh130 deleted the issue-502-ios-resume-window branch October 13, 2026 07:17
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.

Flake (iOS): testOneFewerThanTheBoundIsStillRemembered misses its 1.0 s window under load

1 participant