Skip to content

fix: AudioContext::resume reanimates the failed Oboe stream due to the change in AudioPlayer::resume - #1252

Open
basiav wants to merge 18 commits into
mainfrom
fix/oboe-stream-dies
Open

basiav wants to merge 18 commits into
mainfrom
fix/oboe-stream-dies

Conversation

@basiav

@basiav basiav commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Closes #1230

⚠️ Breaking changes ⚠️

Introduced changes

AudioPlayer::onErrorAfterClose

  • More errors are handled in AudioPlayer::onErrorAfterClose: ErrorDisconnected, ErrorTimeout, ErrorNoService, ErrorInternal.
  • For ErrorDisconnected, there is one try of the stream rebuild, if it fails AudioContext::OnStreamFail is called.
  • For ErrorTimeout, ErrorNoService and ErrorInternal onStreamFail is called.

AudioContext::onStreamFail

  • Calls the AudioPlayer::cleanup procedure, sets isInitialized_ to false, invokes onerror defined in JS.

Checklist

  • Linked relevant issue
  • Updated relevant documentation
  • Added/Conducted relevant tests
  • Performed self-review of the code
  • Updated Web Audio API coverage
  • Added support for web
  • Updated old arch android spec file

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown

WPT non-regression comparison

PASS — no regressions · 0 improved section(s) · overall 2717 → 2717 (0)

Unchanged sections (28)
Spec section Base pass Head pass Delta
Processing model 0 0 0
Other 52 52 0
AnalyserNode 138 138 0
AudioBuffer 140 140 0
AudioBufferSourceNode 218 218 0
AudioContext 58 58 0
AudioNode 261 261 0
AudioParam 629 629 0
BiquadFilterNode 275 275 0
ChannelMergerNode 30 30 0
ChannelSplitterNode 7 7 0
ConstantSourceNode 64 64 0
ConvolverNode 203 203 0
DelayNode 104 104 0
DestinationNode 0 0 0
DynamicsCompressorNode 4 4 0
GainNode 15 15 0
IIRFilterNode 87 87 0
MediaElementAudioSourceNode 0 0 0
MediaStreamAudioDestinationNode 1 1 0
MediaStreamAudioSourceNode 0 0 0
OfflineAudioContext 35 35 0
OscillatorNode 104 104 0
PannerNode 75 75 0
PeriodicWave 33 33 0
ScriptProcessorNode 0 0 0
StereoPannerNode 102 102 0
WaveShaperNode 82 82 0

Baseline: d02cb81021cee302f7408c3e801ac59148223636 · Candidate: 1fa64a251643ce14eb0d6232a4beb276f701abef

Workflow run · this comment is updated on every push.

@closetcaiman closetcaiman changed the title Fix on Android: AudioContext::resume reanimates the failed Oboe stream due to the change in AudioPlayer::resume fix: AudioContext::resume reanimates the failed Oboe stream due to the change in AudioPlayer::resume Aug 25, 2026
@closetcaiman closetcaiman added fix Code changes specifically addressing and resolving a bug android Native Android implementation, C++/Java/Kotlin bindings, or Android-specific issues labels Aug 25, 2026
Comment on lines -161 to +171
if (error != oboe::Result::ErrorDisconnected || driverMutex_ == nullptr) {
// error != oboe::Result::ErrorDisconnected condition is deleted to handle more cases of errors
if (driverMutex_ == nullptr) {

@closetcaiman closetcaiman Aug 25, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing this guard seems really dangerous to me. Consider this: should all types of errors result in stream rebuild retry? What about some non-recoverable errors, won't that create an infinite loop? Maybe it is better to choose action in response to specific errors - oboe::Result is not that big of an enum.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point. I've modified this to include 4 common errors: ErrorDisconnected, ErrorTimeout, ErrorInternal, ErrorNoService, 2 of them mentioned in the issue. We are not sure what ErrorTimeout, ErrorInternal and ErrorNoService rebuild will turn out in, it depends on the situation in question (especially hardware aspects) and it is not completely clear how to proceed. An infinite loop is possible, however:

  • I've let only ErrorDisconnected try to rebuild the stream once, if it fails then AudioContext::onStreamFail is called.
  • I've let ErrorTimeout, ErrorInternal and ErrorNoService only to call AudioContext::onStreamFail, so that there is the aspect of invoking onerror (absent in a regular stream rebuild), so we are letting some possible logic in (mainly of waiting, e.g. 2 seconds before AudioContext::resume). This can help reduce the risk of an infinite loop, which is however possible.

@basiav
basiav marked this pull request as ready for review September 14, 2026 13:09
@basiav
basiav requested a review from mdydek September 14, 2026 15:13

@mdydek mdydek left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add also mention in the docs about onerror

Comment thread packages/react-native-audio-api/common/cpp/audioapi/core/AudioContext.cpp Outdated
Comment thread packages/react-native-audio-api/src/core/AudioContext.ts Outdated
Comment thread packages/react-native-audio-api/src/jsi-interfaces.ts Outdated
Comment on lines +82 to +96
JSI_PROPERTY_SETTER_IMPL(AudioContextHostObject, onerror) {
auto audioContext = std::static_pointer_cast<AudioContext>(context_);

if (!value.isObject() || !value.getObject(runtime).isFunction(runtime)) {
audioContext->setOnError(nullptr);
return;
}

auto jsFunc = std::make_shared<jsi::Function>(value.getObject(runtime).getFunction(runtime));

audioContext->setOnError([jsFunc, invoker = callInvoker_, rt = &runtime]() {
invoker->invokeAsync([jsFunc, rt]() { jsFunc->call(*rt); });
});
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

even if it looked simple, it can introduce some errors, such as datarace when swaping the function mid oboe's onError, reference cycle due to strong ptr and that it can be destroyed on different thread. Utilize existing AudioEventHandlerRegistry and compare how events are done using it and implement the onerror this way.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

corrected

@basiav
basiav force-pushed the fix/oboe-stream-dies branch from 0db88c5 to d43fedc Compare September 16, 2026 11:50
@basiav
basiav requested a review from mdydek September 16, 2026 12:07
Comment thread packages/audiodocs/docs/core/audio-context.mdx Outdated
@basiav
basiav requested a review from closetcaiman September 16, 2026 14:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

android Native Android implementation, C++/Java/Kotlin bindings, or Android-specific issues fix Code changes specifically addressing and resolving a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Android] AudioContext.resume() fails permanently after the Oboe stream dies with a non-Disconnected error (audio silent for the rest of the session)

3 participants