question

Martin Dearne avatar image
Martin Dearne asked Martin Dearne commented

Pause & Resume API Call

I am using the test harness call here https://developers.ringcentral.com/api-reference/Call-Control/pauseResumeCallRecording

The attributes are being populated from the webhook subscription response when on a call "/restapi/v1.0/account/~/telephony/sessions" and believe the values being used are correct.

accountId *

Internal identifier of a RingCentral account or tilde (~) to indicate the account logged-in within the current session

telephonySessionId *

Internal identifier of a call session

partyId *

Internal identifier of a call party

recordingId *

Internal identifier of a recording

Using the CURL example the data being sent

--data '{"active":true}'


The response being received for this request is "400 Bad Request" with message "Invalid Request"

I have logged a support ticket for this but wait a response so if anybody could shine some light on this would be appreciated.

rest api
1 |3000

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

Phong Vu avatar image
Phong Vu answered

Did you authenticate your app with the user's credentials of the user who is a party of the call?

1 |3000

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

Martin Dearne avatar image
Martin Dearne answered Martin Dearne commented

Ye it was myself on the call and authenticated via the website ui to get authorisation token.

2 comments
1 |3000

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

Phong Vu avatar image Phong Vu ♦♦ commented ·

Can you run Node JS code? Let me know so I can share some sample code.

0 Likes 0 ·
Martin Dearne avatar image Martin Dearne Phong Vu ♦♦ commented ·

Yes I have this installed on my machine so this is possible.

0 Likes 0 ·
Phong Vu avatar image
Phong Vu answered

Here you go with a quick sample app.

1. Provide your app credentials and user login credentials

2. Put the caller phone number into the 'customerPhoneNumbers' array. This is just a fake condition to decide a recording.

3. Make a subfolder 'recordings'

4. Make a call from that 'customer' number to one of the numbers belong to the authenticated user extension (the one logs in the app)

The demo app will start recording (after 10 secs), pause after 1 min and resume after 10 secs. Then it will download the recording after 30 secs when the call is disconnected.

You need to install the RingCentral JS SDK before running the script.

const RingCentral = require('@ringcentral/sdk').SDK
const Subscriptions = require('@ringcentral/subscriptions').Subscriptions;
var fs = require('fs')
var async = require("async");

RINGCENTRAL_CLIENTID = "";
RINGCENTRAL_CLIENTSECRET = "";
RINGCENTRAL_SERVER = 'https://platform.devtest.ringcentral.com'

RINGCENTRAL_USERNAME = "";
RINGCENTRAL_PASSWORD = ""
RINGCENTRAL_EXTENSION = ""

const rcsdk = new RingCentral({
  server: RINGCENTRAL_SERVER,
  clientId: RINGCENTRAL_CLIENTID,
  clientSecret: RINGCENTRAL_CLIENTSECRET
})

var platform = rcsdk.platform()
const subscriptions = new Subscriptions({
   sdk: rcsdk
});
var subscription = subscriptions.createSubscription();

platform.login({
        username: RINGCENTRAL_USERNAME,
        extension: RINGCENTRAL_EXTENSION,
        password: RINGCENTRAL_PASSWORD
      })

platform.on(platform.events.loginSuccess, async function(e){
    console.log("Login success")
    subscribeForNotification
});

function subscribeForNotification(){
  var eventFilter = ['/restapi/v1.0/account/~/extension/~/telephony/sessions']
  subscription.setEventFilters(eventFilter)
   .register()
   .then(async function(resp){
     console.log('Ready for getting account NSN events')
   })
   .catch(function(e){
     throw e
   })
}

var customerPhoneNumbers = ['+1650xxxxxxx']

var callInfo = {
  sessionId: '', // use this to identify the call when handle multiple calls
  partyId: '', // use this to create the recording endpoint
  telSessionId: '', // use this to create the recording endpoint
  recordingId: ''
}


