mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2026-02-15 02:39:35 +00:00
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
102
test/config-test.js
Normal 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -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');
|
||||||
@@ -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');
|
||||||
|
|||||||
Reference in New Issue
Block a user