From b356e893c025e31ecfd71604cb158fe763267da4 Mon Sep 17 00:00:00 2001 From: Sam Machin Date: Wed, 29 Jul 2026 10:42:12 +0100 Subject: [PATCH] Update call-session.js --- lib/session/call-session.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/lib/session/call-session.js b/lib/session/call-session.js index 6499caff..794486f2 100644 --- a/lib/session/call-session.js +++ b/lib/session/call-session.js @@ -1779,6 +1779,32 @@ class CallSession extends Emitter { this.logger.info('CallSession:_lccDtmf - invalid command as we do not have a stable call'); return; } + + /* Prefer media-path DTMF: have the media server generate RFC 2833 tones directly into the + * media stream (via send_dtmf), which the SBC/rtpengine simply forwards. This is reliable + * regardless of whether rtpengine is transcoding the call. Sending SIP INFO to the SBC + * instead relies on rtpengine 'play DTMF' injection, which silently no-ops on calls that + * are not being transcoded (rtpengine has no DSP to synthesize the tone into). We fall back + * to SIP INFO only when we have no endpoint in the media path for the targeted leg, e.g. when + * media has been released to the SBC. + * this.ep is the endpoint facing the caller (A) leg; this.currentTask.ep is the endpoint + * facing the dialed (B) leg. + */ + const ep = callSid === this.callSid ? this.ep : this.currentTask?.ep; + if (ep?.connected) { + try { + this.logger.debug(`CallSession:_lccDtmf - sending ${digit}@${duration} via send_dtmf on ${ep.uuid}`); + await ep.execute('send_dtmf', `${digit}@${duration}`); + return; + } catch (err) { + this.logger.error({err}, + 'CallSession:_lccDtmf - error sending DTMF via send_dtmf, falling back to SIP INFO'); + } + } + + /* fallback: signal the SBC to inject DTMF via SIP INFO (used when media is released + * and we have no endpoint in the media path for the targeted leg) + */ try { const dlg = callSid === this.callSid ? this.dlg : this.currentTask.dlg; const res = await dlg.request({