export type { CaptureSessionState, MediaRestorationState } from './Lifecycle.js'; export type { NetworkProbe, NetworkProbeOptions, NetworkProbeOnceOptions, NetworkProbeSnapshot, NetworkPeerSnapshot, NetworkDirectionMetrics, NetworkRoute, NetworkSummary, NetworkQuality } from './NetworkProbe.js'; import { Lifecycle, type CaptureSessionState, type MediaRestorationState, type P2pPeerState, type P2pPeerEvent } from './Lifecycle.js'; import { type NetworkProbe, type NetworkProbeOptions, type NetworkProbeOnceOptions, type NetworkProbeSnapshot } from './NetworkProbe.js'; import type { MediaSessionProvider } from './MediaSession.js'; import type { SfuSession } from './SfuSession.js'; /** Small-room WebRTC voice. The room transports signaling only, never audio. */ export interface VoiceRoom { stream(channel: string, payload: unknown): Promise; onStream(listener: (message: { channel: string; payload: unknown; }) => void): () => void; onConnectionChange(listener: (state: string) => void): () => void; readonly connectionState: string; getMediaSession?: MediaSessionProvider; getIceServers?(signal: AbortSignal): Promise; } export interface VoiceOptions { /** Supply short-lived TURN credentials for connectivity across restrictive NATs. */ iceServers?: RTCIceServer[]; /** Fetch fresh, short-lived credentials on each join. Explicit iceServers takes precedence. */ iceServersProvider?: (signal: AbortSignal) => Promise; iceTransportPolicy?: RTCIceTransportPolicy; /** Opus payload bits/second per remote participant, default 24000. */ maxBitrate?: number; /** Mesh fanout limit, default 3 remote participants. */ maxPeers?: number; channel?: string; } export interface VoicePeer { id: string; state: RTCPeerConnectionState; } interface Peer { pc: RTCPeerConnection; lifecycle: Lifecycle; token: string; /** Remote media-join incarnation; separates reordered frames across rejoins. */ remoteIncarnation?: string; seen: number; started: number; answer?: RTCSessionDescriptionInit; offer?: RTCSessionDescriptionInit; busy: boolean; trickle: boolean; candidates: RTCIceCandidateInit[]; remoteCandidates: Map; appliedCandidates: Set; cancelGathering?: () => void; recoveryTimer?: ReturnType; remoteStream?: MediaStream; } /** Pure SDP transform: request mono Opus, DTX (silence suppression), and FEC. */ export declare function voiceDescription(description: RTCSessionDescriptionInit, maxBitrate: number): RTCSessionDescriptionInit; export declare class VoiceClient { private readonly room; private readonly options; protected stream?: MediaStream; protected joining?: Promise; private leaving?; private sessionLifecycle; protected epoch: number; private id; private incarnation; private retiredTokens; private retiredIncarnations; private pendingResets; private iceServers; private iceAbort?; private refreshTimer?; private refreshAbort?; protected sfu?: SfuSession; private sfuPeers; private muted; private restoring?; private restorationLifecycle; private restorationGeneration; private restorationTimer?; private audioPublicationPending; private activity; protected peers: Map; private offs; private timer?; private tracks; private errors; private changes; readonly maxBitrate: number; readonly maxPeers: number; readonly channel: string; constructor(room: VoiceRoom, options?: VoiceOptions); static isSupported(): boolean; get sessionState(): CaptureSessionState; get state(): 'idle' | 'joining' | 'joined'; get isMuted(): boolean; get restorationState(): MediaRestorationState; get restorationHistory(): { from: MediaRestorationState; event: string; to: MediaRestorationState; }[]; getPeers(): VoicePeer[]; onTrack(listener: (peerId: string, stream: MediaStream) => void): () => void; onError(listener: (error: Error) => void): () => void; onPeersChange(listener: (peers: VoicePeer[]) => void): () => void; protected report(error: unknown): void; protected notifyListeners(listeners: Set, invoke: (listener: T) => void): void; private changed; /** Call from a user gesture. Microphone permission is requested only here. */ join(): Promise; private waitForRoomReady; private retryMediaRequest; private scheduleMediaRefresh; private refreshMediaConfig; setMuted(muted: boolean): void; private send; leave(): Promise; protected restartPeer(id: string, peer: Peer): void; private remove; private schedulePeerRecovery; private recoverDisconnectedPeers; /** * A suspended page cannot send heartbeats. Renew the local observation lease * before the timer runs again, then repair only peers whose RTC state actually * disconnected. A genuinely departed peer still expires after a fresh 15s * window if it does not answer the immediate hello. */ private reactivatePeerSession; protected get mediaActive(): boolean; private cancelTrackRestoration; private restorationRetryable; private startTrackRestoration; protected isTrackRestorationCurrent(generation: number, epoch?: number): boolean; protected restoreLocalTracks(restorationGeneration?: number): Promise; protected mediaKind(): 'audio' | 'video'; protected videoBitrate(): number; protected captureMedia(): Promise; protected prepareLocalStream(_stream: MediaStream): void; protected prepareAnswer(_pc: RTCPeerConnection): Promise; protected localStreamChanged(_stream: MediaStream | null): void; protected senderBitrate(_sender: RTCRtpSender): number; protected addLocalTracks(pc: RTCPeerConnection, stream: MediaStream): void; private createPeer; private description; private sendCandidates; private applyCandidates; private receive; /** Native RTC reports for bandwidth/packet-loss monitoring; no audio leaves through JSON. */ getStats(): Promise<{ peerId: string; report: RTCStatsReport; }[]>; /** Creates a controllable RTC latency/loss monitor for diagnostics UIs. */ networkProbe(options?: NetworkProbeOptions): NetworkProbe; /** Samples current latency, jitter, loss, bitrate and candidate routes over a short window. */ probeNetwork(options?: NetworkProbeOnceOptions): Promise; /** Starts a foreground-aware monitor and returns one cleanup function. */ observeNetwork(listener: (snapshot: NetworkProbeSnapshot) => void, options?: NetworkProbeOptions): () => void; }