Found while measuring PR #533 on a source whose timestamps do not start at zero, but this is not about that source: it costs a zero-origin session 0.375 s of cue accuracy on the arm below, on main today.
The rule
snapAxisAfterSeek discards the axis at a seek when it is under a second:
static let axisSnapsBelowSeconds = 1.0
static func axisShiftAfterSeek(_ shift: Double) -> Double {
return abs(shift) < axisSnapsBelowSeconds ? 0 : shift
}
The premise (AE#418 round 2) is that AVPlayer throws a sub-second offset away at a seek and snaps back to the playlist, measured at -0.500 and -0.875 against -1.000 and above.
What the picture says
Two arms, same fixture (Scripts/timecode-fixture.sh + Scripts/mkv-cue-fixture.py, tc-cues-lie.mkv over Scripts/slowrange.py at 600 kbps / 300 ms), same standing axis of -0.375 (a resume at 73 s re-aims seg18 from its boundary at 72.000 to the covering sync sample at 71.625). The only difference is whether the seek target is inside what AVPlayer already holds:
| arm |
seek |
buffered to |
landing held |
AVPlayer |
snap fires |
capErr after |
--seek-every 30 --seek-pattern 78 |
73 → 78 |
95.62 |
yes |
KEEPS -0.375 |
yes |
-0.400 |
--seek-every 12 --seek-pattern 80 |
73 → 80 |
75.62 |
no |
discards it |
yes |
+0.025 |
aetherctl play --seconds 60 --start-position 73 --picture-probe \
--seek-every 30 --seek-count 1 --seek-pattern 78 http://127.0.0.1:8880/tc-cues-lie.mkv
In the first arm the session publishes #418 axis -0.375s discarded at the seek landing 78.375s (AVPlayer snaps a sub-second axis), and from the next tick the picture disagrees with it by exactly that: capErr reads -0.400 for the rest of the run, a cue placed at sourceTime lands 0.4 s early, and nothing corrects it because a seek that stays inside the run fetches nothing, so neither a placement nor the AE#481 landing reading is armed.
The second arm is the same code being right, for the same reason the rule was written.
So the discriminator is not the size
It is whether the seek makes AVPlayer rebuild its timeline. That is also what AE#481 concluded from the other direction ("a seek INSIDE the run keeps the axis"), and both arms above agree with it. The size test fits the arms round 2 measured because those all left the buffer; it is a proxy that happens to correlate.
Second half, the same conflation PR #533 was about
The test is applied to the published axis, which is AVPlayer's displacement PLUS the source-to-item normalization the bytes carry. On a source whose timestamps begin at 600 s the standing axis is 599.625 for the same 0.375 s displacement, so the rule can never fire at all. Measured on the same fixture remuxed with -output_ts_offset 600: no discarded line, the axis stays 599.625, and capErr reads -599.983, i.e. correct, because the run really had kept its displacement. The two geometries also diverge behaviourally from there: the seek target conversion differs by 0.375 s, which changed which segment the next seek landed in (1 of 3 runs against 3 of 3 on the zero-origin twin).
Fix direction
Ask whether the landing is inside a loaded range before discarding anything, and express the test on the displacement rather than on the axis so both geometries answer the same. The first half needs loadedTimeRanges at seek time, which today is an off-main read (avPlayerLoadedRanges(), AE#422) in a path that currently issues the seek without awaiting anything, so it wants a seek-latency measurement before it goes in. That is why PR #533's follow-up left the rule alone and deliberately kept it inert on offset sources: on the one arm that separates the two, inert was the right answer.
Found while measuring PR #533 on a source whose timestamps do not start at zero, but this is not about that source: it costs a zero-origin session 0.375 s of cue accuracy on the arm below, on
maintoday.The rule
snapAxisAfterSeekdiscards the axis at a seek when it is under a second:The premise (AE#418 round 2) is that AVPlayer throws a sub-second offset away at a seek and snaps back to the playlist, measured at -0.500 and -0.875 against -1.000 and above.
What the picture says
Two arms, same fixture (
Scripts/timecode-fixture.sh+Scripts/mkv-cue-fixture.py,tc-cues-lie.mkvoverScripts/slowrange.pyat 600 kbps / 300 ms), same standing axis of -0.375 (a resume at 73 s re-aims seg18 from its boundary at 72.000 to the covering sync sample at 71.625). The only difference is whether the seek target is inside what AVPlayer already holds:capErrafter--seek-every 30 --seek-pattern 78--seek-every 12 --seek-pattern 80In the first arm the session publishes
#418 axis -0.375s discarded at the seek landing 78.375s (AVPlayer snaps a sub-second axis), and from the next tick the picture disagrees with it by exactly that:capErrreads -0.400 for the rest of the run, a cue placed atsourceTimelands 0.4 s early, and nothing corrects it because a seek that stays inside the run fetches nothing, so neither a placement nor the AE#481 landing reading is armed.The second arm is the same code being right, for the same reason the rule was written.
So the discriminator is not the size
It is whether the seek makes AVPlayer rebuild its timeline. That is also what AE#481 concluded from the other direction ("a seek INSIDE the run keeps the axis"), and both arms above agree with it. The size test fits the arms round 2 measured because those all left the buffer; it is a proxy that happens to correlate.
Second half, the same conflation PR #533 was about
The test is applied to the published axis, which is AVPlayer's displacement PLUS the source-to-item normalization the bytes carry. On a source whose timestamps begin at 600 s the standing axis is 599.625 for the same 0.375 s displacement, so the rule can never fire at all. Measured on the same fixture remuxed with
-output_ts_offset 600: nodiscardedline, the axis stays 599.625, andcapErrreads -599.983, i.e. correct, because the run really had kept its displacement. The two geometries also diverge behaviourally from there: the seek target conversion differs by 0.375 s, which changed which segment the next seek landed in (1 of 3 runs against 3 of 3 on the zero-origin twin).Fix direction
Ask whether the landing is inside a loaded range before discarding anything, and express the test on the displacement rather than on the axis so both geometries answer the same. The first half needs
loadedTimeRangesat seek time, which today is an off-main read (avPlayerLoadedRanges(), AE#422) in a path that currently issues the seek without awaiting anything, so it wants a seek-latency measurement before it goes in. That is why PR #533's follow-up left the rule alone and deliberately kept it inert on offset sources: on the one arm that separates the two, inert was the right answer.