subscription.on(subscription.events.notification, async function(msg) {
    var body = msg.body
    var party = msg.body.parties[
    if (party.hasOwnProperty("extensionId")){
      console.log(JSON.stringify(msg.body));
      if (party.direction == "Inbound" && party.status.code == "Answered"){
        if (!party.hasOwnProperty('recordings')){
          var canRecord = customerPhoneNumbers.find(customer => customer == party.from.phoneNumber)
          if (canRecord) {
            console.log("Callee Answered => It's time to call recording.")
            callInfo.sessionId = body.sessionId
            callInfo.partyId = party.id
            callInfo.telSessionId = body.telephonySessionId
            // start recording immediately or make a delay
            setTimeout(function(){
              startRecording()
            }, 10000)
          }
        }
      }else if (party.status.code == "Disconnected"){
        // Want to download the call recording after the call ended?
        if (body.sessionId == callInfo.sessionId && party.hasOwnProperty("recordings")){
          console.log("Call has recording => Download it in 30 seconds")
          setTimeout(function(sessionId){
            readExtensionCallLogs(sessionId)
          }, 30000, callInfo.sessionId)
        }
        callInfo.sessionId = ""
        callInfo.partyId = ""
        callInfo.telSessionId = ""
        console.log("Call Disconnected => Reset getCallSessionInfo")
      }
    }else{
      console.log("caller of inbound call")
    }
});


async function startRecording(){
  console.log("startRecording")
  if (callInfo.telSessionId == '' || callInfo.partyId == '')
    return
  var endpoint = `/restapi/v1.0/account/~/telephony/sessions/${callInfo.telSessionId}/parties/${callInfo.partyId}/recordings`
  try {
    var resp = await platform.post(endpoint)
    var jsonObj = await resp.json()
    callInfo.recordingId = jsonObj.id
    console.log(jsonObj)
    // stop recording after 1 min
    setTimeout(function(){
      pauseRecording()
    }, 60000)
  }catch(e){
    console.log(e)
  }
}

async function pauseRecording(){
  console.log("pauseRecording")
  if (callInfo.telSessionId == '' || callInfo.partyId == '')
    return
  var endpoint = `/restapi/v1.0/account/~/telephony/sessions/${callInfo.telSessionId}/parties/${callInfo.partyId}/recordings/${callInfo.recordingId}`
  var params = {
    active: false
  }
  try {
    var resp = await platform.patch(endpoint, params)
    var jsonObj = await resp.json()
    console.log(jsonObj)
    // resume recording after 10 secs
    setTimeout(function(){
      resumeRecording()
    }, 10000)
  }catch(e){
    console.log(e)
  }
}

async function resumeRecording(){
  console.log("resumeRecording")
  if (callInfo.telSessionId == '' || callInfo.partyId == '')
    return
  var endpoint = `/restapi/v1.0/account/~/telephony/sessions/${callInfo.telSessionId}/parties/${callInfo.partyId}/recordings/${callInfo.recordingId}`
  var params = {
    active: true
  }
  try {
    var resp = await platform.patch(endpoint, params)
    var jsonObj = await resp.json()
    console.log(jsonObj)
  }catch(e){
    console.log(e)
  }
}

async function readExtensionCallLogs(sessionId){
  var endpoint = `/restapi/v1.0/account/~/extension/~/call-log`
  console.log(endpoint)
  var params = {
      sessionId: sessionId
  }
  try {
    var resp = await platform.get(endpoint, params)
    var jsonObj = await resp.json()
    console.log(jsonObj)
    async.each(jsonObj.records,
        function(record, callback){
          console.log("THIS CALL HAS A RECORDING: " + record.recording.contentUri)
          saveAudioFile(record)
        },
        function(err){
          console.log("This call does not have a recording.")
        }
    );
  }catch(e){
    var err = e.toString();
    console.log("READ FAILED: " + err)
  }
}


async function saveAudioFile(record){
  var resp = await platform.get(record.recording.contentUri)
  var buffer = await resp.buffer()
  var destFile = './recordings/' + record.recording.id + '.mp3'
  fs.writeFileSync(destFile, buffer);
  console.log("CALL RECORDING SAVED AT: " + destFile)
}

Let me know if you have further questions

1 |3000

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

Martin Dearne avatar image
Martin Dearne answered Phong Vu commented

Is it possible to have a .net equivalent for the above?

1 comment
1 |3000

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

Phong Vu avatar image Phong Vu ♦♦ commented ·

I can implement it later. Just too busy right now.

1 Like 1 ·
Phong Vu avatar image
Phong Vu answered Phong Vu commented

Here you go. A quick and dirty .Net sample code. You need to install the latest RingCentral .Net SDK

using System;
using System.IO;
using Newtonsoft.Json.Linq;
using System.Threading;
using System.Threading.Tasks;
using RingCentral;
using RingCentral.Net.Events;
using Newtonsoft.Json;
using RingCentral.Net.PubNub;
using System.Net;
using System.Collections.Specialized;
using System.Linq;

namespace Quick_Sample
{
    class Program
    {
        const string RINGCENTRAL_CLIENTID = "";
        const string RINGCENTRAL_CLIENTSECRET = "";
        const bool   RINGCENTRAL_PRODUCTION = false;

        const string RINGCENTRAL_USERNAME = "";
        const string RINGCENTRAL_PASSWORD = "";
        const string RINGCENTRAL_EXTENSION = "";
        
        static RestClient rcsdk;

        static string[] customerPhoneNumbers = { "+1650xxxxxxx" };

        class CallInfo {
            public String sessionId = ""; // use this to identify the call when handle multiple calls
            public String partyId = ""; // use this to create the recording endpoint
            public String telSessionId = ""; // use this to create the recording endpoint
            public String recordingId = "";
        }
        static CallInfo callInfo;

        static void Main(string[] args)
        {
            rcsdk = new RestClient(RINGCENTRAL_CLIENTID, RINGCENTRAL_CLIENTSECRET, RINGCENTRAL_PRODUCTION);
            rcsdk.Authorize(RINGCENTRAL_USERNAME, RINGCENTRAL_EXTENSION, RINGCENTRAL_PASSWORD).Wait();
            var eventsExtension = new EventsExtension();
            rcsdk.InstallExtension(eventsExtension).Wait();

            eventsExtension.RequestSuccess += EventHandler;
            callInfo = new CallInfo();
            pubnub_notification_record_call().Wait();
        }
        static void EventHandler(object sender, HttpMessages httpMessages)
        {
            var rateLimitRemaining = httpMessages.httpResponseMessage.Headers.First(i => i.Key == "X-Rate-Limit-Remaining").Value.First();
            Console.WriteLine("Remaining: " + rateLimitRemaining);
        }
        static private async Task pubnub_notification_record_call()
        {
            var pubNubExtension = new PubNubExtension();
            await rcsdk.InstallExtension(pubNubExtension);
            try
            {
                var eventFilters = new[]
                {
                    "/restapi/v1.0/account/~/extension/~/telephony/sessions"
                };
                var subscription = await pubNubExtension.Subscribe(eventFilters, message =>
                {
                    dynamic jsonObj = JObject.Parse(message);
                    var body = jsonObj.body;
                    var party = jsonObj.body.parties[0];
                    if (party.extensionId != null)
                    {
                        if (party.direction == "Inbound" && party.status.code == "Answered")
                        {
                            if (party.recordings == null)
                            {
                                var canRecord = customerPhoneNumbers.Where(x => x.ToString() == party.from.phoneNumber);
                                if (canRecord != null)
                                {
                                    Console.WriteLine("Callee Answered => It's time to call recording.");
                                    callInfo.sessionId = body.sessionId;
                                    callInfo.partyId = party.id;
                                    callInfo.telSessionId = body.telephonySessionId;
                                    Console.WriteLine("Can start recording");
                                    // start recording immediately or make a delay
                                    startRecording().Wait();
                                }
                            }
                        }
                        else if (party.status.code == "Disconnected")
                        {
                            // Want to download the call recording after the call ended?
                            if (body.sessionId == callInfo.sessionId && party.recordings != null)
                            {
                                Console.WriteLine("Call has recording => Download it in 30 seconds");
                                Thread.Sleep(30000);
                                readExtensionCallLog(callInfo.sessionId).Wait();
                                Console.WriteLine("Call Disconnected => Reset getCallSessionInfo");
                            }
                        }
                    }
                    else
                    {
                        Console.WriteLine("Remote party event");
                    }
                });

                Console.WriteLine(subscription.SubscriptionInfo.id);
                Console.WriteLine("Subscribed");
                while (true)
                {
                    Console.WriteLine("looping ...");
                    Thread.Sleep(5000);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
        static private async Task startRecording()
        {
            if (callInfo.telSessionId == "" || callInfo.partyId == "")
            {
                return;
            }
            try
            {
                var resp = await rcsdk.Restapi().Account().Telephony().Sessions(callInfo.telSessionId).Parties(callInfo.partyId).Recordings().Post();
                dynamic jsonObj = JObject.Parse(resp);
                callInfo.recordingId = jsonObj.id;
                Console.WriteLine(jsonObj.id);
                // stop recording after 1 min
                Thread.Sleep(60000);
                pauseRecording().Wait();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
        static private async Task pauseRecording()
        {
            if (callInfo.telSessionId == "" || callInfo.partyId == "" || callInfo.recordingId == "")
            {
                return;
            }
            try
            {
                CallRecordingUpdate update = new CallRecordingUpdate();
                update.active = false;
                var resp = await rcsdk.Restapi().Account().Telephony().Sessions(callInfo.telSessionId).Parties(callInfo.partyId).Recordings(callInfo.recordingId).Patch(update);
                callInfo.recordingId = resp.id;
                Console.WriteLine(resp.id);
                // resume recording after 10 secs
                Thread.Sleep(10000);
                resumeRecording().Wait();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
        static private async Task resumeRecording()
        {
            if (callInfo.telSessionId == "" || callInfo.partyId == "" || callInfo.recordingId == "")
            {
                return;
            }
            try
            {
                CallRecordingUpdate update = new CallRecordingUpdate();
                update.active = true;
                var resp = await rcsdk.Restapi().Account().Telephony().Sessions(callInfo.telSessionId).Parties(callInfo.partyId).Recordings(callInfo.recordingId).Patch(update);
                callInfo.recordingId = resp.id;
                Console.WriteLine(resp.id);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
        static private async Task readExtensionCallLog(String sessionId)
        {
            Console.WriteLine("readExtensionCallLog");
            var parameters = new ReadUserCallLogParameters();
            parameters.sessionId = sessionId;
            var path = "recordings/";
            System.IO.Directory.CreateDirectory(path);
            var resp = await rcsdk.Restapi().Account().Extension().CallLog().List(parameters);
            foreach (var record in resp.records)
            {
                var destFile = path + record.recording.id + ".mp3";
                var contentUrl = record.recording.contentUri + "?access_token=" + rcsdk.token.access_token;
                WebClient webClient = new WebClient();
                webClient.DownloadFile(contentUrl, destFile);
                Console.WriteLine("CALL RECORDING SAVED AT: " + destFile);
                
            }
        }
    }
}
2 comments
1 |3000

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

Martin Dearne avatar image Martin Dearne commented ·

Thanks for the above I cannot seem to get it to authorise and failing line 43

rcsdk.Authorize(RINGCENTRAL_USERNAME, RINGCENTRAL_EXTENSION, RINGCENTRAL_PASSWORD).Wait();

To check the details are found below: -

  1. const string RINGCENTRAL_USERNAME = ""; //Telephone number of account?
  2. const string RINGCENTRAL_PASSWORD = ""; //My Password?
  3. const string RINGCENTRAL_EXTENSION = ""; //My extension number?
0 Likes 0 ·
Phong Vu avatar image Phong Vu ♦♦ Martin Dearne commented ·

What is the error? Are you login with a user on your production or sandbox?

Production: const bool RINGCENTRAL_PRODUCTION = true;

Sandbox: const bool RINGCENTRAL_PRODUCTION = false;

Also change the app credentials accordingly.

  1. const string RINGCENTRAL_USERNAME = ""; //Your user name which can be your phone number or the email address associated with your extension.
  2. const string RINGCENTRAL_PASSWORD = ""; //Your Password
  3. const string RINGCENTRAL_EXTENSION = ""; //Your extension number

These are user credentials you login the RC app. RC desktop phone app etc.

0 Likes 0 ·
Martin Dearne avatar image
Martin Dearne answered Martin Dearne commented

Hi Phong,

I now have the credentials working in production and getting the following error: -

{

"error" : "invalid_grant",

"errors" : [ {

"errorCode" : "OAU-140",

"message" : "Invalid resource owner credentials"

} ],

"error_description" : "Invalid resource owner credentials"

}


I believe this may be related to the permissions for the app as the user being used in the app owner


If you could advise here it would be appreciated.


Regards,

Martin Dearne

2 comments
1 |3000

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

Phong Vu avatar image Phong Vu ♦♦ commented ·

I believe you messed up the sandbox user and production user login credentials. Or you have not update your app with production app client id and client secret. Search the site for that error and you will find ways how to resolve.

0 Likes 0 ·
Martin Dearne avatar image Martin Dearne Phong Vu ♦♦ commented ·

I am unable to use the sandbox user as they can't dial out. I have setup the oAuth call in postman with the same credentials and this now works.

0 Likes 0 ·

Developer sandbox tools

Using the RingCentral Phone for Desktop, you can dial or receive test calls, send and receive test SMS or Fax messages in your sandbox environment.

Download RingCentral Phone for Desktop:

Tip: switch to the "sandbox mode" before logging in the app:

  • On MacOS: press "fn + command + f2" keys
  • On Windows: press "Ctrl + F2" keys