@audin.ai/operator-sdk
Version:
Headless browser SDK for the Audin operator softphone — make and receive calls over the Audin operator WebSockets.
75 lines (74 loc) • 2.95 kB
TypeScript
/**
* AudioBridge — the audio leg of a single call.
*
* Joins three things for one `callSid`:
* 1. the microphone (`getUserMedia`),
* 2. an `AudioWorklet` that does the μ-law 8 kHz transcoding + resampling
* (see `audio-worklet.ts`), and
* 3. the audio WebSocket to the Audin service.
*
* Data flow:
* mic → AudioContext source → worklet → (μ-law bytes) → WS binary frame
* WS binary frame → (μ-law bytes) → worklet → AudioContext destination → 🔊
*
* Control (mute / DTMF / hangup) is sent as JSON text frames on the same WS.
*
* The bridge owns the lifecycle of everything it creates and tears it ALL
* down on `close()` (mic tracks stopped, worklet disconnected, context closed,
* socket closed) so a call leak can't keep the microphone hot.
*
* No provider names appear anywhere here — it is just "audio over a WebSocket".
*/
import type { OperatorLogger } from "./types.js";
/** Reason strings the bridge reports to its `onClosed` callback. */
export type BridgeCloseReason = "local_hangup" | "remote_close" | "ws_error" | "mic_error" | "teardown";
export interface AudioBridgeOptions {
/** `wss://…/operator-audio/ws/:callSid?token=…` — already fully formed. */
audioWsUrl: string;
/** Microphone constraints for `getUserMedia`. */
audioConstraints: MediaTrackConstraints;
logger: OperatorLogger;
/** Called once when the bridge finishes tearing down (any reason). */
onClosed: (reason: BridgeCloseReason) => void;
}
export declare class AudioBridge {
private readonly opts;
private ws;
private audioContext;
private micStream;
private sourceNode;
private workletNode;
private blobUrl;
private closed;
private muted;
constructor(opts: AudioBridgeOptions);
/**
* Open the WS, capture the mic, load the worklet and wire the graph. Resolves
* once audio is flowing both ways (or throws on a fatal setup error, having
* already torn down whatever was partially created).
*/
start(): Promise<void>;
/** Mute / unmute the operator microphone toward the far end. */
setMuted(on: boolean): void;
get isMuted(): boolean;
/**
* Send a DTMF digit as a control message.
*
* Note: not yet honored end-to-end — the server does not currently forward
* the tones onto the telephone network, so this is a functional no-op for
* the far end today (the control message itself stays valid for the future).
*/
sendDtmf(digit: string): void;
/** Tell the server to hang up, then tear down locally. */
hangup(): void;
/**
* Tear down everything this bridge created. Idempotent. Fires `onClosed`
* exactly once with the FIRST reason it was closed with.
*/
close(reason: BridgeCloseReason): void;
private openSocket;
private onWsMessage;
private setupAudioGraph;
private sendAudio;
private sendControl;
}