1️⃣ Prerequisites
- Asterisk (16+) installed with PJSIP.
- Modern web browser (Chrome, Firefox) with microphone access.
- Local network setup (no STUN/ICE needed for LAN testing).
- Microphone and speakers connected.
2️⃣ Asterisk Configuration
2.1 Enable HTTP & WebSocket
/etc/asterisk/http.conf:
[general]
enabled=yes
bindaddr=0.0.0.0
bindport=8088
Reload:
asterisk -rx "module reload res_http_websocket.so"
2.2 Configure PJSIP Transport
/etc/asterisk/pjsip.conf:
[transport-ws]
type=transport
protocol=ws
bind=0.0.0.0
local_net=192.168.0.0/24 ; your LAN subnet
2.3 Create a WebRTC Endpoint
[webrtc-client]
type=aor
max_contacts=1
[webrtc-client]
type=auth
auth_type=userpass
username=webrtc-client
password=webrtcpass
[webrtc-client]
type=endpoint
aors=webrtc-client
auth=webrtc-client
context=from-internal
disallow=all
allow=opus,ulaw,alaw
direct_media=no
media_encryption=no
ice_support=no
2.4 Configure Dialplan
/etc/asterisk/extensions.conf:
[from-internal]
exten => 1000,1,Answer()
same => n,Playback(hello-world)
same => n,Hangup()
Reload:
asterisk -rx "pjsip reload"
asterisk -rx "dialplan reload"
3️⃣ Web Client Code (HTML + JsSIP.js)
Save as index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JsSIP WebRTC SIP Agent</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
button { margin: 5px; padding: 10px; }
#ua-status { font-weight: bold; }
</style>
</head>
<body>
<h2>WebRTC SIP Agent</h2>
<div>Status: <span id="ua-status">Disconnected</span></div>
<button id="callBtn">Call 1000</button>
<button id="hangupBtn" disabled>Hang Up</button>
<h3>Remote Audio</h3>
<audio id="remoteAudio" autoplay></audio>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jssip/3.11.0/jssip.min.js"></script>
<script>
/* --- 1. Configure JsSIP User Agent --- */
const socket = new JsSIP.WebSocketInterface('ws://192.168.0.10:8088/ws');
const ua = new JsSIP.UA({
sockets: [socket],
uri: 'sip:webrtc-client@192.168.0.10',
password: 'webrtcpass',
session_timers: false
});
/* --- 2. UA Event Listeners --- */
ua.on('connected', () => document.getElementById('ua-status').textContent = 'Connected');
ua.on('disconnected', () => document.getElementById('ua-status').textContent = 'Disconnected');
ua.on('registered', () => console.log('Registered with SIP server'));
ua.on('registrationFailed', (e) => console.log('Registration failed:', e.cause));
ua.start();
/* --- 3. Handle Outgoing Call --- */
let session = null;
document.getElementById('callBtn').onclick = async () => {
try { await navigator.mediaDevices.getUserMedia({ audio: true }); }
catch { alert('Microphone access denied!'); return; }
session = ua.call('sip:1000@192.168.0.10', {
mediaConstraints: { audio: true, video: false },
rtcOfferConstraints: { offerToReceiveAudio: 1 }
});
document.getElementById('hangupBtn').disabled = false;
session.connection.addEventListener('track', (e) => {
document.getElementById('remoteAudio').srcObject = e.streams[0];
});
session.on('ended', () => { session = null; document.getElementById('hangupBtn').disabled = true; });
session.on('failed', () => { session = null; document.getElementById('hangupBtn').disabled = true; });
};
/* --- 4. Handle Hang Up --- */
document.getElementById('hangupBtn').onclick = () => {
if(session) session.terminate();
session = null;
document.getElementById('hangupBtn').disabled = true;
};
/* --- 5. Handle Incoming Calls --- */
ua.on('newRTCSession', (e) => {
const s = e.session;
if(e.originator === 'remote') {
session = s;
s.answer({ mediaConstraints: { audio: true, video: false } });
s.connection.addEventListener('track', (ev) => {
document.getElementById('remoteAudio').srcObject = ev.streams[0];
});
s.on('ended', () => session = null);
s.on('failed', () => session = null);
}
});
</script>
</body>
</html>
4️⃣ Testing
- Open
index.htmlin Chrome/Firefox. - Allow microphone access.
- Click Call 1000 → you should hear the Asterisk playback.
- Incoming calls to the WebRTC client will auto-answer and play audio.
5️⃣ Next Steps / Enhancements
- Add video calls: set
video: trueinmediaConstraints. - Show call status: ringing, connected, ended.
- Handle multiple simultaneous calls with multiple
RTCSessionobjects. - Implement mute/unmute, hold, and transfer features.
- Learn SIP signaling basics:
INVITE,ACK,BYE,REGISTER.
6️⃣ Troubleshooting Tips
| Issue | Fix |
|---|---|
| One-way audio | Ensure direct_media=no and media_encryption=no in Asterisk. |
| Call fails | Verify WebSocket connection: telnet 192.168.0.10 8088. |
| Browser blocks microphone | Allow access in browser settings. |
| Audio not playing | Check remoteAudio.srcObject is correctly set. |
✅ This is a fully functional example of a JsSIP WebRTC SIP client for Asterisk
1️⃣ HTML + CSS + JsSIP.js
Save this as index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JsSIP WebRTC Full Client</title>
<style>
body { font-family: Arial; padding: 20px; }
button { margin: 5px; padding: 10px; }
#ua-status { font-weight: bold; }
#calls { margin-top: 20px; }
.call { border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; }
video, audio { width: 300px; height: 200px; background: #000; }
</style>
</head>
<body>
<h2>JsSIP WebRTC SIP Client</h2>
<div>Status: <span id="ua-status">Disconnected</span></div>
<input type="text" id="dest" placeholder="Enter SIP URI e.g. 1000">
<button id="callBtn">Call</button>
<button id="hangupAllBtn">Hang Up All</button>
<button id="muteBtn">Mute</button>
<h3>Active Calls</h3>
<div id="calls"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jssip/3.11.0/jssip.min.js"></script>
<script>
/* --- 1. Configure JsSIP UA --- */
const socket = new JsSIP.WebSocketInterface('ws://192.168.0.10:8088/ws');
const ua = new JsSIP.UA({
sockets: [socket],
uri: 'sip:webrtc-client@192.168.0.10',
password: 'webrtcpass',
session_timers: false
});
ua.on('connected', () => document.getElementById('ua-status').textContent = 'Connected');
ua.on('disconnected', () => document.getElementById('ua-status').textContent = 'Disconnected');
ua.on('registered', () => console.log('Registered'));
ua.on('registrationFailed', e => console.log('Registration failed', e.cause));
ua.start();
/* --- 2. Call Management --- */
let sessions = [];
let localStream = null;
let isMuted = false;
/* Get microphone & camera */
async function initMedia() {
try {
localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
} catch (e) {
alert('Cannot access microphone/camera: ' + e);
}
}
/* --- 3. Outgoing Call --- */
document.getElementById('callBtn').onclick = async () => {
const target = document.getElementById('dest').value;
if(!target) { alert('Enter SIP URI'); return; }
await initMedia();
const session = ua.call(target, {
mediaConstraints: { audio: true, video: true },
pcConfig: { iceServers: [] }, // no STUN/ICE for LAN
rtcOfferConstraints: { offerToReceiveAudio: 1, offerToReceiveVideo: 1 }
});
addSession(session);
};
/* --- 4. Manage Sessions --- */
function addSession(session) {
sessions.push(session);
const callDiv = document.createElement('div');
callDiv.className = 'call';
callDiv.innerHTML = `<strong>Call with ${session.remote_identity.uri.user}</strong>
<div>Status: <span class="status">Connecting</span></div>
<video autoplay playsinline></video>
<audio autoplay></audio>`;
document.getElementById('calls').appendChild(callDiv);
const statusSpan = callDiv.querySelector('.status');
const videoEl = callDiv.querySelector('video');
const audioEl = callDiv.querySelector('audio');
session.on('progress', () => statusSpan.textContent = 'Ringing');
session.on('accepted', () => statusSpan.textContent = 'Connected');
session.on('ended', () => { statusSpan.textContent = 'Ended'; removeSession(session, callDiv); });
session.on('failed', () => { statusSpan.textContent = 'Failed'; removeSession(session, callDiv); });
session.connection.addEventListener('track', e => {
if(e.track.kind === 'video') videoEl.srcObject = e.streams[0];
if(e.track.kind === 'audio') audioEl.srcObject = e.streams[0];
});
// Attach local tracks
localStream.getTracks().forEach(track => session.connection.addTrack(track, localStream));
}
/* --- 5. Hang Up All Calls --- */
document.getElementById('hangupAllBtn').onclick = () => {
sessions.forEach(s => s.terminate());
sessions = [];
document.getElementById('calls').innerHTML = '';
};
/* --- 6. Mute / Unmute --- */
document.getElementById('muteBtn').onclick = () => {
if(!localStream) return;
isMuted = !isMuted;
localStream.getAudioTracks().forEach(track => track.enabled = !isMuted);
document.getElementById('muteBtn').textContent = isMuted ? 'Unmute' : 'Mute';
};
/* --- 7. Handle Incoming Calls --- */
ua.on('newRTCSession', async e => {
const session = e.session;
if(e.originator === 'remote') {
await initMedia();
session.answer({ mediaConstraints: { audio: true, video: true } });
addSession(session);
}
});
/* --- 8. Optional: Handle Multiple Calls --- */
// sessions array already handles multiple active calls
</script>
</body>
</html>
Features Included
- Audio & Video calls (set
video: trueinmediaConstraints). - Multiple call handling using an array of sessions.
- Call status display – connecting, ringing, connected, ended, failed.
- Mute/Unmute button.
- Hang Up All Calls button.
- Incoming call auto-answer with video/audio.
- Local & remote media streams properly attached.
Testing
- Open in a browser (Chrome or Firefox).
- Allow microphone and camera.
- Enter a SIP URI (e.g.,
sip:1000@192.168.0.10) and click Call. - Multiple calls can be made simultaneously.
- Incoming calls will auto-answer with video/audio.
- Use Mute and Hang Up All buttons for control.
This is a complete WebRTC SIP client for LAN environments using JsSIP.js + Asterisk, and it’s ready for extension:
- Add call transfer
- Add hold/resume
- Add call recording
- Enhance UI/UX with call queues





