10-01-2024 12:30 AM
Hello everyone, has anyone successfully deployed WebRTC integration with SBC/CUBE. If so, could you please share a sample configuration and any relevant Cisco documentation links? Thank you.
Solved! Go to Solution.
09-04-2025 01:51 AM - edited 09-04-2025 01:59 AM
Installation of Asterisk
Step 1: Install the dependencies
sudo apt-get update
sudo apt-get install -y build-essential libxml2-dev libncurses5-dev libssl-dev libjansson-dev libsqlite3-dev uuid-dev
Step 2: Download the source code
Navigate to /usr/src and download the latest version of Asterisk 22:
sudo wget http://downloads.asterisk.org/pub/telephony/asterisk/asterisk-22-current.tar.gz
cd /usr/src
tar -xzvf asterisk-22-current.tar.gz
Step 3: Compile Asterisk
sudo tar -xvzf asterisk-21-current.tar.gz
cd asterisk-22*/
sudo contrib/scripts/install_prereq install
sudo ./configure
sudo make menuselect
sudo make && sudo make install
sudo make samples
sudo make config
Step 4:
sudo systemctl start asterisk
systemctl status asterisk
sudo asterisk -rvvv
Step 5: Test if Asterisk is running
Start Asterisk in the foreground to verify that it’s working:
sudo asterisk -vvvvvvvvgc
Step 6: Stop Asterisk
From the Asterisk CLI, stop the process:
cli core stop now
Step 7: Start Asterisk in the background
Run Asterisk as a background process:
sudo asterisk
Step 8: Connect to the Asterisk Console
To connect to the running Asterisk console:
sudo asterisk -r
You can press ctrl z to exit
Step 9 ***Creating certificates**
sudo openssl req -nodes -newkey rsa:2048 -keyout /etc/asterisk/keys//asterisk.key -out /etc/asterisk/keys/asterisk.csr
enter your common name and other details when prompted
Step 10:
grant permission
sudo chown asterisk:asterisk /etc/asterisk/keys/asterisk.crt
sudo chown asterisk:asterisk /etc/asterisk/keys/asterisk.pem
sudo chmod 644 /etc/asterisk/keys/asterisk.crt
sudo chmod 600 /etc/asterisk/keys/asterisk.pem
Step 11:optional (ref:
https://vitalpbx.com/blog/asterisk-webrtc-from-scratch/?srsltid=AfmBOoqsJzR4lmLpmihFfwLDBXxPqwiAcTjdMNz11rcrckxodCfPl4Em)
Creat a user asterisk and add to the group
groupadd asterisk
useradd -r -d /var/lib/asterisk -g asterisk asterisk
usermod -aG audio,dialout asterisk
chown asterisk. -R /etc/asterisk
chown asterisk. -R /var/{lib,log,spool}/asterisk
chown -R asterisk.asterisk /usr/lib64/asterisk
Step 12: modify the http.conf
sudo nano /etc/asterisk/http.conf
add this
[general]
enabled=yes
bindaddr=0.0.0.0
bindport=8088
tlsenable=yes
tlsbindaddr=0.0.0.0:8089
tlscertfile=/etc/asterisk/keys/asterisk.cer (replace with your cert)
tlsprivatekey=/etc/asterisk/keys/asterisk.key (replace with your cert)
Step 13: modify the pjsip.conf for webrtc and cucm-trunk
you can replace the cucm-trunk with the cube ip
**defining websocket secure transport**
[transport-wss]
type=transport
protocol=wss
bind=0.0.0.0:8089
cert_file=/etc/asterisk/keys/asterisk.cer
priv_key_file=/etc/asterisk/keys/asterisk.key
** webrtc section**
[webcall]
type=endpoint
context=default
disallow=all
direct_media=no
allow=ulaw,alaw,gsm,h264,VP8
webrtc=yes
aors=webcall
auth=webcall_auth
; Make Asterisk trust CallerID from the client
trust_id_inbound=yes
trust_id_outbound=yes
send_pai=yes
send_rpid=yes
dtls_auto_generate_cert=yes
rtp_engine=asterisk
use_avpf=yes
ice_support=yes
force_rport=yes
rewrite_contact=yes
[webcall_auth]
type=auth
auth_type=userpass
password=your password
username=webcall
[webcall]
type=aor
max_contacts=100
[CUCM-Trunk]
type=endpoint
transport=transport-udp ; or transport-tcp if CUCM requires TCP
context=from-external
disallow=all
allow=ulaw,alaw,gsm,h264,VP8
aors=CUCM-Trunk
outbound_auth=CUCM-Auth
direct_media=no
[CUCM-Trunk]
type=aor
contact=sip:IP:5060 ; CUCM or CUBE IP and SIP Port
[CUCM-Auth]
type=auth
auth_type=userpass
username=
password= ; Must match CUCM OR CUBE SIP trunk configuration
Step 14: modify extensions.conf
[default]
exten => 7100,1,NoOp(Forwarding Call to CUCM IVR)
same => n,Dial(PJSIP/7100@CUCM-Trunk)
same => n,Hangup()
step 15:
Connect to the Atserik console
sudo asterisk -r
pjsip reload
dialplan reload
module reload http
step 16 :verify if Asterik is running on 8089
sudo ss -tulnp | grep -E '8088|8089|5060'
step 17. setting up webrtc client using javascript
modify with your real parameters
<!DOCTYPE html>
<html>
<head>
<title>Collnetwork Care</title>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<script src="{{ url_for('static', filename='sip.min.js') }}"></script>
<style>
body {
background-color: #f0f2f5;
font-family: sans-serif;
display: flex;
flex-direction: column;
align-items: center;
margin: 0;
padding: 20px;
}
.keypad {
display: grid;
grid-template-columns: repeat(3, 80px);
gap: 15px;
justify-content: center;
margin-top: 20px;
}
.keypad button {
width: 80px;
height: 80px;
font-size: 24px;
font-weight: bold;
border-radius: 50%;
border: none;
background-color: #007bff;
color: white;
cursor: pointer;
transition: background-color 0.3s;
}
.keypad button:hover {
background-color: #0056b3;
}
/* Overlay keypad on video */
#dtmfKeypad {
position: absolute;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
background: rgba(255, 255, 255, 0.9);
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
z-index: 20;
}
.header {
margin-bottom: 20px;
}
.logo {
height: 50px;
}
.call-container {
position: relative;
width: 100%;
max-width: 900px;
background-color: #fff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 8px;
padding: 20px;
}
.main-video-wrapper {
width: 100%;
height: 500px;
background-color: #000;
border-radius: 8px;
overflow: hidden;
}
#remoteVideo {
width: 100%;
height: 100%;
object-fit: contain;
}
.local-video-wrapper {
position: absolute;
top: 30px;
right: 30px;
width: 200px;
height: 150px;
border: 2px solid #fff;
border-radius: 8px;
overflow: hidden;
z-index: 10;
}
#localVideo {
width: 100%;
height: 100%;
object-fit: cover;
}
.controls-container {
display: flex;
justify-content: center;
margin-top: 20px;
gap: 15px;
}
.controls-container button {
background-color: #555;
color: #fff;
border: none;
border-radius: 50%;
width: 50px;
height: 50px;
cursor: pointer;
font-size: 20px;
transition: background-color 0.3s;
}
.controls-container button:hover {
background-color: #777;
}
.call-btn {
background-color: #28a745 !important;
}
.call-btn:hover {
background-color: #218838 !important;
}
.hangup-btn {
background-color: #dc3545 !important;
}
.hangup-btn:hover {
background-color: #c82333 !important;
}
.welcome-message {
height: 500px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
background-color: #f8f9fa;
color: #495057;
border-radius: 8px;
padding: 20px;
}
</style>
</head>
<body>
<div class="header">
<img src="{{ url_for('static', filename='logo.png') }}" alt="Collnetwork Logo" class="logo">
</div>
<div class="call-container">
<div id="welcomeMessage" class="welcome-message">
<h2>Welcome to Collnetwork Care!</h2>
<p>Please enter your details to start a video call with our representative.</p>
<form id="callForm" style="margin-top: 20px;">
<div style="margin-bottom: 10px;">
<label for="callerName">Your Name:</label>
<input type="text" id="callerName" name="callerName" placeholder="e.g., Jane Doe" required>
</div>
<div style="margin-bottom: 20px;">
<label for="callerPhone">Phone Number:</label>
<input type="tel" id="callerPhone" name="callerPhone" placeholder="e.g., +15551234567" required>
</div>
</form>
<button id="callButton" class="call-btn">
<i class="fas fa-phone"></i> Start Call
</button>
</div>
<div class="main-video-wrapper" style="display:none;">
<video id="remoteVideo" autoplay playsinline></video>
</div>
<div class="local-video-wrapper" style="display:none;">
<video id="localVideo" autoplay muted playsinline></video>
</div>
<div class="controls-container">
<button id="hangupButton" class="hangup-btn" style="display:none;">
<i class="fas fa-phone-slash"></i>
</button>
<button id="muteButton" title="Mute/Unmute" style="display:none;">
<i class="fas fa-microphone"></i>
</button>
<button id="videoButton" title="Video On/Off" style="display:none;">
<i class="fas fa-video"></i>
</button>
<button id="screenShareButton" title="Share Screen" style="display:none;">
<i class="fas fa-desktop"></i>
</button>
<button id="dtmfButton" title="Keypad" style="display:none;">
<i class="fas fa-th"></i>
</button>
</div>
<div id="dtmfKeypad" style="display:none;">
<div class="keypad">
<button class="dtmf" data-digit="1">1</button>
<button class="dtmf" data-digit="2">2</button>
<button class="dtmf" data-digit="3">3</button>
<button class="dtmf" data-digit="4">4</button>
<button class="dtmf" data-digit="5">5</button>
<button class="dtmf" data-digit="6">6</button>
<button class="dtmf" data-digit="7">7</button>
<button class="dtmf" data-digit="8">8</button>
<button class="dtmf" data-digit="9">9</button>
<button class="dtmf" data-digit="*">*</button>
<button class="dtmf" data-digit="0">0</button>
<button class="dtmf" data-digit="#">#</button>
</div>
</div>
</div>
</body>
<script>
let userAgent;
let registerer;
let session;
let localStream;
let uaReady = false;
let screenSharing = false;
// Toggle Call
window.toggleCall = async function toggleCall() {
if (session) {
session.dispose();
session = null;
resetUI();
return;
}
const callerName = document.getElementById('callerName').value || 'WebRTC Client';
const callerPhone = document.getElementById('callerPhone').value || 'anonymous';
const combinedDisplayName = `${callerName} (${callerPhone})`;
if (!userAgent || userAgent.isTerminated()) {
userAgent = new SIP.UserAgent({
uri: SIP.UserAgent.makeURI(`sip:webcall@172.16.48.3`),
transportOptions: { server: 'wss://172.16.48.3:8089/ws', traceSip: true },
displayName: combinedDisplayName,
authorizationUsername: 'webcall',
authorizationPassword: 'your password as you defined in pjsip'
});
userAgent.delegate = {
onTransportError: () => {
console.warn('WebSocket closed unexpectedly');
alert('WebSocket connection closed. Please refresh the page.');
},
onInvite: (invitation) => {
handleSession(invitation);
invitation.accept();
}
};
try {
await userAgent.start();
registerer = new SIP.Registerer(userAgent);
await registerer.register();
console.log('SIP UA registered successfully');
uaReady = true;
} catch (err) {
console.error('Failed to connect/register', err);
alert('Failed to connect/register to SIP server');
return;
}
}
const target = SIP.UserAgent.makeURI('sip:0000@cucm ip');
if (!target) {
alert('Bad SIP URI for target');
return;
}
const inviter = new SIP.Inviter(userAgent, target, {
sessionDescriptionHandlerOptions: {
constraints: { audio: true, video: true }
}
});
try {
await inviter.invite();
handleSession(inviter);
// UI update
document.getElementById('welcomeMessage').style.display = 'none';
document.querySelector('.main-video-wrapper').style.display = 'block';
document.querySelector('.local-video-wrapper').style.display = 'block';
document.getElementById('callButton').style.display = 'none';
document.getElementById('hangupButton').style.display = 'inline-block';
document.getElementById('muteButton').style.display = 'inline-block';
document.getElementById('videoButton').style.display = 'inline-block';
document.getElementById('screenShareButton').style.display = 'inline-block';
document.getElementById('dtmfButton').style.display = 'inline-block';
} catch (e) {
console.error('Failed to make call', e);
alert('Failed to make call');
}
};
document.addEventListener('DOMContentLoaded', async () => {
await startLocalVideo();
document.getElementById('callButton').addEventListener('click', (e) => {
e.preventDefault();
const form = document.getElementById('callForm');
if (!form.checkValidity()) {
form.reportValidity();
return;
}
window.toggleCall();
});
document.getElementById('hangupButton').addEventListener('click', () => window.toggleCall());
document.getElementById('screenShareButton').addEventListener('click', toggleScreenShare);
document.getElementById('muteButton').addEventListener('click', toggleMute);
document.getElementById('videoButton').addEventListener('click', toggleVideo);
document.getElementById('dtmfButton').addEventListener('click', toggleDTMFKeypad);
document.querySelectorAll('.dtmf').forEach(button => {
button.addEventListener('click', function () {
sendDTMF(this.getAttribute('data-digit'));
});
});
window.addEventListener('beforeunload', () => {
if (userAgent) userAgent.stop().catch(()=>{});
});
});
async function startLocalVideo() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
localStream = stream;
const localVideo = document.getElementById('localVideo');
if (localVideo) localVideo.srcObject = stream;
} catch (error) {
console.error("Error accessing media devices:", error);
alert("Unable to access your camera and microphone.");
}
}
function handleSession(newSession) {
session = newSession;
session.stateChange.addListener((st) => {
if (st === SIP.SessionState.Terminated) {
session = null;
resetUI();
}
});
session.sessionDescriptionHandler.peerConnection.ontrack = (ev) => {
const remoteVideo = document.getElementById('remoteVideo');
if (remoteVideo.srcObject !== ev.streams[0]) {
remoteVideo.srcObject = ev.streams[0];
}
};
localStream.getTracks().forEach((t) =>
session.sessionDescriptionHandler.peerConnection.addTrack(t, localStream)
);
}
function resetUI() {
// Reset UI to welcome state
document.getElementById('welcomeMessage').style.display = 'flex';
document.querySelector('.main-video-wrapper').style.display = 'none';
document.querySelector('.local-video-wrapper').style.display = 'none';
document.getElementById('callButton').style.display = 'inline-block';
document.getElementById('hangupButton').style.display = 'none';
document.getElementById('muteButton').style.display = 'none';
document.getElementById('videoButton').style.display = 'none';
document.getElementById('screenShareButton').style.display = 'none';
document.getElementById('dtmfButton').style.display = 'none';
document.getElementById('dtmfKeypad').style.display = 'none';
document.getElementById('remoteVideo').srcObject = null;
document.getElementById('callButton').textContent = 'Call';
// Re-enable form inputs
document.getElementById('callerName').disabled = false;
document.getElementById('callerPhone').disabled = false;
}
function toggleMute() {
if (!session) return;
const sender = session.sessionDescriptionHandler.peerConnection.getSenders().find(s => s.track && s.track.kind === 'audio');
if (!sender) return;
sender.track.enabled = !sender.track.enabled;
const icon = document.querySelector('#muteButton i');
if (sender.track.enabled) {
icon.classList.remove('fa-microphone-slash');
icon.classList.add('fa-microphone');
} else {
icon.classList.remove('fa-microphone');
icon.classList.add('fa-microphone-slash');
}
}
function toggleVideo() {
if (!session) return;
const sender = session.sessionDescriptionHandler.peerConnection.getSenders().find(s => s.track && s.track.kind === 'video');
if (!sender) return;
sender.track.enabled = !sender.track.enabled;
const icon = document.querySelector('#videoButton i');
const localEl = document.getElementById('localVideo');
if (sender.track.enabled) {
icon.classList.remove('fa-video-slash');
icon.classList.add('fa-video');
localEl.style.display = 'block';
} else {
icon.classList.remove('fa-video');
icon.classList.add('fa-video-slash');
localEl.style.display = 'none';
}
}
async function toggleScreenShare() {
if (!session) return;
if (screenSharing) {
const camTrack = localStream.getVideoTracks()[0];
const sender = session.sessionDescriptionHandler.peerConnection.getSenders().find(s => s.track && s.track.kind === 'video');
if (sender && camTrack) await sender.replaceTrack(camTrack);
screenSharing = false;
document.querySelector('#screenShareButton i').classList.remove('fa-compress');
document.querySelector('#screenShareButton i').classList.add('fa-desktop');
return;
}
try {
const screenStream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: true });
const screenTrack = screenStream.getVideoTracks()[0];
const sender = session.sessionDescriptionHandler.peerConnection.getSenders().find(s => s.track && s.track.kind === 'video');
if (sender && screenTrack) await sender.replaceTrack(screenTrack);
screenSharing = true;
document.querySelector('#screenShareButton i').classList.remove('fa-desktop');
document.querySelector('#screenShareButton i').classList.add('fa-compress');
} catch (e) {
console.error('Screen share failed', e);
alert('Failed to start screen sharing.');
}
}
function toggleDTMFKeypad() {
const keypad = document.getElementById('dtmfKeypad');
if (keypad.style.display === 'none' || !keypad.style.display) {
keypad.style.display = 'block';
} else {
keypad.style.display = 'none';
}
}
function sendDTMF(digit) {
if (session) {
// Find the RTCRtpSender for the audio track
const audioSender = session.sessionDescriptionHandler.peerConnection
.getSenders()
.find(sender => sender.track && sender.track.kind === 'audio');
// Check if the sender exists and has a DTMF sender object
if (audioSender && audioSender.dtmf) {
try {
// The .dtmf property IS the RTCDTMFSender object
audioSender.dtmf.insertDTMF(digit, 100, 50);
console.log(`Sent DTMF tone: ${digit}`);
} catch (error) {
console.error('Error sending DTMF tone:', error);
}
} else {
console.warn("RTCDTMFSender is not available for the audio track.");
}
} else {
console.warn("No active session to send DTMF.");
}
}
</script>
</html>
step 18:
https://asteriskip:8089/ws to accept the seilf signed ca (you wont do this if you have public ca)
open your html and try to make call
01-18-2025 12:35 PM
Hello, were you able to integrate Webrtc with CUBE? If yes, can you share the steps.
01-19-2025 05:08 AM
What is the use case ?
01-19-2025 09:44 AM
I want to deploy Webrtc so that users can make use of a browser and make calls to uccx agents. I have been trying to set it up using opensips as the Webrtc gateway but media is not traversing.
As a result,I started to think of CUBE to see if it can handle Webrtc calls and forward them to cucm. So the user is to make webrtc calls to cucm via CUBE
09-03-2025 05:09 AM
Finally ,I was able to integrate webrtc with CUCM via Asterisk SIP Gateway.
09-03-2025 05:15 PM
wow! congrats. would you be so kind sharing how it is configured in CUBE and docs references on how it is setup.
09-03-2025 11:02 PM - edited 09-04-2025 01:52 AM
I don't know if CUBE handles wss but I am sure CUBE handles sip calls.
Asterisk and some other sip servers handle wss calls
So you can install Asterisk on Ubuntu and configure sip trunk to cube using
dial plan.
Once Asterisk receives webrtc calls destined for the end point at cube, it
will route those calls to that end point .let me know if you want to
install Asterisk as a sip gateway so I can share with steps I used .
09-03-2025 10:03 PM - edited 09-03-2025 11:49 PM
Would you mind sharing more details of your setup and the configurations.
09-03-2025 10:10 PM
From what I know Cube does not have support for WebRTC. All references found has a WebRTC to SIP gateway in the mix and that is integrated with the SBC with a SIP trunk to then connect with Communication Manager.
09-04-2025 01:55 AM
yes, and i have made use of Asterisk as the sip gateway. There are other sip gateways such as opensips and kamailio.
09-04-2025 12:02 AM
09-04-2025 01:51 AM - edited 09-04-2025 01:59 AM
Installation of Asterisk
Step 1: Install the dependencies
sudo apt-get update
sudo apt-get install -y build-essential libxml2-dev libncurses5-dev libssl-dev libjansson-dev libsqlite3-dev uuid-dev
Step 2: Download the source code
Navigate to /usr/src and download the latest version of Asterisk 22:
sudo wget http://downloads.asterisk.org/pub/telephony/asterisk/asterisk-22-current.tar.gz
cd /usr/src
tar -xzvf asterisk-22-current.tar.gz
Step 3: Compile Asterisk
sudo tar -xvzf asterisk-21-current.tar.gz
cd asterisk-22*/
sudo contrib/scripts/install_prereq install
sudo ./configure
sudo make menuselect
sudo make && sudo make install
sudo make samples
sudo make config
Step 4:
sudo systemctl start asterisk
systemctl status asterisk
sudo asterisk -rvvv
Step 5: Test if Asterisk is running
Start Asterisk in the foreground to verify that it’s working:
sudo asterisk -vvvvvvvvgc
Step 6: Stop Asterisk
From the Asterisk CLI, stop the process:
cli core stop now
Step 7: Start Asterisk in the background
Run Asterisk as a background process:
sudo asterisk
Step 8: Connect to the Asterisk Console
To connect to the running Asterisk console:
sudo asterisk -r
You can press ctrl z to exit
Step 9 ***Creating certificates**
sudo openssl req -nodes -newkey rsa:2048 -keyout /etc/asterisk/keys//asterisk.key -out /etc/asterisk/keys/asterisk.csr
enter your common name and other details when prompted
Step 10:
grant permission
sudo chown asterisk:asterisk /etc/asterisk/keys/asterisk.crt
sudo chown asterisk:asterisk /etc/asterisk/keys/asterisk.pem
sudo chmod 644 /etc/asterisk/keys/asterisk.crt
sudo chmod 600 /etc/asterisk/keys/asterisk.pem
Step 11:optional (ref:
https://vitalpbx.com/blog/asterisk-webrtc-from-scratch/?srsltid=AfmBOoqsJzR4lmLpmihFfwLDBXxPqwiAcTjdMNz11rcrckxodCfPl4Em)
Creat a user asterisk and add to the group
groupadd asterisk
useradd -r -d /var/lib/asterisk -g asterisk asterisk
usermod -aG audio,dialout asterisk
chown asterisk. -R /etc/asterisk
chown asterisk. -R /var/{lib,log,spool}/asterisk
chown -R asterisk.asterisk /usr/lib64/asterisk
Step 12: modify the http.conf
sudo nano /etc/asterisk/http.conf
add this
[general]
enabled=yes
bindaddr=0.0.0.0
bindport=8088
tlsenable=yes
tlsbindaddr=0.0.0.0:8089
tlscertfile=/etc/asterisk/keys/asterisk.cer (replace with your cert)
tlsprivatekey=/etc/asterisk/keys/asterisk.key (replace with your cert)
Step 13: modify the pjsip.conf for webrtc and cucm-trunk
you can replace the cucm-trunk with the cube ip
**defining websocket secure transport**
[transport-wss]
type=transport
protocol=wss
bind=0.0.0.0:8089
cert_file=/etc/asterisk/keys/asterisk.cer
priv_key_file=/etc/asterisk/keys/asterisk.key
** webrtc section**
[webcall]
type=endpoint
context=default
disallow=all
direct_media=no
allow=ulaw,alaw,gsm,h264,VP8
webrtc=yes
aors=webcall
auth=webcall_auth
; Make Asterisk trust CallerID from the client
trust_id_inbound=yes
trust_id_outbound=yes
send_pai=yes
send_rpid=yes
dtls_auto_generate_cert=yes
rtp_engine=asterisk
use_avpf=yes
ice_support=yes
force_rport=yes
rewrite_contact=yes
[webcall_auth]
type=auth
auth_type=userpass
password=your password
username=webcall
[webcall]
type=aor
max_contacts=100
[CUCM-Trunk]
type=endpoint
transport=transport-udp ; or transport-tcp if CUCM requires TCP
context=from-external
disallow=all
allow=ulaw,alaw,gsm,h264,VP8
aors=CUCM-Trunk
outbound_auth=CUCM-Auth
direct_media=no
[CUCM-Trunk]
type=aor
contact=sip:IP:5060 ; CUCM or CUBE IP and SIP Port
[CUCM-Auth]
type=auth
auth_type=userpass
username=
password= ; Must match CUCM OR CUBE SIP trunk configuration
Step 14: modify extensions.conf
[default]
exten => 7100,1,NoOp(Forwarding Call to CUCM IVR)
same => n,Dial(PJSIP/7100@CUCM-Trunk)
same => n,Hangup()
step 15:
Connect to the Atserik console
sudo asterisk -r
pjsip reload
dialplan reload
module reload http
step 16 :verify if Asterik is running on 8089
sudo ss -tulnp | grep -E '8088|8089|5060'
step 17. setting up webrtc client using javascript
modify with your real parameters
<!DOCTYPE html>
<html>
<head>
<title>Collnetwork Care</title>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<script src="{{ url_for('static', filename='sip.min.js') }}"></script>
<style>
body {
background-color: #f0f2f5;
font-family: sans-serif;
display: flex;
flex-direction: column;
align-items: center;
margin: 0;
padding: 20px;
}
.keypad {
display: grid;
grid-template-columns: repeat(3, 80px);
gap: 15px;
justify-content: center;
margin-top: 20px;
}
.keypad button {
width: 80px;
height: 80px;
font-size: 24px;
font-weight: bold;
border-radius: 50%;
border: none;
background-color: #007bff;
color: white;
cursor: pointer;
transition: background-color 0.3s;
}
.keypad button:hover {
background-color: #0056b3;
}
/* Overlay keypad on video */
#dtmfKeypad {
position: absolute;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
background: rgba(255, 255, 255, 0.9);
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
z-index: 20;
}
.header {
margin-bottom: 20px;
}
.logo {
height: 50px;
}
.call-container {
position: relative;
width: 100%;
max-width: 900px;
background-color: #fff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 8px;
padding: 20px;
}
.main-video-wrapper {
width: 100%;
height: 500px;
background-color: #000;
border-radius: 8px;
overflow: hidden;
}
#remoteVideo {
width: 100%;
height: 100%;
object-fit: contain;
}
.local-video-wrapper {
position: absolute;
top: 30px;
right: 30px;
width: 200px;
height: 150px;
border: 2px solid #fff;
border-radius: 8px;
overflow: hidden;
z-index: 10;
}
#localVideo {
width: 100%;
height: 100%;
object-fit: cover;
}
.controls-container {
display: flex;
justify-content: center;
margin-top: 20px;
gap: 15px;
}
.controls-container button {
background-color: #555;
color: #fff;
border: none;
border-radius: 50%;
width: 50px;
height: 50px;
cursor: pointer;
font-size: 20px;
transition: background-color 0.3s;
}
.controls-container button:hover {
background-color: #777;
}
.call-btn {
background-color: #28a745 !important;
}
.call-btn:hover {
background-color: #218838 !important;
}
.hangup-btn {
background-color: #dc3545 !important;
}
.hangup-btn:hover {
background-color: #c82333 !important;
}
.welcome-message {
height: 500px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
background-color: #f8f9fa;
color: #495057;
border-radius: 8px;
padding: 20px;
}
</style>
</head>
<body>
<div class="header">
<img src="{{ url_for('static', filename='logo.png') }}" alt="Collnetwork Logo" class="logo">
</div>
<div class="call-container">
<div id="welcomeMessage" class="welcome-message">
<h2>Welcome to Collnetwork Care!</h2>
<p>Please enter your details to start a video call with our representative.</p>
<form id="callForm" style="margin-top: 20px;">
<div style="margin-bottom: 10px;">
<label for="callerName">Your Name:</label>
<input type="text" id="callerName" name="callerName" placeholder="e.g., Jane Doe" required>
</div>
<div style="margin-bottom: 20px;">
<label for="callerPhone">Phone Number:</label>
<input type="tel" id="callerPhone" name="callerPhone" placeholder="e.g., +15551234567" required>
</div>
</form>
<button id="callButton" class="call-btn">
<i class="fas fa-phone"></i> Start Call
</button>
</div>
<div class="main-video-wrapper" style="display:none;">
<video id="remoteVideo" autoplay playsinline></video>
</div>
<div class="local-video-wrapper" style="display:none;">
<video id="localVideo" autoplay muted playsinline></video>
</div>
<div class="controls-container">
<button id="hangupButton" class="hangup-btn" style="display:none;">
<i class="fas fa-phone-slash"></i>
</button>
<button id="muteButton" title="Mute/Unmute" style="display:none;">
<i class="fas fa-microphone"></i>
</button>
<button id="videoButton" title="Video On/Off" style="display:none;">
<i class="fas fa-video"></i>
</button>
<button id="screenShareButton" title="Share Screen" style="display:none;">
<i class="fas fa-desktop"></i>
</button>
<button id="dtmfButton" title="Keypad" style="display:none;">
<i class="fas fa-th"></i>
</button>
</div>
<div id="dtmfKeypad" style="display:none;">
<div class="keypad">
<button class="dtmf" data-digit="1">1</button>
<button class="dtmf" data-digit="2">2</button>
<button class="dtmf" data-digit="3">3</button>
<button class="dtmf" data-digit="4">4</button>
<button class="dtmf" data-digit="5">5</button>
<button class="dtmf" data-digit="6">6</button>
<button class="dtmf" data-digit="7">7</button>
<button class="dtmf" data-digit="8">8</button>
<button class="dtmf" data-digit="9">9</button>
<button class="dtmf" data-digit="*">*</button>
<button class="dtmf" data-digit="0">0</button>
<button class="dtmf" data-digit="#">#</button>
</div>
</div>
</div>
</body>
<script>
let userAgent;
let registerer;
let session;
let localStream;
let uaReady = false;
let screenSharing = false;
// Toggle Call
window.toggleCall = async function toggleCall() {
if (session) {
session.dispose();
session = null;
resetUI();
return;
}
const callerName = document.getElementById('callerName').value || 'WebRTC Client';
const callerPhone = document.getElementById('callerPhone').value || 'anonymous';
const combinedDisplayName = `${callerName} (${callerPhone})`;
if (!userAgent || userAgent.isTerminated()) {
userAgent = new SIP.UserAgent({
uri: SIP.UserAgent.makeURI(`sip:webcall@172.16.48.3`),
transportOptions: { server: 'wss://172.16.48.3:8089/ws', traceSip: true },
displayName: combinedDisplayName,
authorizationUsername: 'webcall',
authorizationPassword: 'your password as you defined in pjsip'
});
userAgent.delegate = {
onTransportError: () => {
console.warn('WebSocket closed unexpectedly');
alert('WebSocket connection closed. Please refresh the page.');
},
onInvite: (invitation) => {
handleSession(invitation);
invitation.accept();
}
};
try {
await userAgent.start();
registerer = new SIP.Registerer(userAgent);
await registerer.register();
console.log('SIP UA registered successfully');
uaReady = true;
} catch (err) {
console.error('Failed to connect/register', err);
alert('Failed to connect/register to SIP server');
return;
}
}
const target = SIP.UserAgent.makeURI('sip:0000@cucm ip');
if (!target) {
alert('Bad SIP URI for target');
return;
}
const inviter = new SIP.Inviter(userAgent, target, {
sessionDescriptionHandlerOptions: {
constraints: { audio: true, video: true }
}
});
try {
await inviter.invite();
handleSession(inviter);
// UI update
document.getElementById('welcomeMessage').style.display = 'none';
document.querySelector('.main-video-wrapper').style.display = 'block';
document.querySelector('.local-video-wrapper').style.display = 'block';
document.getElementById('callButton').style.display = 'none';
document.getElementById('hangupButton').style.display = 'inline-block';
document.getElementById('muteButton').style.display = 'inline-block';
document.getElementById('videoButton').style.display = 'inline-block';
document.getElementById('screenShareButton').style.display = 'inline-block';
document.getElementById('dtmfButton').style.display = 'inline-block';
} catch (e) {
console.error('Failed to make call', e);
alert('Failed to make call');
}
};
document.addEventListener('DOMContentLoaded', async () => {
await startLocalVideo();
document.getElementById('callButton').addEventListener('click', (e) => {
e.preventDefault();
const form = document.getElementById('callForm');
if (!form.checkValidity()) {
form.reportValidity();
return;
}
window.toggleCall();
});
document.getElementById('hangupButton').addEventListener('click', () => window.toggleCall());
document.getElementById('screenShareButton').addEventListener('click', toggleScreenShare);
document.getElementById('muteButton').addEventListener('click', toggleMute);
document.getElementById('videoButton').addEventListener('click', toggleVideo);
document.getElementById('dtmfButton').addEventListener('click', toggleDTMFKeypad);
document.querySelectorAll('.dtmf').forEach(button => {
button.addEventListener('click', function () {
sendDTMF(this.getAttribute('data-digit'));
});
});
window.addEventListener('beforeunload', () => {
if (userAgent) userAgent.stop().catch(()=>{});
});
});
async function startLocalVideo() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
localStream = stream;
const localVideo = document.getElementById('localVideo');
if (localVideo) localVideo.srcObject = stream;
} catch (error) {
console.error("Error accessing media devices:", error);
alert("Unable to access your camera and microphone.");
}
}
function handleSession(newSession) {
session = newSession;
session.stateChange.addListener((st) => {
if (st === SIP.SessionState.Terminated) {
session = null;
resetUI();
}
});
session.sessionDescriptionHandler.peerConnection.ontrack = (ev) => {
const remoteVideo = document.getElementById('remoteVideo');
if (remoteVideo.srcObject !== ev.streams[0]) {
remoteVideo.srcObject = ev.streams[0];
}
};
localStream.getTracks().forEach((t) =>
session.sessionDescriptionHandler.peerConnection.addTrack(t, localStream)
);
}
function resetUI() {
// Reset UI to welcome state
document.getElementById('welcomeMessage').style.display = 'flex';
document.querySelector('.main-video-wrapper').style.display = 'none';
document.querySelector('.local-video-wrapper').style.display = 'none';
document.getElementById('callButton').style.display = 'inline-block';
document.getElementById('hangupButton').style.display = 'none';
document.getElementById('muteButton').style.display = 'none';
document.getElementById('videoButton').style.display = 'none';
document.getElementById('screenShareButton').style.display = 'none';
document.getElementById('dtmfButton').style.display = 'none';
document.getElementById('dtmfKeypad').style.display = 'none';
document.getElementById('remoteVideo').srcObject = null;
document.getElementById('callButton').textContent = 'Call';
// Re-enable form inputs
document.getElementById('callerName').disabled = false;
document.getElementById('callerPhone').disabled = false;
}
function toggleMute() {
if (!session) return;
const sender = session.sessionDescriptionHandler.peerConnection.getSenders().find(s => s.track && s.track.kind === 'audio');
if (!sender) return;
sender.track.enabled = !sender.track.enabled;
const icon = document.querySelector('#muteButton i');
if (sender.track.enabled) {
icon.classList.remove('fa-microphone-slash');
icon.classList.add('fa-microphone');
} else {
icon.classList.remove('fa-microphone');
icon.classList.add('fa-microphone-slash');
}
}
function toggleVideo() {
if (!session) return;
const sender = session.sessionDescriptionHandler.peerConnection.getSenders().find(s => s.track && s.track.kind === 'video');
if (!sender) return;
sender.track.enabled = !sender.track.enabled;
const icon = document.querySelector('#videoButton i');
const localEl = document.getElementById('localVideo');
if (sender.track.enabled) {
icon.classList.remove('fa-video-slash');
icon.classList.add('fa-video');
localEl.style.display = 'block';
} else {
icon.classList.remove('fa-video');
icon.classList.add('fa-video-slash');
localEl.style.display = 'none';
}
}
async function toggleScreenShare() {
if (!session) return;
if (screenSharing) {
const camTrack = localStream.getVideoTracks()[0];
const sender = session.sessionDescriptionHandler.peerConnection.getSenders().find(s => s.track && s.track.kind === 'video');
if (sender && camTrack) await sender.replaceTrack(camTrack);
screenSharing = false;
document.querySelector('#screenShareButton i').classList.remove('fa-compress');
document.querySelector('#screenShareButton i').classList.add('fa-desktop');
return;
}
try {
const screenStream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: true });
const screenTrack = screenStream.getVideoTracks()[0];
const sender = session.sessionDescriptionHandler.peerConnection.getSenders().find(s => s.track && s.track.kind === 'video');
if (sender && screenTrack) await sender.replaceTrack(screenTrack);
screenSharing = true;
document.querySelector('#screenShareButton i').classList.remove('fa-desktop');
document.querySelector('#screenShareButton i').classList.add('fa-compress');
} catch (e) {
console.error('Screen share failed', e);
alert('Failed to start screen sharing.');
}
}
function toggleDTMFKeypad() {
const keypad = document.getElementById('dtmfKeypad');
if (keypad.style.display === 'none' || !keypad.style.display) {
keypad.style.display = 'block';
} else {
keypad.style.display = 'none';
}
}
function sendDTMF(digit) {
if (session) {
// Find the RTCRtpSender for the audio track
const audioSender = session.sessionDescriptionHandler.peerConnection
.getSenders()
.find(sender => sender.track && sender.track.kind === 'audio');
// Check if the sender exists and has a DTMF sender object
if (audioSender && audioSender.dtmf) {
try {
// The .dtmf property IS the RTCDTMFSender object
audioSender.dtmf.insertDTMF(digit, 100, 50);
console.log(`Sent DTMF tone: ${digit}`);
} catch (error) {
console.error('Error sending DTMF tone:', error);
}
} else {
console.warn("RTCDTMFSender is not available for the audio track.");
}
} else {
console.warn("No active session to send DTMF.");
}
}
</script>
</html>
step 18:
https://asteriskip:8089/ws to accept the seilf signed ca (you wont do this if you have public ca)
open your html and try to make call
Discover and save your favorite ideas. Come back to expert answers, step-by-step guides, recent topics, and more.
New here? Get started with these tips. How to use Community New member guide