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

How to Build Simple-Peer WebRTC App with JavaScript?

Learn how to use Simple-Peer for WebRTC to build real-time peer-to-peer communication applications with Node.js and JavaScript. Step-by-step guide including code snippets, controls, and participant management.

Introduction to Simple-Peer WebRTC

What is Simple-Peer?

Simple-Peer is an efficient, lightweight library designed to streamline the complexities of establishing WebRTC connections in both Node.js and JavaScript environments. Developed by Feross Aboukhadijeh, Simple-Peer simplifies peer-to-peer (P2P) data streams and video/audio call setups, making it a go-to solution for developers seeking robust and reliable real-time communication tools.

Benefits and Use Cases

Simple-Peer excels in facilitating direct P2P communication, making it ideal for a variety of applications. These include video conferencing, file sharing, and real-time data transfer in web and mobile apps. The library's ease of use and minimal configuration requirements enable developers to quickly integrate P2P functionality, enhancing the user experience with low-latency and high-quality connections.

How Simple-Peer Works?

By abstracting the underlying WebRTC API complexities, Simple-Peer allows developers to focus on building features rather than managing connections. Its intuitive API provides straightforward methods for creating, connecting, and managing peers, handling tasks such as signaling, NAT traversal, and media stream management seamlessly.
In the following sections, we'll delve deeper into setting up and utilizing Simple-Peer in your projects, providing step-by-step instructions and practical examples to help you get started quickly and efficiently.

Getting Started with Simple-Peer

Create a New Simple-Peer App

To begin, you'll need to set up a new Node.js project. Open your terminal and create a new directory for your project:

bash

1mkdir simple-peer-app
2cd simple-peer-app
Initialize a new Node.js project with:

bash

1npm init -y
This command creates a package.json file with default settings.

Install Simple-Peer

Next, install the Simple-Peer library and any necessary dependencies:

bash

1npm install simple-peer
You can also install additional packages like express for serving the application and socket.io for signaling:

bash

1npm install express socket.io

Structure of the Project

Organize your project directory as follows:
1simple-peer-app/
2├── index.html
3├── server.js
4├── public/
5│   ├── main.js
6│   └── style.css
7├── node_modules/
8└── package.json
  • index.html: The main HTML file.
  • server.js: The server script to handle signaling.
  • public/main.js: The client-side JavaScript file.
  • public/style.css: The CSS file for styling the app.

App Architecture

simple peer webrtc
The Simple-Peer app follows a basic architecture where a server handles signaling between peers, and the client-side JavaScript manages the peer connections. When two clients join the same room, the server facilitates the exchange of signaling data, allowing the peers to establish a direct WebRTC connection for real-time communication.
In the next sections, we'll build this project step-by-step, starting with the initial setup and moving towards a fully functional peer-to-peer communication app.

Step 1: Get Started with Initial Setup

Setting Up the Environment

First, let's set up the development environment. Ensure you have Node.js installed on your machine. If not, download and install it from

nodejs.org

.
Initialize your project directory and install the necessary dependencies as described in Part 2. Your package.json should now include Simple-Peer, Express, and Socket.io.

Basic Configuration

[a] Create server.js

This file will handle the signaling server, which is crucial for establishing WebRTC connections.

JavaScript

1   const express = require('express');
2   const http = require('http');
3   const socketIo = require('socket.io');
4
5   const app = express();
6   const server = http.createServer(app);
7   const io = socketIo(server);
8
9   app.use(express.static('public'));
10
11   io.on('connection', (socket) => {
12       socket.on('signal', (data) => {
13           io.emit('signal', data);
14       });
15   });
16
17   server.listen(3000, () => {
18       console.log('Server is running on port 3000');
19   });

[b] Create index.html

This is the main HTML file that includes references to your client-side JavaScript and CSS.

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>Simple-Peer WebRTC App</title>
7       <link rel="stylesheet" href="style.css">
8   </head>
9   <body>
10       <h1>Simple-Peer WebRTC App</h1>
11       <div id="video-container"></div>
12       <script src="/socket.io/socket.io.js"></script>
13       <script src="main.js"></script>
14   </body>
15   </html>

[c] Create public/main.js

This file will handle the client-side logic for creating and managing peer connections.

JavaScript

1   const socket = io();
2
3   socket.on('connect', () => {
4       console.log('Connected to signaling server');
5   });
6
7   socket.on('signal', (data) => {
8       // Handle signaling data here
9   });

[d] Create public/style.css

Add some basic styling for the application.

CSS

1   body {
2       font-family: Arial, sans-serif;
3       text-align: center;
4   }
5   #video-container {
6       display: flex;
7       justify-content: center;
8       align-items: center;
9       height: 80vh;
10   }
With these files in place, you've set up the initial environment for your Simple-Peer WebRTC application. In the next section, we'll dive into wireframing the components and adding the necessary functionality to establish peer connections.

Step 2: Wireframe All the Components

Creating the User Interface

First, let's create a basic user interface to host our video elements. Update your index.html to include containers for local and remote video streams:

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>Simple-Peer WebRTC App</title>
7    <link rel="stylesheet" href="style.css">
8</head>
9<body>
10    <h1>Simple-Peer WebRTC App</h1>
11    <div id="video-container">
12        <video id="localVideo" autoplay muted></video>
13        <video id="remoteVideo" autoplay></video>
14    </div>
15    <script src="/socket.io/socket.io.js"></script>
16    <script src="main.js"></script>
17</body>
18</html>

Styling the App

Next, add some CSS to style the video elements and the overall layout. Update public/style.css:

CSS

1body {
2    font-family: Arial, sans-serif;
3    text-align: center;
4    background-color: #f0f0f0;
5    margin: 0;
6    padding: 0;
7}
8
9#video-container {
10    display: flex;
11    justify-content: center;
12    align-items: center;
13    flex-direction: column;
14    height: 80vh;
15}
16
17video {
18    width: 45%;
19    margin: 10px;
20    border: 2px solid #333;
21    background-color: #000;
22}

Adding Event Listeners

Now, we need to add event listeners to manage user interactions and establish peer connections. Update public/main.js:

JavaScript

1const socket = io();
2const localVideo = document.getElementById('localVideo');
3const remoteVideo = document.getElementById('remoteVideo');
4let localStream;
5let peer;
6
7navigator.mediaDevices.getUserMedia({ video: true, audio: true })
8    .then(stream => {
9        localVideo.srcObject = stream;
10        localStream = stream;
11
12        // Create a new Simple-Peer instance
13        peer = new SimplePeer({
14            initiator: location.hash === '#init',
15            trickle: false,
16            stream: localStream
17        });
18
19        // Handle signaling data
20        peer.on('signal', data => {
21            socket.emit('signal', data);
22        });
23
24        // Handle incoming stream
25        peer.on('stream', stream => {
26            remoteVideo.srcObject = stream;
27        });
28
29        // Listen for signaling data from the server
30        socket.on('signal', data => {
31            peer.signal(data);
32        });
33    })
34    .catch(error => {
35        console.error('Error accessing media devices.', error);
36    });
37
38socket.on('connect', () => {
39    console.log('Connected to signaling server');
40});
41
42socket.on('signal', data => {
43    peer.signal(data);
44});
With these changes, your Simple-Peer app will have a basic user interface with local and remote video elements, styled for a better user experience. Event listeners are set up to handle user interactions and establish peer connections. In the next section, we will focus on implementing the join screen and connecting peers.

Step 3: Implement Join Screen

Building the Join Screen

To make your application user-friendly, we need to implement a join screen where users can enter a room name to connect with peers. Update your index.html to include a join screen:

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>Simple-Peer WebRTC App</title>
7    <link rel="stylesheet" href="style.css">
8</head>
9<body>
10    <h1>Simple-Peer WebRTC App</h1>
11    <div id="join-container">
12        <input type="text" id="roomInput" placeholder="Enter room name">
13        <button id="joinButton">Join Room</button>
14    </div>
15    <div id="video-container" style="display: none;">
16        <video id="localVideo" autoplay muted></video>
17        <video id="remoteVideo" autoplay></video>
18    </div>
19    <script src="/socket.io/socket.io.js"></script>
20    <script src="main.js"></script>
21</body>
22</html>

