Repository navigation
Stop reporting playback on a clip that ended during the play call - #75
Merged
Merged
Conversation
PlayAsync sets IsPlaying once its play operation completes, with nothing checking whether the clip is still running by then. A clip that reaches its end while that operation is in flight raises Ended, whose handler clears IsPlaying, and then the play call sets it straight back. The transport is left claiming to play a clip parked on its last frame, and nothing clears it until the user presses something else. Traced on the real stack, with a clip paused just before its end, seeked, and played straight away: play-enter +28ms Flyleaf PlaybackStopped, status Ended (the clip finishes inside the play call) +30ms controller IsPlaying=True (the play call then asserts playback) play-return The Ended handler did run; its IsPlaying=false simply raised nothing, because the value was already false. That sequence failed 3 of 40 iterations on current main. With a human-paced gap of 300ms between the seek and the play it failed 0 of 40, because the window is only as long as the play call, and a play call is only slow while Flyleaf still has a seek in flight. It is rare by hand, but the invariant is plainly wrong, and anything that makes play wait on a pending seek widens exactly this window. A counter bumped by the Ended handler lets the play call tell that the clip finished underneath it and leave the settled state alone. FakeCameraPlayer gains a PlayCallback hook, mirroring the SeekCallback it already had for interleaving an action mid-call. With the guard, the same sequence passed 80 of 80 across two runs. Two unit tests cover it: one ends the clip from inside the play call and asserts the controller does not report playback, one asserts an ordinary resume still does. Verified the first fails without the guard. This does not fix pressing play on a clip that has already finished, which can also leave the video frozen while the transport reports playback; that is a separate race inside Flyleaf's seek and play handling. 374 tests green.
danielchalmers
force-pushed
the
claude/playing-state-after-clip-end
branch
from
September 17, 2026 05:06
25dbffb to
62cd391
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.
PlayAsyncsetsIsPlayingonce its play operation completes, with nothing checking whether the clip is still running by then. A clip that reaches its end while that operation is in flight raisesEnded, whose handler clearsIsPlaying, and then the play call sets it straight back. The transport is left claiming to play a clip parked on its last frame until you press something else.Evidence, re-measured on current main
Traced on the real stack (FlyleafLib 3.11.5) with a clip paused just before its end, seeked, and played straight away:
The
Endedhandler did run. ItsIsPlaying = falsejust raised nothing, because the value was already false.Rare by hand, since the window is only as long as the play call, and a play call is only slow while Flyleaf still has a seek in flight. The invariant is still wrong, though, and anything that makes play wait on a pending seek widens exactly this window.
A correction
The original description said the window came from "a play queued behind an open still joining secondary cameras, which took eight seconds". That was wrong. Those measurement runs still had the seek-settled gate from the Flyleaf 9.0 work, which waited up to 2s per player in the speed setter, and
PlayAsyncapplies speed to 4 players: 4 × 2s = 8s. The gate was deleted before #74 merged. So the original trace overstated how reachable this is, and the numbers above are the honest ones.The fix
A counter bumped by the
Endedhandler lets the play call tell that the clip finished underneath it and leave the settled state alone.FakeCameraPlayergains aPlayCallbackhook, mirroring its existingSeekCallback.Two tests: one ends the clip from inside the play call and asserts the controller does not report playback, one asserts an ordinary resume still does. Verified the first fails without the guard.
Not in scope
This does not fix pressing play on a clip that has already finished, which can also leave the video frozen while the transport reports playback. That's a separate race between Flyleaf's asynchronous seek and its
Play(), and it predates v1.0.0.Rebased onto
9e2f466. 374 tests green, formatting clean.