UNPKG

pw-client

Version:

Node.js wrapper for developing PipeWire clients

265 lines (264 loc) 8.43 kB
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 {};