fix(ios): recording resume after an interruption - #1257
SomePersonFromMars wants to merge 17 commits into
Conversation
…ting a closed writer
36d372e to
900e04b
Compare
…s actually delivered
900e04b to
6283fb7
Compare
Also JS handler is only executed on an engine state change.
534c3c0 to
3bd07bf
Compare
3bd07bf to
86a9ae7
Compare
maciejmakowski2003
left a comment
There was a problem hiding this comment.
could you perform the same research on android? in perfect world we would like to provide SotA for interruptions om both platforms.
|
|
||
| Enable emission with `AudioManager.observeAudioInterruptions(true)`, then listen. | ||
|
|
||
| **Playback:** pause on `began` (native does not resume players). The AudioFile example resumes on `ended` when it had been playing. |
There was a problem hiding this comment.
| **Playback:** pause on `began` (native does not resume players). The AudioFile example resumes on `ended` when it had been playing. | |
| **Playback:** native pause on `began`, but does not resume any audio contexts. The AudioFile example resumes on `ended` when it had been playing. |
There was a problem hiding this comment.
could you research and add a section about background resume for playback? I am quite sure that below audio session settings allow it.
AudioManager.setAudioSessionOptions({
iosCategory: 'playback',
iosMode: 'spokenAudio',
})| - (void)emitInterruptionBeganIfAccepted:(bool)accepted | ||
| { | ||
| if (!self.audioInterruptionsObserved || !accepted) { | ||
| return; | ||
| } | ||
|
|
||
| [self.audioAPIModule invokeHandlerWithEventName:audioapi::AudioEvent::INTERRUPTION | ||
| payload:audioapi::InterruptionPayload{ | ||
| .type = "began", .shouldResume = false}]; | ||
| } |
There was a problem hiding this comment.
let's inline it
| AudioEngine *audioEngine = self.audioAPIModule.audioEngine; | ||
|
|
||
| if (self.interruptionEndedDelivered && [audioEngine getState] == AudioEngineStateInterrupted) { | ||
| [self performInterruptionEndOnEngine:audioEngine shouldResume:true]; |
There was a problem hiding this comment.
verify hardcoded shouldResume: true
| if (!shouldResume && self.inputRegistration == nil) { | ||
| [self stopEngine]; | ||
| [self rebuildAudioEngine]; | ||
| self.state = AudioEngineState::AudioEngineStatePaused; | ||
| [self notifyInput:AudioEngineInputNotificationHardwareChanged]; | ||
| return AudioEngineInterruptionEndOutcomePaused; | ||
| } | ||
|
|
||
| if (![self.sessionManager ensureActive:true error:&error]) { | ||
| NSLog(@"Error while activating audio session after interruption: %@", [error debugDescription]); | ||
| return AudioEngineInterruptionEndOutcomeStillInterrupted; |
There was a problem hiding this comment.
what if we have both player and recorder paused <==> engine paused and interruption happens? does it mean that engine will auto-start after interruption
WPT non-regression comparisonPASS — no regressions · 0 improved section(s) · overall 2717 → 2717 (0) Unchanged sections (28)
Baseline: Workflow run · this comment is updated on every push. |
Closes #1220
observeAudioInterruptions(true)no longer replaces native recovery. NativeonInterruptionBegin/onInterruptionEndalways run. The flag only enables JSinterruptionevents, which matches the existingAudioManagerdocs. Apps that subscribed because observing used to skip native resume should not own engine recovery for a recorder (in particular, do not callRecorder.pause()onbegan- that moves the engine toPausedand disables the native retry). Playback can still pause onbegan; native does not resume players.Introduced changes
Fixes iOS recording that never came back after an interruption. Updated library policy: the same take resumes when the app is, or becomes, foreground. Background restart after the interrupter leaves is best-effort; iOS can still refuse I/O while the app is backgrounded.
Responds to the issue
§1
onInterruptionEndnever activated the session; failure poisonedIdle; no input-node guardstartEngine).startAndReturnErrorfailure, stayInterruptedand return (do not setIdle). A later retry can run.Interruptedand treat it as capture lost (do not look like a successful start with no buffers).shouldResumeis false; playback-only still pauses when the OS says not to resume.§2 Docs vs behavior for
observeAudioInterruptionsobserveAudioInterruptions, an appropriate handler runs only whenAudioEngineactually transitioned to a new state. For example, if the engine wasInterrupted, the handler runs on a successful transition toRunning.§3 No-op
setActive:falsestill ran deactivationsetAudioSessionActivity(false)only calls session-deactivation handling if the session was actually managed and active beforesetActive. It no longer transitionsInterruptedtoPaused, which used to disableonInterruptionEnd.§3 / footnote -
resume()/ storeRecordingon a dead engineresume()still does not have a return type, but it is now guaranteed thatisRecording()is true if and only if the audio engine is not in an interrupted state and the recorder is actually running.AudioEngine'sonInterruptionEndrecovery afterAVAudioSessionInterruptionTypeEndedcan still fail. That can happen, for example, due to a race with a higher-priority app or internal iOS privacy policy. In that case we retry once the app returns to the foreground, which is then guaranteed by iOS to succeed.Consumer note -
mixWithOthers/CannotInterruptOthersplayAndRecordis required to avoidCannotInterruptOtherswhen activating in the background, and is not sufficient for background restart. The Record demo enablesmixWithOtherswithdefaultmode.Additional useful tweaks and fixes
WillEnterForegroundNotificationandDidBecomeActiveNotification. iOS guarantees that a foreground audio start succeeds.handleInputConfigurationChangeto handle errors. A failed engine resume now finalizes the audio recorder.HardwareChanged) must open a new rotating segment instead of callingrotateFiles()on a writer that is already closed.IOSRotatingFileWriter::reprepareStreamFormatreopens when the inner file is closed;rotateFiles()no-ops if nothing is open.startIfNecessary, matching the recorder. A later recorder start must not play leftover source audio.observeAudioInterruptions(true); freeze the waveform onbegan, unfreeze only onended.Docs
AudioManagersubsection "Resume recording after an interruption".Manual tests and reproductions used while debugging
This issue is hard to reproduce with automated tests. During research and implementation I used the following manual tests, with the
apps/fabric-exampleapp and the included Recorder demo.Checklist