Styling the Join Screen

Add CSS to style the join screen elements. Update public/style.css:

CSS

1#join-container {
2    display: flex;
3    justify-content: center;
4    align-items: center;
5    flex-direction: column;
6    height: 80vh;
7}
8
9#roomInput, #joinButton {
10    padding: 10px;
11    margin: 10px;
12    font-size: 1.2em;
13}
14
15#video-container {
16    display: flex;
17    justify-content: center;
18    align-items: center;
19    flex-direction: column;
20    height: 80vh;
21}
22
23video {
24    width: 45%;
25    margin: 10px;
26    border: 2px solid #333;
27    background-color: #000;
28}

Connecting Peers

Modify public/main.js to handle the join functionality:

JavaScript

1const socket = io();
2const joinContainer = document.getElementById('join-container');
3const videoContainer = document.getElementById('video-container');
4const roomInput = document.getElementById('roomInput');
5const joinButton = document.getElementById('joinButton');
6const localVideo = document.getElementById('localVideo');
7const remoteVideo = document.getElementById('remoteVideo');
8let localStream;
9let peer;
10
11joinButton.addEventListener('click', () => {
12    const roomName = roomInput.value;
13    if (roomName) {
14        socket.emit('join-room', roomName);
15        joinContainer.style.display = 'none';
16        videoContainer.style.display = 'flex';
17        initializePeer();
18    }
19});
20
21function initializePeer() {
22    navigator.mediaDevices.getUserMedia({ video: true, audio: true })
23        .then(stream => {
24            localVideo.srcObject = stream;
25            localStream = stream;
26
27            // Create a new Simple-Peer instance
28            peer = new SimplePeer({
29                initiator: location.hash === '#init',
30                trickle: false,
31                stream: localStream
32            });
33
34            // Handle signaling data
35            peer.on('signal', data => {
36                socket.emit('signal', data);
37            });
38
39            // Handle incoming stream
40            peer.on('stream', stream => {
41                remoteVideo.srcObject = stream;
42            });
43
44            // Listen for signaling data from the server
45            socket.on('signal', data => {
46                peer.signal(data);
47            });
48        })
49        .catch(error => {
50            console.error('Error accessing media devices.', error);
51        });
52}
53
54socket.on('connect', () => {
55    console.log('Connected to signaling server');
56});
57
58socket.on('signal', data => {
59    peer.signal(data);
60});
61
62socket.on('joined-room', () => {
63    initializePeer();
64});

Handling Errors

Ensure that the application handles errors gracefully. Modify public/main.js to add error handling:

JavaScript

1navigator.mediaDevices.getUserMedia({ video: true, audio: true })
2    .then(stream => {
3        localVideo.srcObject = stream;
4        localStream = stream;
5
6        peer = new SimplePeer({
7            initiator: location.hash === '#init',
8            trickle: false,
9            stream: localStream
10        });
11
12        peer.on('signal', data => {
13            socket.emit('signal', data);
14        });
15
16        peer.on('stream', stream => {
17            remoteVideo.srcObject = stream;
18        });
19
20        peer.on('error', err => {
21            console.error('Peer error:', err);
22        });
23
24        socket.on('signal', data => {
25            peer.signal(data);
26        });
27    })
28    .catch(error => {
29        console.error('Error accessing media devices.', error);
30        alert('Could not access your camera and microphone. Please ensure they are connected and enabled.');
31    });
With these updates, your application now includes a join screen, allowing users to enter a room name and connect with peers. In the next section, we will implement controls for managing audio and video streams, as well as handling data channels.

Step 4: Implement Controls

Audio/Video Controls

To enhance user experience, we'll add controls to manage audio and video streams. Update your index.html to include buttons for these controls:

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>Simple-Peer WebRTC App</title>
7    <link rel="stylesheet" href="style.css">
8</head>
9<body>
10    <h1>Simple-Peer WebRTC App</h1>
11    <div id="join-container">
12        <input type="text" id="roomInput" placeholder="Enter room name">
13        <button id="joinButton">Join Room</button>
14    </div>
15    <div id="video-container" style="display: none;">
16        <video id="localVideo" autoplay muted></video>
17        <video id="remoteVideo" autoplay></video>
18        <div id="controls">
19            <button id="muteAudioButton">Mute Audio</button>
20            <button id="muteVideoButton">Mute Video</button>
21        </div>
22    </div>
23    <script src="/socket.io/socket.io.js"></script>
24    <script src="main.js"></script>
25</body>
26</html>
Update public/style.css to style these controls:

CSS

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

Implementing the Controls

Modify public/main.js to add functionality to the mute buttons:

JavaScript

1const socket = io();
2const joinContainer = document.getElementById('join-container');
3const videoContainer = document.getElementById('video-container');
4const roomInput = document.getElementById('roomInput');
5const joinButton = document.getElementById('joinButton');
6const localVideo = document.getElementById('localVideo');
7const remoteVideo = document.getElementById('remoteVideo');
8const muteAudioButton = document.getElementById('muteAudioButton');
9const muteVideoButton = document.getElementById('muteVideoButton');
10let localStream;
11let peer;
12let audioMuted = false;
13let videoMuted = false;
14
15joinButton.addEventListener('click', () => {
16    const roomName = roomInput.value;
17    if (roomName) {
18        socket.emit('join-room', roomName);
19        joinContainer.style.display = 'none';
20        videoContainer.style.display = 'flex';
21        initializePeer();
22    }
23});
24
25muteAudioButton.addEventListener('click', () => {
26    audioMuted = !audioMuted;
27    localStream.getAudioTracks()[0].enabled = !audioMuted;
28    muteAudioButton.textContent = audioMuted ? 'Unmute Audio' : 'Mute Audio';
29});
30
31muteVideoButton.addEventListener('click', () => {
32    videoMuted = !videoMuted;
33    localStream.getVideoTracks()[0].enabled = !videoMuted;
34    muteVideoButton.textContent = videoMuted ? 'Unmute Video' : 'Mute Video';
35});
36
37function initializePeer() {
38    navigator.mediaDevices.getUserMedia({ video: true, audio: true })
39        .then(stream => {
40            localVideo.srcObject = stream;
41            localStream = stream;
42
43            // Create a new Simple-Peer instance
44            peer = new SimplePeer({
45                initiator: location.hash === '#init',
46                trickle: false,
47                stream: localStream
48            });
49
50            // Handle signaling data
51            peer.on('signal', data => {
52                socket.emit('signal', data);
53            });
54
55            // Handle incoming stream
56            peer.on('stream', stream => {
57                remoteVideo.srcObject = stream;
58            });
59
60            peer.on('error', err => {
61                console.error('Peer error:', err);
62            });
63
64            socket.on('signal', data => {
65                peer.signal(data);
66            });
67        })
68        .catch(error => {
69            console.error('Error accessing media devices.', error);
70            alert('Could not access your camera and microphone. Please ensure they are connected and enabled.');
71        });
72}
73
74socket.on('connect', () => {
75    console.log('Connected to signaling server');
76});
77
78socket.on('signal', data => {
79    peer.signal(data);
80});
81
82socket.on('joined-room', () => {
83    initializePeer();
84});

Data Channels

To enable data transfer between peers, add a data channel. Update the initializePeer function in public/main.js:

JavaScript

1function initializePeer() {
2    navigator.mediaDevices.getUserMedia({ video: true, audio: true })
3        .then(stream => {
4            localVideo.srcObject = stream;
5            localStream = stream;
6
7            peer = new SimplePeer({
8                initiator: location.hash === '#init',
9                trickle: false,
10                stream: localStream
11            });
12
13            peer.on('signal', data => {
14                socket.emit('signal', data);
15            });
16
17            peer.on('stream', stream => {
18                remoteVideo.srcObject = stream;
19            });
20
21            peer.on('data', data => {
22                console.log('Received message:', data.toString());
23            });
24
25            peer.on('error', err => {
26                console.error('Peer error:', err);
27            });
28
29            socket.on('signal', data => {
30                peer.signal(data);
31            });
32        })
33        .catch(error => {
34            console.error('Error accessing media devices.', error);
35            alert('Could not access your camera and microphone. Please ensure they are connected and enabled.');
36        });
37}

