Skip to content

nativeRemoteHLS: a session that never reaches readyToPlay has no terminal state, and a settled carriage verdict sits unused #334

Description

@superuser404notfound

Found while verifying #321 against a synthetic live master. A nativeRemoteHLS session whose item never reaches readyToPlay stays in state = .loading indefinitely: no error, no timeout, and nothing acts on a carriage verdict that has already been settled.

Repro

A live master advertising CODECS="hvc1.1.6.L120.90" whose segments carry HEVC in MPEG-TS, and which has no audio track:

[NativeAVPlayerHost] #1 asset.load(tracks) ok count=0
[NativeAVPlayerHost] #1 timeControlStatus=waitingToPlay reason=AVPlayerWaitingWithNoItemToPlayReason
[AetherEngine] AE#154: remote-HLS legible group surfaced 1 subtitle rendition(s)
[NativeAVPlayerHost] #1 carriage probe: hevcInMPEGTS from playlist evidence (#293)
  t=01 state=loading ...
  t=25 state=loading ...

The identical fixture with an AAC track muxed in reaches readyToPlay, the watchdog arms, and the #293 verdict reroutes it within a second. The only difference is whether AVFoundation could build any track.

Why it hangs

Three things compose:

  1. The watchdog is anchored at readyToPlay (NativeAVPlayerHost.startVideoCarriageWatchdog, armed from the .readyToPlay branch). The anchor is deliberate and correct for the grace-based verdict: a dead origin must never be able to fire it. But it also gates the evidence-based verdict, which needs no grace at all.
  2. The Live HLS carriage is learned only after a full native mount plus the 4 s watchdog grace; probe it alongside the mount #293 probe settles anyway and the verdict is dropped. hevcInMPEGTS from playlist evidence is conclusive on its own: CODECS names an fMP4-only sample type and the media playlist carries no EXT-X-MAP, so AVFoundation will never build a video track for this source. It is recorded in carriageProbeEvidence and then only ever read by a loop that has not started.
  3. The bypass has no readiness deadline at all. By design (AetherEngine+Loading.swift, surfaceEndFailures: true): the lean path leaves a dead upstream to AVPlayer's own "gave up" signal. This source is not dead. It serves every segment it is asked for, so AVPlayer neither fails nor becomes ready, and no terminal state is ever published.

The deferred segment-head probe has the same shape: awaitReadyForDeferredProbe waits 20 s (400 x 0.05 s) for a readiness that will not come and then returns without probing, so the one read that could settle the case is skipped precisely when it is needed.

Field relevance

Video-only channels are uncommon but real (camera feeds, some IPTV service channels). The failure is worse than a black picture: the host sees .loading forever, so a retune loop or a user-visible error never triggers. Anything that leaves AVFoundation unable to build a single track lands here, not just HEVC-in-TS.

Direction

  1. Act on a settled transportStreamHEVC verdict when it arrives, rather than only inside a loop that starts at readyToPlay. The evidence is read off the source and does not need the grace.
  2. When the readiness ceiling expires, run the deferred segment-head probe instead of giving up. After 20 s without readiness the mount has demonstrably not settled, so "do not compete with the mount" no longer applies.
  3. Give the bypass a terminal state: a generous readiness budget that ends in .error when nothing became ready and no reroute happened, so an unjudgeable source stops being silent.

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