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()