Screen Sharing

Add functionality for screen sharing. Update index.html to include a button for screen sharing:

HTML

1<button id="shareScreenButton">Share Screen</button>
Add styles in public/style.css:

CSS

1#shareScreenButton {
2    padding: 10px;
3    margin: 5px;
4    font-size: 1em;
5}
Update public/main.js to handle screen sharing:

JavaScript

1const shareScreenButton = document.getElementById('shareScreenButton');
2
3shareScreenButton.addEventListener('click', () => {
4    navigator.mediaDevices.getDisplayMedia({ video: true })
5        .then(screenStream => {
6            peer.replaceTrack(localStream.getVideoTracks()[0], screenStream.getVideoTracks()[0], localStream);
7            localVideo.srcObject = screenStream;
8
9            screenStream.getVideoTracks()[0].addEventListener('ended', () => {
10                peer.replaceTrack(screenStream.getVideoTracks()[0], localStream.getVideoTracks()[0], localStream);
11                localVideo.srcObject = localStream;
12            });
13        })
14        .catch(error => {
15            console.error('Error sharing screen:', error);
16        });
17});
With these updates, your application now includes controls for managing audio and video streams, data channels for messaging, and screen sharing functionality. In the next section, we will implement the participant view to manage multiple participants effectively.

Get Free 10,000 Minutes Every Months

No credit card required to start.

Step 5: Implement Participant View

Displaying Participants

To manage multiple participants, we'll dynamically add video elements for each participant. First, update your index.html to include a container for remote videos:

HTML

1<div id="video-container" style="display: none;">
2    <video id="localVideo" autoplay muted></video>
3    <div id="remoteVideos"></div>
4    <div id="controls">
5        <button id="muteAudioButton">Mute Audio</button>
6        <button id="muteVideoButton">Mute Video</button>
7        <button id="shareScreenButton">Share Screen</button>
8    </div>
9</div>

Managing Multiple Streams

Update public/main.js to handle multiple remote streams. We'll modify the peer setup to include unique IDs for each peer connection:

JavaScript

1const remoteVideos = document.getElementById('remoteVideos');
2let peers = {};
3
4joinButton.addEventListener('click', () => {
5    const roomName = roomInput.value;
6    if (roomName) {
7        socket.emit('join-room', roomName);
8        joinContainer.style.display = 'none';
9        videoContainer.style.display = 'flex';
10        initializePeer();
11    }
12});
13
14function initializePeer(isInitiator = false, peerId) {
15    navigator.mediaDevices.getUserMedia({ video: true, audio: true })
16        .then(stream => {
17            localVideo.srcObject = stream;
18            localStream = stream;
19
20            const peer = new SimplePeer({
21                initiator: isInitiator,
22                trickle: false,
23                stream: localStream
24            });
25
26            peer.on('signal', data => {
27                socket.emit('signal', { peerId, data });
28            });
29
30            peer.on('stream', stream => {
31                addRemoteStream(stream, peerId);
32            });
33
34            peer.on('data', data => {
35                console.log('Received message:', data.toString());
36            });
37
38            peer.on('error', err => {
39                console.error('Peer error:', err);
40            });
41
42            peers[peerId] = peer;
43        })
44        .catch(error => {
45            console.error('Error accessing media devices.', error);
46            alert('Could not access your camera and microphone. Please ensure they are connected and enabled.');
47        });
48}
49
50socket.on('connect', () => {
51    console.log('Connected to signaling server');
52});
53
54socket.on('signal', ({ peerId, data }) => {
55    if (!peers[peerId]) {
56        initializePeer(false, peerId);
57    }
58    peers[peerId].signal(data);
59});
60
61socket.on('joined-room', ({ peerId }) => {
62    initializePeer(true, peerId);
63});
64
65function addRemoteStream(stream, peerId) {
66    const videoElement = document.createElement('video');
67    videoElement.id = `video-${peerId}`;
68    videoElement.srcObject = stream;
69    videoElement.autoplay = true;
70    remoteVideos.appendChild(videoElement);
71}

