playback improvements to visualizer

1.	Reactivate audio session
	2.	Process stale widget commands
	3.	Sync currentTime/duration from live AVPlayer ← new
	4.	Reinstall periodic time observer
	5.	Resume crossfade manager
	6.	Restart nowPlayingSyncTimer
	7.	Restart vis timers (offline vis or level simulation)
The vis timer’s very first frame now reads the correct position
instead of the stale one from backgrounding.
This commit is contained in:
Dallas Groot 2026-04-12 17:12:07 -07:00
parent 3c28413af8
commit 551e59a148

View file

@ -259,6 +259,19 @@ class AudioPlayer: NSObject, ObservableObject {
// by widget intents sit in App Group UserDefaults until we check here.
WidgetBridge.shared.processAnyPendingCommand()
// Sync currentTime/duration from the live AVPlayer state.
// While backgrounded, the time observer was removed (suspendVisTimers)
// but AVPlayer kept advancing. Without this sync, the vis timer's first
// frames read the stale position from when the app backgrounded, causing
// the waveform to render from the wrong song position then snap-correct
// once the reinstalled time observer fires (~0.1s later).
if let p = player, p.currentTime().isNumeric {
currentTime = p.currentTime().seconds
}
if let dur = playerItem?.duration.seconds, dur.isFinite, dur > 0 {
duration = dur
}
// Always reinstall time observers they were removed on background regardless
// of play state. Without this, currentTime is frozen after background+pause+play.
reinstallTimeObserver()
@ -445,6 +458,7 @@ class AudioPlayer: NSObject, ObservableObject {
// Prepare next track in standby
prepareNextForCrossfade()
pushWidgetState()
return
}
}
@ -1138,6 +1152,7 @@ class AudioPlayer: NSObject, ObservableObject {
self.updateNowPlayingInfo()
self.fetchAndSetArtwork(coverArtId: self.currentSong?.coverArt)
self.pushWidgetState()
// Prepare next-next (picks up any queue changes that happened mid-fade)
self.prepareNextForCrossfade()
@ -1784,7 +1799,9 @@ class AudioPlayer: NSObject, ObservableObject {
// produce the same "custom_al-123" key blur is reused, not redone.
artKey = "custom_\(id)"
} else if let url = ServerManager.shared.client.coverArtURL(id: id, size: 600) {
coverImage = ImageCache.shared.memoryOnlyImage(for: url)
// cachedImage checks memory first, then disk acceptable here because
// pushWidgetState only fires on song change / pause / resume, not per-frame.
coverImage = ImageCache.shared.cachedImage(for: url)
}
}