diff --git a/Shared/Audio/AudioPlayer.swift b/Shared/Audio/AudioPlayer.swift index c055beb..018e49c 100644 --- a/Shared/Audio/AudioPlayer.swift +++ b/Shared/Audio/AudioPlayer.swift @@ -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) } }