Skip to content

The sub-second axis snap asks about the size of the axis, not about whether AVPlayer rebuilt the timeline (and cannot fire at all on a source whose timestamps start above zero) #534

Description

@superuser404notfound

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions