What if your agent just wanted to open a can of soda real quick without interrupting the caller with hold music and their headset mute switch was a little wonky? Or you were messing around with the Amazon Connect JavaScript libraries and wanted a nice scenario to dig deeper on? Whatever the case may be, let’s take a look at implementing a mute button for Amazon Connect.
This post will build off our work in my previous post (https://blogs.perficient.com/integrate/2017/10/11/intro-to-amazon-connect-streams-api-part-2/) and uses the Amazon Connect Streams (Streams) and Amazon Connect connect-rtc.js (https://github.com/aws/connect-rtc-js) libraries.
As we saw, Streams gives us access to agent and contact (call) state details and basic call control functionality. We can use the default CCP or implement our own agent experience. However, we do not get access to the underlying media (audio) that is flowing from the agent to the caller.
For when you want to get deep into the weeds, Amazon gives us the connect-rtc.js library. From the GitHub site, connect-rtc.js “…implements Amazon Connect WebRTC signaling protocol and integrates with browser WebRTC APIs to provide a simple contact session interface which can be integrated with Amazon Connect StreamJS seamlessly.”
Let’s break that description down a bit before we go any farther.
…implements Amazon Connect WebRTC signaling protocol and integrates with browser WebRTC APIs…
Amazon Connect uses WebRTC for the agents’ communications. WebRTC is a set of JavaScript APIs to allow for in-browser (web) real-time communications (RTC). Audio, video, screen sharing, etc. When Chrome or Firefox asks permission to use your microphone when you open the CCP, that allows WebRTC get media from your PC to share with Amazon Connect and the caller.
At the heart of any WebRTC JavaScript application is the RTCPeerConnection object, which represents a connection between two peers. Audio and video are exchanged over this connection and the peers can talk to and see each other. The RTCPeerConnection object does a lot, and the interface is a bit overwhelming (https://w3c.github.io/webrtc-pc/#rtcpeerconnection-interface), but for now just keep in mind that it’s a container for the audio connection.
When Amazon Connect delivers a call to an agent in the browser, it establishes an RTCPeerConnection between the agent and Amazon Connect. If we could get access to that connection object in our code, we could manipulate the audio, i.e. mute it.
…to provide a simple contact session interface
connect-rtc.js gives us an RTCSession object which wraps the RTCPeerConnection in a developer friendly interface. For example, using the pauseLocalAudio() and resumeLocalAudio() methods we can mute and unmute calls.
Avoid Contact Center Outages: Plan Your Upgrade to Amazon Connect
Learn the six most common pitfalls when upgrading your contact center, and how Amazon Connect can help you avoid them.
…which can be integrated with Amazon Connect StreamJS seamlessly
Here’s where connect-rtc.js can be a bit inscrutable. The demo application has a text input field to enter your “softphone media info” and then a block of JavaScript that creates an RTCSession object using that media info.
var mediaInfo = JSON.parse($('#softphoneMediaInfo').val());
var rtcConfig = mediaInfo.webcallConfig || JSON.parse(mediaInfo.callConfigJson);//mediaInfo.webcallConfig is used internally by Amazon Connect team only
var session = new connect.RTCSession(rtcConfig.signalingEndpoint,
rtcConfig.iceServers,
mediaInfo.callContextToken,
console);
With that RTCSession object, we can stop and start local and remote audio, mess with echo cancellation and get some low-level call connection quality stats.
The index.html of the demo points us to a line of code in the Streams API to get the softphone media info by calling contact.getAgentConnection().getSoftphoneMediaInfo() and then JSON stringify’ing it.
All of which is good, gives the developer a lot of control and quite frankly gives me at least another blog post of material to cover between softphone media info, signaling endpoints, ICE servers and the rest. For today though, we just want to hook connect-rtc.js into our custom agent experience for a mute toggle.
SoftphoneManager
To be fair, the connect-rtc-js gives us almost everything we need to integrate with our Streams application in a handy 4 point list:
- Load connect-rtc-js along with amazon-connect-streams
- Following amazon-connect-streams instructions to initialize CCP
- Replace the softphone parameter (within the second parameter of connect.core.initCCP()) with
allowFramedSoftphone: false
This would stop embedded CCP from handling softphone call - Add this line after initCCP
connect.core.initSoftphoneManager({allowFramedSoftphone: true});
This would allow your page to handle softphone call with the connect-rtc-js loaded by your page. *allowFramedSoftphone* is necessary if your page also lives in a frame, otherwise you can remove that parameter.
If we just follow these steps, our custom agent application will still work, we can log in as an agent and answer calls, no problem. And if you open the JavaScript debugger in your browser, you could set some breakpoints and see that the call is using the RTCSession object from the connect-rtc.js.
However, this is not yet sufficient. We can get to the the current contact via the contact object, but there’s no property or method we can use to get the underlying RTCSession. So we still can’t do a mute.
Remember step 4 from the above list? That call to initSoftphoneManager tells the Streams API to use the SoftphoneManager class to handle setting up the WebRTC objects for incoming calls. Since we have access to the Streams code, we can build in access to the RTCSession via a contact.session property.
The SoftphoneManager class constructor defines a new contact handler method via connect.contact(). Within that handler, it defines a method to handle any changes to that new contact via contact.onRefresh(). If the contact is incoming, this handler creates an RTCSession in a very similar way to what the connect-rtc.js demo code does:
var session = new connect.RTCSession(
callConfig.signalingEndpoint,
callConfig.iceServers,
softphoneInfo.callContextToken,
logger,
contact.getContactId());
Rather than just let this locally scoped variable fall out of scope, we can instead attach it to the contact object for later use at the end of this handler:
connect.contact(function(contact) {
contact.onRefresh(function() {
session.remoteAudioElement = document.getElementById('remote-audio');
session.connect();
contact.session = session;
}
}
With our modified Streams JavaScript file loaded, we can add some methods to our application to mute, un-mute and get mute status:
function muteSelf() {
window.myCPP.contact.session.pauseLocalAudio();
logInfoMsg("Tried to mute self, mute status is now " + getSelfMuteStatus());
}
function unMuteSelf() {
window.myCPP.contact.session.resumeLocalAudio();
logInfoMsg("Tried to un-mute self, mute status is now " + getSelfMuteStatus());
}
function getSelfMuteStatus() {
//we are using a private property here, so proceed with caution
var audioTrack = window.myCPP.contact.session._localStream.getAudioTracks()[0];
if (audioTrack && !audioTrack.enabled) {
return true;
} else {
return false;
}
}
From here, we can wire up buttons to invoke these methods and we’re all set with a custom agent experience that lets an agent mute themselves! As the connect-rtc.js demo shows, we could also insert an audio file from the agent or engage in whatever other useful mischief you can think of with access to the underlying media.
When I was working with this I added a new event, contact.onSession, so my application was notified exactly when that RTCSession was available. If you want a little challenge, try adding that to the Streams source yourself.
Any questions, comments or corrections are greatly appreciated. I glossed over a fair amount of low level details that I plan to address in future posts, and if you have anything specific that was confusing or you’d like to hear more about, let me know.
To learn more about what we can do with Amazon Connect, check out Helping You Get the Most Out of Amazon Connect
Will this same thing work when using the CTI with Salesforce? If this is creating a custom agent then how will this impact CTI?
I’m not familiar with all the details of the Salesforce CTI, but unless you can customize the CCP, you won’t be able to take advantage of what I described in the blog post. If the Salesforce CTI is just loading a given URL, you could maybe switch it to point to the URL of your custom CCP.
I have a custom CCP configured with some basic functionality such as end call, dial, transfer, hold, etc. I’m trying to implement this mute button into my softphone. I wasn’t sure how I could insert connect-rtc into my application so I winded up copying the dependencies in the packages.json into my application package and then copied over the .js files in src/js folder on the github repository. I can see my front end using the connect-rtc.js file but now im having connection issues. An incoming call gets stuck on connecting and I see some error about contact.refresh in my developer tools. Any ideas?
The connect-rtc GitHub page has some instructions on including connect-rtc.js. I’d get that going before troubleshooting other issues. https://github.com/aws/connect-rtc-js