End of Life for Twilio Programmable Video - Upgrade to VideoSDKLearn More

How to Build Video Chat App with Node.js & WebRTC?

Learn to build a WebRTC video chat app with Node.js! Follow our step-by-step guide for seamless video communication. Perfect for developers.

Introduction to Node-WebRTC

Node-WebRTC is a powerful library that brings the capabilities of WebRTC (Web Real-Time Communication) to Node.js, allowing developers to create real-time, peer-to-peer communication applications such as video conferencing, file sharing, and live streaming directly from a Node.js server. By leveraging Node-WebRTC, developers can integrate real-time communication features into their applications without relying on traditional server-client architectures, making it easier to build scalable and efficient solutions.
WebRTC is an open-source project that enables web applications and websites to capture and potentially broadcast audio and/or video media. It also allows them to exchange any type of data between browsers without the need for an intermediary. This technology is widely used for applications such as video chat, file transfer, and live streaming.

What is Node WebRTC?

The Node-WebRTC library acts as a bridge, implementing the WebRTC API for Node.js, thereby extending WebRTC's capabilities beyond the browser. This is particularly useful for backend services that need to handle real-time communication, such as signaling servers or media servers that manage multiple WebRTC connections. The library allows developers to use familiar JavaScript syntax and Node.js conventions to build these backend services, thus maintaining consistency across the tech stack.
With Node-WebRTC, developers can create highly interactive applications that perform smoothly in real-time. Whether you're building a video conferencing tool, a live streaming service, or a real-time collaboration platform, Node-WebRTC provides the necessary tools and flexibility to implement robust and scalable solutions.
For those interested in exploring Node-WebRTC further, the library's

GitHub repository

provides extensive documentation, examples, and resources to help get started. This repository is a valuable resource for understanding the implementation details and exploring various use cases of Node-WebRTC.

Getting Started with Node-WebRTC

To harness the power of Node-WebRTC, you need to set up a new project and configure it correctly. This section will guide you through the initial steps of creating a Node-WebRTC application, including installation, project structure, and understanding the app architecture.

Creating a New Node-WebRTC App

Before you start, ensure that you have Node.js and npm (Node Package Manager) installed on your machine. You can download and install them from the

official Node.js website

. Once you have Node.js set up, follow these steps to create a new Node-WebRTC application:

[a] Initialize a New Node.js Project

Open your terminal and create a new directory for your project. Navigate into this directory and initialize a new Node.js project by running the following command:

bash

1   mkdir node-webrtc-app
2   cd node-webrtc-app
3   npm init -y
This command will create a package.json file with default settings.

[b] Installing Node-WebRTC

To use Node-WebRTC, you need to install it via npm. This can be done with a simple command:

bash

1npm install node-webrtc
This command will download and install the Node-WebRTC library and its dependencies into your project, making it ready for use.

Project Structure

A well-organized project structure is crucial for maintaining and scaling your application. Here’s a recommended structure for your Node-WebRTC app:
1node-webrtc-app/
2├── node_modules/
3├── src/
4│   ├── controllers/
5│   ├── models/
6│   ├── views/
7│   ├── utils/
8│   └── index.js
9├── .gitignore
10├── package.json
11└── README.md
  • src/controllers: Contains the logic for handling different routes and endpoints.
  • src/models: Defines data models and schemas.
  • src/views: Holds the HTML/CSS files for the frontend (if applicable).
  • src/utils: Utility functions and helper modules.
  • src/index.js: The main entry point of your application.

App Architecture

node-webrtc
Understanding the architecture of a typical Node-WebRTC application will help you build a robust and scalable solution. Here’s an overview of the key components:
  1. Signaling Server: Facilitates the exchange of signaling data (like session descriptions and ICE candidates) between peers. This server coordinates the connection setup.
  2. Media Streams: Handles audio and video streams, allowing users to share their media.
  3. Data Channels: Manages data transfer between peers, enabling features like file sharing or real-time messaging.
  4. Peer Connections: Establishes and maintains peer-to-peer connections, ensuring smooth communication between clients.
