WebRTC & SIP: The Demo!
WebRTC and SIP are two of the most important technologies in today’s real-time communication ecosystem. Session Initiation Protocol (SIP) is heavily used in VoIP technology; webRTC is used for browsers, mobile devices and native communication capabilities without additional software plugins. In this article we will show you a demo of how these two can be used together to build a simple video conferencing web application.
Session Initiation Protocol (SIP) is a text-based communication protocol for signaling and controlling multimedia sessions on an IP network. In other words, SIP is used to set up and modify calls between two or more endpoints.
WebRTC defines APIs and standards that enable browsers the access to media devices, (camera and microphone)and peer-to-peer connections to other endpoints.
An important thing to note is that in WebRTC, the mechanism to negotiate the connection is not specified. This is fully intentional to allow the developer to implement what suits best.
It is precisely there where the SIP protocol fits perfectly as a powerful signaling mechanism to negotiate call parameters.
Requirements.
We will use HTML + plain ol’ Javascript to build the application. Nothing too fancy so that we can focus on explaining the relevant details without distraction. Then you can apply the concepts learned here, with the technologies that all the cool kids of the web are using.
You also need a SIP server, in this example we’re using Asterisk. If you’ve never used it before, don’t worry we will cover the installation too.
For this application we used two libraries: JsSIP and sdp-interop-sl. The first is used to enable SIP support and the latter to make some modifications to SDPs that are currently required when using Chrome browser.
Both libraries are already included in the example code, which is hosted on Github. Go ahead and clone it to follow along.
Overview.
We’re building a simple video conferencing web application. You log in as Bob and Lucy, two friends that met after college. Lucy Steele, interviewed the young magnate Bob Grey, an intimidating and good looking guy with singular tastes… Ok I think we’re drifting off our course, let’s just say that they want to communicate with each other using nothing more than the browser.
So they open our web application and log in, the app will send a REGISTER message to the SIP server which responds with an OK message.
Then, Lucy types Bob’s name and clicks “Call”, the app gets access to media devices using WebRTC APIs and sends media details in the INVITE message. After a couple of ACKs and other types of messages the call initiates and communication begins to flow.
Finally Bob clicks on “Hang up”, the app sends a BYE message to the server and after Lucy sends an ACK, the call ends.
All these uppercase words are typical SIP messages, and as you can see in the image below we are looking at a traditional SIP call flow. The difference is that we use the WebRTC APIs to bring the communication process to the browser.
Can we Build it? Yes we can!
Alright, let’s get our hands dirty and get this application built. Use your favorite code editor to open the project folder. The directory structure is shown below.
The asterisk-conf directory contains the configuration files for our Asterisk instance, the js folder contains our application code and the required libraries. Ssl contains the certificates to serve the application using https, and in the root folder resides the index.html, .gitignore, package.json and README.md files.
Setting up Asterisk
First you’ll need a SIP server, we will use Asterisk 15. Grab a server with Ubuntu 16.04 and configure it by typing in a terminal. The below commands are shown in Italic font:
Download Asterisk 15:
curl -o asterisk.tar.gz \
http://downloads.asterisk.org/pub/telephony/asterisk/asterisk-15-current.tar.gz
- Install the required dependencies:
sudo apt install -y wget gcc g++ ncurses-dev libxml2-dev libsqlite3-dev \
libsrtp-dev uuid-dev libssl-dev libjansson-dev build-essential - Uncompress Asterisk:
tar zxfv asterisk.tar.gz - Enter extracted asterisk directory:
cd asterisk* - Configure it:
./configure - Compile it:
make - Install it:
sudo make install - Generate init services and samples:
sudo make config
sudo make samples - Delete sip.conf file
rm /etc/asterisk/sip.conf - Generate SSL keys
sudo mkdir /etc/asterisk/keys/sudo ./contrib/scripts/ast_tls_cert -C ip-or-server-domain -O “My Cool App” -d /etc/asterisk/keys
Next, let’s add the modify configuration files inside /etc/asterisk/ directory, as follows:
;/etc/asterisk/http.conf
[general] enabled=yes bindaddr=0.0.0.0 bindport=8088 tlsenable=yes tlsbindaddr=0.0.0.0:8089 tlscertfile=/etc/asterisk/keys/asterisk.pem ;/etc/asterisk/extensions.conf [default] exten => bob,1,Dial(PJSIP/${EXTEN}) exten => lucy,1,Dial(PJSIP/${EXTEN}) ;/etc/asterisk/rtp.conf [general] rtpstart=10000 rtpend=20000 stunaddr=stun.l.google.com:19302 ;/etc/asterisk/pjsip.conf [transport_wss] type=transport bind=0.0.0.0 protocol=wss [bob] type=aor max_contacts=1 [bob] type=auth auth_type=userpass username=bob password=123456 ; This is an insecure password [bob] type=endpoint context=default direct_media=no allow=!all,ulaw,vp8,h264 aors=bob auth=bob max_audio_streams=10 max_video_streams=10 webrtc=yes dtls_cert_file=/etc/asterisk/keys/asterisk.pem dtls_ca_file=/etc/asterisk/keys/ca.crt [lucy] type=aor max_contacts=1 [lucy] type=auth auth_type=userpass username=lucy password=123456 ; This is an insecure password [lucy] type=endpoint context=default direct_media=no allow=!all,ulaw,vp8,h264 aors=lucy auth=lucy max_audio_streams=10 max_video_streams=10 webrtc=yes dtls_cert_file=/etc/asterisk/keys/asterisk.pem dtls_ca_file=/etc/asterisk/keys/ca.crt |
Note that we are using a very insecure password for the endpoints, when setting up in production be sure to change it for a stronger one.
Finally start the asterisk service by typing: sudo systemctl start asterisk.
Building the App
The application consists of two main files: index.html and js/main.js. We will show you the most important aspects of each.
The index.html file contains the HTML code for the app, this includes: the text fields, buttons and video elements. Below is the body of the web page, you can see that the login and call controls, and video elements are in different div tabs.
Also note that some elements are not visible from the beginning, like the “Hangup” button. The visibility of the HTML objects is controlled via the CSS property display.
…
<h1>SIP + WebRTC</h1> <div id=”login”> <input type=”text” id=”localUser” /> <button id=”btnLogin”>Log in</button> <span id=”showUser”></span> <input type=”text” id=”remoteUser” style=”display:none”/> <button id=”btnCall” style=”display:none”>Call</button> <button id=”btnHangup” style=”display:none”>Hangup</button> </div> <div id=”call”> <video id=”localVideo” autoplay></video> <video id=”remoteVideo” autoplay></video> </div> <script src=”js/jssip-3.2.4.min.js”></script> <script src=”js/sdp-interop-sl.js”></script> <script src=”js/main.js”></script> … |
Now, let’s take a look at the main.js file. We begin by getting the HTML elements into javascript variables and defining some constants.
//getting DOM elements
var btnLogin = document.getElementById(‘btnLogin’) var btnCall = document.getElementById(‘btnCall’) var localUser = document.getElementById(‘localUser’) var remoteUser = document.getElementById(‘remoteUser’) var localVideo = document.getElementById(‘localVideo’) var remoteVideo = document.getElementById(‘remoteVideo’) //some useful variables var ua = null var session = null var interop = new SdpInterop.InteropChrome() var isChrome = window.chrome //change this ip to your asterisk server const domain = “10.182.106.16” |
Then we add the event listeners for the buttons. First let’s look at the ‘Click’ event on the login button. Some of the lines of code have been omitted for brevity and replaced with comments.
We begin by creating a websocket connection to the server, which is passed as a parameter, along with other values like the caller uri and password, to the User Agent instantiation.
Then we define some event listeners for the User Agent object, the most important is the ‘newRTCSession’ shown below. This event is triggered when a new session begins and receives such session as a parameter.
Inside the ‘newRTCSession’ we define some session events that manipulate the sdp when using Chrome, and that add and remove the media stream when the call begins and ends.
//when user clicks “Login”
btnLogin.addEventListener(‘click’, () => { //creates a socket connection with SIP server var socket = new JsSIP.WebSocketInterface(‘wss://’ + domain + ‘:8089/ws’)
//defines configuration var configuration = { sockets: [socket], uri: ‘sip:’+ localUser.value +’@’ + domain, authorization_user: localUser.value, password: ‘123456’ } //creates the User Agent object and starts it ua = new JsSIP.UA(configuration) ua.start() /* … Some User Agent (ua) events definitions go here … */ //when a new session is received ua.on(‘newRTCSession’, function (e) { console.log(‘newRTCSession’, e) session = e.session //answer the call if(e.originator === ‘remote’) { session.answer() } //manipulates the sdp on Chrome. session.on(‘sdp’, (data) => { if(isChrome) { console.log(‘doing dark magic in chrome…’) let desc = new RTCSessionDescription({ type: data.type, sdp: data.sdp }) if (data.originator === ‘local’) { converted = interop.toUnifiedPlan(desc) } else { converted = interop.toPlanB(desc) }
data.sdp = converted.sdp } }) //when the call ends session.on(‘ended’, data => { //clean video elements localVideo.src = ” remoteVideo.src = ” /* … some DOM manipulation code goes here … */ }) //when the session has been received in remote side session.on(‘confirmed’, () => { //adds local media stream stream = session.connection.getLocalStreams()[0] localVideo.srcObject = stream /* … some DOM manipulation code … */ }) //when remote media stream is receive session.connection.ontrack = evt => { remoteVideo.srcObject = evt.streams[0] } }) /* … More User Agent (ua) events definitions go here … */ }) |
After the user has logged in, he is ready to initiate a call, and once in a call, he will want to eventually end it. Below are the event listeners for the “Call” and “Hangup” buttons.
//when user clicks “Call”
btnCall.addEventListener(‘click’, () => { //defines call options var options = { ‘mediaConstraints’: { ‘audio’: false, //let’s keep audio disabled for now ‘video’: true } } //calls the other user session = ua.call(‘sip:’ + remoteUser.value + ‘@’ + domain, options) }) //when user clicks Hangup btnHangup.addEventListener(‘click’, () => { session.terminate() session = null }) |
Now Bob and Lucy only need to run the application. The example code hosted in Github contains a package.json file that sets the http-server package as a dependency and a start script that serves the files in the project as shown below:
{
“name”: “sip-webrtc”, “version”: “1.0.0”, “description”: “”, “main”: “index.js”, “scripts”: { “start”: “node_modules/.bin/http-server -S -C ssl/cert.pem -K ssl/key.pem .” }, “keywords”: [], “author”: “”, “license”: “ISC”, “dependencies”: { “http-server”: “^0.11.1” } } |
Running the app is as easy as typing the following commands inside the project folder:
npm install
npm start
With the application running, open two browser tabs and go to https://localhost:8080 and log in as bob and lucy. And voilá, you will have your video conference powered by WebRTC and SIP.
Conclusion
WebRTC and SIP play very well together. WebRTCprovides video conferencing capabilities to browsers and SIPprovides a mechanism to negotiate call parameters.
Contact us today! At webRTC.ventures we can help you capitalize using these two applications to create your best simple video conferencing web application.