FreeSWITCH WebRTC is the combination of the open-source FreeSWITCH telephony platform with browser-based WebRTC media, letting Chrome, Firefox, and Safari place and receive calls directly on a carrier-grade softswitch. FreeSWITCH terminates WebRTC signaling over secure WebSocket and media over DTLS-SRTP, then bridges it to SIP trunks, PSTN gateways, or internal extensions. This guide walks through signaling choices, certificate and ICE setup, media handling, bridging, scaling, and troubleshooting silent audio so you can ship a working deployment in 2026.
Introduction
Every major browser now ships a mature WebRTC stack, which means your users already have a softphone installed. They just don't know it yet. FreeSWITCH, the open-source, carrier-grade telephony platform behind thousands of production PBXs and contact centers, has supported WebRTC natively for years, and in 2026 that support is more relevant than ever as teams replace desktop SIP clients with browser-based calling.
The pairing solves a real problem. WebRTC gives you instant, plugin-free calling from any web page with sub-300 millisecond latency, while FreeSWITCH gives you the telephony muscle: SIP routing, conferencing, recording, voicemail, and PSTN interconnection. Together they let a browser call a desk phone, a mobile number, or a conference bridge without installing anything.
In this article you'll learn how FreeSWITCH WebRTC works end to end: the two signaling paths (SIP-over-WebSocket and Verto), how to configure the SIP profile with secure WebSocket bindings and ICE, how media flows through DTLS-SRTP, how to build a WebRTC-to-SIP bridge, how to scale for conferences and multi-tenant deployments, and how to diagnose the silent-audio problems that bite nearly every first deployment.
What is FreeSWITCH WebRTC?
FreeSWITCH is defined as an open-source, carrier-grade telephony platform that handles voice, video, and messaging using standard protocols such as SIP, and it can act as a softswitch, PBX, or media server. WebRTC is defined as a browser-native real-time communication framework that provides audio and video calling, plus data channels, directly between browsers and compatible endpoints using secure, encrypted media transport.
The two complement each other cleanly. WebRTC has no native concept of SIP routing, voicemail, or PSTN trunks, and FreeSWITCH has no native browser client. FreeSWITCH WebRTC closes both gaps: the platform's Sofia SIP stack accepts WebRTC-style SIP sessions over WebSocket, terminates the encrypted media, and bridges it into the existing telephony world. Developers building on this stack often pair it with hosted RTC platforms; for example, VideoSDK's video calling SDKs offer a managed alternative when you want browser calling without operating your own media server.
Signaling Options: SIP-over-WebSocket vs. Verto
FreeSWITCH gives you two distinct signaling paths for WebRTC, and choosing between them is your first architectural decision. Both terminate on the same media engine, so the choice affects your client library and interoperability surface, not your call quality.
SIP-over-WebSocket (RFC 7118)
SIP-over-WebSocket, standardized in RFC 7118, transports ordinary SIP signaling over a WebSocket connection, typically secured with TLS (wss). Because it's a published IETF standard, any standards-compliant browser library can talk to it. JsSIP, SIP.js, and sipML5 all use this path. The advantage is interoperability: your browser client speaks the same SIP dialect as every other SIP device on your network, so features like presence, transfer, and BLF work consistently across desk phones and browsers.
Verto (mod_verto)
Verto is FreeSWITCH's native JSON-RPC signaling protocol, delivered through the mod_verto module. Instead of SIP text, Verto exchanges compact JSON messages over a secure WebSocket. Its official JavaScript client is purpose-built for FreeSWITCH, so setup is faster and the protocol maps directly onto FreeSWITCH concepts like live arrays for conferencing. The trade-off is lock-in: Verto clients only talk to FreeSWITCH, and you're dependent on a smaller ecosystem of client libraries.
Which Path Should You Choose?
Choose SIP-over-WebSocket when you need interoperability with existing SIP infrastructure, want a wide choice of client libraries, or plan to migrate clients between platforms later. Choose Verto when you're building a greenfield FreeSWITCH-only application, especially one centered on FreeSWITCH conferencing, and want the tightest integration with the least glue code. In practice, most production teams in 2026 pick SIP-over-WebSocket for its portability.
Both roads lead to the same place: the FreeSWITCH core, where media is mixed, recorded, and routed.
Setting Up the FreeSWITCH SIP Profile for WebRTC
The internal SIP profile, managed by mod_sofia, is where WebRTC support is enabled. Getting this right is where most deployments succeed or stall, because WebRTC demands three things plain SIP doesn't: secure WebSocket bindings, valid TLS certificates, and ICE for NAT traversal.
Enabling WebSocket Bindings
The SIP profile needs two binding parameters enabled: a plain WebSocket binding for local testing and a secure WebSocket binding for production. The secure binding listens on the wss port (commonly 7443) and requires a TLS certificate before it will accept connections. Browsers on HTTPS pages will only connect to wss endpoints, so the secure binding is mandatory for any real deployment.
TLS Certificate Requirements
FreeSWITCH expects its certificate and private key in its TLS directory, and the certificate must match the hostname clients connect to. A self-signed certificate works for local testing if you accept the browser warning, but production demands a certificate from a trusted authority, most commonly a free Let's Encrypt certificate renewed on a schedule. A mismatched or expired certificate is the single most common cause of "client won't connect" reports, and the browser console will usually tell you exactly which validation step failed.
Configuring ICE and STUN for NAT Traversal
WebRTC endpoints sit behind NAT, so the profile must have ICE enabled and a STUN server configured so FreeSWITCH can gather its own public-facing candidates. If your FreeSWITCH box has a public IP address directly attached, you can often run with a minimal STUN setup or even rely on host candidates. If it sits behind a firewall, you'll need to open the RTP port range, ensure the advertised IP in signaling matches the media IP, and consider a TURN server for clients behind symmetric NAT. The W3C WebRTC specification describes the ICE framework FreeSWITCH implements.
Common Pitfalls
Three pitfalls account for most failed setups. First, a missing or unreadable TLS certificate silently prevents the wss binding from starting, so check the FreeSWITCH console log at startup. Second, firewalls blocking the WebSocket port or the RTP media port range produce clients that register but can't complete calls. Third, advertising a private IP in signaling while media flows on a public one causes one-way audio, which we cover in troubleshooting below.
Browser Client Configuration
With the server side ready, the browser side is straightforward. JsSIP and SIP.js are the two most actively maintained SIP-over-WebSocket libraries in 2026, with sipML5 now mostly of historical interest. Both handle the heavy lifting: WebSocket connection management, SIP registration, SDP offer/answer negotiation, and ICE.
Your client needs four pieces of configuration. The WebSocket URL must point at your wss binding, using the same hostname as the certificate. The SIP extension credentials are the same username and password you'd configure on a desk phone. The ICE server list should include your STUN (and optionally TURN) servers so the browser can gather reachable candidates. Finally, the media constraints and codec preferences should request Opus audio, since it's the default and best-sounding codec on the WebRTC side.
For testing, browser developer tools are your best friend. The network tab confirms the WebSocket upgrade and shows the SIP message stream. The WebRTC internals page (available in Chrome at chrome://webrtc-internals) displays ICE candidate gathering, DTLS handshake state, and live bitrate graphs, which is invaluable when diagnosing media problems before touching FreeSWITCH logs.
Media Handling in FreeSWITCH
Signaling gets the call set up; media is where the actual work happens. FreeSWITCH WebRTC media handling has three layers you need to understand: encryption termination, codec negotiation, and media services.
DTLS-SRTP Termination
Browsers only send encrypted media using DTLS-SRTP, so FreeSWITCH terminates the DTLS handshake and decrypts incoming SRTP on the WebRTC leg. On the SIP or PSTN side, FreeSWITCH can re-encrypt with standard SRTP or send plain RTP depending on what the far end negotiated. This makes FreeSWITCH a full media termination point rather than a pass-through relay, which is what enables recording, mixing, and transcoding.
Codec Negotiation: Opus vs G.711
Browsers speak Opus, and Opus is what you want: it delivers wideband audio at dramatically lower bitrates than the G.711 codecs traditional telephony uses. The negotiation challenge is that PSTN trunks and older SIP endpoints often only support G.711 or G.722. When a browser call bridges to a G.711-only trunk, FreeSWITCH transcodes between Opus and G.711 in software.
Transcoding works but costs CPU. Each transcoded leg consumes real processing capacity, and the cost scales linearly with concurrent calls. For capacity planning, assume a transcoded call is several times more expensive than a pass-through call on the same hardware. Where possible, prefer G.722 on SIP trunks so both sides run wideband, or use a provider that accepts Opus natively.
Recording and Media Injection
Because FreeSWITCH terminates media, it can record any leg, mix conference audio, and inject audio (prompts, hold music, announcements) into the stream. Recording a WebRTC call is identical to recording any SIP call, and the same session can feed a transcription pipeline. Teams building AI features on top of this often move to managed platforms; VideoSDK's AI voice agent documentation describes how real-time transcription and LLM pipelines attach to live media sessions without self-hosting the media layer.
Building a Simple WebRTC-to-SIP Bridge
A WebRTC-to-SIP bridge is the canonical FreeSWITCH WebRTC application: a browser user calls in, and FreeSWITCH connects them to a SIP endpoint, whether that's an internal extension, a conference room, or an outbound PSTN number.
The Call Flow
The flow runs in four stages. First, the browser client registers as a SIP extension over the secure WebSocket, exactly as a desk phone would. Second, when the user dials, the browser sends a SIP invite with a WebRTC SDP offer containing DTLS fingerprints and ICE candidates. Third, FreeSWITCH completes ICE and DTLS with the browser, establishing an encrypted media path. Fourth, FreeSWITCH originates a second leg to the destination, bridges the two, and relays media between them, transcoding if the codec sets don't overlap.
Call Control with the Event Socket Layer
For application-driven call control, FreeSWITCH exposes the Event Socket Layer (ESL), a socket API through which your backend application can listen to call events and issue commands: answer, bridge, transfer, hold, start recording, or drop a participant into a conference. ESL is how you build features beyond basic dialing without embedding logic in dialplan XML. Your web backend connects to ESL, watches for the browser client's incoming session, and orchestrates the bridge to the SIP side. This is the same pattern used by production contact-center and click-to-call applications.
Adding Call Features
Hold, transfer, and conferencing all build on the same foundation. Hold is a re-invite that pauses media on one leg. Transfer re-points the bridged leg to a new destination mid-call. Conferencing moves the participant into a conference room where FreeSWITCH mixes multiple legs. Because the browser leg is just another SIP session from FreeSWITCH's perspective, every dialplan application and API command works on it identically.
Scaling and Multi-Tenant Scenarios
FreeSWITCH WebRTC scales well, but scaling is a planning exercise, not an afterthought.
FreeSWITCH as an MCU for Large Conferences
For multi-party calls, FreeSWITCH acts as an MCU (multipoint control unit): it decodes every participant's stream, mixes them, and re-encodes a personalized mix for each participant. Mixing is CPU-intensive, and with transcoding participants (browsers on Opus, phones on G.711) the cost compounds. A practical rule of thumb from production deployments: a modern server core handles on the order of dozens of mixed, transcoded conference participants, so plan cores accordingly and load-test before committing to numbers. For very large audiences, consider a one-to-few interactive model or a streaming approach instead of full mixing; VideoSDK's interactive live streaming handles the host-to-many pattern without MCU costs on your hardware.
Multi-Tenant PBX Considerations
Multi-tenant deployments isolate tenants at three layers. Signaling isolation uses separate SIP profiles or domains so each tenant registers in its own namespace. Routing isolation uses per-tenant dialplan contexts so tenant A can never dial tenant B's extensions. Resource isolation can go as far as separate FreeSWITCH instances per large tenant, which also contains blast radius when something breaks. Certificate management matters here too: a wildcard or SAN certificate covering all tenant hostnames keeps wss bindings working across domains.
Resource Planning
Budget CPU for transcoding and mixing, memory for concurrent session state (modest per call but real at thousands of sessions), and bandwidth for the media itself. A single Opus call runs at roughly 24 to 40 kbps per direction; G.711 runs at 64 kbps plus overhead. At hundreds of concurrent calls, media bandwidth, not CPU, is often the first bottleneck. The FreeSWITCH GitHub repository documents scaling parameters and tuning options in detail.
Troubleshooting Common Issues
Every FreeSWITCH WebRTC deployment hits at least one of these four problems. Here's the rapid diagnosis checklist.
Silent or one-way audio is almost always ICE or IP advertisement. Check that the IP FreeSWITCH advertises in its SDP matches the address media actually flows on, that the RTP port range is open on the firewall, and that ICE completed rather than falling back to a candidate that can't route. Chrome's webrtc-internals page shows whether packets are flowing in both directions.
Certificate errors on the wss binding show up as clients that can't connect at all. Verify the certificate file exists, is readable by the FreeSWITCH process, matches the connecting hostname, and hasn't expired. The FreeSWITCH console log reports binding failures at startup.
Codec mismatch warnings appear when the browser offers only Opus but the far end only accepts G.711. Ensure the transcoding codec set includes both, and confirm the dialplan isn't forcing a codec the WebRTC leg can't provide.
Registration failures usually mean wrong credentials, wrong WebSocket URL, or a profile that didn't restart after configuration changes. Reload the Sofia profile and watch the SIP trace to see the exact rejection reason.
Definitions Glossary
FreeSWITCH: An open-source, carrier-grade telephony platform that functions as a softswitch, PBX, and media server, handling SIP routing, conferencing, and PSTN interconnection.
SIP-over-WebSocket (RFC 7118): An IETF standard that transports SIP signaling over a WebSocket connection, letting standards-compliant browser libraries register and place calls against a SIP server like FreeSWITCH.
Verto: FreeSWITCH's native JSON-RPC signaling protocol delivered through mod_verto, designed for tight integration with FreeSWITCH conferencing and call control.
DTLS-SRTP: The mandatory encryption scheme for WebRTC media, combining a DTLS handshake for key exchange with SRTP for encrypted audio and video transport.
ICE (Interactive Connectivity Establishment): The NAT traversal framework that lets endpoints exchange candidate addresses and find a working media path, optionally using STUN and TURN servers.
MCU (Multipoint Control Unit): A conference architecture where the server decodes, mixes, and re-encodes every participant's media stream, as FreeSWITCH does for multi-party calls.
Key Takeaways
- Choose SIP-over-WebSocket for interoperability and library choice; choose Verto only for FreeSWITCH-only greenfield builds that lean on native conferencing.
- A production wss binding requires a valid, hostname-matched TLS certificate, an open WebSocket port, and an open RTP media range on the firewall.
- Browsers speak Opus and DTLS-SRTP, so calls bridging to G.711 PSTN trunks incur software transcoding, which is your main CPU cost at scale.
- FreeSWITCH terminates media, which is what enables recording, conferencing, and media injection on WebRTC legs using the same tooling as SIP.
- Silent or one-way audio is nearly always an ICE or advertised-IP mismatch; start with browser webrtc-internals and the FreeSWITCH SIP trace.
Conclusion
FreeSWITCH WebRTC in 2026 remains one of the most powerful open-source combinations for browser-based calling: standards-based signaling, encrypted media, and a full telephony feature set behind it. The decisions that matter most are your signaling path, your certificate and ICE hygiene, and your transcoding budget at scale. Get those right and the rest is dialplan work you already know.
If you'd rather not operate your own media server, VideoSDK's video and audio calling SDKs provide managed WebRTC infrastructure with recording, transcription, and conferencing built in, and the REST API reference covers server-side orchestration. You can start free at app.videosdk.live/login. What are you building with FreeSWITCH WebRTC? Drop a comment, I'd love to hear what kind of browser-calling use case you're working on.
Step 5: Implement Participant View
Building the Participant View
The participant view is an essential component of your FreeSwitch WebRTC application. It allows users to see and interact with multiple participants in a session. In this section, we will implement the participant view, including the layout for displaying multiple video streams and managing participant streams and layouts.
Displaying Multiple Participants
To display multiple participants, we need to dynamically create video elements for each participant and add them to the participant view. The main interface HTML will have a container for remote video elements.
[a] HTML
Update your main communication interface HTML file to include a container for remote videos:
HTML
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>FreeSwitch WebRTC Main Interface</title>
7 <link rel="stylesheet" href="styles.css">
8</head>
9<body>
10 <div class="main-interface">
11 <div id="local-video-container">
12 <video id="local-video" autoplay muted></video>
13 </div>
14 <div id="remote-video-container">
15 <!-- Remote video elements will be added here dynamically -->
16 </div>
17 <div class="control-panel">
18 <button id="mute-audio">Mute Audio</button>
19 <button id="toggle-video">Stop Video</button>
20 <button id="switch-camera">Switch Camera</button>
21 <button id="end-call">End Call</button>
22 </div>
23 </div>
24 <script src="webrtc.js"></script>
25</body>
26</html>[b] CSS
Add styling for the remote video container to your
styles.css file:CSS
1#remote-video-container {
2 display: flex;
3 flex-wrap: wrap;
4 justify-content: center;
5 gap: 10px;
6}
7
8.remote-video {
9 width: 300px;
10 border-radius: 8px;
11 box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
12}Managing Participant Streams and Layouts
We will now update the JavaScript to handle multiple participant streams and manage their layout dynamically.
[c] JavaScript (webrtc.js)
JavaScript
1document.addEventListener("DOMContentLoaded", function () {
2 const muteAudioButton = document.getElementById("mute-audio");
3 const toggleVideoButton = document.getElementById("toggle-video");
4 const switchCameraButton = document.getElementById("switch-camera");
5 const endCallButton = document.getElementById("end-call");
6
7 let localStream;
8 let remoteStreams = {};
9 let isAudioMuted = false;
10 let isVideoStopped = false;
11
12 // Get user media
13 navigator.mediaDevices.getUserMedia({ video: true, audio: true })
14 .then(stream => {
15 localStream = stream;
16 document.getElementById("local-video").srcObject = stream;
17 // Initialize connection to signaling server
18 initializeWebSocket();
19 })
20 .catch(error => {
21 console.error("Error accessing media devices.", error);
22 });
23
24 // WebSocket initialization and handling
25 function initializeWebSocket() {
26 // Replace with your actual WebSocket signaling server URL
27 const signalingServerUrl = "wss://your-signaling-server-url";
28 const ws = new WebSocket(signalingServerUrl);
29
30 ws.onopen = function () {
31 console.log("Connected to the signaling server");
32
33 // Send join message
34 const joinMessage = {
35 type: "join",
36 username: "user",
37 roomId: "room"
38 };
39 ws.send(JSON.stringify(joinMessage));
40 };
41
42 ws.onmessage = function (message) {
43 console.log("Received message from server:", message.data);
44
45 const data = JSON.parse(message.data);
46 switch (data.type) {
47 case "joined":
48 console.log(`${data.username} joined room ${data.roomId}`);
49 break;
50 case "new-participant":
51 handleNewParticipant(data.participantId);
52 break;
53 case "participant-left":
54 handleParticipantLeft(data.participantId);
55 break;
56 case "offer":
57 handleOffer(data.offer, data.participantId);
58 break;
59 case "answer":
60 handleAnswer(data.answer, data.participantId);
61 break;
62 case "ice-candidate":
63 handleIceCandidate(data.candidate, data.participantId);
64 break;
65 // Handle other message types as needed
66 }
67 };
68
69 ws.onerror = function (error) {
70 console.error("WebSocket error:", error);
71 };
72
73 ws.onclose = function () {
74 console.log("Disconnected from the signaling server");
75 };
76 }
77
78 function handleNewParticipant(participantId) {
79 // Create new video element for the participant
80 const remoteVideo = document.createElement("video");
81 remoteVideo.id = `remote-video-${participantId}`;
82 remoteVideo.classList.add("remote-video");
83 remoteVideo.autoplay = true;
84 document.getElementById("remote-video-container").appendChild(remoteVideo);
85
86 // Initialize a new RTCPeerConnection
87 const peerConnection = new RTCPeerConnection();
88
89 // Add local stream tracks to the peer connection
90 localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
91
92 // Handle incoming tracks
93 peerConnection.ontrack = function (event) {
94 if (remoteVideo.srcObject !== event.streams[0]) {
95 remoteVideo.srcObject = event.streams[0];
96 console.log("Received remote stream");
97 }
98 };
99
100 // Handle ICE candidates
101 peerConnection.onicecandidate = function (event) {
102 if (event.candidate) {
103 const candidateMessage = {
104 type: "ice-candidate",
105 candidate: event.candidate,
106 participantId: participantId
107 };
108 ws.send(JSON.stringify(candidateMessage));
109 }
110 };
111
112 remoteStreams[participantId] = peerConnection;
113
114 // Create and send an offer
115 peerConnection.createOffer()
116 .then(offer => {
117 return peerConnection.setLocalDescription(offer);
118 })
119 .then(() => {
120 const offerMessage = {
121 type: "offer",
122 offer: peerConnection.localDescription,
123 participantId: participantId
124 };
125 ws.send(JSON.stringify(offerMessage));
126 })
127 .catch(error => {
128 console.error("Error creating offer:", error);
129 });
130 }
131
132 function handleParticipantLeft(participantId) {
133 // Remove the video element for the participant
134 const remoteVideo = document.getElementById(`remote-video-${participantId}`);
135 if (remoteVideo) {
136 remoteVideo.remove();
137 }
138
139 // Close the peer connection
140 if (remoteStreams[participantId]) {
141 remoteStreams[participantId].close();
142 delete remoteStreams[participantId];
143 }
144 }
145
146 function handleOffer(offer, participantId) {
147 const peerConnection = new RTCPeerConnection();
148
149 // Add local stream tracks to the peer connection
150 localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
151
152 // Handle incoming tracks
153 peerConnection.ontrack = function (event) {
154 const remoteVideo = document.getElementById(`remote-video-${participantId}`);
155 if (remoteVideo.srcObject !== event.streams[0]) {
156 remoteVideo.srcObject = event.streams[0];
157 console.log("Received remote stream");
158 }
159 };
160
161 // Handle ICE candidates
162 peerConnection.onicecandidate = function (event) {
163 if (event.candidate) {
164 const candidateMessage = {
165 type: "ice-candidate",
166 candidate: event.candidate,
167 participantId: participantId
168 };
169 ws.send(JSON.stringify(candidateMessage));
170 }
171 };
172
173 peerConnection.setRemoteDescription(new RTCSessionDescription(offer))
174 .then(() => {
175 return peerConnection.createAnswer();
176 })
177 .then(answer => {
178 return peerConnection.setLocalDescription(answer);
179 })
180 .then(() => {
181 const answerMessage = {
182 type: "answer",
183 answer: peerConnection.localDescription,
184 participantId: participantId
185 };
186 ws.send(JSON.stringify(answerMessage));
187 })
188 .catch(error => {
189 console.error("Error handling offer:", error);
190 });
191
192 remoteStreams[participantId] = peerConnection;
193 }
194
195 function handleAnswer(answer, participantId) {
196 const peerConnection = remoteStreams[participantId];
197 peerConnection.setRemoteDescription(new RTCSessionDescription(answer))
198 .catch(error => {
199 console.error("Error setting remote description:", error);
200 });
201 }
202
203 function handleIceCandidate(candidate, participantId) {
204 const peerConnection = remoteStreams[participantId];
205 peerConnection.addIceCandidate(new RTCIceCandidate(candidate))
206 .catch(error => {
207 console.error("Error adding ICE candidate:", error);
208 });
209 }
210
211 // Mute/Unmute Audio
212 muteAudioButton.addEventListener("click", function () {
213 isAudioMuted = !isAudioMuted;
214 localStream.getAudioTracks()[0].enabled = !isAudioMuted;
215 muteAudioButton.textContent = isAudioMuted ? "Unmute Audio" : "Mute Audio";
216 });
217
218 // Start/Stop Video
219 toggleVideoButton.addEventListener("click", function () {
220 isVideoStopped = !isVideoStopped;
221 localStream.getVideoTracks()[0].enabled = !isVideoStopped;
222 toggleVideoButton.textContent = isVideoStopped ? "Start Video" : "Stop Video";
223 });
224
225 // Switch Camera
226 switchCameraButton.addEventListener("click", async function () {
227 if (localStream) {
228 const videoTrack =
229
230 localStream.getVideoTracks()[0];
231 const currentConstraints = videoTrack.getConstraints();
232
233 const devices = await navigator.mediaDevices.enumerateDevices();
234 const videoDevices = devices.filter(device => device.kind === 'videoinput');
235
236 if (videoDevices.length > 1) {
237 const currentDeviceId = videoTrack.getSettings().deviceId;
238 const newDeviceId = videoDevices.find(device => device.deviceId !== currentDeviceId).deviceId;
239
240 const newConstraints = {
241 video: {
242 deviceId: { exact: newDeviceId }
243 }
244 };
245
246 const newStream = await navigator.mediaDevices.getUserMedia(newConstraints);
247 const newVideoTrack = newStream.getVideoTracks()[0];
248 localStream.removeTrack(videoTrack);
249 localStream.addTrack(newVideoTrack);
250
251 document.getElementById("local-video").srcObject = localStream;
252 }
253 }
254 });
255
256 // End Call
257 endCallButton.addEventListener("click", function () {
258 if (localStream) {
259 localStream.getTracks().forEach(track => track.stop());
260 }
261 // Implement additional logic to handle call termination
262 window.location.href = "join.html";
263 });
264});Explanation of the JavaScript Code
WebSocket Initialization and Handling
- Establishes a WebSocket connection to the signaling server and handles incoming messages.
- Manages participant join, leave, offer, answer, and ICE candidate messages.
Handling New Participants
- Creates a new video element for each participant.
- Initializes a new RTCPeerConnection for each participant.
- Adds local stream tracks to the peer connection.
- Handles incoming tracks and ICE candidates.
- Creates and sends an offer to the new participant.
Handling Participant Left
- Removes the video element for the participant who left.
- Closes the peer connection and removes it from the
remoteStreamsobject.
Handling Offers and Answers
- Sets up the RTCPeerConnection for incoming offers and answers.
- Manages remote descriptions and ICE candidates.
Implementing Control Features
- Mute/unmute audio.
- Start/stop video.
- Switch camera.
- End call and redirect to the join screen.
This completes the implementation of the participant view for your FreeSwitch WebRTC application. Users can now see and interact with multiple participants in a session. In the next part, we will cover how to run your code and test the application to ensure everything works as expected.
Step 6: Run Your Code Now
Running the Application
Now that you have implemented all the core features of your FreeSwitch WebRTC application, it’s time to run and test your code. This section will guide you through the steps to ensure your application is up and running smoothly.
Steps to Run the FreeSwitch WebRTC App
Start FreeSwitch**
- Ensure FreeSwitch is properly configured and running.
- Start FreeSwitch by running:
sh
1 sudo freeswitch -ncLaunch the Web Server
- Serve your HTML, CSS, and JavaScript files using a web server. You can use a simple HTTP server like
http-serverfor this purpose. - Install
http-serverif you haven't already:
sh
1 npm install -g http-server- Navigate to the directory containing your
index.htmlfile and start the server:sh1http-server - The server will start and provide you with a URL (e.g.,
http://localhost:8080).
Access the Application
- Open a web browser and navigate to the URL provided by your HTTP server (e.g.,
http://localhost:8080). - You should see the join screen of your FreeSwitch WebRTC application.
Testing and Debugging Tips
Test the Join Screen
- Enter a username and room ID, then click the "Join" button.
- Ensure the WebSocket connection to the signaling server is established and that you can join the session without errors.
Test Audio and Video Controls
- Verify that you can mute/unmute audio and start/stop video using the respective buttons.
- Check that switching the camera works if you have multiple video input devices.
Test Participant View
- Open multiple browser tabs or windows and join the same room from different tabs.
- Ensure that video streams from all participants are displayed correctly.
- Test the handling of participants joining and leaving the session.
Check Console Logs
- Open the browser's developer console (usually accessible with F12 or right-clicking the page and selecting "Inspect").
- Look for any error messages or warnings in the console log that might indicate issues with your WebRTC implementation.
Network Debugging
- Use the network tab in the browser's developer tools to monitor WebSocket communication and network traffic.
- Ensure that signaling messages (offer, answer, ICE candidates) are being sent and received correctly.
Troubleshooting Common Issues
WebSocket Connection Issues**
- Ensure that the signaling server URL is correct and that the server is running.
- Check network connectivity and firewall settings that might block WebSocket connections.
Media Device Access Errors
- Verify that your browser has permission to access the microphone and camera.
- Check that the correct media devices are selected and available.
ICE Candidate Gathering Issues
- Ensure that ICE candidates are being gathered and sent correctly.
- Verify that STUN/TURN servers are configured if necessary.
Video/Audio Stream Issues
- Ensure that video and audio tracks are being added to the RTCPeerConnection.
- Check that the correct video and audio elements are being updated with the media streams.
Example Scenario: Testing a Two-Participant Session
Participant 1
- Open a browser window and navigate to
http://localhost:8080. - Enter a username (e.g., "User1") and a room ID (e.g., "TestRoom").
- Click "Join" to enter the session.
Participant 2
- Open another browser window or tab and navigate to
http://localhost:8080. - Enter a different username (e.g., "User2") and the same room ID (e.g., "TestRoom").
- Click "Join" to enter the session.
Verify Interaction
- Ensure that both participants can see each other's video streams.
- Test the audio and video controls for both participants.
- Verify that messages and streams are handled correctly by the signaling server.
Conclusion
Congratulations! You have successfully built a FreeSwitch WebRTC application that allows users to join sessions, control their audio and video, and interact with multiple participants. This guide covered the essential steps, from setting up FreeSwitch and configuring WebRTC to implementing the user interface and testing the application.
Free $20 Balance for AI Voice Agents & Video Calls
FAQ
