diff --git a/Shared/Audio/AudioPlayer.swift b/Shared/Audio/AudioPlayer.swift index e32faae..1049c31 100644 --- a/Shared/Audio/AudioPlayer.swift +++ b/Shared/Audio/AudioPlayer.swift @@ -134,7 +134,9 @@ class AudioPlayer: NSObject, ObservableObject { vDSP_hann_window(&fftWindow, 1024, Int32(vDSP_HANN_NORM)) super.init() - configureAudioSession() + // Audio session is configured lazily on first play — not here. + // Setting .playback category on init interrupts whatever audio is + // already playing (podcasts, other music apps). setupRemoteControls() #if os(iOS) @@ -245,15 +247,6 @@ class AudioPlayer: NSObject, ObservableObject { } private func resumeVisTimers() { - // Re-activate the audio session — another app may have taken it while we - // were in the background (e.g. a game or Spotify). Without this, player.play() - // silently fails and isPlaying shows true with no audio output. - do { - try AVAudioSession.sharedInstance().setActive(true) - } catch { - alog("Foreground: session reactivation failed: \(error)") - } - // Pick up any widget commands that arrived while the process was suspended. // Darwin notifications can't wake suspended processes, so commands written // by widget intents sit in App Group UserDefaults until we check here. @@ -278,9 +271,19 @@ class AudioPlayer: NSObject, ObservableObject { if isUsingCrossfade { SmartCrossfadeManager.shared.resumeFromBackground() } - // Only restart vis timers if actually playing + // Only restart vis timers + reclaim audio session if actually playing. + // If nothing is playing, don't touch the session — let podcasts/other apps keep it. guard isPlaying else { return } + // Re-activate the audio session — another app may have taken it while we + // were in the background (e.g. a game or Spotify). Without this, player.play() + // silently fails and isPlaying shows true with no audio output. + do { + try AVAudioSession.sharedInstance().setActive(true) + } catch { + alog("Foreground: session reactivation failed: \(error)") + } + // Restart Now Playing sync timer — keeps Lock Screen / Dynamic Island // seek bar accurate. Created in playWithAVPlayer but never restarted // after background cycle; without this the system seek bar drifts. @@ -414,6 +417,10 @@ class AudioPlayer: NSObject, ObservableObject { stopAll() isUsingCrossfade = true + // Claim audio session — deferred from init() to first play + configureAudioSession() + activateAudioSession() + // Wire callbacks crossfade.timeUpdate = { [weak self] time, dur in self?.currentTime = time @@ -886,6 +893,7 @@ class AudioPlayer: NSObject, ObservableObject { private func playLocalWithEngine(_ url: URL) { #if os(iOS) alog("Engine path: \(url.lastPathComponent)") + configureAudioSession() activateAudioSession() stopAll()