Reload the avfoundation audio output when it restarts while paused

The heat in the iOS 27.2 TestFlight report (~36% CPU for 85 minutes with
playback paused) started right after an audio route change while paused:
mpv's ao_avfoundation logged "restarting due to system notification"
and an unnamed thread went to ~36% immediately, still in the foreground.
Its restart handler calls stop() then start() regardless of pause state;
start() sets the synchronizer rate to 1 and requests media data, but
paused mpv supplies no samples, so the empty renderer re-requests in a
tight loop and the running renderer keeps iOS from suspending the app.

When that log line arrives and mpv is paused, issue ao-reload so the AO
is rebuilt paused. (The display link guard in the previous commit did not
reproduce the spin when tested on a device.)
This commit is contained in:
Arkadiusz Fal
2026-10-31 11:44:27 +01:00
parent c44edd23e6
commit 913b61980d

View File

@@ -252,6 +252,10 @@ final class MPVClient: @unchecked Sendable {
let line = MPVLogLine(prefix: prefix, level: level, text: text)
if prefix == "ao/avfoundation", text.hasPrefix("restarting due to system notification") {
reloadAudioOutputIfPaused()
}
logBufferLock.lock()
recentLogBuffer.append(line)
if recentLogBuffer.count > recentLogBufferCapacity {
@@ -277,6 +281,22 @@ final class MPVClient: @unchecked Sendable {
}
}
/// mpv's avfoundation AO restarts its renderer on route changes and automatic
/// flushes, and the restart always starts it (synchronizer rate 1, requesting
/// data) even while paused. Paused mpv supplies no samples, so the empty
/// renderer re-requests in a tight loop on its dispatch queue (~35% CPU), and
/// the running renderer keeps iOS from suspending the app in the background.
/// Reloading the AO rebuilds it in the paused state.
private func reloadAudioOutputIfPaused() {
mpvQueue.async { [weak self] in
guard let self, let mpv = self.mpv, !self.isDestroyed else { return }
var paused: Int32 = 0
guard mpv_get_property(mpv, "pause", MPV_FORMAT_FLAG, &paused) >= 0, paused != 0 else { return }
self.logWarning("avfoundation AO restarted while paused - reloading audio output")
_ = self.commandUnsafe(["ao-reload"])
}
}
/// Snapshot the recent log buffer (most-recent-last). Thread-safe.
func recentLogLines(minimumSeverity: Int = 0) -> [MPVLogLine] {
logBufferLock.lock()