AudioTee.js: macOS system audio capture for Node.js

Introduction

AudioTee.js is an open source Node.js library and npm package that captures macOS system audio and streams it as PCM-encoded chunks through an EventEmitter interface. It's built on top of AudioTee, but designed specifically for use within Node.js applications.

Design approach

AudioTee.js bundles the AudioTee Swift binary (under 600KB for a universal macOS arm64/x86 binary) and spawns it as a child process. This keeps the implementation straightforward while leveraging the functionality of the original AudioTee project.

The library presents an interface that should feel at home to Node.js developers:

import { AudioTee } from 'audiotee'

const audiotee = new AudioTee({ 
  sampleRate: 16000,
  chunkDuration: 0.2 
})

audiotee.on('data', ({ data: Buffer }) => {
  // Process audio chunks as they arrive
  console.log(`Received ${data.length} bytes of audio`)
})

await audiotee.start()

Each chunk of data is a binary buffer representing chunkDuration seconds of audio in PCM format. Its length will equal exactly sampleRate * chunkDuration * bytesPerSample * channels bytes. AudioTee always forces mono output (meaning channels is always 1), and specifying any sampleRate value will result in 16-bit samples (meaning bytesPerSample is 2). Note that these last two behaviours are dictated by the underlying AudioTee library, not the Node.js wrapper.

Configuration options

AudioTee.js exposes most of AudioTee's configuration through its constructor:

const audiotee = new AudioTee({
  sampleRate: 16000,        // Target sample rate in Hz
  chunkDuration: 0.2,       // Duration of each audio chunk in seconds
  mute: false,              // Whether to mute the system audio
  includeProcesses: [],     // Array of process IDs to include
  excludeProcesses: []      // Array of process IDs to exclude
})

If you share the same ASR use case I do, you always want to pass a sampleRate which matches the one you're using in your ASR service. Similarly, most ASR services recommend audio packets of fixed duration, so you should pass an appropriate chunkDuration value.

Current limitations

AudioTee.js inherits all of AudioTee's limitations: it only works on macOS 14.2 or later, only captures from the default output device, and forces audio to mono. These constraints made sense for the original AudioTee design and continue to work well for most ASR use cases.

The library is currently at version 0.x.x, which means the API is still evolving. I expect some changes as I use it in more projects and get feedback from other developers, but the core EventEmitter is unlikely to change.

What’s next?

AudioTee.js represents the interface I personally want to use for system audio capture in Node.js. Hopefully others will find it useful too. If you have any feedback or suggestions, please open an issue or submit a pull request. More importantly, let me know what you're building with it!


If you're interested in the original AudioTee project, you can read about it in my earlier article on system audio capture on macOS. Both projects are open source and available on GitHub.

If you enjoyed this article, please consider sharing it on Bluesky or your platform of choice - it helps a lot. I’m all ears for any feedback, corrections or errors, so please if you have any. Thanks!