By organizing your project and understanding these components, you’ll be well-equipped to build a Node-WebRTC application. The following sections will delve deeper into each step, providing code snippets and detailed instructions to guide you through the development process.

Step 1: Initial Setup

In this section, we'll walk through the initial setup required to get your Node-WebRTC application up and running. This includes creating the entry point of your application and initializing the necessary components to establish a WebRTC connection.

Get Started with index.js

The index.js file will serve as the main entry point for your Node-WebRTC application. It will handle the setup of the server and the initialization of WebRTC connections. Follow these steps to get started:

[a] Create the index.js File

Navigate to the src directory and create a new file named index.js:

bash

1   touch src/index.js

[b] Set Up a Basic Server

Use Node.js to create a basic HTTP server. This server will later handle WebRTC signaling and manage connections between peers. Add the following code to your index.js file:

JavaScript

1   const http = require('http');
2   const express = require('express');
3   const { Server } = require('socket.io');
4   const { RTCPeerConnection, RTCSessionDescription } = require('wrtc');
5
6   const app = express();
7   const server = http.createServer(app);
8   const io = new Server(server);
9
10   const port = 3000;
11
12   app.get('/', (req, res) => {
13     res.send('Node-WebRTC Server is running');
14   });
15
16   server.listen(port, () => {
17     console.log(`Server is listening on http://localhost:${port}`);
18   });

[c] Initialize WebRTC Peer Connections

Now, set up the logic to handle WebRTC peer connections. This includes managing signaling data and establishing connections between peers. Add the following code to your index.js file:

JavaScript

1   let peers = {};
2
3   io.on('connection', socket => {
4     console.log('A user connected:', socket.id);
5
6     socket.on('offer', async (id, description) => {
7       const peer = new RTCPeerConnection();
8       peers[id] = peer;
9
10       await peer.setRemoteDescription(new RTCSessionDescription(description));
11       const answer = await peer.createAnswer();
12       await peer.setLocalDescription(answer);
13
14       socket.emit('answer', id, peer.localDescription);
15     });
16
17     socket.on('answer', async (id, description) => {
18       const peer = peers[id];
19       await peer.setRemoteDescription(new RTCSessionDescription(description));
20     });
21
22     socket.on('candidate', async (id, candidate) => {
23       const peer = peers[id];
24       await peer.addIceCandidate(new RTCIceCandidate(candidate));
25     });
26
27     socket.on('disconnect', () => {
28       delete peers[socket.id];
29       console.log('A user disconnected:', socket.id);
30     });
31   });

[d] Handling ICE Candidates

ICE (Interactive Connectivity Establishment) candidates are used to find the best path to connect peers. Add the following logic to handle ICE candidates:

JavaScript

1   io.on('connection', socket => {
2     console.log('A user connected:', socket.id);
3
4     const peer = new RTCPeerConnection();
5     peers[socket.id] = peer;
6
7     peer.onicecandidate = event => {
8       if (event.candidate) {
9         socket.emit('candidate', socket.id, event.candidate);
10       }
11     };
12
13     socket.on('offer', async (id, description) => {
14       await peer.setRemoteDescription(new RTCSessionDescription(description));
15       const answer = await peer.createAnswer();
16       await peer.setLocalDescription(answer);
17       socket.emit('answer', id, peer.localDescription);
18     });
19
20     socket.on('answer', async (id, description) => {
21       await peer.setRemoteDescription(new RTCSessionDescription(description));
22     });
23
24     socket.on('candidate', async (id, candidate) => {
25       await peer.addIceCandidate(new RTCIceCandidate(candidate));
26     });
27
28     socket.on('disconnect', () => {
29       delete peers[socket.id];
30       console.log('A user disconnected:', socket.id);
31     });
32   });
With these steps, you have set up the initial configuration for your Node-WebRTC application. This includes creating a basic server, initializing WebRTC peer connections, and handling ICE candidates. This foundational setup will allow you to build more advanced features and functionalities in the subsequent steps.

