diff --git a/streamer/src/pcm-tap.ts b/streamer/src/pcm-tap.ts new file mode 100644 index 0000000..9333b2c --- /dev/null +++ b/streamer/src/pcm-tap.ts @@ -0,0 +1,51 @@ +import { EventEmitter } from "node:events"; +import { Socket, connect } from "node:net"; + +export interface PcmTapOpts { + host: string; + port: number; + reconnectInitialMs?: number; + reconnectMaxMs?: number; +} + +export class PcmTap extends EventEmitter { + private socket: Socket | null = null; + private stopped = false; + private backoff: number; + + constructor(private opts: PcmTapOpts) { + super(); + this.backoff = opts.reconnectInitialMs ?? 1000; + } + + start(): void { + if (this.stopped) return; + this.connect(); + } + + stop(): void { + this.stopped = true; + this.socket?.destroy(); + } + + private connect(): void { + this.emit("connecting"); + const sock = connect({ host: this.opts.host, port: this.opts.port }); + this.socket = sock; + + sock.on("connect", () => { + this.emit("connected"); + this.backoff = this.opts.reconnectInitialMs ?? 1000; + }); + sock.on("data", (chunk) => this.emit("data", chunk)); + sock.on("error", (err) => this.emit("warn", err)); + sock.on("close", () => { + if (this.stopped) return; + this.emit("disconnected"); + const max = this.opts.reconnectMaxMs ?? 30000; + const wait = Math.min(this.backoff, max); + this.backoff = Math.min(this.backoff * 2, max); + setTimeout(() => this.connect(), wait); + }); + } +}