/

Using WebRTC for Real-Time Webcam Communication

Using WebRTC for Real-Time Webcam Communication

Learn how to leverage WebRTC to create a direct webcam communication application with this simple tutorial.

WebRTC, which stands for Web Real-Time Communication, allows for the creation of direct data communication between web browsers. It enables various functionalities such as audio and video streaming, file sharing, video chat, peer-to-peer data sharing services, and multiplayer games, among others. The goal is to make real-time communication applications easy to create by leveraging Web technologies, eliminating the need for third-party plugins or external technologies.

WebRTC is supported by all modern browsers, with the exception of partial support from Microsoft Edge, which does not support the RTCDataChannel API.

WebRTC provides the following APIs:

  • MediaStream: Enables access to data streams from the user’s end, such as the camera and microphone.
  • RTCPeerConnection: Handles audio and video streaming between peers.
  • RTCDataChannel: Handles communication of other types of data.

For video and audio communication, the MediaStream and RTCPeerConnection APIs are used. On the other hand, applications like gaming and file sharing rely on the RTCDataChannel API.

In this article, we will create an example using WebRTC to connect two remote webcams, utilizing a WebSocket server implemented in Node.js.

Tip: In real-world projects, you would most likely use a library that abstracts many of the technical details. However, this tutorial aims to explain the WebRTC technology so that you understand what is happening under the hood.

MediaStream

The MediaStream API allows you to access the camera and microphone stream using JavaScript. Here is a simple example that demonstrates how to access the video camera and display the video on a webpage:

WebRTC MediaStream simple example

The example consists of a button that requests access to the camera and a video element with the autoplay attribute. We also include the WebRTC Adapter, which helps with cross-browser compatibility.

1
2
3
<button id="get-access">Get access to camera</button>
<video autoplay></video>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>

The JavaScript code listens for a click on the button and calls the navigator.mediaDevices.getUserMedia() method to request video access. It then sets the obtained stream as the source for the video element:

1
2
3
4
5
6
7
8
9
10
11
12
13
document.querySelector('#get-access').addEventListener('click', async function init(e) {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
document.querySelector('video').srcObject = stream;
document.querySelector('#get-access').setAttribute('hidden', true);
setTimeout(() => {
track.stop();
}, 3 * 1000);
} catch (error) {
alert(`${error.name}`);
console.error(error);
}
});

The getUserMedia() method can also specify additional requirements for the video stream, such as minimum and maximum aspect ratio, minimum frame rate, maximum width, and maximum height.

Signaling

Signaling is an essential part of real-time communication, although it is not part of the WebRTC protocol itself.
Signaling involves devices communicating with each other to agree on how to initialize the communication, sharing information like IP addresses, ports, and resolutions. You can choose any communication mechanism for signaling, such as WebSockets, Channel Messaging API, or XHR.

In this example, we will use WebSockets for signaling. To get started, install the ws library using npm:

1
2
npm init
npm install ws

Next, create a WebSocket server using the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
console.log('User connected');

ws.on('message', (message) => {
console.log(`Received message => ${message}`);
});

ws.on('close', () => {
//handle closing
});
});

On the frontend, add a username input field and a login button:

1
2
3
4
5
<div id="login">
<label for="username">Login</label>
<input id="username" placeholder="Login" required autofocus />
<button id="login">Login</button>
</div>

In the client-side JavaScript, establish a WebSocket connection to the server:

1
2
3
4
5
6
7
8
9
const ws = new WebSocket('ws://localhost:8080');

ws.onopen = () => {
console.log('Connected to the signaling server');
};

ws.onerror = (err) => {
console.error(err);
};

When the user enters their username and clicks the login button, retrieve the username value, check its validity, and send it to the server:

1
2
3
4
5
6
7
8
9
10
11
12
13
document.querySelector('button#login').addEventListener('click', (event) => {
const username = document.querySelector('input#username').value;

if (username.length === 0) {
alert('Please enter a username 🙂');
return;
}

sendMessage({
type: 'login',
username: username
});
});

In the code above, sendMessage is a helper function for sending a JSON-encoded message to the WebSocket server. The server receives the message, decodes it, and handles different types of messages accordingly.

Once the login is successful, the client requests access to the camera by calling navigator.mediaDevices.getUserMedia(). Upon receiving the local stream, the client hides the login div and displays a div with video elements for local and remote streams. It also starts streaming the local stream on the video element with the id “local”.

To establish an RTCPeerConnection, we configure the connection with an ICE server and add the local stream:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const configuration = {
iceServers: [{ url: 'stun:stun2.1.google.com:19302' }]
};

connection = new RTCPeerConnection(configuration);

connection.addStream(localStream);

connection.onaddstream = (event) => {
document.querySelector('video#remote').srcObject = event.stream;
};

connection.onicecandidate = (event) => {
if (event.candidate) {
sendMessage({
type: 'candidate',
candidate: event.candidate
});
}
};

Further interactions involve exchanging offers, answers, and ICE candidates to establish the connection. Once the connection is established, the two peers can directly communicate and exchange their webcam streams.

To close the connection, you can programmatically handle it by adding a button with the ID “close-call” and calling the handleClose() function when clicked. The handleClose() function removes the remote stream, closes the RTCPeerConnection, and sets the associated callback functions to null.

Remember to handle all the different message types on the WebSocket server and client sides for a seamless real-time webcam communication experience.

This tutorial covers the basic concepts of using WebRTC for real-time webcam communication. With this knowledge, you can start building your own applications leveraging this powerful Web API.

Tags: WebRTC, Real-Time Communication, WebSocket, Camera Stream, Peer-to-Peer Communication