UNPKG

@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
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;