Step 2: Wireframe All the Components

In this section, we will define the structure and essential components required for your Node-WebRTC application. This involves setting up the user interface (UI) and the backend logic to manage connections and interactions between peers.

Define Component Structure

A well-organized component structure is crucial for the maintainability and scalability of your application. Here's an overview of the essential components we'll be working with:
  1. HTML/CSS for the UI
  2. JavaScript for handling user interactions and WebRTC logic
Let's start by creating the basic HTML and CSS for the UI.

HTML and CSS for the UI

[a] Create an HTML File

Create a new file named index.html in the src/views directory:

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>Node-WebRTC App</title>
7       <link rel="stylesheet" href="styles.css">
8   </head>
9   <body>
10       <div id="container">
11           <h1>Node-WebRTC Video Chat</h1>
12           <div id="join-screen">
13               <input type="text" id="room-name" placeholder="Enter Room Name">
14               <button id="join-btn">Join</button>
15           </div>
16           <div id="video-chat" style="display: none;">
17               <video id="local-video" autoplay playsinline></video>
18               <div id="remote-videos"></div>
19               <button id="leave-btn">Leave</button>
20           </div>
21       </div>
22       <script src="/socket.io/socket.io.js"></script>
23       <script src="script.js"></script>
24   </body>
25   </html>

[b] Create a CSS File

Create a new file named styles.css in the src/views directory and add the following styles:

CSS

1   body {
2       font-family: Arial, sans-serif;
3       display: flex;
4       justify-content: center;
5       align-items: center;
6       height: 100vh;
7       margin: 0;
8       background-color: #f0f0f0;
9   }
10
11   #container {
12       text-align: center;
13       background: white;
14       padding: 20px;
15       border-radius: 8px;
16       box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
17   }
18
19   #join-screen, #video-chat {
20       margin-top: 20px;
21   }
22
23   video {
24       width: 300px;
25       border: 1px solid #ddd;
26       margin: 5px;
27   }

JavaScript for Handling User Interactions

Next, we will create the JavaScript logic to manage user interactions, such as joining a room and handling WebRTC connections.

[a] Create a JavaScript File

Create a new file named script.js in the src/views directory and add the following code:

JavaScript

1   const socket = io();
2
3   const joinScreen = document.getElementById('join-screen');
4   const videoChat = document.getElementById('video-chat');
5   const joinBtn = document.getElementById('join-btn');
6   const leaveBtn = document.getElementById('leave-btn');
7   const roomNameInput = document.getElementById('room-name');
8   const localVideo = document.getElementById('local-video');
9   const remoteVideos = document.getElementById('remote-videos');
10
11   let localStream;
12   let peerConnections = {};
13
14   joinBtn.addEventListener('click', () => {
15       const roomName = roomNameInput.value;
16       if (roomName) {
17           joinRoom(roomName);
18       }
19   });
20
21   leaveBtn.addEventListener('click', leaveRoom);
22
23   async function joinRoom(roomName) {
24       joinScreen.style.display = 'none';
25       videoChat.style.display = 'block';
26
27       localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
28       localVideo.srcObject = localStream;
29
30       socket.emit('join', roomName);
31
32       socket.on('offer', async (id, description) => {
33           const peerConnection = new RTCPeerConnection();
34           peerConnections[id] = peerConnection;
35
36           localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
37
38           await peerConnection.setRemoteDescription(new RTCSessionDescription(description));
39           const answer = await peerConnection.createAnswer();
40           await peerConnection.setLocalDescription(answer);
41
42           socket.emit('answer', id, peerConnection.localDescription);
43
44           peerConnection.ontrack = event => {
45               const remoteVideo = document.createElement('video');
46               remoteVideo.srcObject = event.streams[0];
47               remoteVideo.autoplay = true;
48               remoteVideos.appendChild(remoteVideo);
49           };
50
51           peerConnection.onicecandidate = event => {
52               if (event.candidate) {
53                   socket.emit('candidate', id, event.candidate);
54               }
55           };
56       });
57
58       socket.on('answer', async (id, description) => {
59           const peerConnection = peerConnections[id];
60           await peerConnection.setRemoteDescription(new RTCSessionDescription(description));
61       });
62
63       socket.on('candidate', (id, candidate) => {
64           const peerConnection = peerConnections[id];
65           peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
66       });
67
68       socket.on('leave', id => {
69           const peerConnection = peerConnections[id];
70           peerConnection.close();
71           delete peerConnections[id];
72       });
73   }
74
75   function leaveRoom() {
76       for (let id in peerConnections) {
77           peerConnections[id].close();
78           delete peerConnections[id];
79       }
80       localStream.getTracks().forEach(track => track.stop());
81       joinScreen.style.display = 'block';
82       videoChat.style.display = 'none';
83       socket.emit('leave');
84   }
In this step, we've set up the basic structure of our Node-WebRTC application. We've created the HTML and CSS for the user interface and added JavaScript logic to manage user interactions and WebRTC connections. This setup will serve as the foundation for building more advanced features in the subsequent steps.

