@opentui/core
Version:
OpenTUI is a TypeScript library on a native Zig core for building terminal user interfaces (TUIs)
491 lines (490 loc) • 17.3 kB
TypeScript
import { EventEmitter } from "events";
import { type AudioStreamDemuxer, type AudioStreamDemuxerFactory } from "./audio-stream/demuxer.js";
import { type AudioStats } from "./zig-structs.js";
export interface AudioSetupOptions {
autoStart?: boolean;
sampleRate?: number;
playbackChannels?: number;
startOptions?: AudioStartOptions;
}
export interface AudioStartOptions {
periodSizeInFrames?: number;
periodSizeInMilliseconds?: number;
periods?: number;
performanceProfile?: number;
shareMode?: number;
noPreSilencedOutputBuffer?: boolean;
noClip?: boolean;
noDisableDenormals?: boolean;
noFixedSizedCallback?: boolean;
wasapiNoAutoConvertSrc?: boolean;
wasapiNoDefaultQualitySrc?: boolean;
alsaNoMMap?: boolean;
alsaNoAutoFormat?: boolean;
alsaNoAutoChannels?: boolean;
alsaNoAutoResample?: boolean;
}
export interface AudioPlayOptions {
volume?: number;
pan?: number;
loop?: boolean;
groupId?: number;
}
export type AudioStreamBody = ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>;
export type AudioStreamFormat = "mp3" | "flac";
export interface AudioStreamContentTypeContext {
readonly format: AudioStreamFormat;
readonly contentType: string | null;
readonly status: number;
readonly url: string;
}
export type AudioStreamContentTypePolicy = "validate" | "ignore" | ((context: AudioStreamContentTypeContext) => boolean);
export interface AudioStreamConnectContext {
readonly signal: AbortSignal;
readonly attempt: number;
}
export interface AudioStreamConnection<I = unknown> {
readonly body: AudioStreamBody;
readonly info: I;
close?(): void | Promise<void>;
}
export type AudioStreamRetryPhase = "connect" | "read";
export interface AudioStreamRetryContext {
readonly attempt: number;
readonly maxRetries: number;
readonly phase: AudioStreamRetryPhase;
}
export type AudioStreamRetryDecision = false | {
readonly delayMs?: number;
};
export interface AudioStreamConnector<I = unknown> {
connect(context: AudioStreamConnectContext): Promise<AudioStreamConnection<I>>;
}
export interface AudioStreamBufferOptions {
capacityMs?: number;
startupMs?: number;
resumeMs?: number;
}
export interface AudioStreamReconnectOptions {
maxRetries?: number;
initialDelayMs?: number;
maxDelayMs?: number;
backoffFactor?: number;
retryOnEnd?: boolean;
retry?(error: AudioStreamError, context: AudioStreamRetryContext): AudioStreamRetryDecision;
}
export interface AudioStreamOptions {
format?: AudioStreamFormat;
volume?: number;
pan?: number;
groupId?: number;
maxProbeBytes?: number;
buffer?: AudioStreamBufferOptions;
signal?: AbortSignal;
}
export interface AudioStreamBodyOptions<M = AudioStreamMetadata> extends AudioStreamOptions {
demuxer?: AudioStreamDemuxerFactory<M>;
contentTypePolicy?: never;
request?: never;
reconnect?: never;
metadataEncoding?: never;
}
export interface AudioStreamSourceOptions<I = unknown, M = AudioStreamMetadata> extends AudioStreamOptions {
demuxer?: (info: I) => AudioStreamDemuxer<M> | null;
reconnect?: AudioStreamReconnectOptions;
contentTypePolicy?: never;
request?: never;
metadataEncoding?: never;
}
export interface AudioStreamUrlOptions extends AudioStreamOptions {
request?: Omit<RequestInit, "body" | "signal">;
reconnect?: AudioStreamReconnectOptions;
metadataEncoding?: string;
contentTypePolicy?: AudioStreamContentTypePolicy;
demuxer?: never;
}
export type AudioStreamState = "initializing" | "buffering" | "playing" | "reconnecting" | "ended" | "errored" | "disposed";
export interface AudioStreamStats {
state: AudioStreamState;
sampleRate: number;
channels: number;
bufferedFrames: number;
capacityFrames: number;
bufferedDurationMs: number;
bytesReceived: bigint;
framesDecoded: bigint;
framesPlayed: bigint;
underruns: number;
reconnectAttempts: number;
}
export type AudioStreamMetadataFormat = "icy";
export interface AudioStreamMetadata {
readonly format: AudioStreamMetadataFormat;
readonly headers: Readonly<Record<string, string>>;
readonly fields: Readonly<Record<string, string>>;
}
export type AudioStreamAction = "fetch" | "response" | "source" | "demuxer" | "create" | "write" | "end" | "restart" | "stats" | "decoder" | "destroy" | "setVolume" | "setPan" | "setGroup";
export interface AudioStreamErrorContext {
action: AudioStreamAction;
status?: number;
errorCode?: number;
attempt?: number;
}
export interface AudioStreamReconnectEvent {
attempt: number;
delayMs: number;
maxRetries: number;
error: AudioStreamError;
}
export interface AudioStreamEvents<M = AudioStreamMetadata> {
metadata: [metadata: M | null];
reconnecting: [event: AudioStreamReconnectEvent];
ended: [];
error: [error: Error, context: AudioStreamErrorContext];
disposed: [];
}
export type AudioGroup = number;
export type AudioVoice = number;
export type AudioSound = number;
export interface AudioPlaybackDevice {
index: number;
name: string;
isDefault: boolean;
}
export interface AudioCaptureDevice {
index: number;
name: string;
isDefault: boolean;
}
export interface AudioCaptureOptions {
channels?: number;
capacityFrames?: number;
startOptions?: AudioStartOptions;
}
export interface AudioCaptureStreamOptions extends AudioCaptureOptions {
chunkFrames?: number;
signal?: AbortSignal;
}
export type AudioCaptureStreamState = "initializing" | "capturing" | "stopping" | "stopped" | "errored" | "disposed";
export type AudioCaptureStreamAction = "start" | "read" | "stop" | "stats" | "destroy";
export interface AudioCaptureStreamErrorContext {
action: AudioCaptureStreamAction;
status?: number;
}
export interface AudioCaptureStreamEvents {
stopped: [];
error: [error: AudioCaptureStreamError, context: AudioCaptureStreamErrorContext];
disposed: [];
}
export interface AudioCaptureStats {
sampleRate: number;
channels: number;
capacityFrames: number;
bufferedFrames: number;
framesReceived: bigint;
framesRead: bigint;
framesDropped: bigint;
}
export interface AudioCaptureStreamStats extends AudioCaptureStats {
state: AudioCaptureStreamState;
bufferedDurationMs: number;
}
export interface AudioCaptureReadResult {
frames: Float32Array;
framesRead: number;
}
export type AudioRecordToFileOptions = AudioCaptureStreamOptions;
export type AudioRecorderState = "initializing" | "recording" | "stopping" | "stopped" | "errored" | "disposed";
export type AudioRecorderAction = "open" | "start" | "read" | "write" | "stop" | "finalize" | "publish" | "stats" | "destroy";
export interface AudioRecorderErrorContext {
action: AudioRecorderAction;
status?: number;
}
export interface AudioRecorderEvents {
stopped: [];
error: [error: AudioRecorderError, context: AudioRecorderErrorContext];
disposed: [];
}
export interface AudioRecorderStats extends AudioCaptureStats {
state: AudioRecorderState;
bufferedDurationMs: number;
framesWritten: bigint;
dataBytesWritten: bigint;
durationMs: number;
}
export type AudioAction = "createAudioEngine" | "start" | "startMixer" | "stop" | "loadSound" | "loadSoundFile" | "unloadSound" | "group" | "play" | "stopVoice" | "setVoiceGroup" | "setGroupVolume" | "setMasterVolume" | "mixFrames" | "enableTap" | "readTapFrames" | "listPlaybackDevices" | "selectPlaybackDevice" | "clearPlaybackDeviceSelection" | "listCaptureDevices" | "selectCaptureDevice" | "clearCaptureDeviceSelection" | "startCapture" | "readCaptureFrames" | "getCaptureStats" | "stopCapture" | "getStats";
export interface AudioErrorContext {
action: AudioAction;
status?: number;
}
export interface AudioEvents {
error: [error: Error, context: AudioErrorContext];
started: [];
mixerStarted: [];
captureStarted: [];
captureStopped: [];
stopped: [];
disposed: [];
}
export type AudioInitializationAction = "resolveRenderLib" | "createAudioEngine" | "start";
export declare class AudioInitializationError extends Error {
readonly action: AudioInitializationAction;
readonly status?: number;
constructor(action: AudioInitializationAction, message: string, status?: number, cause?: unknown);
}
export declare class AudioCaptureStreamError extends Error {
readonly context: AudioCaptureStreamErrorContext;
constructor(message: string, context: AudioCaptureStreamErrorContext, cause?: unknown);
}
export declare class AudioRecorderError extends Error {
readonly context: AudioRecorderErrorContext;
constructor(message: string, context: AudioRecorderErrorContext, cause?: unknown);
}
export declare class AudioStreamError extends Error {
readonly context: AudioStreamErrorContext;
constructor(message: string, context: AudioStreamErrorContext, cause?: unknown);
}
export declare class AudioStream<M = AudioStreamMetadata> extends EventEmitter<AudioStreamEvents<M>> {
readonly closed: Promise<void>;
readonly format: AudioStreamFormat;
private readonly lib;
private readonly engine;
private readonly connector;
private readonly demuxerFactory?;
private readonly readAction;
private readonly options;
private readonly removeFromOwner;
private readonly lifecycleController;
private nativeStreamId;
private nativeStats;
private activeAttempt;
private pendingCleanup;
private reconnectAttempts;
private consecutiveReconnectAttempts;
private disposed;
private exposed;
private terminalError;
private metadata;
private pendingMetadataEvent;
private metadataEventScheduled;
private terminalEventScheduled;
private setupResolve;
private setupReject;
private closedResolve;
private readonly setupPromise;
private readonly overallAbortListener;
private constructor();
get state(): AudioStreamState;
private open;
getStats(): AudioStreamStats;
getMetadata(): M | null;
setVolume(volume: number): boolean;
setPan(pan: number): boolean;
setGroup(groupId: number): boolean;
private control;
dispose(): void;
private runLifecycle;
private createNativeStream;
private consumeSource;
private pumpSource;
private processDemuxOutput;
private writeStreamChunk;
private resolveConnection;
private beginResourceAcquisition;
private cleanupAttempt;
private performAttemptCleanup;
private runBoundedAttemptCleanup;
private retry;
private awaitReady;
private observeReady;
private awaitEnded;
private pollNativeSnapshot;
private snapshotError;
private finish;
private publishMetadata;
private emitMetadata;
private emitAsync;
private emitTerminal;
private isAttemptActive;
private stopSource;
private closeNativeStream;
private readNativeStats;
private toPublicStats;
private removeOwner;
}
export declare class AudioCaptureStream extends EventEmitter<AudioCaptureStreamEvents> {
readonly readable: ReadableStream<Float32Array>;
readonly sampleRate: number;
readonly channels: number;
readonly chunkFrames: number;
readonly closed: Promise<void>;
private readonly init;
private readonly lifecycleController;
private streamController;
private nativeStats;
private currentState;
private pendingFrames;
private readonly pendingSamples;
private producerStopAttempted;
private producerStopped;
private producerMayBeRunning;
private ownerRemoved;
private exposed;
private terminal;
private discardRequested;
private discardDecisionScheduled;
private pumpPromise;
private producerCleanupPromise;
private lastCleanupFailure;
private terminalCompletionPromise;
private closedResolve;
private readonly signalAbortListener;
private constructor();
get state(): AudioCaptureStreamState;
private open;
getStats(): AudioCaptureStreamStats;
stop(): void;
dispose(): void;
private disposeInternal;
private pull;
private pump;
private pumpSource;
private discardNativeRing;
private requestDiscardDrain;
private scheduleDiscardIfIdle;
private refreshStats;
private observeProducer;
private stopProducer;
private finishStopped;
private fail;
private operationError;
private cleanupProducer;
private retryTerminalCleanup;
private publicStats;
private refreshFinalStats;
private removeOwner;
private emitTerminal;
}
export declare class AudioRecorder extends EventEmitter<AudioRecorderEvents> {
private static readonly fileSystem;
readonly filePath: string;
readonly format: "wav";
readonly sampleRate: number;
readonly channels: number;
readonly closed: Promise<void>;
private readonly init;
private currentState;
private capture;
private reader;
private fileHandle;
private tempPath;
private captureStats;
private framesWritten;
private dataBytesWritten;
private stopRequested;
private terminal;
private terminationRequest;
private publicationStarted;
private exposed;
private ownerRemoved;
private cleanupPromise;
private resourceCleanupPromise;
private retainedCleanupScheduled;
private lifecyclePromise;
private closedResolve;
private readonly signalAbortListener;
private readonly captureErrorListener;
private constructor();
get state(): AudioRecorderState;
private open;
getStats(): AudioRecorderStats;
stop(): void;
dispose(): void;
private consume;
private writeSamples;
private readWithStats;
private complete;
private publish;
private fail;
private requestTermination;
private finishCleanup;
private cleanupOwnedResources;
private retryRetainedCleanup;
private scheduleRetainedCleanup;
private hasRetainedResources;
private openTemporaryFile;
private writeFully;
private ensureOpening;
private fromCaptureError;
private removeOwner;
private emitTerminal;
}
export declare class Audio extends EventEmitter<AudioEvents> {
static create(options?: AudioSetupOptions): Audio;
readonly sampleRate: number;
private readonly lib;
private readonly defaultStartOptions;
private engine;
private readonly groups;
private readonly streams;
private playbackStarted;
private mixerStarted;
private captureStarted;
private captureDeviceOpen;
private captureBufferAvailable;
private captureChannels;
private captureCapacityFrames;
private captureOwner;
private captureStream;
private disposing;
private constructor();
private throwAfterInitializationCleanup;
private emitError;
start(options?: AudioStartOptions): boolean;
startMixer(): boolean;
stop(): boolean;
isStarted(): boolean;
isMixerStarted(): boolean;
loadSound(data: Uint8Array | ArrayBuffer): AudioSound | null;
loadSoundFile(filePath: string): Promise<AudioSound | null>;
unloadSound(sound: AudioSound): boolean;
group(name: string): AudioGroup | null;
play(sound: AudioSound, options?: AudioPlayOptions): AudioVoice | null;
playStream<M = AudioStreamMetadata>(source: AudioStreamBody, options?: AudioStreamBodyOptions<M>): Promise<AudioStream<M>>;
playStreamUrl(source: string | URL, options?: AudioStreamUrlOptions): Promise<AudioStream<AudioStreamMetadata>>;
playStreamSource<I, M = AudioStreamMetadata>(connector: AudioStreamConnector<I>, options?: AudioStreamSourceOptions<I, M>): Promise<AudioStream<M>>;
private openStream;
stopVoice(voice: AudioVoice): boolean;
setVoiceGroup(voice: AudioVoice, group: AudioGroup): boolean;
setGroupVolume(group: AudioGroup, volume: number): boolean;
setMasterVolume(volume: number): boolean;
mixFrames(frameCount: number, channels?: number): Float32Array | null;
enableTap(capacityFrames?: number): boolean;
disableTap(): boolean;
readTapFrames(frameCount: number, channels?: number): {
frames: Float32Array;
framesRead: number;
} | null;
listPlaybackDevices(): AudioPlaybackDevice[] | null;
selectPlaybackDevice(index: number): boolean;
clearPlaybackDeviceSelection(): void;
openCapture(options?: AudioCaptureStreamOptions): Promise<AudioCaptureStream>;
recordToFile(filePath: string, options?: AudioRecordToFileOptions): Promise<AudioRecorder>;
listCaptureDevices(): AudioCaptureDevice[] | null;
selectCaptureDevice(index: number): boolean;
clearCaptureDeviceSelection(): void;
startCapture(options?: AudioCaptureOptions): boolean;
isCapturing(): boolean;
private isCapturingInternal;
readCaptureFrames(frameCount: number): AudioCaptureReadResult | null;
getCaptureStats(): AudioCaptureStats | null;
stopCapture(): boolean;
private emitCaptureOwnershipError;
private startCaptureInternal;
private readCaptureInternal;
private getCaptureStatsInternal;
private stopCaptureInternal;
getStats(): AudioStats | null;
dispose(): void;
}
export declare function setupAudio(options?: AudioSetupOptions): Audio;