Fix audio session stealing from other apps on launch
Deferred audio session configuration from AudioPlayer.init() to first play. Setting .playback category on init interrupted podcasts and other audio before the user even tapped a song. - configureAudioSession() removed from init, added to crossfade path and playLocalWithEngine (playWithAVPlayer already had it) - resumeVisTimers: setActive(true) moved below guard isPlaying so foregrounding the app without active playback leaves other audio alone - All 4 setActive(true) sites now gated behind actual playback or system interruption recovery
This commit is contained in:
parent
842cb6353b
commit
2b81032455
1 changed files with 19 additions and 11 deletions
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue