fix onholdHOok (#540)

* fix onholdHOok

* wip

* wip

* wip

* wip

* adding more debug log

* wip

* wip

* wip
This commit is contained in:
Hoan Luu Huu
2024-01-15 20:34:45 +07:00
committed by GitHub
parent f22d66dfd6
commit b4ff2ea702
3 changed files with 94 additions and 43 deletions

View File

@@ -1744,7 +1744,7 @@ Duration=${duration} `
res.send(200, {body: this.ep.local.sdp}); res.send(200, {body: this.ep.local.sdp});
} }
else { else {
if (this.currentTask.name === TaskName.Dial && this.currentTask.isOnHold) { if (this.currentTask.name === TaskName.Dial && this.currentTask.isOnHoldEnabled) {
this.logger.info('onholdMusic reINVITE after media has been released'); this.logger.info('onholdMusic reINVITE after media has been released');
await this.currentTask.handleReinviteAfterMediaReleased(req, res); await this.currentTask.handleReinviteAfterMediaReleased(req, res);
} else { } else {

View File

@@ -138,13 +138,14 @@ class TaskDial extends Task {
get name() { return TaskName.Dial; } get name() { return TaskName.Dial; }
get isOnHold() { get isOnHoldEnabled() {
return this.isIncomingLegHold || this.isOutgoingLegHold; return !!this.data.onHoldHook;
} }
get canReleaseMedia() { get canReleaseMedia() {
const keepAnchor = this.data.anchorMedia || const keepAnchor = this.data.anchorMedia ||
this.cs.isBackGroundListen || this.cs.isBackGroundListen ||
this.cs.onHoldMusic ||
ANCHOR_MEDIA_ALWAYS || ANCHOR_MEDIA_ALWAYS ||
this.listenTask || this.listenTask ||
this.transcribeTask || this.transcribeTask ||
@@ -570,7 +571,8 @@ class TaskDial extends Task {
accountInfo: cs.accountInfo, accountInfo: cs.accountInfo,
rootSpan: cs.rootSpan, rootSpan: cs.rootSpan,
startSpan: this.startSpan.bind(this), startSpan: this.startSpan.bind(this),
dialTask: this dialTask: this,
onHoldMusic: this.cs.onHoldMusic
}); });
this.dials.set(sd.callSid, sd); this.dials.set(sd.callSid, sd);
@@ -679,22 +681,43 @@ class TaskDial extends Task {
async _onReinvite(req, res) { async _onReinvite(req, res) {
try { try {
let isHandled = false; let isHandled = false;
if (this.cs.onHoldMusic) { if (this.isOnHoldEnabled) {
if (isOnhold(req.body) && !this.epOther && !this.ep) { if (isOnhold(req.body)) {
await this.cs.handleReinviteAfterMediaReleased(req, res); this.logger.debug('Dial: _onReinvite receive hold Request');
// Onhold but media is already released if (!this.epOther && !this.ep) {
// reconnect A Leg and Response B leg this.logger.debug(`Dial: _onReinvite receive hold Request,
await this.reAnchorMedia(this.cs, this.sd); media already released, reconnect media server`);
this.isOutgoingLegHold = true; // update caller leg for new SDP from callee.
await this.cs.handleReinviteAfterMediaReleased(req, res);
// Freeswitch media is released, reconnect
await this.reAnchorMedia(this.cs, this.sd);
this.isOutgoingLegHold = true;
} else {
this.logger.debug('Dial: _onReinvite receive hold Request, update SDP');
const newSdp = await this.ep.modify(req.body);
res.send(200, {body: newSdp});
}
isHandled = true; isHandled = true;
this._onHoldHook(); // Media already connected, ask for onHoldHook
} else if (!isOnhold(req.body) && this.epOther && this.ep && this.isOutgoingLegHold && this.canReleaseMedia) { this._onHoldHook(req);
// Offhold, time to release media } else if (!isOnhold(req.body)) {
const newSdp = await this.ep.modify(req.body); this.logger.debug('Dial: _onReinvite receive unhold Request');
await res.send(200, {body: newSdp}); if (this.epOther && this.ep && this.isOutgoingLegHold && this.canReleaseMedia) {
await this._releaseMedia(this.cs, this.sd); this.logger.debug('Dial: _onReinvite receive unhold Request, release media');
// Offhold, time to release media
const newSdp = await this.ep.modify(req.body);
await res.send(200, {body: newSdp});
await this._releaseMedia(this.cs, this.sd);
this.isOutgoingLegHold = false;
} else {
this.logger.debug('Dial: _onReinvite receive unhold Request, update media server');
const newSdp = await this.ep.modify(req.body);
res.send(200, {body: newSdp});
}
if (this._onHoldSession) {
this._onHoldSession.kill();
}
isHandled = true; isHandled = true;
this.isOutgoingLegHold = false;
} }
} }
if (!isHandled) { if (!isHandled) {
@@ -811,23 +834,34 @@ class TaskDial extends Task {
this.epOther = cs.ep; this.epOther = cs.ep;
} }
// Handle RE-INVITE hold from caller leg.
async handleReinviteAfterMediaReleased(req, res) { async handleReinviteAfterMediaReleased(req, res) {
let isHandled = false; let isHandled = false;
if (isOnhold(req.body) && !this.epOther && !this.ep) { if (this.isOnHoldEnabled) {
const sdp = await this.dlg.modify(req.body); if (isOnhold(req.body)) {
res.send(200, {body: sdp}); if (!this.epOther && !this.ep) {
// Onhold but media is already released // update callee leg for new SDP from caller.
await this.reAnchorMedia(this.cs, this.sd); const sdp = await this.dlg.modify(req.body);
isHandled = true; res.send(200, {body: sdp});
this.isIncomingLegHold = true; // Onhold but media is already released, reconnect
this._onHoldHook(); await this.reAnchorMedia(this.cs, this.sd);
} else if (!isOnhold(req.body) && this.epOther && this.ep && this.isIncomingLegHold && this.canReleaseMedia) { isHandled = true;
// Offhold, time to release media this.isIncomingLegHold = true;
const newSdp = await this.epOther.modify(req.body); }
await res.send(200, {body: newSdp}); this._onHoldHook(req);
await this._releaseMedia(this.cs, this.sd); } else if (!isOnhold(req.body)) {
isHandled = true; if (this.epOther && this.ep && this.isIncomingLegHold && this.canReleaseMedia) {
this.isIncomingLegHold = false; // Offhold, time to release media
const newSdp = await this.epOther.modify(req.body);
await res.send(200, {body: newSdp});
await this._releaseMedia(this.cs, this.sd);
isHandled = true;
}
this.isIncomingLegHold = false;
if (this._onHoldSession) {
this._onHoldSession.kill();
}
}
} }
if (!isHandled) { if (!isHandled) {
@@ -846,7 +880,7 @@ class TaskDial extends Task {
}); });
} }
async _onHoldHook(allowed = [TaskName.Play, TaskName.Say, TaskName.Pause]) { async _onHoldHook(req, allowed = [TaskName.Play, TaskName.Say, TaskName.Pause]) {
if (this.data.onHoldHook) { if (this.data.onHoldHook) {
// send silence for keep Voice quality // send silence for keep Voice quality
await this.epOther.play('silence_stream://500'); await this.epOther.play('silence_stream://500');
@@ -856,7 +890,13 @@ class TaskDial extends Task {
const b3 = this.getTracingPropagation(); const b3 = this.getTracingPropagation();
const httpHeaders = b3 && {b3}; const httpHeaders = b3 && {b3};
const json = await this.cs.application.requestor. const json = await this.cs.application.requestor.
request('verb:hook', this.data.onHoldHook, this.cs.callInfo.toJSON(), httpHeaders); request('verb:hook', this.data.onHoldHook, {
...this.cs.callInfo.toJSON(),
hold_detail: {
from: req.get('From'),
to: req.get('To')
}
}, httpHeaders);
const tasks = normalizeJambones(this.logger, json).map((tdata) => makeTask(this.logger, tdata)); const tasks = normalizeJambones(this.logger, json).map((tdata) => makeTask(this.logger, tdata));
allowedTasks = tasks.filter((t) => allowed.includes(t.name)); allowedTasks = tasks.filter((t) => allowed.includes(t.name));
if (tasks.length !== allowedTasks.length) { if (tasks.length !== allowedTasks.length) {
@@ -865,7 +905,7 @@ class TaskDial extends Task {
} }
this.logger.debug(`DialTask:_onHoldHook: executing ${tasks.length} tasks`); this.logger.debug(`DialTask:_onHoldHook: executing ${tasks.length} tasks`);
if (tasks.length) { if (tasks.length) {
this._playSession = new ConfirmCallSession({ this._onHoldSession = new ConfirmCallSession({
logger: this.logger, logger: this.logger,
application: this.cs.application, application: this.cs.application,
dlg: this.isIncomingLegHold ? this.dlg : this.cs.dlg, dlg: this.isIncomingLegHold ? this.dlg : this.cs.dlg,
@@ -875,12 +915,12 @@ class TaskDial extends Task {
tasks, tasks,
rootSpan: this.cs.rootSpan rootSpan: this.cs.rootSpan
}); });
await this._playSession.exec(); await this._onHoldSession.exec();
this._playSession = null; this._onHoldSession = null;
} }
} catch (error) { } catch (error) {
this.logger.info(error, 'DialTask:_onHoldHook: failed retrieving waitHook'); this.logger.info(error, 'DialTask:_onHoldHook: failed retrieving waitHook');
this._playSession = null; this._onHoldSession = null;
break; break;
} }
} while (allowedTasks && allowedTasks.length > 0 && !this.killed && this.isOnHold); } while (allowedTasks && allowedTasks.length > 0 && !this.killed && this.isOnHold);

View File

@@ -18,7 +18,8 @@ const WsRequestor = require('./ws-requestor');
const {makeOpusFirst} = require('./sdp-utils'); const {makeOpusFirst} = require('./sdp-utils');
class SingleDialer extends Emitter { class SingleDialer extends Emitter {
constructor({logger, sbcAddress, target, opts, application, callInfo, accountInfo, rootSpan, startSpan, dialTask}) { constructor({logger, sbcAddress, target, opts, application, callInfo, accountInfo, rootSpan, startSpan, dialTask,
onHoldMusic}) {
super(); super();
assert(target.type); assert(target.type);
@@ -41,6 +42,7 @@ class SingleDialer extends Emitter {
this.callSid = uuidv4(); this.callSid = uuidv4();
this.dialTask = dialTask; this.dialTask = dialTask;
this.onHoldMusic = onHoldMusic;
this.on('callStatusChange', this._notifyCallStatusChange.bind(this)); this.on('callStatusChange', this._notifyCallStatusChange.bind(this));
} }
@@ -131,6 +133,7 @@ class SingleDialer extends Emitter {
this.serviceUrl = srf.locals.serviceUrl; this.serviceUrl = srf.locals.serviceUrl;
this.ep = await ms.createEndpoint(); this.ep = await ms.createEndpoint();
this._configMsEndpoint();
this.logger.debug(`SingleDialer:exec - created endpoint ${this.ep.uuid}`); this.logger.debug(`SingleDialer:exec - created endpoint ${this.ep.uuid}`);
/** /**
@@ -253,7 +256,7 @@ class SingleDialer extends Emitter {
.on('modify', async(req, res) => { .on('modify', async(req, res) => {
try { try {
if (this.ep) { if (this.ep) {
if (this.dialTask && this.dialTask.isOnHold) { if (this.dialTask && this.dialTask.isOnHoldEnabled) {
this.logger.info('dial is onhold, emit event'); this.logger.info('dial is onhold, emit event');
this.emit('reinvite', req, res); this.emit('reinvite', req, res);
} else { } else {
@@ -320,6 +323,12 @@ class SingleDialer extends Emitter {
} }
} }
_configMsEndpoint() {
if (this.onHoldMusic) {
this.ep.set({hold_music: `shout://${this.onHoldMusic.replace(/^https?:\/\//, '')}`});
}
}
/** /**
* Run an application on the call after answer, e.g. call screening. * Run an application on the call after answer, e.g. call screening.
* Once the application completes in some fashion, emit an 'accepted' event * Once the application completes in some fashion, emit an 'accepted' event
@@ -436,6 +445,7 @@ class SingleDialer extends Emitter {
async reAnchorMedia() { async reAnchorMedia() {
assert(this.dlg && this.dlg.connected && !this.ep); assert(this.dlg && this.dlg.connected && !this.ep);
this.ep = await this.ms.createEndpoint({remoteSdp: this.dlg.remote.sdp}); this.ep = await this.ms.createEndpoint({remoteSdp: this.dlg.remote.sdp});
this._configMsEndpoint();
await this.dlg.modify(this.ep.local.sdp, { await this.dlg.modify(this.ep.local.sdp, {
headers: { headers: {
'X-Reason': 'anchor-media' 'X-Reason': 'anchor-media'
@@ -466,11 +476,12 @@ class SingleDialer extends Emitter {
} }
function placeOutdial({ function placeOutdial({
logger, srf, ms, sbcAddress, target, opts, application, callInfo, accountInfo, rootSpan, startSpan, dialTask logger, srf, ms, sbcAddress, target, opts, application, callInfo, accountInfo, rootSpan, startSpan, dialTask,
onHoldMusic
}) { }) {
const myOpts = deepcopy(opts); const myOpts = deepcopy(opts);
const sd = new SingleDialer({ const sd = new SingleDialer({
logger, sbcAddress, target, myOpts, application, callInfo, accountInfo, rootSpan, startSpan, dialTask logger, sbcAddress, target, myOpts, application, callInfo, accountInfo, rootSpan, startSpan, dialTask, onHoldMusic
}); });
sd.exec(srf, ms, myOpts); sd.exec(srf, ms, myOpts);
return sd; return sd;