Step 3: Implement Join Screen

In this section, we will implement the join screen functionality of your Node-WebRTC application. This screen will allow users to enter a room name and join a video chat session. We will create the necessary UI elements and add JavaScript to handle user input and signaling.

Create the Join Screen UI

The join screen is where users will input the room name they want to join. We have already created the basic HTML structure in the previous step. Now, let's focus on the JavaScript functionality to handle user input and connect them to the specified room.

Enhance the Join Screen HTML

Ensure your index.html file has the following structure for the join screen:

HTML

1   <div id="join-screen">
2       <input type="text" id="room-name" placeholder="Enter Room Name">
3       <button id="join-btn">Join</button>
4   </div>
5   <div id="video-chat" style="display: none;">
6       <video id="local-video" autoplay playsinline></video>
7       <div id="remote-videos"></div>
8       <button id="leave-btn">Leave</button>
9   </div>

JavaScript to Handle Join Screen Functionality

In your script.js file, add the following code to manage the join screen interactions and signaling:

JavaScript

1   const socket = io();
2
3   const joinScreen = document.getElementById('join-screen');
4   const videoChat = document.getElementById('video-chat');
5   const joinBtn = document.getElementById('join-btn');
6   const leaveBtn = document.getElementById('leave-btn');
7   const roomNameInput = document.getElementById('room-name');
8   const localVideo = document.getElementById('local-video');
9   const remoteVideos = document.getElementById('remote-videos');
10
11   let localStream;
12   let peerConnections = {};
13
14   joinBtn.addEventListener('click', () => {
15       const roomName = roomNameInput.value;
16       if (roomName) {
17           joinRoom(roomName);
18       }
19   });
20
21   leaveBtn.addEventListener('click', leaveRoom);
22
23   async function joinRoom(roomName) {
24       joinScreen.style.display = 'none';
25       videoChat.style.display = 'block';
26
27       localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
28       localVideo.srcObject = localStream;
29
30       socket.emit('join', roomName);
31
32       socket.on('offer', async (id, description) => {
33           const peerConnection = new RTCPeerConnection();
34           peerConnections[id] = peerConnection;
35
36           localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
37
38           await peerConnection.setRemoteDescription(new RTCSessionDescription(description));
39           const answer = await peerConnection.createAnswer();
40           await peerConnection.setLocalDescription(answer);
41
42           socket.emit('answer', id, peerConnection.localDescription);
43
44           peerConnection.ontrack = event => {
45               const remoteVideo = document.createElement('video');
46               remoteVideo.srcObject = event.streams[0];
47               remoteVideo.autoplay = true;
48               remoteVideo.playsinline = true;
49               remoteVideos.appendChild(remoteVideo);
50           };
51
52           peerConnection.onicecandidate = event => {
53               if (event.candidate) {
54                   socket.emit('candidate', id, event.candidate);
55               }
56           };
57       });
58
59       socket.on('answer', async (id, description) => {
60           const peerConnection = peerConnections[id];
61           await peerConnection.setRemoteDescription(new RTCSessionDescription(description));
62       });
63
64       socket.on('candidate', (id, candidate) => {
65           const peerConnection = peerConnections[id];
66           peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
67       });
68
69       socket.on('leave', id => {
70           const peerConnection = peerConnections[id];
71           peerConnection.close();
72           delete peerConnections[id];
73       });
74   }
75
76   function leaveRoom() {
77       for (let id in peerConnections) {
78           peerConnections[id].close();
79           delete peerConnections[id];
80       }
81       localStream.getTracks().forEach(track => track.stop());
82       joinScreen.style.display = 'block';
83       videoChat.style.display = 'none';
84       socket.emit('leave');
85   }

