/** * ILeg interface — abstract connection from a Call hub to an endpoint. * * Concrete implementations: SipLeg (SIP devices + providers) and WebRtcLeg (browsers). * Shared RTP utilities (header building, clock rates) are also defined here. */ import { Buffer } from 'node:buffer'; import type dgram from 'node:dgram'; import type { IEndpoint } from '../sip/index.ts'; import type { TLegState, TLegType, ILegStatus } from './types.ts'; import type { IRtpTranscoder } from '../codec.ts'; import type { SipDialog } from '../sip/index.ts'; import type { SipMessage } from '../sip/index.ts'; // --------------------------------------------------------------------------- // ILeg interface // --------------------------------------------------------------------------- export interface ILeg { readonly id: string; readonly type: TLegType; state: TLegState; /** The SIP Call-ID used by this leg (for CallManager routing). */ readonly sipCallId: string; /** Where this leg sends/receives RTP. */ readonly rtpPort: number | null; readonly rtpSock: dgram.Socket | null; remoteMedia: IEndpoint | null; /** Negotiated codec payload type (e.g. 9 = G.722, 111 = Opus). */ codec: number | null; /** Transcoder for converting to this leg's codec (set by Call when codecs differ). */ transcoder: IRtpTranscoder | null; /** Packet counters. */ pktSent: number; pktReceived: number; /** SIP dialog (SipLegs only, null for WebRtcLegs). */ readonly dialog: SipDialog | null; /** * Send an RTP packet toward this leg's remote endpoint. * If a transcoder is set, the Call should transcode before calling this. */ sendRtp(data: Buffer): void; /** * Callback set by the owning Call — invoked when this leg receives an RTP packet. * The Call uses this to forward to other legs. */ onRtpReceived: ((data: Buffer) => void) | null; /** * Handle an incoming SIP message routed to this leg (SipLegs only). * Returns a SipMessage response if one needs to be sent, or null. */ handleSipMessage(msg: SipMessage, rinfo: IEndpoint): void; /** Release all resources (sockets, peer connections, etc.). */ teardown(): void; /** Status snapshot for the dashboard. */ getStatus(): ILegStatus; } // --------------------------------------------------------------------------- // Shared RTP utilities // --------------------------------------------------------------------------- /** RTP clock increment per 20ms frame for each codec. */ export function rtpClockIncrement(pt: number): number { if (pt === 111) return 960; // Opus: 48000 Hz x 0.02s if (pt === 9) return 160; // G.722: 8000 Hz x 0.02s (SDP clock rate quirk) return 160; // PCMU/PCMA: 8000 Hz x 0.02s } /** Build a fresh RTP header with correct PT, timestamp, seq, SSRC. */ export function buildRtpHeader(pt: number, seq: number, ts: number, ssrc: number, marker: boolean): Buffer { const hdr = Buffer.alloc(12); hdr[0] = 0x80; // V=2 hdr[1] = (marker ? 0x80 : 0) | (pt & 0x7f); hdr.writeUInt16BE(seq & 0xffff, 2); hdr.writeUInt32BE(ts >>> 0, 4); hdr.writeUInt32BE(ssrc >>> 0, 8); return hdr; } /** Codec name for status display. */ export function codecDisplayName(pt: number | null): string | null { if (pt === null) return null; switch (pt) { case 0: return 'PCMU'; case 8: return 'PCMA'; case 9: return 'G.722'; case 111: return 'Opus'; case 101: return 'telephone-event'; default: return `PT${pt}`; } }