From bd6faac5bb77c0652c67cf4500ab868318049956 Mon Sep 17 00:00:00 2001 From: Matthew Alvernaz Date: Sun, 12 Apr 2026 14:08:49 -0700 Subject: [PATCH] Throttle playback progress publishers to reduce UI redraws The .bookPlaying and .listeningProgressChanged notifications fire every second from the AVPlayer periodic time observer, causing recalculateProgress() to update multiple @Published properties on every tick. Similarly, currentProgressPublisher and immediateProgressUpdatePublisher in ItemProgressView trigger per-second redraws for every visible library row. On lower-powered devices (e.g. iPhone SE 2), this causes noticeable UI lag during playback. This adds 500ms throttling on the player progress publishers (keeping slider updates smooth) and 1s throttling on the library progress views (matching the existing folderProgressUpdated throttle). Co-Authored-By: Claude Opus 4.6 (1M context) --- BookPlayer/Library/ItemList/Views/ItemProgressView.swift | 2 ++ BookPlayer/Player/ViewModels/PlayerViewModel.swift | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/BookPlayer/Library/ItemList/Views/ItemProgressView.swift b/BookPlayer/Library/ItemList/Views/ItemProgressView.swift index 0590dc40a..671823346 100644 --- a/BookPlayer/Library/ItemList/Views/ItemProgressView.swift +++ b/BookPlayer/Library/ItemList/Views/ItemProgressView.swift @@ -33,12 +33,14 @@ struct ItemProgressView: View { .onReceive( playerManager.currentProgressPublisher() .filter { $0.0 == item.relativePath } + .throttle(for: .seconds(1), scheduler: DispatchQueue.main, latest: true) ) { (_, progress) in self.progress = progress } .onReceive( libraryService.immediateProgressUpdatePublisher .filter { item.relativePath == $0["relativePath"] as? String } + .throttle(for: .seconds(1), scheduler: DispatchQueue.main, latest: true) ) { params in if let percentCompleted = params["percentCompleted"] as? Double { self.progress = percentCompleted / 100 diff --git a/BookPlayer/Player/ViewModels/PlayerViewModel.swift b/BookPlayer/Player/ViewModels/PlayerViewModel.swift index 57c98da09..c050c51d6 100644 --- a/BookPlayer/Player/ViewModels/PlayerViewModel.swift +++ b/BookPlayer/Player/ViewModels/PlayerViewModel.swift @@ -223,7 +223,7 @@ final class PlayerViewModel: ObservableObject { self.playingProgressSubscriber?.cancel() self.playingProgressSubscriber = NotificationCenter.default.publisher(for: .bookPlaying) - .receive(on: DispatchQueue.main) + .throttle(for: .milliseconds(500), scheduler: DispatchQueue.main, latest: true) .sink { [weak self] _ in guard let self = self else { return } self.recalculateProgress() @@ -231,7 +231,7 @@ final class PlayerViewModel: ObservableObject { self.listeningProgressSubscriber?.cancel() self.listeningProgressSubscriber = NotificationCenter.default.publisher(for: .listeningProgressChanged) - .receive(on: DispatchQueue.main) + .throttle(for: .milliseconds(500), scheduler: DispatchQueue.main, latest: true) .sink { [weak self] _ in guard let self = self else { return } self.recalculateProgress()