Skip to content

bufferedPosition freezes while paused (written only by the playhead sinks) #835

Open

Description

@shinier-lichen

What happens

While playback is paused, clock.bufferedPosition stops moving, although the source keeps downloading. A host's buffered band freezes on pause and jumps forward the moment playback resumes. Seen on 7.37.0 through 7.39.0, tvOS, native (loopback) route. Going by the code, the software route has the same gap.

This also matters for scrubThumbnail: stills only come from resident bytes, so a paused viewer scrubbing ahead can't see how far the stills now reach.

Why

bufferedPosition is written only inside the playhead subscribers in AetherEngine+Loading.swift:

  • native: the host.$renderedTime sink (renderedDisplay + max(0, contiguousForwardReadAheadSeconds(playlistSeconds:)))
  • software: the host.$currentTime sink (SoftwareBufferFrontier.bufferedPosition(...))

Paused, neither publisher ticks, so nothing republishes. The read-ahead inputs are internal, so a host can't recompute it.

What we do in our fork for now

A host-called refresh that evaluates the same two expressions from the current state, called by the host about 4×/s while paused:

extension AetherEngine {
    public func refreshBufferedPosition() {
        if let host = softwareHost {
            clock.bufferedPosition = SoftwareBufferFrontier.bufferedPosition(
                currentTime: clock.currentTime,
                liveFrontier: host.bufferedSessionTime,
                cushion: host.displayCushionSeconds,
                cachedVODFrontier: host.cachedVODSessionTime)
            return
        }
        guard nativeHost != nil, let session = nativeVideoSession else { return }
        let value = renderedPositionMirror.get()
        let shift = presentationAxis.shiftSeconds(atItemSeconds: value) ?? playlistShiftSeconds
        let renderedDisplay = isLive && videoRoute == .loopback
            ? value + liveSessionShiftSeconds + liveItemAxisOffsetSeconds
            : PresentationAxis.display(sourcePTS: value + shift, origin: displayOrigin(forShift: shift))
        let readAhead = session.contiguousForwardReadAheadSeconds(playlistSeconds: value)
        let buffered = renderedDisplay + max(0, readAhead)
        if buffered != clock.bufferedPosition { clock.bufferedPosition = buffered }
    }
}

Confirmed on device: the band keeps growing while paused, and scrub stills follow it.

It duplicates the two sink expressions, so it would be better in the engine: for example, a ~1 Hz republish while state == .paused, with the sinks and the timer sharing one function. Happy to open a PR if that shape suits you.

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