#238 - add 'listen' option to config verb (#239)

This commit is contained in:
Dave Horton
2023-01-31 14:39:55 -05:00
committed by GitHub
parent 86fed4ec90
commit f9921cf4e9
7 changed files with 190 additions and 8 deletions

View File

@@ -270,6 +270,10 @@ class CallSession extends Emitter {
return this.backgroundGatherTask; return this.backgroundGatherTask;
} }
get isListenEnabled() {
return this.backgroundListenTask;
}
get b3() { get b3() {
return this.rootSpan?.getTracingPropagation(); return this.rootSpan?.getTracingPropagation();
} }
@@ -461,6 +465,50 @@ class CallSession extends Emitter {
} }
} }
async startBackgroundListen(opts) {
if (this.isListenEnabled) {
this.logger.info('CallSession:startBackgroundListen - listen is already enabled, ignoring request');
return;
}
try {
this.logger.debug({opts}, 'CallSession:startBackgroundListen');
const t = normalizeJambones(this.logger, [opts]);
this.backgroundListenTask = makeTask(this.logger, t[0]);
const resources = await this._evaluatePreconditions(this.backgroundListenTask);
const {span, ctx} = this.rootSpan.startChildSpan(`background-gather:${this.backgroundListenTask.summary}`);
this.backgroundListenTask.span = span;
this.backgroundListenTask.ctx = ctx;
this.backgroundListenTask.exec(this, resources)
.then(() => {
this.logger.info('CallSession:startBackgroundListen: listen completed');
this.backgroundListenTask && this.backgroundListenTask.removeAllListeners();
this.backgroundListenTask && this.backgroundListenTask.span.end();
this.backgroundListenTask = null;
return;
})
.catch((err) => {
this.logger.info({err}, 'CallSession:startBackgroundListen: listen threw error');
this.backgroundListenTask && this.backgroundListenTask.removeAllListeners();
this.backgroundListenTask && this.backgroundListenTask.span.end();
this.backgroundListenTask = null;
});
} catch (err) {
this.logger.info({err, opts}, 'CallSession:startBackgroundListen - Error creating listen task');
}
}
async stopBackgroundListen() {
try {
if (this.backgroundListenTask) {
this.backgroundListenTask.removeAllListeners();
this.backgroundListenTask.kill().catch(() => {});
}
} catch (err) {
this.logger.info({err}, 'CallSession:stopBackgroundListen - Error stopping listen task');
}
this.backgroundListenTask = null;
}
async enableBotMode(gather, autoEnable) { async enableBotMode(gather, autoEnable) {
try { try {
if (this.backgroundGatherTask) { if (this.backgroundGatherTask) {

View File

@@ -8,7 +8,8 @@ class TaskConfig extends Task {
'synthesizer', 'synthesizer',
'recognizer', 'recognizer',
'bargeIn', 'bargeIn',
'record' 'record',
'listen'
].forEach((k) => this[k] = this.data[k] || {}); ].forEach((k) => this[k] = this.data[k] || {});
if ('notifyEvents' in this.data) { if ('notifyEvents' in this.data) {
@@ -30,7 +31,7 @@ class TaskConfig extends Task {
}); });
} }
if (this.bargeIn.sticky) this.autoEnable = true; if (this.bargeIn.sticky) this.autoEnable = true;
this.preconditions = (this.bargeIn.enable || this.record?.action || this.data.amd) ? this.preconditions = (this.bargeIn.enable || this.record?.action || this.listen?.url || this.data.amd) ?
TaskPreconditions.Endpoint : TaskPreconditions.Endpoint :
TaskPreconditions.None; TaskPreconditions.None;
} }
@@ -38,8 +39,9 @@ class TaskConfig extends Task {
get name() { return TaskName.Config; } get name() { return TaskName.Config; }
get hasSynthesizer() { return Object.keys(this.synthesizer).length; } get hasSynthesizer() { return Object.keys(this.synthesizer).length; }
get hasRecognizer() { return Object.keys(this.recognizer).length; } get hasRecognizer() { return Object.keys(this.recognizer).length; }
get hasRecording() { return Object.keys(this.record).length; }
get hasListen() { return Object.keys(this.listen).length; }
get summary() { get summary() {
const phrase = []; const phrase = [];
@@ -54,6 +56,10 @@ class TaskConfig extends Task {
const s = `{${v},${l}}`; const s = `{${v},${l}}`;
phrase.push(`set recognizer${s}`); phrase.push(`set recognizer${s}`);
} }
if (this.hasRecording) phrase.push(this.record.action);
if (this.hasListen) {
phrase.push(this.listen.enable ? `listen ${this.listen.url}` : 'stop listen');
}
if (this.data.amd) phrase.push('enable amd'); if (this.data.amd) phrase.push('enable amd');
if (this.notifyEvents) phrase.push(`event notification ${this.notifyEvents ? 'on' : 'off'}`); if (this.notifyEvents) phrase.push(`event notification ${this.notifyEvents ? 'on' : 'off'}`);
return `${this.name}{${phrase.join(',')}`; return `${this.name}{${phrase.join(',')}`;
@@ -64,8 +70,7 @@ class TaskConfig extends Task {
if (this.notifyEvents) { if (this.notifyEvents) {
this.logger.debug(`turning event notification ${this.notifyEvents ? 'on' : 'off'}`); this.logger.debug(`turning event notification ${this.notifyEvents ? 'on' : 'off'}`);
cs.notifyEvents = !!this.data.notifEvents; cs.notifyEvents = !!this.data.notifyEvents;
} }
if (this.data.amd) { if (this.data.amd) {
@@ -147,6 +152,16 @@ class TaskConfig extends Task {
this.logger.info({err}, 'Config: error starting recording'); this.logger.info({err}, 'Config: error starting recording');
} }
} }
if (this.hasListen) {
const {enable, ...opts} = this.listen;
if (enable) {
this.logger.debug({opts}, 'Config: enabling listen');
cs.startBackgroundListen({verb: 'listen', ...opts});
} else {
this.logger.info('Config: disabling listen');
cs.stopBackgroundListen();
}
}
} }
async kill(cs) { async kill(cs) {

View File

@@ -10,7 +10,7 @@ class TaskListen extends Task {
[ [
'action', 'auth', 'method', 'url', 'finishOnKey', 'maxLength', 'metadata', 'mixType', 'passDtmf', 'playBeep', 'action', 'auth', 'method', 'url', 'finishOnKey', 'maxLength', 'metadata', 'mixType', 'passDtmf', 'playBeep',
'sampleRate', 'timeout', 'transcribe', 'wsAuth' 'sampleRate', 'timeout', 'transcribe', 'wsAuth', 'disableBidirectionalAudio'
].forEach((k) => this[k] = this.data[k]); ].forEach((k) => this[k] = this.data[k]);
this.mixType = this.mixType || 'mono'; this.mixType = this.mixType || 'mono';
@@ -136,7 +136,9 @@ class TaskListen extends Task {
} }
/* support bi-directional audio */ /* support bi-directional audio */
ep.addCustomEventListener(ListenEvents.PlayAudio, this._onPlayAudio.bind(this, ep)); if (!this.disableBiDirectionalAudio) {
ep.addCustomEventListener(ListenEvents.PlayAudio, this._onPlayAudio.bind(this, ep));
}
ep.addCustomEventListener(ListenEvents.KillAudio, this._onKillAudio.bind(this, ep)); ep.addCustomEventListener(ListenEvents.KillAudio, this._onKillAudio.bind(this, ep));
ep.addCustomEventListener(ListenEvents.Disconnect, this._onDisconnect.bind(this, ep)); ep.addCustomEventListener(ListenEvents.Disconnect, this._onDisconnect.bind(this, ep));
} }

View File

@@ -42,11 +42,24 @@
"recognizer": "#recognizer", "recognizer": "#recognizer",
"bargeIn": "#bargeIn", "bargeIn": "#bargeIn",
"record": "#recordOptions", "record": "#recordOptions",
"listen": "#listenOptions",
"amd": "#amd", "amd": "#amd",
"notifyEvents": "boolean" "notifyEvents": "boolean"
}, },
"required": [] "required": []
}, },
"listenOptions": {
"properties": {
"enable": "boolean",
"url": "string",
"sampleRate": "number",
"wsAuth": "#auth",
"metadata": "object"
},
"required": [
"enable"
]
},
"bargeIn": { "bargeIn": {
"properties": { "properties": {
"enable": "boolean", "enable": "boolean",
@@ -275,6 +288,7 @@
}, },
"passDtmf": "boolean", "passDtmf": "boolean",
"playBeep": "boolean", "playBeep": "boolean",
"disableBidirectionalAudio": "boolean",
"sampleRate": "number", "sampleRate": "number",
"timeout": "number", "timeout": "number",
"transcribe": "#transcribe", "transcribe": "#transcribe",

102
test/config-test.js Normal file
View File

@@ -0,0 +1,102 @@
const test = require('tape');
const { sippUac } = require('./sipp')('test_fs');
const bent = require('bent');
const getJSON = bent('json')
const clearModule = require('clear-module');
const {provisionCallHook} = require('./utils')
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
function connect(connectable) {
return new Promise((resolve, reject) => {
connectable.on('connect', () => {
return resolve();
});
});
}
test('\'config: listen\'', async(t) => {
clearModule.all();
const {srf, disconnect} = require('../app');
try {
await connect(srf);
// GIVEN
const from = "config_listen_success";
let verbs = [
{
"verb": "config",
"listen": {
"enable": true,
"url": `ws://172.38.0.60:3000/${from}`
}
},
{
"verb": "pause",
"length": 5
}
];
provisionCallHook(from, verbs);
// THEN
await sippUac('uac-gather-account-creds-success-send-bye.xml', '172.38.0.10', from);
let obj = await getJSON(`http://127.0.0.1:3100/ws_packet_count/${from}`);
t.pass('config: successfully started background listen');
disconnect();
} catch (err) {
console.log(`error received: ${err}`);
disconnect();
t.error(err);
}
});
test('\'config: listen - stop\'', async(t) => {
clearModule.all();
const {srf, disconnect} = require('../app');
try {
await connect(srf);
// GIVEN
const from = "config_listen_success";
let verbs = [
{
"verb": "config",
"listen": {
"enable": true,
"url": `ws://172.38.0.60:3000/${from}`
}
},
{
"verb": "pause",
"length": 1
},
{
"verb": "config",
"listen": {
"enable": false
}
},
{
"verb": "pause",
"length": 3
}
];
provisionCallHook(from, verbs);
// THEN
await sippUac('uac-gather-account-creds-success-send-bye.xml', '172.38.0.10', from);
let obj = await getJSON(`http://127.0.0.1:3100/ws_packet_count/${from}`);
t.pass('config: successfully started then stopped background listen');
disconnect();
} catch (err) {
console.log(`error received: ${err}`);
disconnect();
t.error(err);
}
});

View File

@@ -12,5 +12,6 @@ require('./create-call-test');
require('./play-tests'); require('./play-tests');
require('./sip-refer-tests'); require('./sip-refer-tests');
require('./listen-tests'); require('./listen-tests');
require('./config-test');
require('./remove-test-db'); require('./remove-test-db');
require('./docker_stop'); require('./docker_stop');

View File

@@ -40,7 +40,7 @@ test('\'listen-success\'', async(t) => {
// THEN // THEN
await sippUac('uac-gather-account-creds-success-send-bye.xml', '172.38.0.10', from); await sippUac('uac-gather-account-creds-success-send-bye.xml', '172.38.0.10', from);
let obj = await getJSON(`http://127.0.0.1:3100/ws_packet_count/${from}`); let obj = await getJSON(`http://127.0.0.1:3100/ws_packet_count/${from}`);
t.ok(38000 <= obj.count, 'listen: success incomming call audio'); t.ok(38000 <= obj.count, 'listen: success incoming call audio');
obj = await getJSON(`http://127.0.0.1:3100/ws_metadata/${from}`); obj = await getJSON(`http://127.0.0.1:3100/ws_metadata/${from}`);
t.ok(obj.metadata.from === from && obj.metadata.sampleRate === 8000, 'listen: success metadata'); t.ok(obj.metadata.from === from && obj.metadata.sampleRate === 8000, 'listen: success metadata');