diff --git a/Shared/Audio/AudioPlayer.swift b/Shared/Audio/AudioPlayer.swift index 7341d01..41ab592 100644 --- a/Shared/Audio/AudioPlayer.swift +++ b/Shared/Audio/AudioPlayer.swift @@ -725,8 +725,10 @@ class AudioPlayer: NSObject, ObservableObject { #if os(iOS) stopOfflineVisTimer() stopLevelTimer() - // Zero out levels immediately so the wave decays cleanly via viscosity - setLevels(Array(repeating: 0, count: 30)) + // DO NOT zero _audioLevels here. The Canvas reads zeros when isPlaying=false, + // so the wave decays visually via viscosity smoothing regardless. + // Preserving _audioLevels means on resume the Canvas immediately has real data — + // the wave reacts on the very first frame instead of waiting for the timer's first tick. internalLevels = Array(repeating: 0, count: 30) #endif updateNowPlayingInfo() diff --git a/iOS/Views/Visualizer/MitsuhaVisualizerView.swift b/iOS/Views/Visualizer/MitsuhaVisualizerView.swift index 9d45d6f..ec1dcdb 100644 --- a/iOS/Views/Visualizer/MitsuhaVisualizerView.swift +++ b/iOS/Views/Visualizer/MitsuhaVisualizerView.swift @@ -203,12 +203,15 @@ struct MitsuhaVisualizerView: View { var body: some View { Group { if settings.enabled { - // driver.tick is @Published — increments every display frame via CADisplayLink. - // SwiftUI re-evaluates this body each tick, which re-executes the Canvas - // drawing closure. No TimelineView, no .animation schedule dependencies. - let _ = driver.tick + // Reading driver.tick as a local binding forces SwiftUI to register + // a real @Published dependency. The let _ = pattern can be elided by + // the compiler in ViewBuilder context; assigning to a named local is not. + let currentTick = driver.tick Canvas { context, size in + // Reference currentTick so the Canvas closure captures it — + // a new value each frame guarantees SwiftUI re-executes the drawing closure. + _ = currentTick let now = CACurrentMediaTime() let rawLevels: [Float] = isPlaying ? (previewLevels ?? AudioPlayer.shared.currentLevels())