Join Screen Functionality

In the JavaScript code, we have implemented the following functionality:

Joining a Room

  • When the user clicks the "Join" button, the joinRoom function is called with the room name entered by the user.
  • The join screen is hidden, and the video chat screen is displayed.
  • The user's media stream is captured using getUserMedia and displayed in the local video element.
  • The user joins the specified room by emitting a join event to the server.

Handling Offers

  • When an offer is received from the server, a new RTCPeerConnection is created for the peer.
  • The local media tracks are added to the peer connection.
  • The remote description is set, and an answer is created and sent back to the server.
  • The remote video streams are displayed when tracks are received.

Handling Answers and ICE Candidates

  • Answers and ICE candidates received from the server are set on the appropriate peer connection.

Leaving a Room

  • When the user clicks the "Leave" button, the leaveRoom function is called.
  • All peer connections are closed, and the local media tracks are stopped.
  • The user leaves the room by emitting a leave event to the server.
With these steps, we have successfully implemented the join screen functionality for your Node-WebRTC application. Users can now join a room, establish peer connections, and start video chatting with other participants in the room. The next steps will focus on enhancing the user experience and adding more controls to manage the video chat session.

Step 4: Implement Controls

In this section, we will add user controls to our Node-WebRTC application. These controls will allow users to manage their video and audio streams, such as muting/unmuting the microphone and enabling/disabling the video. This step will enhance the user experience by providing essential functionality for real-time communication.

Adding User Controls

We will start by updating our HTML to include buttons for muting/unmuting the microphone and enabling/disabling the video. Then, we will implement the JavaScript logic to handle these controls.

[a] Enhance the Video Chat UI

Update your index.html file to include buttons for the user controls:

HTML

1   <div id="video-chat" style="display: none;">
2       <video id="local-video" autoplay playsinline></video>
3       <div id="remote-videos"></div>
4       <div id="controls">
5           <button id="mute-btn">Mute</button>
6           <button id="video-btn">Disable Video</button>
7           <button id="leave-btn">Leave</button>
8       </div>
9   </div>

[b] Add CSS for the Controls

Update your styles.css file to style the control buttons:

CSS

1   #controls {
2       margin-top: 10px;
3   }
4
5   #controls button {
6       margin: 5px;
7       padding: 10px;
8       font-size: 16px;
9       cursor: pointer;
10   }

[c] JavaScript to Handle Control Functionality

Update your script.js file to implement the logic for the user controls:

JavaScript

