audiotee
v0.0.7
Published
AudioTee.js captures your Mac's system audio output - whatever's playing through your speakers or headphones - and emits it as PCM encoded chunks at regular intervals. It's a tiny Node.js wrapper around the underlying [AudioTee](https://github.com/makeusa
Readme
AudioTee.js
AudioTee.js captures your Mac's system audio output - whatever's playing through your speakers or headphones - 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.
About
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 write it to stdout. AudioTee.js spawns that binary as a child process and relays stdout as data events.
Basic usage
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.
Installation
npm install audiotee
Installation will download a prebuilt universal macOS binary which runs on both Apple and Intel chips, and weighs less than 600Kb.
Options
The AudioTee constructor accepts an optional options object:
interface AudioTeeOptions {
sampleRate?: number // Target sample rate (Hz), default: device default
chunkDurationMs?: number // Duration of each audio chunk in milliseconds, defaults to 200
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
}Events
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', (level: LogLevel, message: MessageData) => {
// System logs from the AudioTee binary
// LogLevel: 'info' | 'debug'
// MessageData includes message string and optional context object
})Event details
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 debug and info messages from the underlying AudioTee binary
Note: Versions prior to 0.0.5 only emit the data event. All other events (start, stop, error, log) were fixed in version 0.0.5.
Requirements
- macOS >= 14.2
API stability
During the 0.x.x release, the API is unstable and subject to change without notice.
Best practices
- 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 and bandwidth compared to the 32-bit float the source stream was probably using
- 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.
Permissions
There is no provision in the underlying AudioTee library to pre-emptively check the state of the required NSAudioCaptureUsageDescription permission. You should be prompted to grant it the first time AudioTee.js tries to record anything, but at least some popular terminal emulators like iTerm and those built in to VSCode/Cursor don't. They will instead happily start recording total silence.
You can work around this either by using the built in macOS terminal emulator, or by granting system audio recording permission manually. Open Settings > Privacy & Security > Screen & System Audio Recording and scroll down to the System Audio Recording Only section (not the top 'Screen & System Audio Recording' section) and add the terminal application you're using.
Code signing
The AudioTee binary included in this package is ad-hoc signed (unsigned with a developer certificate). This is fine for most use cases because:
For Electron applications
When the AudioTee binary is bundled inside an Electron app, it inherits the code signature from the parent application. You don't need to sign it separately:
- Automatic inclusion: The binary at
node_modules/audiotee/bin/audioteegets bundled with your app - Parent signature inheritance: When you sign your Electron app (with properly configured
electron-builderorelectron-forge), all binaries within the app bundle automatically inherit that signature - Entitlements: Ensure your app's entitlements are correct.
For standalone usage
If you're running AudioTee.js outside of a signed parent application (e.g., directly via Node.js in Terminal):
- Development: The ad-hoc signed binary works fine
- First run: macOS may show a Gatekeeper warning that can be bypassed via System Settings
- Production: Consider signing the binary with your Developer ID if distributing as a standalone tool
License
The MIT License
Copyright (C) 2025 Nick Payne.
