iOS 26: ~20s main-thread hang on every play of a live (ICY/SHOUTcast) stream
Summary
On iOS 26, starting playback of a live HTTP stream (SHOUTcast/Icecast, indefinite
duration) freezes the entire app for ~20 seconds: the UI stops responding,
touches are queued and delivered late, and audio only starts once the freeze
resolves. Backgrounding the app during the freeze gets it killed by the watchdog
(0x8BADF00D, "failed to terminate gracefully").
Older iOS versions are not affected — the same app + library were fine for years.
The regression is triggered by an iOS-side behavior change, but the root cause is
in SwiftAudioEx (bundled by react-native-track-player 4.x).
Root cause
AVPlayerWrapper's load completion inspects the asset's chapters/metadata with
synchronous AVAsset property getters, executed on the main thread:
if pendingAsset.availableChapterLocales.count > 0 {
for locale in pendingAsset.availableChapterLocales { ... }
} else {
for format in pendingAsset.availableMetadataFormats {
let timeRange = CMTimeRange(start: ..., end: pendingAsset.duration)
...
}
}
None of these keys (availableChapterLocales, availableMetadataFormats,
duration) are included in the preceding loadValuesAsynchronously(forKeys:)
call — only "playable" is — so each getter resolves synchronously via an XPC
round-trip to mediaserverd. For a live stream with indefinite duration,
iOS 26's media daemon only answers that query after an internal ~20s timeout.
The main thread sits in the XPC reply wait the whole time.
Watchdog backtrace captured mid-hang (iPhone 13 Pro, iOS 26.5):
Thread 0 (main, triggered):
libsystem_kernel mach_msg2_trap
libdispatch _dispatch_mach_send_and_wait_for_reply
libxpc xpc_connection_send_message_with_reply_sync
CoreMedia FigXPCConnectionSendSyncMessageCreatingReply
MediaToolbox remoteXPCAsset_CopyPropertyAndBlockageWarning
AVFCore -[AVFigAssetInspector _valueAsCFTypeForProperty:]
AVFCore -[AVAsset(AVAssetChapterInspection) _loadChapterInfo]
AVFCore -[AVAsset(AVAssetChapterInspection) availableChapterLocales]
(app)
Instruments' Hangs instrument records it as a single "Severe Hang", 20.11 s,
starting the moment the item is prepared.
Reproduction
- Any app using react-native-track-player 4.x (or SwiftAudioEx ≥ 1.0.0-rc-era)
on iOS 26, playing a live SHOUTcast/Icecast URL. We reproduced with
http://hi5.streamingsoundtracks.com/; (ICY 200 OK live MP3).
- Every transition into play (initial autoplay and every stopped→playing)
hangs the main thread ~20s. The iOS Simulator does not reproduce (its
media daemon answers the chapter query immediately).
Suggested fix
Load the inspected keys asynchronously before reading them, e.g. include
"availableChapterLocales", "availableMetadataFormats" and "duration" in
the loadValuesAsynchronously(forKeys:) request and only touch them in its
completion — and/or skip chapter inspection entirely when
pendingAsset.duration.isIndefinite (live streams have no chapters).
Either variant removes the synchronous main-thread XPC fetch without changing
the public API. Happy to send a PR.
Workaround for affected apps (CocoaPods)
Patch the block out in a post_install hook (live radio has no chapters; ICY
timed metadata still arrives via the player-item observer):
['Sources/SwiftAudioEx', 'SwiftAudioEx/Classes'].each do |base|
wrapper = File.join(__dir__, 'Pods', 'SwiftAudioEx', base,
'AVPlayerWrapper', 'AVPlayerWrapper.swift')
next unless File.exist?(wrapper)
contents = File.read(wrapper)
fixed = contents.gsub(
/^ +if pendingAsset\.availableChapterLocales\.count > 0 \{.*?\n +\}\n/m,
"// patched: sync chapter/metadata inspection removed (20s main-thread hang on live streams under iOS 26)\n"
)
if contents != fixed
File.chmod(0644, wrapper)
File.write(wrapper, fixed)
end
end
iOS 26: ~20s main-thread hang on every play of a live (ICY/SHOUTcast) stream
Summary
On iOS 26, starting playback of a live HTTP stream (SHOUTcast/Icecast, indefinite
duration) freezes the entire app for ~20 seconds: the UI stops responding,
touches are queued and delivered late, and audio only starts once the freeze
resolves. Backgrounding the app during the freeze gets it killed by the watchdog
(
0x8BADF00D, "failed to terminate gracefully").Older iOS versions are not affected — the same app + library were fine for years.
The regression is triggered by an iOS-side behavior change, but the root cause is
in SwiftAudioEx (bundled by react-native-track-player 4.x).
Root cause
AVPlayerWrapper's load completion inspects the asset's chapters/metadata withsynchronous
AVAssetproperty getters, executed on the main thread:None of these keys (
availableChapterLocales,availableMetadataFormats,duration) are included in the precedingloadValuesAsynchronously(forKeys:)call — only
"playable"is — so each getter resolves synchronously via an XPCround-trip to
mediaserverd. For a live stream with indefinite duration,iOS 26's media daemon only answers that query after an internal ~20s timeout.
The main thread sits in the XPC reply wait the whole time.
Watchdog backtrace captured mid-hang (iPhone 13 Pro, iOS 26.5):
Instruments' Hangs instrument records it as a single "Severe Hang", 20.11 s,
starting the moment the item is prepared.
Reproduction
on iOS 26, playing a live SHOUTcast/Icecast URL. We reproduced with
http://hi5.streamingsoundtracks.com/;(ICY 200 OKlive MP3).hangs the main thread ~20s. The iOS Simulator does not reproduce (its
media daemon answers the chapter query immediately).
Suggested fix
Load the inspected keys asynchronously before reading them, e.g. include
"availableChapterLocales","availableMetadataFormats"and"duration"inthe
loadValuesAsynchronously(forKeys:)request and only touch them in itscompletion — and/or skip chapter inspection entirely when
pendingAsset.duration.isIndefinite(live streams have no chapters).Either variant removes the synchronous main-thread XPC fetch without changing
the public API. Happy to send a PR.
Workaround for affected apps (CocoaPods)
Patch the block out in a
post_installhook (live radio has no chapters; ICYtimed metadata still arrives via the player-item observer):