mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2025-12-20 16:50:39 +00:00
initial changes to support siprec recording (#120)
* initial changes to support siprec recording * include additional params on SIP INFO to start recording * add support for maniupulating recording via REST API * fixes from testing pause/resume recording
This commit is contained in:
@@ -1,6 +1,13 @@
|
|||||||
const Emitter = require('events');
|
const Emitter = require('events');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const {CallDirection, TaskPreconditions, CallStatus, TaskName, KillReason} = require('../utils/constants');
|
const {
|
||||||
|
CallDirection,
|
||||||
|
TaskPreconditions,
|
||||||
|
CallStatus,
|
||||||
|
TaskName,
|
||||||
|
KillReason,
|
||||||
|
RecordState
|
||||||
|
} = require('../utils/constants');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const sessionTracker = require('./session-tracker');
|
const sessionTracker = require('./session-tracker');
|
||||||
@@ -54,6 +61,8 @@ class CallSession extends Emitter {
|
|||||||
|
|
||||||
assert(rootSpan);
|
assert(rootSpan);
|
||||||
|
|
||||||
|
this._recordState = RecordState.RecordingOff;
|
||||||
|
|
||||||
this.tmpFiles = new Set();
|
this.tmpFiles = new Set();
|
||||||
|
|
||||||
if (!this.isSmsCallSession) {
|
if (!this.isSmsCallSession) {
|
||||||
@@ -85,6 +94,10 @@ class CallSession extends Emitter {
|
|||||||
return this.callInfo.direction;
|
return this.callInfo.direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get applicationSid() {
|
||||||
|
return this.callInfo.applicationSid;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SIP call-id for the call
|
* SIP call-id for the call
|
||||||
*/
|
*/
|
||||||
@@ -234,6 +247,153 @@ class CallSession extends Emitter {
|
|||||||
return this.rootSpan?.getTracingPropagation();
|
return this.rootSpan?.getTracingPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get recordState() { return this._recordState; }
|
||||||
|
|
||||||
|
async notifyRecordOptions(opts) {
|
||||||
|
const {action} = opts;
|
||||||
|
this.logger.debug({opts}, 'CallSession:notifyRecordOptions');
|
||||||
|
|
||||||
|
/* if we have not answered yet, just save the details for later */
|
||||||
|
if (!this.dlg) {
|
||||||
|
if (action === 'startCallRecording') {
|
||||||
|
this.recordOptions = opts;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check validity of request */
|
||||||
|
if (action == 'startCallRecording' && this.recordState !== RecordState.RecordingOff) {
|
||||||
|
this.logger.info({recordState: this.recordState},
|
||||||
|
'CallSession:notifyRecordOptions: recording is already started, ignoring request');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (action == 'stopCallRecording' && this.recordState === RecordState.RecordingOff) {
|
||||||
|
this.logger.info({recordState: this.recordState},
|
||||||
|
'CallSession:notifyRecordOptions: recording is already stopped, ignoring request');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (action == 'pauseCallRecording' && this.recordState !== RecordState.RecordingOn) {
|
||||||
|
this.logger.info({recordState: this.recordState},
|
||||||
|
'CallSession:notifyRecordOptions: cannot pause recording, ignoring request ');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (action == 'resumeCallRecording' && this.recordState !== RecordState.RecordingPaused) {
|
||||||
|
this.logger.info({recordState: this.recordState},
|
||||||
|
'CallSession:notifyRecordOptions: cannot resume recording, ignoring request ');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.recordOptions = opts;
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'startCallRecording':
|
||||||
|
return await this.startRecording();
|
||||||
|
case 'stopCallRecording':
|
||||||
|
return await this.stopRecording();
|
||||||
|
case 'pauseCallRecording':
|
||||||
|
return await this.pauseRecording();
|
||||||
|
case 'resumeCallRecording':
|
||||||
|
return await this.resumeRecording();
|
||||||
|
default:
|
||||||
|
throw new Error(`invalid record action ${action}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async startRecording() {
|
||||||
|
const {recordingID, siprecServerURL} = this.recordOptions;
|
||||||
|
assert(this.dlg);
|
||||||
|
this.logger.debug(`CallSession:startRecording - sending to ${siprecServerURL}`);
|
||||||
|
try {
|
||||||
|
const res = await this.dlg.request({
|
||||||
|
method: 'INFO',
|
||||||
|
headers: {
|
||||||
|
'X-Reason': 'startCallRecording',
|
||||||
|
'X-Srs-Url': siprecServerURL,
|
||||||
|
'X-Srs-Recording-ID': recordingID,
|
||||||
|
'X-Call-Sid': this.callSid,
|
||||||
|
'X-Account-Sid': this.accountSid,
|
||||||
|
'X-Application-Sid': this.applicationSid,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (res.status === 200) {
|
||||||
|
this._recordState = RecordState.RecordingOn;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
this.logger.info(`CallSession:startRecording - ${res.status} failure sending to ${siprecServerURL}`);
|
||||||
|
return false;
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.info({err}, `CallSession:startRecording - failure sending to ${siprecServerURL}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopRecording() {
|
||||||
|
assert(this.dlg);
|
||||||
|
this.logger.debug('CallSession:stopRecording');
|
||||||
|
try {
|
||||||
|
const res = await this.dlg.request({
|
||||||
|
method: 'INFO',
|
||||||
|
headers: {
|
||||||
|
'X-Reason': 'stopCallRecording',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (res.status === 200) {
|
||||||
|
this._recordState = RecordState.RecordingOff;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
this.logger.info(`CallSession:stopRecording - ${res.status} failure`);
|
||||||
|
return false;
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.info({err}, 'CallSession:startRecording - failure sending');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async pauseRecording() {
|
||||||
|
assert(this.dlg);
|
||||||
|
this.logger.debug('CallSession:pauseRecording');
|
||||||
|
try {
|
||||||
|
const res = await this.dlg.request({
|
||||||
|
method: 'INFO',
|
||||||
|
headers: {
|
||||||
|
'X-Reason': 'pauseCallRecording',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (res.status === 200) {
|
||||||
|
this._recordState = RecordState.RecordingPaused;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
this.logger.info(`CallSession:pauseRecording - ${res.status} failure`);
|
||||||
|
return false;
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.info({err}, 'CallSession:pauseRecording - failure sending');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async resumeRecording() {
|
||||||
|
assert(this.dlg);
|
||||||
|
this.logger.debug('CallSession:resumeRecording');
|
||||||
|
try {
|
||||||
|
const res = await this.dlg.request({
|
||||||
|
method: 'INFO',
|
||||||
|
headers: {
|
||||||
|
'X-Reason': 'resumeCallRecording',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (res.status === 200) {
|
||||||
|
this._recordState = RecordState.RecordingOn;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
this.logger.info(`CallSession:resumeRecording - ${res.status} failure`);
|
||||||
|
return false;
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.info({err}, 'CallSession:resumeRecording - failure sending');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async enableBotMode(gather, autoEnable) {
|
async enableBotMode(gather, autoEnable) {
|
||||||
try {
|
try {
|
||||||
const t = normalizeJambones(this.logger, [gather]);
|
const t = normalizeJambones(this.logger, [gather]);
|
||||||
@@ -724,6 +884,9 @@ class CallSession extends Emitter {
|
|||||||
const res = await this._lccSipRequest(opts, callSid);
|
const res = await this._lccSipRequest(opts, callSid);
|
||||||
return {status: res.status, reason: res.reason};
|
return {status: res.status, reason: res.reason};
|
||||||
}
|
}
|
||||||
|
else if (opts.record) {
|
||||||
|
await this.notifyRecordOptions(opts.record);
|
||||||
|
}
|
||||||
|
|
||||||
// whisper may be the only thing we are asked to do, or it may that
|
// whisper may be the only thing we are asked to do, or it may that
|
||||||
// we are doing a whisper after having muted, paused reccording etc..
|
// we are doing a whisper after having muted, paused reccording etc..
|
||||||
@@ -1076,6 +1239,9 @@ class CallSession extends Emitter {
|
|||||||
this.dlg.callSid = this.callSid;
|
this.dlg.callSid = this.callSid;
|
||||||
this.emit('callStatusChange', {sipStatus: 200, sipReason: 'OK', callStatus: CallStatus.InProgress});
|
this.emit('callStatusChange', {sipStatus: 200, sipReason: 'OK', callStatus: CallStatus.InProgress});
|
||||||
|
|
||||||
|
if (this.recordOptions && this.recordState === RecordState.RecordingOff) {
|
||||||
|
this.startRecording();
|
||||||
|
}
|
||||||
this.dlg.on('modify', this._onReinvite.bind(this));
|
this.dlg.on('modify', this._onReinvite.bind(this));
|
||||||
this.dlg.on('refer', this._onRefer.bind(this));
|
this.dlg.on('refer', this._onRefer.bind(this));
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ class TaskConfig extends Task {
|
|||||||
[
|
[
|
||||||
'synthesizer',
|
'synthesizer',
|
||||||
'recognizer',
|
'recognizer',
|
||||||
'bargeIn'
|
'bargeIn',
|
||||||
|
'record'
|
||||||
].forEach((k) => this[k] = this.data[k] || {});
|
].forEach((k) => this[k] = this.data[k] || {});
|
||||||
|
|
||||||
if (this.bargeIn.enable) {
|
if (this.bargeIn.enable) {
|
||||||
@@ -100,6 +101,7 @@ class TaskConfig extends Task {
|
|||||||
cs.disableBotMode();
|
cs.disableBotMode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.record) cs.notifyRecordOptions(this.record);
|
||||||
}
|
}
|
||||||
|
|
||||||
async kill(cs) {
|
async kill(cs) {
|
||||||
|
|||||||
@@ -36,7 +36,8 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"synthesizer": "#synthesizer",
|
"synthesizer": "#synthesizer",
|
||||||
"recognizer": "#recognizer",
|
"recognizer": "#recognizer",
|
||||||
"bargeIn": "#bargeIn"
|
"bargeIn": "#bargeIn",
|
||||||
|
"record": "#recordOptions"
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
},
|
},
|
||||||
@@ -307,6 +308,19 @@
|
|||||||
"path"
|
"path"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"recordOptions": {
|
||||||
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["startCallRecording", "stopCallRecording", "pauseCallRecording", "resumeCallRecording"]
|
||||||
|
},
|
||||||
|
"recordingID": "string",
|
||||||
|
"siprecServerURL": "string"
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"action"
|
||||||
|
]
|
||||||
|
},
|
||||||
"redirect": {
|
"redirect": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"actionHook": "object|string"
|
"actionHook": "object|string"
|
||||||
|
|||||||
@@ -120,6 +120,11 @@
|
|||||||
"verb:hook",
|
"verb:hook",
|
||||||
"jambonz:error"
|
"jambonz:error"
|
||||||
],
|
],
|
||||||
|
"RecordState": {
|
||||||
|
"RecordingOn": "recording_on",
|
||||||
|
"RecordingOff": "recording_off",
|
||||||
|
"RecordingPaused": "recording_paused"
|
||||||
|
},
|
||||||
"MAX_SIMRINGS": 10,
|
"MAX_SIMRINGS": 10,
|
||||||
"BONG_TONE": "tone_stream://v=-7;%(100,0,941.0,1477.0);v=-7;>=2;+=.1;%(1400,0,350,440)",
|
"BONG_TONE": "tone_stream://v=-7;%(100,0,941.0,1477.0);v=-7;>=2;+=.1;%(1400,0,350,440)",
|
||||||
"FS_UUID_SET_NAME": "fsUUIDs"
|
"FS_UUID_SET_NAME": "fsUUIDs"
|
||||||
|
|||||||
Reference in New Issue
Block a user