1   const socket = io();
2
3   const joinScreen = document.getElementById('join-screen');
4   const videoChat = document.getElementById('video-chat');
5   const joinBtn = document.getElementById('join-btn');
6   const leaveBtn = document.getElementById('leave-btn');
7   const roomNameInput = document.getElementById('room-name');
8   const localVideo = document.getElementById('local-video');
9   const remoteVideos = document.getElementById('remote-videos');
10   const muteBtn = document.getElementById('mute-btn');
11   const videoBtn = document.getElementById('video-btn');
12
13   let localStream;
14   let peerConnections = {};
15   let isMuted = false;
16   let isVideoDisabled = false;
17
18   joinBtn.addEventListener('click', () => {
19       const roomName = roomNameInput.value;
20       if (roomName) {
21           joinRoom(roomName);
22       }
23   });
24
25   leaveBtn.addEventListener('click', leaveRoom);
26   muteBtn.addEventListener('click', toggleMute);
27   videoBtn.addEventListener('click', toggleVideo);
28
29   async function joinRoom(roomName) {
30       joinScreen.style.display = 'none';
31       videoChat.style.display = 'block';
32
33       localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
34       localVideo.srcObject = localStream;
35
36       socket.emit('join', roomName);
37
38       socket.on('offer', async (id, description) => {
39           const peerConnection = new RTCPeerConnection();
40           peerConnections[id] = peerConnection;
41
42           localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
43
44           await peerConnection.setRemoteDescription(new RTCSessionDescription(description));
45           const answer = await peerConnection.createAnswer();
46           await peerConnection.setLocalDescription(answer);
47
48           socket.emit('answer', id, peerConnection.localDescription);
49
50           peerConnection.ontrack = event => {
51               const remoteVideo = document.createElement('video');
52               remoteVideo.srcObject = event.streams[0];
53               remoteVideo.autoplay = true;
54               remoteVideo.playsinline = true;
55               remoteVideos.appendChild(remoteVideo);
56           };
57
58           peerConnection.onicecandidate = event => {
59               if (event.candidate) {
60                   socket.emit('candidate', id, event.candidate);
61               }
62           };
63       });
64
65       socket.on('answer', async (id, description) => {
66           const peerConnection = peerConnections[id];
67           await peerConnection.setRemoteDescription(new RTCSessionDescription(description));
68       });
69
70       socket.on('candidate', (id, candidate) => {
71           const peerConnection = peerConnections[id];
72           peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
73       });
74
75       socket.on('leave', id => {
76           const peerConnection = peerConnections[id];
77           peerConnection.close();
78           delete peerConnections[id];
79       });
80   }
81
82   function leaveRoom() {
83       for (let id in peerConnections) {
84           peerConnections[id].close();
85           delete peerConnections[id];
86       }
87       localStream.getTracks().forEach(track => track.stop());
88       joinScreen.style.display = 'block';
89       videoChat.style.display = 'none';
90       socket.emit('leave');
91   }
92
93   function toggleMute() {
94       isMuted = !isMuted;
95       localStream.getAudioTracks()[0].enabled = !isMuted;
96       muteBtn.textContent = isMuted ? 'Unmute' : 'Mute';
97   }
98
99   function toggleVideo() {
100       isVideoDisabled = !isVideoDisabled;
101       localStream.getVideoTracks()[0].enabled = !isVideoDisabled;
102       videoBtn.textContent = isVideoDisabled ? 'Enable Video' : 'Disable Video';
103   }

Control Handlers

In the JavaScript code, we have implemented the following functionality:

Mute/Unmute Microphone

  • The toggleMute function toggles the audio track's enabled state.
  • The button text changes to reflect the current state ("Mute" or "Unmute").

Enable/Disable Video

  • The toggleVideo function toggles the video track's enabled state.
  • The button text changes to reflect the current state ("Disable Video" or "Enable Video").

Joining a Room

  • When the user clicks the "Join" button, the joinRoom function is called with the room name entered by the user.
  • The join screen is hidden, and the video chat screen is displayed.
  • The user's media stream is captured using getUserMedia and displayed in the local video element.
  • The user joins the specified room by emitting a join event to the server.

Handling Offers

  • When an offer is received from the server, a new RTCPeerConnection is created for the peer.
  • The local media tracks are added to the peer connection.
  • The remote description is set, and an answer is created and sent back to the server.
  • The remote video streams are displayed when tracks are received.

Handling Answers and ICE Candidates

  • Answers and ICE candidates received from the server are set on the appropriate peer connection.

Leaving a Room

  • When the user clicks the "Leave" button, the leaveRoom function is called.
  • All peer connections are closed, and the local media tracks are stopped.
  • The user leaves the room by emitting a leave event to the server.
