AudioTee.js captures your Mac's system audio output and emits it as PCM encoded chunks at regular intervals. It's a tiny Node.js wrapper around the underlying AudioTee swift binary, which is bundled in this repository and distributed with the package published to npm.
AudioTee is a standalone swift binary which uses the Core Audio taps API introduced in macOS 14.2 to 'tap' whatever's playing through your speakers and emit it to stdout. AudioTee.js spawns that binary as a child process and forwards stdout as data events.
import { AudioTee, AudioChunk } from 'audiotee'
const audiotee = new AudioTee({ sampleRate: 16000 })
audiotee.on('data', (chunk: AudioChunk) => {
// chunk.data contains a raw PCM chunk of captured system audio
})
await audiotee.start()
// ... later
await audiotee.stop()Unless otherwise specified, AudioTee will capture system audio from all running processes.
npm install audiotee
Installation will download a prebuilt universal macOS binary which runs on both Apple and Intel chips, and weighs less than 600Kb.
The AudioTee constructor accepts an optional options object:
interface AudioTeeOptions {
sampleRate?: number // Target sample rate (Hz), default: device default
chunkDuration?: number // Duration of each audio chunk in seconds, default: 0.2
mute?: boolean // Mute system audio whilst capturing, default: false
includeProcesses?: number[] // Only capture audio from these process IDs
excludeProcesses?: number[] // Exclude audio from these process IDs
}sampleRate: Converts audio to the specified sample rate. Common values are16000,44100,48000.chunkDuration: Controls how frequently data events are emitted. Smaller values = more frequent events with smaller chunks. Specified in secondsmute: Whentrue, system audio is muted whilst AudioTee is capturingincludeProcesses: Array of process IDs to capture audio from (all others filtered out)excludeProcesses: Array of process IDs to exclude from capture
AudioTee uses an EventEmitter interface to stream audio data and system events:
// Audio data events
audiotee.on('data', (chunk: { data: Buffer }) => {
// Raw PCM audio data - mono channel, 32-bit float or 16-bit int depending on conversion
})
// Lifecycle events
audiotee.on('start', () => {
// Audio capture has started
})
audiotee.on('stop', () => {
// Audio capture has stopped
})
// Error handling
audiotee.on('error', (error: Error) => {
// Process errors, permission issues, etc.
})
// Logging
audiotee.on('log', (message: string, level: LogLevel) => {
// System logs from the AudioTee binary
// LogLevel: 'metadata' | 'stream_start' | 'stream_stop' | 'info' | 'error' | 'debug'
})Note: a bug in versions up to 0.0.2 means only the data lifecycle event is actually currently emitted.
data: Emitted for each audio chunk. Thedataproperty contains raw PCM audio bytesstart: Emitted when audio capture begins successfullystop: Emitted when audio capture endserror: Emitted for process errors, permission failures, or system issueslog: Emitted for all system messages from the underlying AudioTee binary
- macOS >= 14.2
During the 0.x.x release, the API is unstable and subject to change without notice.
- Always specify a sample rate. Tell AudioTee what you want, rather than having to parse the
metadatamessage to see what you got from the output device - Specifying any sample rate automatically switches encoding to use 16-bit signed integers, which is half the byte size compared to the 32-bit float the stream probably uses natively
- You'll probably need to specify a different
chunkDurationdepending on your use case. For example, some ASRs are quite particular about the exact length of each chunk they expect to process.
Copyright (C) 2025 Nick Payne.