Seek with precision
Learn how to request a seek precision, read back where the player actually landed, coalesce scrub gestures, and step frames in the FastPix iOS Player.
The FastPix iOS Player SDK lets you ask how precisely a seek should land, and then tells you where it actually landed. Nothing here promises frame accuracy. AVFoundation seeks in media time, the frame grid of an HLS rendition is not knowable from the player, and a zero-tolerance request on a sparsely-keyframed encode still has to decode forward from the preceding keyframe. What .exact guarantees is that no tolerance is requested and that the landing position is reported.
The verdict “did this land inside the tolerance I asked for?” is computed in rational media time with CMTimeCompare, never from Double seconds. TimeInterval cannot represent 1001/30000 s, so a seconds-based comparison flips on rounding error at exactly the boundary you care about. The TimeInterval members are derived for display and are never read back as inputs.
Precision seek requires the seek manager, which is created by setupSeekManager(delegate:). For a thumbnail preview while scrubbing, see Add seek preview thumbnails.
Seek with an explicit precision
Set up the seek manager, then pass a FastPixSeekPrecision on each seek. The completion handler reports what you asked for, where the player landed, the delta, whether it was within tolerance, and whether the target was clamped:
FastPixSeekPrecision
FastPixSeekOutcome
FastPixSeekAccuracy
The CMTime members are authoritative. requestedTime, landedTime, delta, and absoluteDelta are lossy conveniences for display and logging.
FastPixSeekClamp
Derived properties: didClamp (false for .none and .seekableRangeUnknown), clampedTarget, and explanation. The distinction matters for a scrubber: a real clamp should snap the thumb, an unknown range should not.
Listen for precision events
Every method has a no-op default, so adopting one event does not oblige you to implement the rest. Events are delivered on the main queue. Listeners are held weakly, so retain yours.
Coalesce a scrub into one seek
updateScrubbing(to:) issues zero seeks, which is precisely why it is safe to call at touch rate. One seek is issued, at endScrubbing:
FastPixScrubState
isActive is derived and true for anything but .idle. FastPixScrubPosition carries time and sampleCount, the latter published so you can see the coalescing working rather than taking it on trust. FastPixScrubSessionResult carries outcome (nil when cancelled), sampleCount, and the derived wasCancelled.
Step frames
Step a single frame forward or backward. The result is either the landed media time or a typed reason the step could not happen:
FastPixFrameStepUnavailableReason
didStep and unavailableReason are derived on FastPixFrameStepResult. landedMediaTime is read immediately after the step is issued. AVFoundation’s step is asynchronous in general, so treat it as the position at the moment of the call and take the periodic time observer as authoritative afterwards.
Seek by wall clock, and to the live edge
For a date-addressable live stream, seek to a wall-clock time, or jump to the live edge:
FastPixLiveSeekWindow
FastPixDateSeekUnavailableReason
Note:
- No frame-accuracy promise.
.exactrequests zero tolerance and reports the landing; it does not guarantee a nominated frame. On a sparsely-keyframed encode, AVFoundation decodes forward from the preceding keyframe, and “one frame” is not a meaningful unit for audio-only content at all.- Judge tolerance from
accuracy.withinRequestedToleranceor theCMTimemembers, not fromdeltain seconds.TimeIntervalcannot represent1001/30000 s, and a seconds-based comparison flips on rounding error exactly at the boundary.accuracy.requestedMediaTimeis what you asked for, not the clamped target. A seek to -5 s on a stream starting at 0 is clamped and lands at 0; the delta is+5, andclampsays why.finished == falsemeans cancelled or superseded. AVFoundation’s own flag covers only the first; a host scrubbing quickly generates the second constantly, and they are different facts.didClampis false for.seekableRangeUnknown. That case reports an absence of information, the seek proceeded unmodified, and treating it as a clamp would move your scrubber thumb for no reason.updateScrubbing(to:)issues no seek. Sampling it at touch rate is safe by construction, and a cancelled gesture needs no undo because the playhead never moved.- Precision seek needs
setupSeekManager(delegate:). Without it,seek(to:precision:)still calls back, withfinished: falseandclamp: .seekableRangeUnknown, rather than silently doing nothing.- Seek precision listeners are held weakly. Keep a strong reference, or your events stop arriving.
Frequently asked questions
Does the FastPix iOS Player support frame-accurate seeking?
The SDK does not promise frame accuracy. AVFoundation seeks in media time, and the frame grid of an HLS rendition is not knowable from the player. The .exact precision requests zero tolerance and reports where the seek actually landed, but on a sparsely-keyframed encode it still decodes forward from the preceding keyframe. For single-frame movement, use stepFrame(_:count:), which refuses with a typed reason when stepping is not possible.
How do I find out where a seek actually landed in the FastPix iOS Player?
The seek completion handler returns a FastPixSeekOutcome with landedTime, the requested time, and an accuracy verdict. Judge whether the seek hit the tolerance from accuracy.withinRequestedTolerance or the CMTime members, not from delta in seconds, because TimeInterval rounding flips exactly at the boundary. Precision seeking requires setupSeekManager(delegate:).
Why does my seek report finished: false?
finished == false means the seek was cancelled or superseded. AVFoundation’s own flag covers only cancellation, while a newer seek overtaking an in-flight one, which happens constantly when a user scrubs quickly, produces the superseded case. They are different facts reported through the same flag.
How do I avoid firing a seek on every scrub movement?
Use the scrub session. Call beginScrubbing(at:) when the gesture starts, updateScrubbing(to:) as it moves, and endScrubbing(at:precision:) when it ends. updateScrubbing(to:) issues zero seeks, so it is safe to call at touch rate, and exactly one coalesced seek is issued at endScrubbing. A cancelled gesture needs no undo because the playhead never moved.