My App announces a place name aloud via AVSpeechSynthesizer (through expo-speech) while a background location task detects the user has entered a new town. This works correctly in the foreground. In the background, with the device screen locked, speech that has already started reliably pauses mid-utterance the moment the screen locks, and resumes and completes the moment the screen is unlocked — even though:
The app has UIBackgroundModes: ['audio', 'location'] declared.
A genuinely active, native-confirmed AVAudioSession is already running continuously in the background (a looping AVAudioPlayer via expo-audio, separate from the speech itself).
AVSpeechSynthesizer.usesApplicationAudioSession is at its default (true), so the synthesizer shares that same active session rather than managing its own.
This is not a crash, and AVSpeechSynthesizer never reports onError, the utterance simply stalls and then continues later, which looks exactly like a pause/resume cycle tied to the screen lock event itself rather than any audio session interruption we can detect.
What is implemented
app.config.js declares the audio and location background modes, and expo-audio's config plugin with enableBackgroundPlayback: true.
At app launch, expo-audio's setAudioModeAsync is called once with:
{ playsInSilentMode: true, shouldPlayInBackground: true, interruptionMode: 'duckOthers' }
(We also tested interruptionMode: 'doNotMix' see below.)
For the duration of a tracked journey, a silent/near-silent AVAudioPlayer-backed loop is started via expo-audio's createAudioPlayer(), specifically so the app has genuine continuous audio output (not just a configured session) for the whole background session. The player is also registered as the active Now Playing session via setActiveForLockScreen() (expo-audio's wrapper around MPNowPlayingInfoCenter / MPRemoteCommandCenter).
When a new place is detected (from a background expo-location task), we call Speech.speak() with the place name. onStart, onDone, onStopped, and onError callbacks are all logged for diagnostics. 5. We listen to the player's native playbackStatusUpdate event to know, from real OS-reported status (not just "the .play() call didn't throw"), whether the keep-alive loop is actually playing.
Observed behavior: field test evidence
Device log example (stationary repeated test):
08:29:01.948 onStart
08:29:02.092 keep-alive loop confirmed playing (native status)
08:29:07.666 onDone
That's a 5.7s onStart-to-onDone gap for a phrase that normally takes under 2s — consistent with the utterance pausing mid-speech when the screen locked, then resuming on unlock. A separate test that stayed unlocked completed the same kind of phrase in 1.9s. On a real 68-minute drive, locked cycles showed 9.4-12.5s gaps; some builds with more aggressive session-reactivation never completed at all (onStart with no onDone/onStopped/onError).
Throughout, the background AVAudioPlayer loop's native status still reports playing: true, and Control Center shows the app as Now Playing. So the AVAudioSession is confirmably alive — only the speech utterance itself stalls on lock.
What we've ruled out:
usesApplicationAudioSession already defaults to true (confirmed via WWDC 2020 session 10022), the synthesizer already shares our configured session, this isn't a missing option.
Tried both doNotMix and duckOthers interruption modes. Neither stops the pause-on-lock. duckOthers additionally stops the app showing as Now Playing in Control Center (doNotMix does show it).
Keep-alive liveness: native playback status confirms the background AVAudioPlayer reaches playing: true within 100-800ms every time — the session itself isn't failing to activate.
We can't observe AVAudioSessionInterruptionNotification directly from our React Native audio libraries, only each library's own polled status — so we can't confirm whether a real interruption notification fires at the moment of lock. That's one of our questions below.
Questions:
Is it expected that AVSpeechSynthesizer, with usesApplicationAudioSession=true and an already-active background AVAudioSession (.playback category, UIBackgroundModes including audio), still pauses an in-progress utterance when the screen locks and resumes on unlock? If so, is there a supported way to prevent that for a navigation/voice-prompt app?
Does locking the screen alone generate an AVAudioSessionInterruptionNotification for an app in this configuration, and if so what interruption type is reported? We can't observe this through our current libraries and want to know what native behavior to expect.
Apple's "Audio Guidelines By App Type" doc gives two sets of guidance that both partly apply to us: navigation apps (activate the session only when a prompt is needed, deactivate after, use duckOthers) vs. playback apps (don't stream silence to avoid suspension, use a background task instead). Is there a recommended pattern for an app needing short, infrequent spoken prompts to continue through a locked screen, triggered by a background location task rather than continuous media playback?
0
0
293