Skip to content

A session AVPlayer refuses is handed to the engine's own decoder (AE#561) - #582

Merged
superuser404notfound merged 1 commit into
mainfrom
fix/software-path-last-rung
Sep 20, 2026
Merged

superuser404notfound merged 1 commit into
mainfrom
fix/software-path-last-rung

Conversation

@superuser404notfound

Copy link
Copy Markdown
Owner

Follow-up to #581, which cut the unparsable sample. This is the rung underneath it, for every other way AVPlayer can refuse what it is served.

The weakness

Every recovery under the native path answers the same bytes again. The #93 revive reloads the item at the position that died, the stage-2 chain refills the same segment. Against a transient that is exactly right. Against a segment Apple's parser refuses on its merits it is a loop, and the reporter's capture shows where it ends: the replacement item dies 62 ms after the first, and the session is over.

The rung

A failure in the CoreMedia domain is offered to SoftwarePlaybackHost before it is made terminal. The session is rebuilt at its playhead with preferredDecodePath = .software, which decodes with libavcodec (one skipped frame rather than a dead session) and reads the demuxer directly rather than the loopback HLS, so it also steps around a local-server wedge. Once per session, because a second escalation could only repeat the first.

The domain is the whole discriminator:

  • A CoreMedia failure is a verdict on the media, which is the one thing a second decoder can disagree with.
  • A URL-loading failure is a verdict on the source, which both paths read through the same reader, so escalating one would spend a rebuild to fail the same way seconds later.

Whether the software path can serve the source at all is the existing AE#461 decodePathRefusal, already called by reloadAtCurrentPosition(applying:), so a source it cannot serve costs a refusal and the original failure rather than a second dead session. Two entry points share the decision: the host's terminal-failure classification, and the #93 gate at the moment its budget runs out on a frozen position.

Measured

A 40 s HEVC fixture with one damaged sample at 20 s, against the same file healthy. AETHER_DISABLE_NAL_SANITIZER restores the shape #581 removed (read once from the environment, never set in a shipped configuration).

arm result
before, damaged plays to 15.87 s, -19602, reload dies on the same segment, session dead
after, damaged -19602 at 15.88 s, rebuilt on the software path at 12.00 s, plays through the damaged sample to the end
after, healthy unchanged, nothing logged, still native
shipping config, damaged the sample is cut, the session stays native, nothing escalates
shipping config, healthy unchanged

Full suite: 3216 tests in 439 suites, green. Seven new unit tests pin the decision (media domain escalates; NSURLErrorDomain, AVFoundationErrorDomain and a nil domain do not; spent budget, a session already on software, the remote-HLS bypass and an unanswered probe all refuse; the budget is taken exactly once).

🤖 Generated with Claude Code

https://claude.ai/code/session_015PM3xUJB6ZQyqnmGK1fp6F

… decoder (AE#561)

Every recovery under the native path answers the same bytes again. The #93 revive
reloads the item at the position that died, and the stage-2 chain refills the same
segment. Against a transient that is exactly right; against a segment Apple's
parser refuses on its merits it is a loop, and the reporter's capture shows it
ending the session with the replacement item dying 62 ms after the first.

A failure in the CoreMedia domain is now offered to SoftwarePlaybackHost before it
is made terminal: the session is rebuilt at its playhead with preferredDecodePath
= .software, which decodes with libavcodec (one skipped frame rather than a dead
session) and reads the demuxer directly instead of the loopback HLS, so it also
steps around a local-server wedge. Once per session, because a second escalation
could only repeat the first.

The domain is the whole discriminator. A CoreMedia failure is a verdict on the
MEDIA, which is the one thing a second decoder can disagree with; a URL-loading
failure is a verdict on the SOURCE, which both paths read through the same reader,
so escalating one would spend a rebuild to fail the same way seconds later.
Whether the software path can serve this source at all is the existing AE#461
decodePathRefusal, so a source it cannot serve costs a refusal and the original
failure rather than a second dead session.

Measured on a 40 s HEVC fixture whose damaged sample sits at 20 s, against the
same file healthy, with AETHER_DISABLE_NAL_SANITIZER restoring the shape the cut
in 729ccaf now removes (that switch is read once from the environment and is
never set in a shipped configuration):

  before, damaged   plays to 15.87s, -19602, reload dies on the same segment, dead
  after,  damaged   -19602 at 15.88s, rebuilt on the software path at 12.00s,
                    plays through the damaged sample to the end of the file
  after,  healthy   unchanged, nothing logged, still native
  shipping config   the sample is cut, the session stays native, nothing escalates

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015PM3xUJB6ZQyqnmGK1fp6F
@superuser404notfound
superuser404notfound merged commit 97f4ca9 into main Sep 20, 2026
7 checks passed
@superuser404notfound
superuser404notfound deleted the fix/software-path-last-rung branch September 20, 2026 21:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant