I would separate distance from AVPlayer’s live edge from true end-to-end live latency.
What should be treated as the live edge?
From the public AVFoundation API, seekableTimeRanges.end is the practical reference for the player-visible live edge.
The current offset from that edge can therefore be measured as:
let currentTime = playerItem.currentTime()
let seekableEnd = playerItem.seekableTimeRanges.last?.timeRangeValue.end
let offsetFromLive =
seekableEnd.map { CMTimeGetSeconds($0 - currentTime) }
I would describe this value as distance to the seekable edge, rather than absolute live latency.
recommendedTimeOffsetFromLive should be treated as AVPlayer’s recommended target offset from live, not as a measurement of the current offset.
If the stream contains correctly aligned EXT-X-PROGRAM-DATE-TIME, currentDate() can additionally be used to estimate wall-clock latency:
wallClockLatency = now - currentDate()
That is a different metric from distance to the seekable edge.
Is playback-rate adjustment appropriate for maintaining the target offset?
Yes. A temporary playback rate above 1.0 is a supported way to catch up toward live.
AVPlayer exposes AVPlayer.RateDidChangeReason.playheadReachedLiveEdge, which indicates that a rate greater than 1.0 was automatically changed back to 1.0 when the playhead reached the live edge.
A control loop should still avoid continuously driving the player all the way to the seekable boundary. A small deadband around the target works better:
offset > target + tolerance
-> slightly increase playback rate
offset within target ± tolerance
-> rate = 1.0
offset < target - tolerance
-> do not continue catching up
For example:
let error = offsetFromLive - targetOffset
if error > tolerance {
player.rate = 1.03
} else {
player.rate = 1.0
}
I would not use AVPlayerItemDidPlayToEndTime as a live-edge signal.
For an ongoing live playlist without EXT-X-ENDLIST, with no intentional forwardPlaybackEndTime, receiving AVPlayerItemDidPlayToEndTime should be treated as something to investigate separately rather than normal live-edge behavior.
configuredTimeOffsetFromLive and automaticallyPreservesTimeOffsetFromLive also solve different parts of the problem:
configuredTimeOffsetFromLive defines the desired offset when starting or seeking to live.
automaticallyPreservesTimeOffsetFromLive helps preserve the existing relative position through buffering.
Neither exposes the continuously updated current live latency.
Regular HLS vs LL-HLS
I would use the same AVFoundation-level model for both:
current player-relative offset
=
seekableTimeRanges.end - currentTime
The main difference is how the live window advances.
With regular HLS, the seekable edge generally advances as complete segments become available.
With LL-HLS, partial segments allow that edge to advance at a finer cadence and permit a much smaller practical live offset.
I would therefore avoid calculating the desired latency directly from segment duration or PART-TARGET. Let recommendedTimeOffsetFromLive provide AVPlayer’s recommended target, and use the seekable range only to measure the current position relative to the player-visible live edge.
For telemetry, I would keep these as separate metrics:
distanceToSeekableEdge
wallClockLatency
recommendedTimeOffsetFromLive