Dynamic UI Updates

To keep the UI updated as participants join or leave, modify public/main.js to handle disconnections:

JavaScript

1socket.on('peer-disconnected', peerId => {
2    const videoElement = document.getElementById(`video-${peerId}`);
3    if (videoElement) {
4        videoElement.remove();
5    }
6    delete peers[peerId];
7});
Update the server-side code in server.js to notify clients about disconnections:

JavaScript

1io.on('connection', (socket) => {
2    const { id } = socket;
3
4    socket.on('join-room', (room) => {
5        socket.join(room);
6        socket.to(room).emit('joined-room', { peerId: id });
7    });
8
9    socket.on('signal', ({ peerId, data }) => {
10        io.to(peerId).emit('signal', { peerId: id, data });
11    });
12
13    socket.on('disconnect', () => {
14        io.emit('peer-disconnected', id);
15    });
16});
With these changes, your Simple-Peer application now supports multiple participants, dynamically adding and removing video elements as peers join and leave the session. In the next section, we'll guide you through running your application and performing final tests.

Step 6: Run Your Code Now

Testing the Application

Now that we have implemented all the essential features, it's time to test your Simple-Peer WebRTC application. Follow these steps to run and test your application locally:

Start the Server

Open your terminal, navigate to your project directory, and run:

bash

1    node server.js
This command starts your Express server, which will handle signaling between peers.

Open the Application in the Browser

Open your web browser and navigate to http://localhost:3000. You'll see the join screen where you can enter a room name and join the session.

Test Peer Connections

Open another tab or a different browser and join the same room. You should see both local and remote video streams appear.

Test Controls

Use the Mute Audio, Mute Video, and Share Screen buttons to verify that the controls work as expected. Ensure that video and audio streams toggle on/off correctly and that screen sharing functionality works seamlessly.

Debugging Tips

  • Console Logs: Check the browser console for any error messages. These logs can help you identify issues with peer connections, media devices, or signaling.
  • Network Issues: Ensure that your network allows WebRTC traffic. Firewalls or restrictive network policies can sometimes block peer-to-peer connections.
  • Media Permissions: Ensure that your browser has the necessary permissions to access the camera and microphone.

Deploying Your Application

To make your application accessible to others, deploy it on a web server. You can use platforms like Heroku, Vercel, or DigitalOcean. Here’s a basic guide to deploying on Heroku:

Initialize a Git Repository

If you haven't already, initialize a git repository in your project directory:

bash

1    git init
2    git add .
3    git commit -m "Initial commit"

Deploy to Heroku

bash

1    heroku create
2    git push heroku master

Open Your Deployed App

Once the deployment is complete, open your application using the URL provided by Heroku.

Common Issues and Resolutions

  • Peer Connection Issues: Ensure that your signaling server is running correctly and that peers are exchanging signaling data.
  • Media Device Errors: Verify that your media devices are properly connected and that your browser has permission to access them.
  • Network Configuration: Check your network configuration and firewall settings to ensure they allow WebRTC traffic.
With these steps, you should have a fully functional Simple-Peer WebRTC application running and accessible to others. In the final part, we'll summarize the key points and address frequently asked questions.

Conclusion

In this article, we've explored how to build a Simple-Peer WebRTC application using Node.js and JavaScript. We've covered setting up the project, creating a user interface, handling peer connections, implementing audio/video controls, and managing multiple participants. By following these steps, you can create robust P2P communication applications for various use cases like video conferencing, real-time data sharing, and more.

Want to level-up your learning? Subscribe now

Subscribe to our newsletter for more tech based insights

FAQ