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
)
}
Fixes #590 — Unable to play alarm: Session activation failed.
Background activation of a non-mixable
.playbackaudio session is denied by iOS withcannotInterruptOthers(560557684) unless the app is already actively playing audio. With Silent Tune the silent loop holds a mixable session alive, so activation succeeds. With a Bluetooth-heartbeat refresh (Dexcom / RileyLink / Omnipod Dash) there's no warm session at the moment of alarm, sosetActive(true)fails and the alarm is silent.Changes
AlarmSound.swift— at the fivesetCategorycall sites, chooseoptions:based on app state:backgroundRefreshType == .silentTune→[](legacy aggressive interrupt — no behavioral change for those users).duckOthers(mixable; permits activation)Centralized in a new
sessionCategoryOptions()helper.AlarmManager.sendNotification— accepts an optionalsoundFile: SoundFile?. When provided, the notification usesUNNotificationSound(named: "<soundFile>.caf")instead of.default.Alarm.trigger()— passes the alarm'ssoundFileto the notification only when bothplaySoundis true AND we're in the fail-prone state (background + no Silent Tune). The system-delivered notification then carries the user's configured alarm sound as an audible fallback in case the in-app player can't activate. In foreground / Silent Tune the notification keeps.defaultso it doesn't echo the in-app loop.