pw-client
Version:
Node.js wrapper for developing PipeWire clients
162 lines (150 loc) • 4.35 kB
text/typescript
import { createRequire } from "node:module";
import {
AudioOutputStreamImpl,
type NativeAudioOutputStream,
type AudioOutputStream,
type AudioOutputStreamOpts,
type AudioOutputStreamProps,
} from "./audio-output-stream.mjs";
import type { Latency, StreamStateEnum } from "./stream.mjs";
const require = createRequire(import.meta.url);
const { PipeWireSession: NativePipeWireSession } =
require("../build/Debug/pipewire.node") as NativeModule;
interface NativeModule {
PipeWireSession: new () => NativePipeWireSession;
startSession: () => Promise<NativePipeWireSession>;
}
export interface NativePipeWireSession {
start: () => Promise<void>;
createAudioOutputStream: (opts: {
name: string;
format: number;
bytesPerSample: number;
rate: number;
channels: number;
buffering?: {
requestedQuanta?: number;
requestedBytes?: number;
requestedMs?: number;
};
props: Record<string, string>;
onStateChange: (state: StreamStateEnum, error: string) => void;
onPropsChange: (props: AudioOutputStreamProps) => void;
onFormatChange: (format: {
format: number;
channels: number;
rate: number;
}) => void;
onLatencyChange: (latency: Latency) => void;
onUnknownParamChange: (param: number) => void;
}) => Promise<NativeAudioOutputStream>;
destroy: () => Promise<void>;
}
/**
* PipeWire session that manages the connection to the PipeWire audio server.
*
* Sessions coordinate with PipeWire to create and manage audio streams.
* They must be properly disposed to prevent resource leaks.
*
* Use `startSession()` to create new instances rather than constructing directly.
*
* @example
* ```typescript
* const session = await startSession();
* const stream = await session.createAudioOutputStream({
* name: "My Audio App",
* quality: AudioQuality.Standard
* });
*
* // Always dispose when done
* await session.dispose();
* ```
*/
export class PipeWireSession {
readonly #nativeSession: NativePipeWireSession;
/**
* Creates and starts a new PipeWire session.
* @internal Use `startSession()` function instead
*/
static async start() {
const session = new PipeWireSession();
await session.#start();
return session;
}
private constructor() {
this.#nativeSession = new NativePipeWireSession();
}
#start() {
return this.#nativeSession.start();
}
/**
* Creates a new audio output stream.
*
* @param opts - Stream configuration options (all optional)
* @returns Promise resolving to AudioOutputStream instance
* @throws Will reject if session is disposed or stream creation fails
*
* @example
* ```typescript
* const stream = await session.createAudioOutputStream({
* name: "My Audio App",
* quality: AudioQuality.Standard,
* channels: 2
* });
* ```
*/
createAudioOutputStream(
opts?: AudioOutputStreamOpts
): Promise<AudioOutputStream> {
if (!this.#nativeSession) {
throw new Error("Session has been disposed");
}
return AudioOutputStreamImpl.create(this.#nativeSession, opts);
}
/**
* Disposes the session and releases PipeWire resources.
*
* After calling dispose(), the session cannot be used again.
* All streams created by this session will be invalidated.
*
* @returns Promise that resolves when cleanup is complete
*/
async dispose() {
if (this.#nativeSession) {
await this.#nativeSession.destroy();
}
}
/**
* Automatic resource cleanup for `await using` syntax.
* @see dispose
*/
[Symbol.asyncDispose]() {
return this.dispose();
}
}
/**
* Creates and starts a new PipeWire session.
*
* This is the main entry point for the pw-client API. Sessions manage
* connections to the PipeWire audio server and create audio streams.
*
* @returns Promise resolving to a started PipeWireSession
* @throws Will reject if PipeWire connection fails or daemon unavailable
*
* @example
* ```typescript
* // Manual resource management
* const session = await startSession();
* try {
* // Use session...
* } finally {
* await session.dispose();
* }
*
* // Automatic cleanup (Node.js 22+)
* await using session = await startSession();
* ```
*/
export async function startSession() {
return await PipeWireSession.start();
}