Skip to content

AudioWorkletOutput: playback freezes due to race condition between async worklet creation and sample delivery #2561

@gisenberg

Description

@gisenberg

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

After seeking while paused and then pressing play, playback appears to start (playerState changes to Playing) but the tick position never advances and no audio is produced. The player becomes permanently stuck — subsequent play/pause cycles do not recover. The only way to recover is to call stop(), seek to a new position, and play() again.

The root cause is a race condition in AlphaSynthAudioWorkletOutput. When play() is called, the AudioWorkletNode is created asynchronously via Environment.createAudioWorklet().then(...). However, the worker-side AlphaSynth immediately begins generating samples after output.play() returns and sends them via addSamples(). Since _worklet is still null during the async gap, addSamples() silently drops the samples via optional chaining (this._worklet?.port.postMessage(...)). This can cause the synth to desynchronize — in particular, _onSamplesPlayed never fires with a non-zero count, so position updates with isSeek: false are never emitted after a seek.

Expected Behavior

Samples produced between play() being called and the AudioWorkletNode being created should be buffered and delivered once the worklet is ready, not silently dropped. Playback should reliably start and advance after any sequence of seek + play operations.

Steps To Reproduce

  1. Load a GuitarPro file with the AlphaTab API using AudioWorklet output (the default on modern browsers)
  2. Start playback and let it play for a few seconds
  3. Pause playback
  4. Seek to a different position (set tickPosition or timePosition)
  5. Press play again
  6. Observe that playerState is 1 (Playing) but tickPosition and timePosition are frozen
  7. No audio is produced; the seek cursor does not move

Link to jsFiddle, CodePen, Project

No response

Version and Environment

alphaTab 1.8.1
Electron 34
Vite bundled, AudioWorklet output
Windows 11 Pro

Platform

Node.js

Anything else?

The AlphaSynthScriptProcessorOutput does not have this bug because ScriptProcessorNode creation is synchronous in its play() method — the node is fully connected before the method returns.

Suggested fix: Buffer messages in addSamples() and resetSamples() when _worklet is null, and flush them after the worklet connects in the .then() callback. Clear the buffer on pause(). This is a minimal change to AlphaSynthAudioWorkletOutput:

  // In addSamples():
  if (this._worklet) {
      this._worklet.port.postMessage(msg);
  } else {
      this._pendingMessages.push(msg);
  }

  // In the .then() callback after worklet creation:
  for (const msg of this._pendingMessages) {
      this._worklet.port.postMessage(msg);
  }
  this._pendingMessages = [];

  // In pause():
  this._pendingMessages = [];

Metadata

Metadata

Assignees

Labels

area-playerRelated to the audio playback engine.platform-javascriptRelated to the JavaScript version of alphaTabstate-acceptedThis is a valid topic to work on.

Type

Projects

Status

No status

Relationships

None yet

Development

No branches or pull requests

Issue actions