Introduction to Ejabberd WebRTC
In today's digital age, real-time communication has become a crucial component of many applications, enabling seamless interaction through text, voice, and video. One of the technologies at the forefront of this revolution is WebRTC (Web Real-Time Communication), an open-source project that facilitates peer-to-peer communication via web browsers and mobile applications. Integrating WebRTC with powerful messaging platforms like Ejabberd can significantly enhance the capabilities of your communication infrastructure.
What is Ejabberd WebRTC?
Ejabberd is a robust, ubiquitous, and massively scalable messaging platform written in Erlang/OTP. It supports a variety of protocols including XMPP, MQTT, and SIP, making it a versatile solution for real-time messaging needs. Ejabberd stands out for its reliability and ability to handle thousands of concurrent connections, making it an ideal choice for large-scale applications.
WebRTC, on the other hand, is designed to enable real-time communication directly between browsers and mobile apps. It handles audio, video, and data sharing, making it a comprehensive solution for modern communication requirements. The integration of Ejabberd with WebRTC leverages the strengths of both technologies, providing a powerful platform for real-time messaging and communication.
By combining Ejabberd’s robust messaging capabilities with the real-time communication features of WebRTC, developers can build applications that support video conferencing, live chat, and other interactive features without the need for additional plugins or third-party services. This integration not only simplifies the development process but also ensures a high level of performance and scalability, crucial for handling the demands of modern communication apps.
In the following sections, we will delve into the technical aspects of setting up and integrating Ejabberd with WebRTC, providing detailed code snippets and step-by-step instructions to help you build your own real-time communication application. Whether you're developing a simple chat app or a complex video conferencing tool, this guide will provide the foundational knowledge and practical insights you need to get started.
Getting Started with the Code!
In this section, we will walk through the initial setup and configuration required to create an Ejabberd WebRTC application. This includes setting up a new project, installing necessary dependencies, structuring the project directory, and understanding the app architecture. By following these steps, you will lay a solid foundation for building a robust real-time communication application.
Create a New Ejabberd WebRTC App
To begin, you need to set up a new project directory for your Ejabberd WebRTC application. Open your terminal and execute the following commands:
bash
1mkdir ejabberd-webrtc-app
2cd ejabberd-webrtc-app
This will create a new directory named
ejabberd-webrtc-app
and navigate into it.[a] Install Dependencies
Next, you need to install the necessary dependencies for your project. Ejabberd and WebRTC require specific packages to function correctly. Here's a list of essential dependencies and how to install them:
[b] Erlang/OTP
Ensure that Erlang/OTP is installed on your system. You can download and install it from the
official Erlang website
.[c] Ejabberd
Download and install Ejabberd from the
official Ejabberd website
. You can follow the instructions provided to install Ejabberd on your operating system.[d] Rebar3
Rebar3 is a build tool for Erlang projects. Install Rebar3 by running the following commands:
bash
1 wget https://s3.amazonaws.com/rebar3/rebar3
2 chmod +x rebar3
3 ./rebar3 local install
[e] WebRTC Libraries
Depending on your platform, you might need to install WebRTC libraries. Refer to the
WebRTC official documentation
for detailed instructions.Structure of the Project
Once the dependencies are installed, it's essential to organize your project directory properly. Here is a suggested structure:
1ejabberd-webrtc-app/
2├── _build/
3├── config/
4│ └── ejabberd.yml
5├── deps/
6├── src/
7│ └── main.erl
8├── rebar.config
9└── README.md
_build/
: Directory where compiled files will be stored.config/
: Contains configuration files, such asejabberd.yml
.deps/
: Directory for project dependencies.src/
: Contains source files, such asmain.erl
.rebar.config
: Configuration file for Rebar3.README.md
: Documentation for your project.
App Architecture
Understanding the architecture of your Ejabberd WebRTC app is crucial for effective development. The core components include:
- Ejabberd Server: Handles XMPP communication and serves as the messaging backbone.
- WebRTC Signaling Server: Manages signaling for WebRTC connections, typically integrated within the Ejabberd server.
- Client Applications: Web or mobile clients that utilize WebRTC for peer-to-peer communication.
The interaction flow is as follows:
- Clients connect to the Ejabberd server using XMPP.
- When a user initiates a call, the signaling data is exchanged via the Ejabberd server.
- WebRTC establishes a peer-to-peer connection for media (audio/video) transmission.
By structuring your project and understanding the architecture, you are now ready to dive into the code and start building your Ejabberd WebRTC application. In the next sections, we will provide detailed step-by-step instructions and code snippets to guide you through the development process.
Step 1: Get Started with "main.erl"
In this step, we'll create and configure the main Erlang file for our Ejabberd WebRTC application. This file will be the entry point of our application, handling the initial setup and integration with Ejabberd and WebRTC.
Creating and Setting Up main.erl
First, navigate to the
src
directory within your project:bash
1cd src
Create a new file named
main.erl
:bash
1touch main.erl
Open
main.erl
in your preferred text editor and start by defining the module and including necessary headers:Erlang
1-module(main).
2-author("Your Name").
3
4%% Import necessary libraries
5-include_lib("ejabberd/include/ejabberd.hrl").
6-include_lib("stdlib/include/erl_opts.hrl").
7
8%% Export functions
9-export([start/0, init/0]).
Configuring Ejabberd to Support WebRTC
Next, we need to set up the initialization function and configure Ejabberd to support WebRTC signaling. Add the following code to your
main.erl
file:Erlang
1%% Initialization function
2init() ->
3 application:start(crypto),
4 application:start(sasl),
5 application:start(ejabberd).
6
7%% Start function
8start() ->
9 init(),
10 %% Setup Ejabberd configurations
11 ejabberd:load_config("config/ejabberd.yml"),
12 ejabberd:start().
In this setup:
- The
init/0
function initializes necessary applications such ascrypto
andsasl
along with Ejabberd. - The
start/0
function loads the Ejabberd configuration file and starts the Ejabberd server.
Ejabberd Configuration
Next, ensure your Ejabberd configuration (
config/ejabberd.yml
) is set up to support WebRTC signaling. Open ejabberd.yml
and add or modify the following sections:YAML
1hosts:
2 - "localhost"
3
4listen:
5 -
6 port: 5222
7 module: ejabberd_c2s
8 certfile: "/etc/ejabberd/ejabberd.pem"
9 starttls: true
10 starttls_required: false
11
12 -
13 port: 5280
14 module: ejabberd_http
15 request_handlers:
16 "/websocket": ejabberd_http_ws
17
18modules:
19 mod_admin_extra: {}
20 mod_announce: # recommends mod_announce in mod_muc
21 access: announce
22 mod_blocking: {}
23 mod_bosh: {}
24 mod_caps: {}
25 mod_carboncopy: {}
26 mod_client_state: {}
27 mod_configure: {}
28 mod_disco: {}
29 mod_http_api: {}
30 mod_http_upload:
31 put_url: "https://@HOST@:5443/upload"
32 mod_last: {}
33 mod_mam:
34 request_archive: true
35 mod_muc:
36 access:
37 - max_users: 1000000
38 default_room_options:
39 mam: true
40 mod_muc_admin: {}
41 mod_offline: {}
42 mod_ping: {}
43 mod_privacy: {}
44 mod_private: {}
45 mod_pubsub:
46 access_createnode: pubsub_createnode
47 plugins:
48 - "flat"
49 - "hometree"
50 - "pep" # XEP-0163 PEP
51 mod_push: {}
52 mod_push_keepalive: {}
53 mod_register:
54 welcome_message:
55 subject: "Welcome!"
56 body: "Hi."
57 access: register
58 mod_roster: {}
59 mod_shared_roster: {}
60 mod_stream_mgmt:
61 resend_on_timeout: if_offline
62 mod_vcard: {}
63 mod_version: {}
This configuration sets up Ejabberd to handle WebRTC signaling via WebSocket and BOSH, crucial for real-time communication.
By the end of this step, you have created the main Erlang file for your Ejabberd WebRTC application and configured Ejabberd to support WebRTC. In the next step, we will design the wireframe for the application, defining the UI components required for WebRTC integration.
Step 2: Wireframe All the Components
In this step, we'll design the wireframe for our Ejabberd WebRTC application, focusing on the essential UI components required for a functional real-time communication interface. This includes the join screen, call controls, and participant view. By setting up a clear wireframe, we can ensure a cohesive and user-friendly design that facilitates seamless interaction.
Designing the Wireframe
A well-thought-out wireframe helps visualize the layout and functionality of the application before diving into the actual code. Here, we'll outline the main components and their purposes.
[a] HTML for Join Screen
The join screen is where users will enter their credentials or room information to join a video call. This screen should be simple and intuitive.
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>Ejabberd WebRTC - Join</title>
7 <link rel="stylesheet" href="styles.css">
8</head>
9<body>
10 <div class="join-container">
11 <h1>Join a Room</h1>
12 <form id="join-form">
13 <input type="text" id="username" placeholder="Enter your username" required>
14 <input type="text" id="room" placeholder="Enter room name" required>
15 <button type="submit">Join</button>
16 </form>
17 </div>
18 <script src="main.js"></script>
19</body>
20</html>
[b] CSS for Join Screen
CSS
1body {
2 font-family: Arial, sans-serif;
3 display: flex;
4 justify-content: center;
5 align-items: center;
6 height: 100vh;
7 background-color: #f0f0f0;
8}
9
10.join-container {
11 text-align: center;
12 background-color: white;
13 padding: 20px;
14 border-radius: 8px;
15 box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
16}
17
18#join-form {
19 display: flex;
20 flex-direction: column;
21}
22
23#join-form input {
24 margin: 10px 0;
25 padding: 10px;
26 font-size: 16px;
27}
28
29#join-form button {
30 padding: 10px;
31 font-size: 16px;
32 background-color: #007bff;
33 color: white;
34 border: none;
35 border-radius: 4px;
36 cursor: pointer;
37}
38
39#join-form button:hover {
40 background-color: #0056b3;
41}
Call Controls
The call controls should allow users to manage their video and audio streams effectively. This includes buttons for muting/unmuting the microphone, starting/stopping the video, and ending the call.
[a] HTML for Call Controls
HTML
1<div class="controls">
2 <button id="mute-btn">Mute</button>
3 <button id="video-btn">Stop Video</button>
4 <button id="end-call-btn">End Call</button>
5</div>
[b] CSS for Call Controls
CSS
1.controls {
2 position: fixed;
3 bottom: 20px;
4 left: 50%;
5 transform: translateX(-50%);
6 display: flex;
7 gap: 10px;
8}
9
10.controls button {
11 padding: 10px;
12 font-size: 14px;
13 background-color: #007bff;
14 color: white;
15 border: none;
16 border-radius: 4px;
17 cursor: pointer;
18}
19
20.controls button:hover {
21 background-color: #0056b3;
22}
Participant View
The participant view displays the video streams of all users in the call. Each participant should have their own video element, arranged in a grid or flex layout.
[a] HTML for Participant View
HTML
1<div class="participant-view">
2 <video id="local-video" autoplay muted></video>
3 <div id="remote-videos"></div>
4</div>
[b] CSS for Participant View
CSS
1.participant-view {
2 display: flex;
3 flex-wrap: wrap;
4 justify-content: center;
5 align-items: center;
6 padding: 20px;
7}
8
9#local-video {
10 width: 200px;
11 height: 150px;
12 margin: 10px;
13 border: 2px solid #007bff;
14 border-radius: 8px;
15}
16
17#remote-videos video {
18 width: 200px;
19 height: 150px;
20 margin: 10px;
21 border: 2px solid #007bff;
22 border-radius: 8px;
23}
Setting Up HTML/CSS for the Join Screen, Controls, and Participant View
By defining the HTML and CSS for the join screen, call controls, and participant view, we establish the basic structure and style of our application. This setup ensures that users can join calls, control their media streams, and view other participants efficiently.
In the next step, we will implement the join screen's functionality, handling user input and initializing WebRTC sessions. This will involve writing JavaScript to manage the user interactions and integrate with the Ejabberd server for signaling.
Step 3: Implement Join Screen
In this step, we will implement the functionality of the join screen, allowing users to enter their username and room name to join a video call. We will handle user input and initialize WebRTC sessions. This involves writing JavaScript to manage user interactions and integrating with the Ejabberd server for signaling.
Developing the Join Screen
We have already created the HTML and CSS for the join screen. Now, let's add the JavaScript functionality.
[a] Setup Event Listeners and Form Handling
First, we need to set up event listeners for the form submission. This will capture the user input and use it to join a room.
JavaScript for Join Screen (
main.js
):JavaScript
1document.addEventListener('DOMContentLoaded', () => {
2 const joinForm = document.getElementById('join-form');
3
4 joinForm.addEventListener('submit', async (event) => {
5 event.preventDefault();
6
7 const username = document.getElementById('username').value;
8 const room = document.getElementById('room').value;
9
10 if (username && room) {
11 await joinRoom(username, room);
12 } else {
13 alert('Please enter both username and room name.');
14 }
15 });
16});
[b] Initialize WebRTC and Ejabberd Connection
Next, we need to define the
joinRoom
function. This function will handle the initialization of the WebRTC session and connect to the Ejabberd server.JavaScript for Join Room (
main.js
):JavaScript
1async function joinRoom(username, room) {
2 try {
3 // Initialize Ejabberd connection
4 const connection = new Strophe.Connection('wss://your-ejabberd-server.com:5280/websocket');
5
6 connection.connect(username, 'password', (status) => {
7 if (status === Strophe.Status.CONNECTED) {
8 console.log('Connected to Ejabberd');
9
10 // Join the room
11 connection.send($pres().c('x', { xmlns: 'http://jabber.org/protocol/muc' }).c('history', { maxstanzas: 0 }));
12
13 // Initialize WebRTC
14 startWebRTC(connection, room);
15 } else {
16 console.error('Failed to connect to Ejabberd');
17 }
18 });
19 } catch (error) {
20 console.error('Error joining room:', error);
21 }
22}
[c] Implement WebRTC Initialization
Now, let's implement the
startWebRTC
function. This function will handle setting up the WebRTC peer connection, managing local and remote streams, and handling signaling via Ejabberd.JavaScript for WebRTC Initialization (
main.js
):JavaScript
1async function startWebRTC(connection, room) {
2 const localVideo = document.getElementById('local-video');
3 const remoteVideos = document.getElementById('remote-videos');
4
5 // Get user media
6 const localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
7 localVideo.srcObject = localStream;
8
9 const peerConnections = {};
10
11 connection.addHandler((msg) => {
12 const from = msg.getAttribute('from');
13 const type = msg.getAttribute('type');
14
15 if (type === 'offer') {
16 const offer = JSON.parse(msg.getElementsByTagName('body')[0].textContent);
17 handleOffer(connection, from, offer, localStream, peerConnections);
18 } else if (type === 'answer') {
19 const answer = JSON.parse(msg.getElementsByTagName('body')[0].textContent);
20 handleAnswer(from, answer, peerConnections);
21 } else if (type === 'candidate') {
22 const candidate = JSON.parse(msg.getElementsByTagName('body')[0].textContent);
23 handleCandidate(from, candidate, peerConnections);
24 }
25
26 return true;
27 }, null, 'message', 'chat');
28
29 connection.send($pres().c('x', { xmlns: 'http://jabber.org/protocol/muc' }).c('history', { maxstanzas: 0 }));
30
31 // Send offer to new participants
32 localStream.getTracks().forEach(track => {
33 for (const peerId in peerConnections) {
34 peerConnections[peerId].addTrack(track, localStream);
35 }
36 });
37
38 // Create offer
39 for (const peerId in peerConnections) {
40 const peerConnection = peerConnections[peerId];
41 const offer = await peerConnection.createOffer();
42 await peerConnection.setLocalDescription(offer);
43
44 connection.send($msg({ to: peerId, type: 'offer' }).c('body').t(JSON.stringify(offer)));
45 }
46}
47
48function handleOffer(connection, from, offer, localStream, peerConnections) {
49 const peerConnection = new RTCPeerConnection();
50 peerConnections[from] = peerConnection;
51
52 peerConnection.ontrack = (event) => {
53 const remoteVideo = document.createElement('video');
54 remoteVideo.srcObject = event.streams[0];
55 remoteVideo.autoplay = true;
56 document.getElementById('remote-videos').appendChild(remoteVideo);
57 };
58
59 localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
60
61 peerConnection.setRemoteDescription(new RTCSessionDescription(offer)).then(() => {
62 return peerConnection.createAnswer();
63 }).then((answer) => {
64 return peerConnection.setLocalDescription(answer);
65 }).then(() => {
66 connection.send($msg({ to: from, type: 'answer' }).c('body').t(JSON.stringify(peerConnection.localDescription)));
67 });
68}
69
70function handleAnswer(from, answer, peerConnections) {
71 const peerConnection = peerConnections[from];
72 peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
73}
74
75function handleCandidate(from, candidate, peerConnections) {
76 const peerConnection = peerConnections[from];
77 peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
78}
By implementing the JavaScript functionality for the join screen, we enable users to enter their credentials and join a video call. The
joinRoom
function establishes a connection to the Ejabberd server, and the startWebRTC
function sets up the WebRTC peer connections, managing local and remote media streams.In the next step, we will add functionality to the call controls, allowing users to manage their audio and video streams effectively during a call.
Step 4: Implement Controls
In this step, we will add functionality to the call controls, allowing users to manage their audio and video streams during a call. This includes buttons for muting/unmuting the microphone, starting/stopping the video, and ending the call. We'll enhance our JavaScript to handle these interactions effectively.
Adding Functional Controls
We have already defined the HTML and CSS for the call controls. Now, let's implement the JavaScript to enable these controls.
[a] Set Up Event Listeners for Controls
First, we need to add event listeners for the control buttons. These listeners will call appropriate functions to handle user actions.
JavaScript for Control Buttons (
main.js
):JavaScript
1document.addEventListener('DOMContentLoaded', () => {
2 // Existing code...
3
4 const muteBtn = document.getElementById('mute-btn');
5 const videoBtn = document.getElementById('video-btn');
6 const endCallBtn = document.getElementById('end-call-btn');
7
8 muteBtn.addEventListener('click', toggleMute);
9 videoBtn.addEventListener('click', toggleVideo);
10 endCallBtn.addEventListener('click', endCall);
11});
[b] Implement Toggle Mute Functionality
The
toggleMute
function will mute or unmute the local audio track.JavaScript for Mute Function (
main.js
):JavaScript
1let isMuted = false;
2
3function toggleMute() {
4 const localStream = document.getElementById('local-video').srcObject;
5 localStream.getAudioTracks().forEach(track => track.enabled = !track.enabled);
6
7 isMuted = !isMuted;
8 document.getElementById('mute-btn').textContent = isMuted ? 'Unmute' : 'Mute';
9}
[c] Implement Toggle Video Functionality
The
toggleVideo
function will start or stop the local video track.JavaScript for Video Function (
main.js
):JavaScript
1let isVideoOff = false;
2
3function toggleVideo() {
4 const localStream = document.getElementById('local-video').srcObject;
5 localStream.getVideoTracks().forEach(track => track.enabled = !track.enabled);
6
7 isVideoOff = !isVideoOff;
8 document.getElementById('video-btn').textContent = isVideoOff ? 'Start Video' : 'Stop Video';
9}
[d] Implement End Call Functionality
The
endCall
function will close the peer connections and stop all media tracks.JavaScript for End Call Function (
main.js
):JavaScript
1function endCall() {
2 const localVideo = document.getElementById('local-video');
3 const remoteVideos = document.getElementById('remote-videos');
4
5 // Stop local media tracks
6 const localStream = localVideo.srcObject;
7 localStream.getTracks().forEach(track => track.stop());
8
9 // Close peer connections
10 for (const peerId in peerConnections) {
11 peerConnections[peerId].close();
12 delete peerConnections[peerId];
13 }
14
15 // Clear video elements
16 localVideo.srcObject = null;
17 while (remoteVideos.firstChild) {
18 remoteVideos.removeChild(remoteVideos.firstChild);
19 }
20
21 // Redirect to join screen
22 window.location.reload();
23}
[e] Update Peer Connections Management
Ensure that
peerConnections
is defined globally to be accessible by all functions handling peer connection operations.JavaScript Global Definition (
main.js
):JavaScript
1let peerConnections = {};
[f] Handling State Changes and Events
To ensure a smooth user experience, we should also handle state changes and events for peer connections.
JavaScript for Handling State Changes (
main.js
):JavaScript
1function handleOffer(connection, from, offer, localStream, peerConnections) {
2 const peerConnection = new RTCPeerConnection();
3 peerConnections[from] = peerConnection;
4
5 peerConnection.ontrack = (event) => {
6 const remoteVideo = document.createElement('video');
7 remoteVideo.srcObject = event.streams[0];
8 remoteVideo.autoplay = true;
9 document.getElementById('remote-videos').appendChild(remoteVideo);
10 };
11
12 peerConnection.onicecandidate = (event) => {
13 if (event.candidate) {
14 connection.send($msg({ to: from, type: 'candidate' }).c('body').t(JSON.stringify(event.candidate)));
15 }
16 };
17
18 localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
19
20 peerConnection.setRemoteDescription(new RTCSessionDescription(offer)).then(() => {
21 return peerConnection.createAnswer();
22 }).then((answer) => {
23 return peerConnection.setLocalDescription(answer);
24 }).then(() => {
25 connection.send($msg({ to: from, type: 'answer' }).c('body').t(JSON.stringify(peerConnection.localDescription)));
26 });
27}
28
29function handleAnswer(from, answer, peerConnections) {
30 const peerConnection = peerConnections[from];
31 peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
32}
33
34function handleCandidate(from, candidate, peerConnections) {
35 const peerConnection = peerConnections[from];
36 peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
37}
By implementing the JavaScript functionality for the call controls, users can now manage their audio and video streams during a call. The
toggleMute
function allows muting and unmuting the microphone, toggleVideo
handles starting and stopping the video, and endCall
ends the call and cleans up the peer connections and media streams.In the next step, we will implement the participant view, ensuring that all participants' video and audio streams are displayed correctly.
Step 5: Implement Participant View
In this step, we will implement the participant view, which displays the video and audio streams of all users in the call. This involves handling multiple WebRTC peer connections and dynamically updating the user interface as participants join or leave the call.
Building the Participant View
We have already defined the HTML and CSS for the participant view. Now, let's focus on the JavaScript functionality required to manage the participant video streams.
[a] Handle Remote Streams
First, we need to update the
handleOffer
function to properly handle remote streams and add video elements dynamically for each participant.JavaScript for Handling Remote Streams (
main.js
):JavaScript
1function handleOffer(connection, from, offer, localStream, peerConnections) {
2 const peerConnection = new RTCPeerConnection();
3 peerConnections[from] = peerConnection;
4
5 peerConnection.ontrack = (event) => {
6 const remoteVideo = document.createElement('video');
7 remoteVideo.srcObject = event.streams[0];
8 remoteVideo.autoplay = true;
9 remoteVideo.id = `remote-video-${from}`;
10 document.getElementById('remote-videos').appendChild(remoteVideo);
11 };
12
13 peerConnection.onicecandidate = (event) => {
14 if (event.candidate) {
15 connection.send($msg({ to: from, type: 'candidate' }).c('body').t(JSON.stringify(event.candidate)));
16 }
17 };
18
19 localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
20
21 peerConnection.setRemoteDescription(new RTCSessionDescription(offer)).then(() => {
22 return peerConnection.createAnswer();
23 }).then((answer) => {
24 return peerConnection.setLocalDescription(answer);
25 }).then(() => {
26 connection.send($msg({ to: from, type: 'answer' }).c('body').t(JSON.stringify(peerConnection.localDescription)));
27 });
28}
[b] Update Local Stream Handling
Ensure that the local video stream is correctly added to each new peer connection. We have already set up this functionality in the
startWebRTC
function, but let's make sure it covers all scenarios.JavaScript for Local Stream Handling (
main.js
):JavaScript
1async function startWebRTC(connection, room) {
2 const localVideo = document.getElementById('local-video');
3 const remoteVideos = document.getElementById('remote-videos');
4
5 // Get user media
6 const localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
7 localVideo.srcObject = localStream;
8
9 connection.addHandler((msg) => {
10 const from = msg.getAttribute('from');
11 const type = msg.getAttribute('type');
12
13 if (type === 'offer') {
14 const offer = JSON.parse(msg.getElementsByTagName('body')[0].textContent);
15 handleOffer(connection, from, offer, localStream, peerConnections);
16 } else if (type === 'answer') {
17 const answer = JSON.parse(msg.getElementsByTagName('body')[0].textContent);
18 handleAnswer(from, answer, peerConnections);
19 } else if (type === 'candidate') {
20 const candidate = JSON.parse(msg.getElementsByTagName('body')[0].textContent);
21 handleCandidate(from, candidate, peerConnections);
22 }
23
24 return true;
25 }, null, 'message', 'chat');
26
27 connection.send($pres().c('x', { xmlns: 'http://jabber.org/protocol/muc' }).c('history', { maxstanzas: 0 }));
28
29 // Send offer to new participants
30 localStream.getTracks().forEach(track => {
31 for (const peerId in peerConnections) {
32 peerConnections[peerId].addTrack(track, localStream);
33 }
34 });
35
36 // Create offer
37 for (const peerId in peerConnections) {
38 const peerConnection = peerConnections[peerId];
39 const offer = await peerConnection.createOffer();
40 await peerConnection.setLocalDescription(offer);
41
42 connection.send($msg({ to: peerId, type: 'offer' }).c('body').t(JSON.stringify(offer)));
43 }
44}
[c] Handling Participant Disconnection
We need to manage the UI and clean up resources when participants leave the call. Update the
endCall
function to handle this scenario properly.JavaScript for Handling Disconnection (
main.js
):JavaScript
1function endCall() {
2 const localVideo = document.getElementById('local-video');
3 const remoteVideos = document.getElementById('remote-videos');
4
5 // Stop local media tracks
6 const localStream = localVideo.srcObject;
7 localStream.getTracks().forEach(track => track.stop());
8
9 // Close peer connections
10 for (const peerId in peerConnections) {
11 peerConnections[peerId].close();
12 delete peerConnections[peerId];
13 }
14
15 // Clear video elements
16 localVideo.srcObject = null;
17 while (remoteVideos.firstChild) {
18 remoteVideos.removeChild(remoteVideos.firstChild);
19 }
20
21 // Redirect to join screen
22 window.location.reload();
23}
[d] Handling Peer Disconnections Gracefully
To handle peers leaving the call gracefully, you should listen for ICE connection state changes and remove video elements when peers disconnect.
JavaScript for Handling Peer Disconnections (
main.js
):JavaScript
1function handlePeerDisconnection(peerId) {
2 const remoteVideo = document.getElementById(`remote-video-${peerId}`);
3 if (remoteVideo) {
4 remoteVideo.srcObject = null;
5 remoteVideo.parentNode.removeChild(remoteVideo);
6 }
7 if (peerConnections[peerId]) {
8 peerConnections[peerId].close();
9 delete peerConnections[peerId];
10 }
11}
12
13function handleOffer(connection, from, offer, localStream, peerConnections) {
14 const peerConnection = new RTCPeerConnection();
15 peerConnections[from] = peerConnection;
16
17 peerConnection.ontrack = (event) => {
18 const remoteVideo = document.createElement('video');
19 remoteVideo.srcObject = event.streams[0];
20 remoteVideo.autoplay = true;
21 remoteVideo.id = `remote-video-${from}`;
22 document.getElementById('remote-videos').appendChild(remoteVideo);
23 };
24
25 peerConnection.onicecandidate = (event) => {
26 if (event.candidate) {
27 connection.send($msg({ to: from, type: 'candidate' }).c('body').t(JSON.stringify(event.candidate)));
28 }
29 };
30
31 peerConnection.oniceconnectionstatechange = () => {
32 if (peerConnection.iceConnectionState === 'disconnected' || peerConnection.iceConnectionState === 'failed' || peerConnection.iceConnectionState === 'closed') {
33 handlePeerDisconnection(from);
34 }
35 };
36
37 localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
38
39 peerConnection.setRemoteDescription(new RTCSessionDescription(offer)).then(() => {
40 return peerConnection.createAnswer();
41 }).then((answer) => {
42 return peerConnection.setLocalDescription(answer);
43 }).then(() => {
44 connection.send($msg({ to: from, type: 'answer' }).c('body').t(JSON.stringify(peerConnection.localDescription)));
45 });
46}
By implementing the participant view functionality, we ensure that all video and audio streams are displayed correctly and dynamically updated as participants join or leave the call. The code handles remote streams, local stream integration, and disconnections gracefully.
In the next step, we will run our code and test the complete functionality of the Ejabberd WebRTC application.
Step 6: Run Your Code Now
In this final step, we will compile and run the Ejabberd WebRTC application, testing the complete functionality to ensure everything works as expected. We will also cover some basic troubleshooting steps in case you encounter any issues.
Compiling and Running the Application
[a] Start Ejabberd Server
Ensure that your Ejabberd server is running and properly configured to handle WebRTC signaling. You can start the Ejabberd server with the following command:
bash
1ejabberdctl start
[b] Compile the Erlang Code
Navigate to your project directory and compile the Erlang code using Rebar3:
bash
1cd /path/to/your/ejabberd-webrtc-app
2rebar3 compile
[c] Run the Application
After compiling, you can start the Erlang application:
bash
1erl -pa _build/default/lib/*/ebin -sname ejabberd_webrtc -s main
This command will start the Erlang shell with your application running. Ensure there are no errors during the startup process.
[d] Access the Web Application
Open a web browser and navigate to the HTML file of your application. If you are running a local server, you might access it via
http://localhost/your-app-path
.Testing the Application
Join a Room
Enter a username and room name on the join screen and click the "Join" button. This should connect you to the Ejabberd server and initialize the WebRTC session.
Verify Local Video
Ensure that your local video stream appears on the screen. This confirms that your webcam is working and the media stream is being captured correctly.
Add Participants
Open the application in multiple browser tabs or devices and join the same room with different usernames. Verify that remote video streams are displayed correctly for all participants.
Test Controls
Use the mute, video, and end call buttons to test their functionality:
- Mute/Unmute: Check that your microphone can be muted and unmuted.
- Start/Stop Video: Verify that your video can be started and stopped.
- End Call: Ensure that the call ends correctly and the UI resets.
Troubleshooting Common Issues
Connection Issues
If you cannot connect to the Ejabberd server, verify the following:
- Ensure the Ejabberd server is running and reachable.
- Check your
ejabberd.yml
configuration for WebSocket and BOSH settings. - Confirm that the Erlang/OTP environment is correctly set up.
Media Stream Issues
If you face issues with capturing or displaying media streams:
- Ensure that your browser has permissions to access the webcam and microphone.
- Check the console for errors related to
getUserMedia
or WebRTC APIs.
Signaling Issues
If WebRTC connections are not established:
- Verify that the signaling messages are correctly exchanged between peers via Ejabberd.
- Check for errors in the JavaScript console related to ICE candidates or SDP exchange.
Conclusion
In this guide, we walked through the steps to set up, configure, and run an Ejabberd WebRTC application. We covered the initial project setup, handling user inputs, managing media streams, and implementing essential call controls. By now, you should have a functional real-time communication application using Ejabberd and WebRTC.
Want to level-up your learning? Subscribe now
Subscribe to our newsletter for more tech based insights
FAQ