feat(apple): make SpokenInstructionObserver's audio session management opt-out - #960
Open
meme-te wants to merge 1 commit into
Open
feat(apple): make SpokenInstructionObserver's audio session management opt-out#960meme-te wants to merge 1 commit into
meme-te wants to merge 1 commit into
Conversation
…t opt-out
`SpokenInstructionObserver` currently always takes over the shared `AVAudioSession`
around each spoken instruction. That is the right default for apps using the built-in
`AVSpeechSynthesizer`, but it strands other apps' audio for apps that inject a custom
`SpeechSynthesizer` playing through their own session.
Why it strands:
* `requestAudioFocus()` sets `.duckOthers` + `.interruptSpokenAudioAndMixWithOthers`
and `mode = .voicePrompt`, then activates the session.
* `releaseAudioFocus()` clears `hasAudioFocus` only *after* `setActive(false)` succeeds.
* `setActive(false)` fails while the session still has active audio I/O.
An app that keeps a microphone tap open (wake-word standby) or plays its own guidance
audio therefore never releases focus: `hasAudioFocus` stays `true`, subsequent
`requestAudioFocus()` calls early-return, and other apps stay ducked for the rest of
the session. We measured this on-device: background music dropped from 0.80 to 0.30
system output volume ~1s after navigation started, and never recovered.
Recovering from the host app is not a workable fix either. Re-applying `setCategory`
on an already-active session interrupts other apps' playback, so "take the session
back" trades a stuck duck for repeated audio dropouts.
This adds `managesAudioSession` (default `true`, so existing behavior is unchanged) so
that a host app which owns its audio session can opt out.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
SpokenInstructionObserveralways takes over the sharedAVAudioSessionaround each spokeninstruction. That is the right default for apps using the built-in
AVSpeechSynthesizer, but itpermanently strands other apps' audio for apps that inject a custom
SpeechSynthesizerplayingthrough their own session.
The mechanism:
requestAudioFocus()sets.duckOthers+.interruptSpokenAudioAndMixWithOthersandmode = .voicePrompt, then activates the session.releaseAudioFocus()clearshasAudioFocusonly aftersetActive(false)succeeds.setActive(false)fails while the session still has active audio I/O.An app that keeps a microphone tap open (wake-word standby) or plays its own guidance audio
therefore never releases focus:
hasAudioFocusstaystrue, every laterrequestAudioFocus()early-returns on
guard !hasAudioFocus, and other apps stay ducked for the rest of the session.We measured this on a device with a Bluetooth headset: background music dropped from
0.80to0.30system output volume about one second after navigation started, and never recovered.Recovering from the host app does not work either. Re-applying
setCategoryon an already-activesession interrupts other apps' playback, so "take the session back" trades a stuck duck for
repeated audio dropouts (we tried it, and it was worse).
There is also no way to opt out today:
FerrostarCoretakesspokenInstructionObserveras anon-optional concrete
SpokenInstructionObserver, and the class ispublicbut notopen, so itcan be neither replaced nor subclassed from another module.
Change
Adds
managesAudioSessiontoSpokenInstructionObserver, defaulting totrueso existingbehavior is unchanged. Apps that own their audio session can pass
false:One file, guarding the three
audioManagercalls. No change to instruction de-duplication or anyother logic.
Testing
Built and run on device (iPhone, Bluetooth A2DP headset) with
managesAudioSession: false:background music keeps playing across app launch and repeated microphone on/off cycles, and the
app's own spoken guidance still plays through its session.