With these steps, we have successfully implemented user controls for muting/unmuting the microphone and enabling/disabling the video in your Node-WebRTC application. These controls enhance the user experience by providing essential functionality for managing the video chat session. The next step will focus on implementing the participant view to display remote video streams.

Get Free 10,000 Minutes Every Months

No credit card required to start.

Step 5: Implement Participant View

In this section, we will focus on implementing the participant view for your Node-WebRTC application. This view will handle the display of remote video streams from other participants in the chat room. We will enhance our existing JavaScript code to manage multiple video streams effectively.

Rendering Participants

To effectively display remote video streams, we need to ensure that our application can dynamically create and manage video elements for each participant. We will update our script.js file to include the necessary logic.

JavaScript to Handle Participant Views

Update your script.js file with the following code to manage remote video streams:

JavaScript

1   const socket = io();
2
3   const joinScreen = document.getElementById('join-screen');
4   const videoChat = document.getElementById('video-chat');
5   const joinBtn = document.getElementById('join-btn');
6   const leaveBtn = document.getElementById('leave-btn');
7   const roomNameInput = document.getElementById('room-name');
8   const localVideo = document.getElementById('local-video');
9   const remoteVideos = document.getElementById('remote-videos');
10   const muteBtn = document.getElementById('mute-btn');
11   const videoBtn = document.getElementById('video-btn');
12
13   let localStream;
14   let peerConnections = {};
15   let isMuted = false;
16   let isVideoDisabled = false;
17
18   joinBtn.addEventListener('click', () => {
19       const roomName = roomNameInput.value;
20       if (roomName) {
21           joinRoom(roomName);
22       }
23   });
24
25   leaveBtn.addEventListener('click', leaveRoom);
26   muteBtn.addEventListener('click', toggleMute);
27   videoBtn.addEventListener('click', toggleVideo);
28
29   async function joinRoom(roomName) {
30       joinScreen.style.display = 'none';
31       videoChat.style.display = 'block';
32
33       localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
34       localVideo.srcObject = localStream;
35
36       socket.emit('join', roomName);
37
38       socket.on('offer', async (id, description) => {
39           const peerConnection = new RTCPeerConnection();
40           peerConnections[id] = peerConnection;
41
42           localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
43
44           await peerConnection.setRemoteDescription(new RTCSessionDescription(description));
45           const answer = await peerConnection.createAnswer();
46           await peerConnection.setLocalDescription(answer);
47
48           socket.emit('answer', id, peerConnection.localDescription);
49
50           peerConnection.ontrack = event => {
51               const remoteVideo = createRemoteVideoElement(id, event.streams[0]);
52               remoteVideos.appendChild(remoteVideo);
53           };
54
55           peerConnection.onicecandidate = event => {
56               if (event.candidate) {
57                   socket.emit('candidate', id, event.candidate);
58               }
59           };
60       });
61
62       socket.on('answer', async (id, description) => {
63           const peerConnection = peerConnections[id];
64           await peerConnection.setRemoteDescription(new RTCSessionDescription(description));
65       });
66
67       socket.on('candidate', (id, candidate) => {
68           const peerConnection = peerConnections[id];
69           peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
70       });
71
72       socket.on('leave', id => {
73           const peerConnection = peerConnections[id];
74           peerConnection.close();
75           delete peerConnections[id];
76           removeRemoteVideoElement(id);
77       });
78   }
79
80   function leaveRoom() {
81       for (let id in peerConnections) {
82           peerConnections[id].close();
83           delete peerConnections[id];
84       }
85       localStream.getTracks().forEach(track => track.stop());
86       joinScreen.style.display = 'block';
87       videoChat.style.display = 'none';
88       socket.emit('leave');
89       remoteVideos.innerHTML = '';
90   }
91
92   function toggleMute() {
93       isMuted = !isMuted;
94       localStream.getAudioTracks()[0].enabled = !isMuted;
95       muteBtn.textContent = isMuted ? 'Unmute' : 'Mute';
96   }
97
98   function toggleVideo() {
99       isVideoDisabled = !isVideoDisabled;
100       localStream.getVideoTracks()[0].enabled = !isVideoDisabled;
101       videoBtn.textContent = isVideoDisabled ? 'Enable Video' : 'Disable Video';
102   }
103
104   function createRemoteVideoElement(id, stream) {
105       const video = document.createElement('video');
106       video.id = `remote-video-${id}`;
107       video.srcObject = stream;
108       video.autoplay = true;
109       video.playsinline = true;
110       return video;
111   }
112
113   function removeRemoteVideoElement(id) {
114       const video = document.getElementById(`remote-video-${id}`);
115       if (video) {
116           video.remove();
117       }
118   }

