From 913b61980d5e59f50e5c73ecea9b3ac277a54e73 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Sat, 31 Oct 2026 11:44:27 +0100 Subject: [PATCH] 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.) --- Yattee/Services/Player/MPV/MPVClient.swift | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Yattee/Services/Player/MPV/MPVClient.swift b/Yattee/Services/Player/MPV/MPVClient.swift index 1d5f452a..0d9c7ccf 100644 --- a/Yattee/Services/Player/MPV/MPVClient.swift +++ b/Yattee/Services/Player/MPV/MPVClient.swift @@ -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()