Skip to content

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

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

basiav wants to merge 20 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 stop function, 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 3420 → 3420 (0)

Unchanged sections (28)
Spec section Base pass Head pass Delta
Processing model 0 0 0
Other 56 56 0
AnalyserNode 138 138 0
AudioBuffer 140 140 0
AudioBufferSourceNode 218 218 0
AudioContext 58 58 0
AudioNode 261 261 0
AudioParam 680 680 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 723 723 0
PeriodicWave 33 33 0
ScriptProcessorNode 0 0 0
StereoPannerNode 102 102 0
WaveShaperNode 82 82 0

Baseline: 87cca81bff30ee15f3a407008cdc2ee30527ef89 · Candidate: 351732765ea5aa1101e55bfbce8fcb3e287a94d9

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
@mdydek
mdydek force-pushed the fix/oboe-stream-dies branch from 667851f to 3517327 Compare September 28, 2026 15:27

This branch has not been deployed

No deployments
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