pw-client
Version:
Node.js wrapper for developing PipeWire clients
265 lines (264 loc) • 8.43 kB
text/typescript
import EventEmitter from "node:events";
import { AudioFormat } from "./audio-format.mjs";
import { AudioQuality } from "./audio-quality.mjs";
import type { NativePipeWireSession } from "./session.mjs";
import { type Latency, type StreamState } from "./stream.mjs";
import { type BufferConfig } from "./buffer-config.mjs";
export interface NativeAudioOutputStream {
connect: (options?: {
preferredFormats?: Array<number>;
preferredRates?: Array<number>;
}) => Promise<void>;
disconnect: () => Promise<void>;
get writableFrames(): number;
get framesPerQuantum(): number;
get bufferSize(): number;
write: (data: ArrayBuffer) => void;
waitForBuffer: () => Promise<number>;
isFinished: () => Promise<void>;
destroy: () => Promise<void>;
}
/**
* Configuration options for creating audio output streams.
* All options are optional with sensible defaults.
*
* @property name - Human-readable name displayed in PipeWire clients (default: "Node.js Audio")
* @property rate - Sample rate in Hz (default: 48000)
* @property channels - Number of audio channels (default: 2 for stereo)
* @property role - Audio role hint for PipeWire routing (default: "Music")
* @property quality - Quality preset that affects format negotiation (default: AudioQuality.Standard)
* @property preferredFormats - Override format negotiation order
* @property preferredRates - Override sample rate negotiation order
* @property autoConnect - Whether to auto-connect after creation (default: false)
* @property buffering - Buffer configuration for performance optimization
* @property enableMonitoring - Enable performance monitoring and diagnostics (default: false)
*
* @example
* ```typescript
* const opts: AudioOutputStreamOpts = {
* name: "My Synthesizer",
* rate: 44100,
* channels: 2,
* quality: AudioQuality.High,
* role: "Music",
* enableMonitoring: true
* };
* ```
*/
export interface AudioOutputStreamOpts {
name?: string;
rate?: number;
channels?: number;
role?: "Movie" | "Music" | "Camera" | "Screen" | "Communication" | "Game" | "Notification" | "DSP" | "Production" | "Accessibility" | "Test";
quality?: AudioQuality;
preferredFormats?: Array<AudioFormat>;
preferredRates?: Array<number>;
autoConnect?: boolean;
buffering?: BufferConfig;
enableMonitoring?: boolean;
}
export interface AudioOutputStreamProps {
volume: number;
mute: boolean;
monitorMute: boolean;
softMute: boolean;
channels: Array<{
id: number;
volume: number;
mute: boolean;
monitorVolume: number;
softVolume: number;
}>;
params: Record<string, unknown>;
}
interface AudioEvents {
propsChange: [AudioOutputStreamProps];
formatChange: [{
format: AudioFormat;
channels: number;
rate: number;
}];
latencyChange: [Latency];
unknownParamChange: [number];
stateChange: [StreamState];
error: [Error];
bufferAdjusted: [{
oldSize: number;
newSize: number;
reason: string;
}];
}
/**
* Audio output stream for playing audio samples to PipeWire.
* Streams are event emitters that provide real-time feedback about format changes,
* latency updates, and connection state.
*
* @interface AudioOutputStream
* @extends EventEmitter
*
* @example
* ```typescript
* const stream = await session.createAudioOutputStream({
* name: "Audio Generator",
* channels: 2,
* quality: AudioQuality.High
* });
*
* await stream.connect();
* await stream.write(audioSamples);
* await stream.disconnect();
* ```
*
* ## Events
*
* AudioOutputStream emits the following events:
*
* ### `formatChange`
* Emitted when the stream's audio format is negotiated or changes.
*
* **Event payload:** `{ format: AudioFormat, channels: number, rate: number }`
*
* ```typescript
* stream.on('formatChange', ({ format, channels, rate }) => {
* console.log(`Format: ${format.description}, ${channels}ch @ ${rate}Hz`);
* });
* ```
*
* ### `stateChange`
* Emitted when the stream's connection state changes.
*
* **Event payload:** `StreamState` (string: "error", "unconnected", "connecting", "paused", "streaming")
*
* ```typescript
* stream.on('stateChange', (state) => {
* console.log(`Stream state: ${state}`);
* });
* ```
*
* ### `latencyChange`
* Emitted when the stream's latency information updates.
*
* **Event payload:** `{ min: number, max: number, default: number }` (all values in nanoseconds)
*
* ```typescript
* stream.on('latencyChange', ({ min, max, default: def }) => {
* console.log(`Latency: ${def/1000000}ms (range: ${min/1000000}-${max/1000000}ms)`);
* });
* ```
*
* ### `propsChange`
* Emitted when stream properties (volume, mute, etc.) change.
*
* **Event payload:** `AudioOutputStreamProps`
*
* ```typescript
* stream.on('propsChange', (props) => {
* console.log(`Volume: ${props.volume}, Muted: ${props.mute}`);
* });
* ```
*
* ### `error`
* Emitted when an error occurs during streaming.
*
* **Event payload:** `Error`
*
* ```typescript
* stream.on('error', (error) => {
* console.error('Stream error:', error.message);
* });
* ```
*
* ### `unknownParamChange`
* Emitted when PipeWire sends an unrecognized parameter change.
*
* **Event payload:** `number` (parameter ID)
*
* ```typescript
* stream.on('unknownParamChange', (paramId) => {
* console.log(`Unknown parameter changed: ${paramId}`);
* });
* ```
*/
export interface AudioOutputStream extends EventEmitter<AudioEvents> {
/**
* Connect the stream to PipeWire audio system.
* Triggers format negotiation and initializes audio processing.
*/
connect: () => Promise<void>;
/**
* Disconnect the stream from PipeWire.
* Stops audio processing and releases resources.
*/
disconnect: () => Promise<void>;
/**
* Write audio samples to the stream.
* Samples are JavaScript Numbers (-1.0 to 1.0) converted to negotiated format.
*/
write: (samples: Iterable<number>) => Promise<void>;
/**
* Wait for all buffered audio to finish playing.
* Useful for ensuring complete playback before cleanup.
*/
isFinished: () => Promise<void>;
/**
* Dispose of the stream and release all resources.
* Alternative to disconnect() for final cleanup.
*/
dispose: () => Promise<void>;
/**
* Get the negotiated audio format after connection.
* Available only after successful connect().
*/
get format(): AudioFormat;
/**
* Get the negotiated number of audio channels.
* Available only after successful connect().
*/
get channels(): number;
/**
* Get the negotiated sample rate in Hz.
* Available only after successful connect().
*/
get rate(): number;
/**
* Get the buffer size in bytes.
* This represents the total internal buffer size as negotiated
* and adjusted by quantum alignment. If you specified a specific
* number of bytes for buffering, the actual buffer may be different
* due to quantum boundary alignment requirements.
* Available only after successful connect().
*/
get bufferSize(): number;
/**
* Check if the stream is currently connected to PipeWire.
*/
get isConnected(): boolean;
/**
* Automatic resource cleanup for `await using` syntax.
* Equivalent to calling dispose().
*/
[Symbol.asyncDispose]: () => Promise<void>;
}
export interface TypedNumericArray {
[index: number]: number;
buffer: ArrayBuffer;
subarray(offset: number, length: number): TypedNumericArray;
}
export type TypedNumericArrayCtor = new (size: number) => TypedNumericArray;
export declare class AudioOutputStreamImpl extends EventEmitter<AudioEvents> implements AudioOutputStream {
#private;
static create(session: NativePipeWireSession, opts?: AudioOutputStreamOpts): Promise<AudioOutputStream>;
private constructor();
connect(): Promise<void>;
disconnect(): Promise<void>;
get isConnected(): boolean;
write(samples: Iterable<number>): Promise<void>;
isFinished(): Promise<void>;
get format(): AudioFormat;
get channels(): number;
get rate(): number;
get bufferSize(): number;
dispose(): Promise<void>;
[Symbol.asyncDispose](): Promise<void>;
}
export {};