Participant View Management

In the JavaScript code, we have implemented the following functionality to manage the participant view:

Creating Remote Video Elements

  • The createRemoteVideoElement function dynamically creates a video element for a remote participant and sets its source to the remote media stream.
  • This function is called whenever a new stream is received from a remote peer.

Adding Remote Video Elements

  • When a remote peer sends an offer, a new RTCPeerConnection is created, and the remote video stream is appended to the remoteVideos container.
  • The ontrack event of the RTCPeerConnection handles the addition of the remote video element.

Removing Remote Video Elements

  • When a remote peer leaves the room, the corresponding RTCPeerConnection is closed, and the remote video element is removed from the remoteVideos container.
  • The removeRemoteVideoElement function is used to remove the video element by its ID.

Updating the Local Stream

  • The local media stream is captured using getUserMedia and displayed in the local video element.
  • The local stream tracks are added to the peer connections for sharing with remote participants.

Handling Room Leave

  • When the user leaves the room, all peer connections are closed, the local media tracks are stopped, and the remoteVideos container is cleared.
With these steps, we have successfully implemented the participant view for your Node-WebRTC application. The application can now dynamically create and manage video elements for multiple participants, enhancing the user experience by providing a seamless real-time video chat experience. The final step will focus on running and testing your application to ensure everything works as expected.

Step 6: Run Your Code Now

In this final step, we will focus on running and testing your Node-WebRTC application to ensure that everything is working as expected. We will also address some common issues you might encounter and how to troubleshoot them.

Running the Node-WebRTC Application

Start the Server

Ensure you are in the root directory of your project (where package.json is located). Start your server using the following command:

bash

1   node src/index.js
This command will start your Node.js server, and you should see a message indicating that the server is listening on http://localhost:3000.

Open the Application in a Browser

Open your web browser and navigate to http://localhost:3000. You should see the join screen of your Node-WebRTC application.

Join a Room

  • Enter a room name in the input field and click the "Join" button.
  • Allow access to your microphone and camera when prompted by the browser.
  • You should see your local video stream displayed on the screen.

Test with Multiple Participants

  • Open another browser tab or use a different device to join the same room.
  • You should see both local and remote video streams displayed on the screen.
  • Test the mute/unmute and enable/disable video functionality to ensure that the controls are working correctly.

Troubleshooting Common Issues

No Video/Audio Stream

  • Ensure that you have granted the necessary permissions for the browser to access your microphone and camera.
  • Check if your device's camera and microphone are working correctly with other applications.

Unable to Connect to Room

  • Ensure that the server is running and accessible at http://localhost:3000.
  • Check if there are any errors in the browser console or the server logs that might indicate issues with the connection or signaling.

Remote Video Not Displayed

  • Verify that the signaling messages (offer, answer, and ICE candidates) are being exchanged correctly between peers.
  • Ensure that the RTCPeerConnection is properly set up and that media tracks are being added to the connection.

Poor Video/Audio Quality

  • Check your network connection for any issues that might affect the quality of the video and audio streams.
  • Consider adjusting the media constraints in the getUserMedia function to optimize the quality based on your network conditions.

Conclusion

With your Node-WebRTC application now up and running, you have a solid foundation for building real-time communication features. This tutorial has guided you through setting up the server, creating the join screen, implementing user controls, and managing participant views.

Want to level-up your learning? Subscribe now

Subscribe to our newsletter for more tech based insights

FAQ