Skip to content

fix: resume the audio context when a buffer source starts - #1272

Open
giaBaoJS wants to merge 1 commit into
software-mansion:mainfrom
giaBaoJS:fix/buffer-source-start-context-state
Open

fix: resume the audio context when a buffer source starts#1272
giaBaoJS wants to merge 1 commit into
software-mansion:mainfrom
giaBaoJS:fix/buffer-source-start-context-state

Conversation

@giaBaoJS

@giaBaoJS giaBaoJS commented Sep 5, 2026

Copy link
Copy Markdown

No linked issue: found by reading AudioScheduledSourceNode.start() against
the two subclasses that override it.

⚠️ Breaking changes ⚠️

None.

Introduced changes

#1239 moved the implicit "a source started, so the driver is running now"
transition out of C++ and into JS. It deleted this block from
AudioScheduledSourceNode::start():

if (std::shared_ptr<BaseAudioContext> context = context_.lock()) {
  if (auto *audioContext = dynamic_cast<AudioContext *>(context.get())) {
    audioContext->start();
  }
}

and replaced it with this.context.markRunningOnSourceStart() in
AudioScheduledSourceNode.start(). The C++ hook covered every scheduled
source, because they all inherit that start(). The JS hook does not:
AudioBufferSourceNode.start() and AudioBufferQueueSourceNode.start()
override it (they take extra offset / duration arguments) and never call
the base or the hook, so nothing publishes the transition for them.
AudioContext::start() on the C++ side now has no callers at all.

Result, on a context that has not been resumed explicitly:

const context = new AudioContext();
const source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
source.start();

context.state; // 'suspended'

context.state stays 'suspended' for the lifetime of the playback, and
markRunningOnSourceStart()'s resume() never reaches the native driver.
context.createOscillator().start() on the same context reports 'running',
because OscillatorNode does not override start().

The fix adds the one call to both overrides. It is the same call
Audio/AudioFileSourceNode.play() already makes for exactly this reason, with
the comment copied from audioscheduledsourcenode. markRunningOnSourceStart()
is a no-op on BaseAudioContext, so OfflineAudioContext is unaffected, and
AudioContext's override only acts while the state is 'suspended', so an
already-running context and a second start() call both stay no-ops.

Tests

tests/context-state-on-source-start.test.ts drives the real
core/AudioContext over a stubbed JSI context and asserts, for each of
createOscillator, createBufferSource and createBufferQueueSource, that
the state is 'suspended' with no resume() before start() and 'running'
with exactly one resume() after it.

Without the fix, the oscillator case passes and both buffer cases fail with
Expected: "running" / Received: "suspended". Removing
markRunningOnSourceStart() from AudioScheduledSourceNode.start() instead
turns all three red, so the assertions are not vacuous. Full suite: 82 passing.

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

On the blank boxes: there is no issue to link, as noted at the top. No public
API, interface support or old-arch spec surface changes, so the docs, the
coverage table and the Android spec file are all unaffected. The web backend
delegates state entirely to the browser's own AudioContext, so src/web-core
has no equivalent hook to fix.

AudioBufferSourceNode.start() and AudioBufferQueueSourceNode.start()
override AudioScheduledSourceNode.start() without calling
markRunningOnSourceStart(), so an AudioContext driven only by a buffer
source keeps reporting 'suspended' and its native driver is never
resumed. Add the call to both overrides, matching the base class and
AudioFileSourceNode.play().
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant