fixes hopefully
This commit is contained in:
parent
3cfcf026d7
commit
16fd347b44
2 changed files with 34 additions and 13 deletions
|
|
@ -26,11 +26,12 @@ class AudioPlayer: NSObject, ObservableObject {
|
|||
@Published var volume: Float = 0.8
|
||||
@Published var repeatMode: RepeatMode = .off
|
||||
@Published var shuffleEnabled = false
|
||||
// audioLevels is NOT @Published — avoids 60fps SwiftUI state thrashing.
|
||||
// Instead, levelTick increments every time levels change. The visualizer
|
||||
// observes levelTick: when it changes, body re-evaluates, Canvas re-executes,
|
||||
// and pulls the latest levels via currentLevels(). Guaranteed dependency.
|
||||
@Published private(set) var levelTick: Int = 0
|
||||
// audioLevels is NOT @Published — avoids SwiftUI state thrashing.
|
||||
// The Canvas reads currentLevels() directly on every TimelineView tick.
|
||||
// levelTick is kept as an internal counter but intentionally NOT @Published —
|
||||
// making it @Published caused 30fps objectWillChange on AudioPlayer, forcing
|
||||
// every observing view (NowPlayingView, MainTabView, etc.) to re-evaluate 30x/sec.
|
||||
private(set) var levelTick: Int = 0
|
||||
nonisolated(unsafe) private var _audioLevels: [Float] = Array(repeating: 0, count: 30)
|
||||
|
||||
func currentLevels() -> [Float] { _audioLevels }
|
||||
|
|
@ -134,14 +135,11 @@ class AudioPlayer: NSObject, ObservableObject {
|
|||
private func suspendVisTimers() {
|
||||
stopOfflineVisTimer()
|
||||
stopLevelTimer()
|
||||
// Cancel any in-progress offline vis analysis — it's a CPU-intensive FFT
|
||||
// loop that will otherwise run to completion in background, burning ~60% CPU
|
||||
analysisTask?.cancel()
|
||||
analysisTask = nil
|
||||
// Remove the periodic time observer — it fires at 10Hz updating @Published
|
||||
// currentTime, driving SwiftUI re-evaluation of the entire view tree in background
|
||||
AudioPreFetcher.shared.cancelAll()
|
||||
removeTimeObserver()
|
||||
alog("Background: timers + time observer + analysis task suspended")
|
||||
alog("Background: timers + time observer + analysis + prefetch suspended")
|
||||
}
|
||||
|
||||
private func resumeVisTimers() {
|
||||
|
|
@ -1234,8 +1232,19 @@ class AudioPlayer: NSObject, ObservableObject {
|
|||
offlineVisFPS = VisualizerSettings.shared.effectiveFPS
|
||||
let points = VisualizerSettings.shared.nowPlaying.numberOfPoints
|
||||
let cutoff = VisualizerSettings.shared.frequencyCutoff
|
||||
|
||||
analysisTask?.cancel() // cancel any in-flight analysis from the previous song
|
||||
|
||||
analysisTask?.cancel()
|
||||
|
||||
// Don't start analysis if already in background — it will be triggered
|
||||
// when the app returns to foreground instead (via resumeVisTimers path
|
||||
// or next song load in foreground).
|
||||
#if os(iOS)
|
||||
guard UIApplication.shared.applicationState != .background else {
|
||||
alog("Offline vis: skipping analysis in background for \(songId)")
|
||||
return
|
||||
}
|
||||
#endif
|
||||
|
||||
analysisTask = Task {
|
||||
let storage = VisualizerStorageManager.shared
|
||||
let hasCache = await storage.hasCache(for: songId)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,13 @@ class AudioPreFetcher {
|
|||
/// Pre-caches the next `maxPrefetch` tracks that aren't already downloaded.
|
||||
func prefetchUpcoming(queue: [Song], currentIndex: Int) {
|
||||
guard !queue.isEmpty else { return }
|
||||
|
||||
|
||||
#if os(iOS)
|
||||
// Don't start prefetch tasks in background — URLSession.shared data tasks
|
||||
// are not background-session tasks and consume CPU/network budget
|
||||
guard UIApplication.shared.applicationState != .background else { return }
|
||||
#endif
|
||||
|
||||
let start = currentIndex + 1
|
||||
let end = min(start + maxPrefetch, queue.count)
|
||||
guard start < end else { return }
|
||||
|
|
@ -95,6 +101,12 @@ class AudioPreFetcher {
|
|||
}
|
||||
}
|
||||
|
||||
/// Cancel all in-flight prefetch tasks — called when app enters background
|
||||
func cancelAll() {
|
||||
prefetchTasks.values.forEach { $0.cancel() }
|
||||
prefetchTasks.removeAll()
|
||||
}
|
||||
|
||||
func clearCache() {
|
||||
let dir = Self.prefetchCacheDir()
|
||||
try? FileManager.default.removeItem(at: dir)
|
||||
|
|
|
|||
Loading…
Reference in a new issue