Fix alarm sound session activation failures in background#596
Fix alarm sound session activation failures in background#596
Conversation
Use .duckOthers instead of empty options when configuring the audio session for alarm playback. The empty options created a non-mixable session that conflicted with the background silent audio player (which uses .mixWithOthers), causing setActive(true) to fail with "Session activation failed" when the app was in the background.
|
Changed it to draft, needs more testing. |
Limit the .duckOthers option to the only state where legacy options: [] fails: background without Silent Tune holding a mixable session alive. In foreground or with Silent Tune, restore options: [] so the alarm continues to dominate other audio with no behavioral change for those users. In that same fail-prone state, plumb the alarm's soundFile through AlarmManager.sendNotification so the system-delivered notification carries the user's configured alarm sound as an audible fallback. In other states the notification keeps .default to avoid an echo with the in-app AVAudioPlayer loop.
Test✅ successful test Demonstrate the problemBuild using dev: 6.0.9, c9f74f4
Demonstrate this PR fixes the problemBuild using fix/alarm-audio-session-activation, 54c2a5d
|
dnzxy
left a comment
There was a problem hiding this comment.
The .duckOthers gating is a reasonable fix for cannotInterruptOthers, but I don’t think the notification path is safe as written. Since the notification with custom sound is scheduled before in-app playback is attempted, it can double-play when .duckOthers succeeds.
I'd suggest to please either make the notification sound truly conditional on playback failure, or intentionally make the background/no-Silent-Tune path notification-only.
We could also consider using applicationState == .background instead of != .active, and centralize the duplicated policy logic.
| }() | ||
|
|
||
| AlarmManager.shared.sendNotification(title: type.rawValue, actionTitle: snoozeDuration == 0 ? "Acknowledge" : "Snooze") | ||
| // When backgrounded without Silent Tune holding a session alive, the in-app |
There was a problem hiding this comment.
Isn't something like potentially safer to avoid unintended duplicate plays?
let playbackStarted = AlarmSound.play(...)
if !playbackStarted && inBackgroundWithoutSilentTune {
AlarmManager.shared.sendNotification(
title: type.rawValue,
actionTitle: ...,
soundFile: soundFile
)
} else {
AlarmManager.shared.sendNotification(
title: type.rawValue,
actionTitle: ...,
soundFile: nil
)
}Replace the static .duckOthers/[] choice with a fallback ladder that tries options in order [] → .duckOthers → .mixWithOthers and stops at the first that activates. In background without Silent Tune the [] candidate is skipped, since iOS denies it there (cannotInterruptOthers, 560557684). Each attempt is logged with the iOS error code so failures are visible in the field. Revert the notification soundFile path; notifications stay on .default and the in-app AVAudioPlayer remains the only source of the alarm tone. Also drops the redundant enableAudio() call from play() — the do-block already activates the session.
|
Thanks for testing and feedback. Code and PR description updated. Tested successfully with RileyLink and SilentTune. |
Fixes #590 — Unable to play alarm: Session activation failed.
In background without Silent Tune holding the audio session alive, iOS denies
AVAudioSession.setActive(true)with the legacy non-mixableoptions: [](cannotInterruptOthers, 560557684). The alarm goes silent.Result
AlarmSoundnow picks its session options from a fallback ladder and logs each attempt:[]— non-mixable, dominates other audio (preferred).duckOthers— mixable, ducks other audio.mixWithOthers— mixable, no ducking (last resort)When the app is in background without Silent Tune, step 1 is skipped (iOS will always deny it there) and the ladder starts at
.duckOthers.Each session activation logs its outcome with the iOS error code, so future regressions or unexpected denials are diagnosable from a user's log.