-
Notifications
You must be signed in to change notification settings - Fork 89
Description
Missing Physical Playback Completion Callback/Event in naudiodon
Problem
naudiodon provides no event that fires when the last audio sample has been physically played by the hardware DAC.
The existing quit('WAIT') method maps to PortAudio's Pa_StopStream, which only flushes the currently active native callback buffer (a few milliseconds). It does not wait for the entire ring-buffer queue — already transferred to the audio driver — to finish playing. As a result, applications must use a setTimeout workaround based on the known buffer duration:
const playbackMs = Math.ceil(NUM_SAMPLES / SAMPLE_RATE * 1000);
ao.start();
ao.write(buffer);
setTimeout(() => {
ao.quit(() => console.log('Playback complete.'));
}, playbackMs);This is fragile for dynamic or streamed content where the duration is not known in advance.
PortAudio Capability
PortAudio already provides the exact mechanism needed: Pa_SetStreamFinishedCallback.
PaError Pa_SetStreamFinishedCallback(
PaStream *stream,
PaStreamFinishedCallback *streamFinishedCallback
);
typedef void PaStreamFinishedCallback(void *userData);This callback is invoked by PortAudio after the last sample has been played by the hardware, making it suitable for precise playback-completion detection.
Reference: PortAudio API docs — Pa_SetStreamFinishedCallback
Required Changes to naudiodon
1. C++ addon — register the callback after stream open
In the output stream initialisation (likely src/AudioOutput.cc), after Pa_OpenStream(...) succeeds:
static void OnStreamFinished(void* userData) {
// userData points to the N-API callback reference
CallbackData* cb = static_cast<CallbackData*>(userData);
// schedule napi_call_function on the libuv event loop (use napi_threadsafe_function)
cb->tsfn.NonBlockingCall();
}
// after Pa_OpenStream succeeds:
Pa_SetStreamFinishedCallback(stream, OnStreamFinished);Because OnStreamFinished is called from the PortAudio audio thread, it must not call N-API directly. Use napi_create_threadsafe_function / napi_threadsafe_function to safely schedule the JS callback back onto the Node.js event loop.
2. JavaScript layer — expose as an event
In index.js, emit a new event from the threadsafe callback landing:
// inside AudioIO(), after stream creation:
audioIOAdon.onStreamFinished(() => {
ioStream.emit('playbackEnd');
});3. Usage (proposed API)
const ao = naudiodon.AudioIO({ outOptions: { ... } });
ao.on('playbackEnd', () => {
console.log('Last sample physically played.');
});
ao.start();
ao.write(buffer);Summary
| Current behaviour | After fix | |
|---|---|---|
quit('WAIT') |
Drains active native buffer only (~few ms) | Unchanged |
'finish' event |
Node.js Writable stream finished | Unchanged |
'finished' event |
Fires after quit('WAIT') returns |
Unchanged |
'playbackEnd' event |
Missing | Fires when last sample leaves the DAC |
References
- PortAudio:
Pa_SetStreamFinishedCallback - N-API thread-safe functions: Node.js docs
- naudiodon source:
index.js, C++ addon insrc/