diff --git a/CHANGELOG.md b/CHANGELOG.md
index 927f26a8..3e5dbff4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
# Change Log
+## [v4.24.0](https://github.com/plivo/plivo-node/tree/v4.24.0) (2021-11-30)
+**Features - Voice: Multiparty calls**
+- The [Add Multiparty Call API](https://www.plivo.com/docs/voice/api/multiparty-call/participants#add-a-participant) allows for greater functionality by accepting options like `start recording audio`, `stop recording audio`, and their HTTP methods.
+- [Multiparty Calls](https://www.plivo.com/docs/voice/api/multiparty-call/) now has new APIs to `stop` and `play` audio.
+
## [v4.23.1](https://github.com/plivo/plivo-node/tree/v4.23.1) (2021-10-13)
**Bug Fix**
- LiveCallInterface.
diff --git a/lib/resources/multiPartyCall.js b/lib/resources/multiPartyCall.js
index 3fee3f86..2c7fe6f1 100644
--- a/lib/resources/multiPartyCall.js
+++ b/lib/resources/multiPartyCall.js
@@ -15,6 +15,7 @@ const clientKey = Symbol();
const action = 'MultiPartyCall/';
const idField = 'mpcUuid';
const secondaryAction = 'Participant/';
+const secondaryMemberAction = 'Member/';
const secondaryIdField = 'participantUuid';
export class MPCError extends Error { }
@@ -306,6 +307,29 @@ export class MultiPartyCall extends PlivoResource{
else {
params.exitSoundMethod = 'GET'
}
+
+ if(params.startRecordingAudio){
+ validUrl('startRecordingAudio', params.startRecordingAudio, false)
+ }
+
+ if(params.startRecordingAudioMethod){
+ validParam('startRecordingAudioMethod', params.startRecordingAudioMethod.toUpperCase(), [String], false, ['GET', 'POST'])
+ }
+ else {
+ params.startRecordingAudioMethod = 'GET'
+ }
+
+ if(params.stopRecordingAudio){
+ validUrl('stopRecordingAudio', params.stopRecordingAudio, false)
+ }
+
+ if(params.stopRecordingAudioMethod){
+ validParam('stopRecordingAudioMethod', params.stopRecordingAudioMethod.toUpperCase(), [String], false, ['GET', 'POST'])
+ }
+ else {
+ params.stopRecordingAudioMethod = 'GET'
+ }
+
if(params.to && (String(params.ringTimeout).split('<').length > params.to.split('<').length)){
throw new MPCError("RingTimeout:number of ring_timeout(s) should be same as number of destination(s)")
}
@@ -449,6 +473,32 @@ export class MultiPartyCallParticipant extends PlivoSecondaryResource{
}
+export class MultiPartyCallMember extends PlivoSecondaryResource{
+ constructor(client, data= {}) {
+ super(action, MultiPartyCall, idField, secondaryMemberAction, MultiPartyCallMember, secondaryIdField, client);
+
+ if (idField in data) {
+ this.id = data[idField];
+ }
+
+ if(secondaryIdField in data){
+ this.secondaryId = data[secondaryIdField];
+ }
+
+ extend(this, data);
+ this[clientKey] = client;
+ }
+
+ startPlayAudio(params){
+ params.isVoiceRequest = 'true';
+ return super.executeAction(this.id, this.secondaryId + '/Play', 'POST', params)
+ }
+
+ stopPlayAudio(){
+ return super.executeAction(this.id, this.secondaryId +'/Play', 'DELETE',{'isVoiceRequest' : 'true'})
+ }
+}
+
export class MultiPartyCallInterface extends PlivoResourceInterface{
constructor(client, data = {}) {
super(action, MultiPartyCall, idField, client);
@@ -737,4 +787,33 @@ export class MultiPartyCallInterface extends PlivoResourceInterface{
return new MultiPartyCallParticipant(this[clientKey], {id: mpcId[0] + mpcId[1], secondaryId: participantId}).getParticipant()
}
+ startPlayAudio(participantId, url, params = {}){
+ validParam('participantId', participantId, [String, Number], true)
+ validUrl('url', url, true)
+ if(params.uuid){
+ validParam('uuid', params.uuid, [String], false)
+ }
+ if(params.friendlyName){
+ validParam('friendlyName', params.friendlyName, [String], false)
+ }
+ let mpcId = this.makeMpcId(params.uuid, params.friendlyName)
+ delete params.uuid
+ delete params.friendlyName
+ params.url = url
+ return new MultiPartyCallMember(this[clientKey], {id: mpcId[0] + mpcId[1], secondaryId: participantId}).startPlayAudio(params)
+ }
+
+ stopPlayAudio(participantId, params = {}){
+ validParam('participantId', participantId, [String, Number], true)
+ if(params.uuid){
+ validParam('uuid', params.uuid, [String], false)
+ }
+ if(params.friendlyName){
+ validParam('friendlyName', params.friendlyName, [String], false)
+ }
+ let mpcId = this.makeMpcId(params.uuid, params.friendlyName)
+ delete params.uuid
+ delete params.friendlyName
+ return new MultiPartyCallMember(this[clientKey], {id: mpcId[0] + mpcId[1], secondaryId: participantId}).stopPlayAudio(params)
+ }
}
diff --git a/lib/rest/request-test.js b/lib/rest/request-test.js
index 3f0abe0a..fc1df286 100644
--- a/lib/rest/request-test.js
+++ b/lib/rest/request-test.js
@@ -883,6 +883,60 @@ export function Request(config) {
});
}
+ else if (method === 'POST' && action === 'MultiPartyCall/name_TestMPC/Participant/10/Record/'){
+ resolve({
+ response: {},
+ body: {
+ "api_id": "036c80f3-3721-11ec-a678-0242ac110002",
+ "message": "MPC: TestMPC participant record started",
+ "recording_id": "24670db8-c723-4ba2-8521-f10ec41ddf8b",
+ "recording_url": "https://media-qa.voice.plivodev.com/v1/Account/MAXXXXXXXXXXXX/Recording/XXXXX-XXXX-XXXX-XXXXX.mp3"
+ }
+ });
+ }
+
+ else if (method === 'DELETE' && action === 'MultiPartyCall/name_TestMPC/Participant/10/Record/'){
+ resolve({
+ response: {},
+ body: {}
+ });
+ }
+
+ else if (method === 'POST' && action === 'MultiPartyCall/name_TestMPC/Participant/10/Record/Pause/'){
+ resolve({
+ response: {},
+ body: {}
+ });
+ }
+
+ else if (method === 'POST' && action === 'MultiPartyCall/name_TestMPC/Participant/10/Record/Resume/'){
+ resolve({
+ response: {},
+ body: {}
+ });
+ }
+
+ else if (method === 'POST' && action === 'MultiPartyCall/name_TestMPC/Member/10/Play/'){
+ resolve({
+ response: {},
+ body: {
+ "api_id": "c07db813-3721-11ec-8bcd-0242ac110008",
+ "message": "play queued into MPC",
+ "mpcMemberId": [
+ "1003"
+ ],
+ "mpcName": "TestMPC"
+ }
+ });
+ }
+
+ else if (method === 'DELETE' && action === 'MultiPartyCall/name_TestMPC/Member/10/Play/'){
+ resolve({
+ response: {},
+ body: {}
+ });
+ }
+
// ============= Numbers ===================
else if (method == 'GET' && action == 'Number/+919999999990/') {
resolve({
diff --git a/lib/utils/plivoxml.js b/lib/utils/plivoxml.js
index f6a30b39..d1009e76 100644
--- a/lib/utils/plivoxml.js
+++ b/lib/utils/plivoxml.js
@@ -473,6 +473,10 @@ Response.prototype = {
* @param {string} [attributes.recordingCallbackUrl]
* @param {string} [attributes.statusCallbackUrl]
* @param {string} [attributes.customerHoldMusicUrl]
+ * @param {string} [attributes.startRecordingAudio]
+ * @param {string} [attributes.stopRecordingAudio]
+ * @param {string} [attributes.startRecordingAudioMethod]
+ * @param {string} [attributes.stopRecordingAudioMethod]
*/
addMultiPartyCall: function (body, attributes){
const VALID_ROLE_VALUES = ['agent', 'supervisor', 'customer']
@@ -664,6 +668,28 @@ Response.prototype = {
if(attributes.customerHoldMusicUrl && !plivoUtils.validUrl('customerHoldMusicUrl', attributes.customerHoldMusicUrl, false)){
throw new PlivoXMLError('Invalid attribute value ' + attributes.customerHoldMusicUrl + ' for customerHoldMusicUrl')
}
+
+ if(attributes.startRecordingAudio && !plivoUtils.validUrl('startRecordingAudio', attributes.startRecordingAudio, false)){
+ throw new PlivoXMLError('Invalid attribute value ' + attributes.startRecordingAudio + ' for startRecordingAudio')
+ }
+
+ if(attributes.stopRecordingAudio && !plivoUtils.validUrl('stopRecordingAudio', attributes.stopRecordingAudio, false)){
+ throw new PlivoXMLError('Invalid attribute value ' + attributes.stopRecordingAudio + ' for stopRecordingAudio')
+ }
+
+ if(attributes.startRecordingAudioMethod && VALID_METHOD_VALUES.indexOf(attributes.startRecordingAudioMethod.toUpperCase())===-1){
+ throw new PlivoXMLError('Invalid attribute value ' + attributes.startRecordingAudioMethod + ' for startRecordingAudioMethod')
+ }
+ else if (!attributes.startRecordingAudioMethod){
+ attributes.startRecordingAudioMethod = 'GET'
+ }
+
+ if(attributes.stopRecordingAudioMethod && VALID_METHOD_VALUES.indexOf(attributes.stopRecordingAudioMethod.toUpperCase())===-1){
+ throw new PlivoXMLError('Invalid attribute value ' + attributes.stopRecordingAudioMethod + ' for stopRecordingAudioMethod')
+ }
+ else if (!attributes.stopRecordingAudioMethod){
+ attributes.stopRecordingAudioMethod = 'GET'
+ }
return this.add(new MultiPartyCall(Response), body, attributes);
},
@@ -984,6 +1010,7 @@ function MultiPartyCall(Response){
'statusCallbackEvents', 'statusCallbackUrl', 'statusCallbackMethod',
'stayAlone', 'coachMode', 'mute', 'hold', 'startMpcOnEnter', 'endMpcOnExit',
'enterSound', 'enterSoundMethod', 'exitSound', 'exitSoundMethod',
- 'onExitActionUrl', 'onExitActionMethod', 'relayDTMFInputs'];
+ 'onExitActionUrl', 'onExitActionMethod', 'relayDTMFInputs',
+ 'startRecordingAudio', 'startRecordingAudioMethod', 'stopRecordingAudio', 'stopRecordingAudioMethod'];
}
util.inherits(MultiPartyCall, Response);
diff --git a/package.json b/package.json
index 38d9b64e..771a4e4e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "plivo",
- "version": "4.23.1",
+ "version": "4.24.0",
"description": "A Node.js SDK to make voice calls and send SMS using Plivo and to generate Plivo XML",
"homepage": "https://github.com/plivo/plivo-node",
"files": [
diff --git a/test/multiPartyCalls.js b/test/multiPartyCalls.js
index 0e848ec7..73cfe30b 100644
--- a/test/multiPartyCalls.js
+++ b/test/multiPartyCalls.js
@@ -92,4 +92,41 @@ describe('multiPartyCalls', function (){
assert.equal(response.resourceUri, '/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_7503f05f-2d6e-4ab3-b9e6-3b0d81ae9087/Participant/2132/')
})
});
+
+ it('should start MPC Participant Recording', function (){
+ return client.multiPartyCalls.startParticipantRecording(10, {friendlyName: 'TestMPC'}).then(function (response){
+ assert(response.message, "MPC: TestMPC participant record started")
+ })
+ });
+
+ it('should stop MPC Participant Recording', function (){
+ return client.multiPartyCalls.stopParticipantRecording(10,{friendlyName: 'TestMPC'}).then(function (response){
+ assert(response instanceof PlivoGenericResponse)
+ })
+ });
+
+ it('should pause MPC Participant Recording', function (){
+ return client.multiPartyCalls.pauseParticipantRecording(10,{friendlyName: 'TestMPC'}).then(function (response){
+ assert(response instanceof PlivoGenericResponse)
+ })
+ });
+
+ it('should resume MPC Participant Recording', function (){
+ return client.multiPartyCalls.resumeParticipantRecording(10,{friendlyName: 'TestMPC'}).then(function (response){
+ assert(response instanceof PlivoGenericResponse)
+ })
+ });
+
+ it('should start MPC Play Audio Member', function (){
+ return client.multiPartyCalls.startPlayAudio(10,'https://s3.amazonaws.com/XXX/XXX.mp3',
+ {friendlyName: 'TestMPC'}).then(function (response){
+ assert(response.message, "play queued into MPC")
+ })
+ });
+
+ it('should stop MPC Play Audio Member', function (){
+ return client.multiPartyCalls.stopPlayAudio(10,{friendlyName: 'TestMPC'}).then(function (response){
+ assert(response instanceof PlivoGenericResponse)
+ })
+ });
})
diff --git a/test/xml.js b/test/xml.js
index 061ac5ee..cfc587ed 100644
--- a/test/xml.js
+++ b/test/xml.js
@@ -35,7 +35,7 @@ describe('PlivoXML', function () {
maxDuration: 1000,
statusCallbackEvents: 'participant-speak-events, participant-digit-input-events, add-participant-api-events, participant-state-changes, mpc-state-changes'
});
- assert.equal('Nairobi',mpcResponse.toXML());
+ assert.equal('Nairobi',mpcResponse.toXML());
done();
});
});