diff --git a/lib/config.js b/lib/config.js index 82ac2623..36ae8e7b 100644 --- a/lib/config.js +++ b/lib/config.js @@ -137,6 +137,8 @@ const JAMBONES_EAGERLY_PRE_CACHE_AUDIO = parseInt(process.env.JAMBONES_EAGERLY_P const JAMBONES_USE_FREESWITCH_TIMER_FD = process.env.JAMBONES_USE_FREESWITCH_TIMER_FD; const JAMBONES_DIAL_SBC_FOR_REGISTERED_USER = process.env.JAMBONES_DIAL_SBC_FOR_REGISTERED_USER || false; +const JAMBONES_MEDIA_TIMEOUT_MS = process.env.JAMBONES_MEDIA_TIMEOUT_MS || 0; +const JAMBONES_MEDIA_HOLD_TIMEOUT_MS = process.env.JAMBONES_MEDIA_HOLD_TIMEOUT_MS || 0; module.exports = { JAMBONES_MYSQL_HOST, @@ -223,5 +225,7 @@ module.exports = { JAMBONZ_DISABLE_DIAL_PAI_HEADER, JAMBONES_DISABLE_DIRECT_P2P_CALL, JAMBONES_USE_FREESWITCH_TIMER_FD, - JAMBONES_DIAL_SBC_FOR_REGISTERED_USER + JAMBONES_DIAL_SBC_FOR_REGISTERED_USER, + JAMBONES_MEDIA_TIMEOUT_MS, + JAMBONES_MEDIA_HOLD_TIMEOUT_MS }; diff --git a/lib/session/call-session.js b/lib/session/call-session.js index 5a881729..ffd9397d 100644 --- a/lib/session/call-session.js +++ b/lib/session/call-session.js @@ -28,7 +28,9 @@ const { JAMBONES_INJECT_CONTENT, JAMBONES_EAGERLY_PRE_CACHE_AUDIO, AWS_REGION, - JAMBONES_USE_FREESWITCH_TIMER_FD + JAMBONES_USE_FREESWITCH_TIMER_FD, + JAMBONES_MEDIA_TIMEOUT_MS, + JAMBONES_MEDIA_HOLD_TIMEOUT_MS } = require('../config'); const bent = require('bent'); const BackgroundTaskManager = require('../utils/background-task-manager'); @@ -2796,15 +2798,25 @@ Duration=${duration} ` _configMsEndpoint() { this._enableInbandDtmfIfRequired(this.ep); + this.ep.once('destroy', this._handleMediaTimeout.bind(this)); const opts = { ...(this.onHoldMusic && {holdMusic: `shout://${this.onHoldMusic.replace(/^https?:\/\//, '')}`}), - ...(JAMBONES_USE_FREESWITCH_TIMER_FD && {timer_name: 'timerfd'}) + ...(JAMBONES_USE_FREESWITCH_TIMER_FD && {timer_name: 'timerfd'}), + ...(JAMBONES_MEDIA_TIMEOUT_MS && {media_timeout: JAMBONES_MEDIA_TIMEOUT_MS}), + ...(JAMBONES_MEDIA_HOLD_TIMEOUT_MS && {media_hold_timeout: JAMBONES_MEDIA_HOLD_TIMEOUT_MS}) }; if (Object.keys(opts).length > 0) { this.ep.set(opts); } } + async _handleMediaTimeout(evt) { + if (evt.reason === 'MEDIA_TIMEOUT' && !this.callGone) { + this.logger.info('CallSession:_handleMediaTimeout: received MEDIA_TIMEOUT, hangup the call'); + this._jambonzHangup('Media Timeout'); + } + } + async _enableInbandDtmfIfRequired(ep) { if (ep.inbandDtmfEnabled) return; // only enable inband dtmf detection if voip carrier dtmf_type === tones diff --git a/lib/session/inbound-call-session.js b/lib/session/inbound-call-session.js index 3ced672f..222a9f40 100644 --- a/lib/session/inbound-call-session.js +++ b/lib/session/inbound-call-session.js @@ -70,8 +70,12 @@ class InboundCallSession extends CallSession { this._hangup('caller'); } - _jambonzHangup() { - this.dlg?.destroy(); + _jambonzHangup(reason) { + this.dlg?.destroy({ + headers: { + ...(reason && {'X-Reason': reason}) + } + }); // kill current task or wakeup the call session. this._callReleased(); } diff --git a/lib/tasks/dial.js b/lib/tasks/dial.js index b90a234e..5050d636 100644 --- a/lib/tasks/dial.js +++ b/lib/tasks/dial.js @@ -273,7 +273,9 @@ class TaskDial extends Task { this._removeDtmfDetection(this.dlg); await this._killOutdials(); if (this.sd) { - this.sd.kill(); + const byeReasonHeader = this.killReason === KillReason.MediaTimeout ? 'Media Timeout' : undefined; + this.sd.kill(byeReasonHeader); + this.sd.ep?.removeListener('destroy', this._handleMediaTimeout.bind(this)); this.sd.removeAllListeners(); this.sd = null; } @@ -887,6 +889,14 @@ class TaskDial extends Task { if (this.canReleaseMedia || this.shouldExitMediaPathEntirely) { setTimeout(this._releaseMedia.bind(this, cs, sd, this.shouldExitMediaPathEntirely), 200); } + + this.sd.ep.once('destroy', this._handleMediaTimeout.bind(this)); + } + + _handleMediaTimeout(evt) { + if (evt.reason === 'MEDIA_TIMEOUT' && this.sd && this.bridged) { + this.kill(this.cs, KillReason.MediaTimeout); + } } _bridgeEarlyMedia(sd) { diff --git a/lib/utils/constants.json b/lib/utils/constants.json index 0705d143..bee95d30 100644 --- a/lib/utils/constants.json +++ b/lib/utils/constants.json @@ -196,7 +196,8 @@ }, "KillReason": { "Hangup": "hangup", - "Replaced": "replaced" + "Replaced": "replaced", + "MediaTimeout": "media_timeout" }, "HookMsgTypes": [ "session:new", diff --git a/lib/utils/place-outdial.js b/lib/utils/place-outdial.js index 1b3123e2..d3d333a2 100644 --- a/lib/utils/place-outdial.js +++ b/lib/utils/place-outdial.js @@ -17,7 +17,9 @@ const HttpRequestor = require('./http-requestor'); const WsRequestor = require('./ws-requestor'); const {makeOpusFirst} = require('./sdp-utils'); const { - JAMBONES_USE_FREESWITCH_TIMER_FD + JAMBONES_USE_FREESWITCH_TIMER_FD, + JAMBONES_MEDIA_TIMEOUT_MS, + JAMBONES_MEDIA_HOLD_TIMEOUT_MS } = require('../config'); class SingleDialer extends Emitter { @@ -317,14 +319,19 @@ class SingleDialer extends Emitter { /** * kill the call in progress or the stable dialog, whichever we have */ - async kill() { + async kill(Reason) { this.killed = true; if (this.inviteInProgress) await this.inviteInProgress.cancel(); else if (this.dlg && this.dlg.connected) { const duration = moment().diff(this.dlg.connectTime, 'seconds'); this.logger.debug('SingleDialer:kill hanging up called party'); this.emit('callStatusChange', {callStatus: CallStatus.Completed, duration}); - this.dlg.destroy(); + const headers = { + ...(Reason && {'X-Reason': Reason}) + }; + this.dlg.destroy({ + headers + }); } if (this.ep) { this.logger.debug(`SingleDialer:kill - deleting endpoint ${this.ep.uuid}`); @@ -335,7 +342,9 @@ class SingleDialer extends Emitter { _configMsEndpoint() { const opts = { ...(this.onHoldMusic && {holdMusic: `shout://${this.onHoldMusic.replace(/^https?:\/\//, '')}`}), - ...(JAMBONES_USE_FREESWITCH_TIMER_FD && {timer_name: 'timerfd'}) + ...(JAMBONES_USE_FREESWITCH_TIMER_FD && {timer_name: 'timerfd'}), + ...(JAMBONES_MEDIA_TIMEOUT_MS && {media_timeout: JAMBONES_MEDIA_TIMEOUT_MS}), + ...(JAMBONES_MEDIA_HOLD_TIMEOUT_MS && {media_hold_timeout: JAMBONES_MEDIA_HOLD_TIMEOUT_MS}) }; if (Object.keys(opts).length > 0) { this.ep.set(opts);