UNPKG

@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
/** * 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; }