Skip to content

Fix SIGABRT on audio interruptions: reject stale render clocks in Deck.play - #93

Merged
sanylax0 merged 1 commit into
mainfrom
claude/repo-review-improvements-yvk3t7-play-at-staleness
Jul 18, 2026
Merged

sanylax0 merged 1 commit into
mainfrom
claude/repo-review-improvements-yvk3t7-play-at-staleness

Conversation

@sanylax2

Copy link
Copy Markdown
Collaborator

What

Fixes the crash that fired exactly when a Siri announcement interrupted playback — and very likely the other "invisible" mid-song deaths since the memory fixes landed.

RCA

A Siri announcement (or phone call, timer, route/config change) delivers an audio interruption: the system stops the engine; on .ended(.shouldResume) the app runs recoverPlayback → engine restart → reschedule → Deck.play(). The stem-sync anchor there used accompPlayer.lastRenderTime guarded only by isHostTimeValid — but after a stop→restart, lastRenderTime can be valid yet stale: a host timestamp from the pre-interruption render timeline. play(at: staleTime + 0.03s) is a start time in the past → AVFAudio raises the uncatchable com.apple.coreaudio.avfaudio NSException → SIGABRT. The same line is reached by AVAudioEngineConfigurationChange recovery (force: true), which fires on invisible system events (Bluetooth chatter, sample-rate switches) — so every such event mid-song rolled the same dice, matching "crashed mid-song, nothing special happening."

How

Deck.play()'s stem branch now requires the render clock to be fresh (a render cycle within the last second — proof the engine is actively rendering) and anchors the shared start at mach_absolute_time() now + 30 ms, never at the render timestamp. Anything else — cold start, post-interruption restart, stale clock — falls back to two plain play() calls (the pre-sync behavior; on a freshly restarted engine they land on the same quantum in practice).

Testing

On device: play a stem-separated track and trigger a few Siri announcements / timers mid-song — playback should pause and resume cleanly every time (the window is racy, so repeat). If a crash from before this fix is still in Settings → Analytics Data, a Continuity-*.ips with EXC_CRASH (SIGABRT) and AVAudioPlayerNode play(at:) in the crashed thread confirms this RCA.

🤖 Generated with Claude Code

https://claude.ai/code/session_013TJoWkqg8bzkGdzxWhWjWP


Generated by Claude Code

…k.play

The stem-sync anchor used lastRenderTime guarded only by
isHostTimeValid — but after a stop + engine restart (Siri announcement
interruptions, route/config changes), lastRenderTime can be valid yet
STALE, a timestamp from the pre-interruption render timeline. Anchoring
play(at:) to it puts the start time in the past, raising the
uncatchable AVFAudio start-time exception — a mid-song SIGABRT whenever
the system pokes the audio environment (matches the Siri-announcement
crash and the invisible mid-song deaths). Require a render cycle within
the last second and anchor to mach_absolute_time() now; otherwise fall
back to plain play() calls.

@sanylax0 sanylax0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like it should work

@sanylax0
sanylax0 merged commit 56bae84 into main Jul 18, 2026
1 of 4 checks passed
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.

3 participants