Building a WebRTC Video Streaming App: A Step-by-Step Guide
Tags: webdev, javascript, webrtc, projectbenatar
Published: 2024-06-25 01:31:20

## Building a WebRTC Video Streaming App: A Step-by-Step Guide

Project link:[GitHub_@1saptarshi ](https://1saptarshi.github.io/webrtc-video-streaming/)

### Introduction

Real-time communication has become an integral part of web applications. Whether it’s for video conferencing, online gaming, or telehealth, the need for robust and efficient streaming solutions is evident. WebRTC (Web Real-Time Communication) is a powerful technology that enables peer-to-peer communication directly between browsers without the need for an intermediary server. In this blog, we’ll walk you through building a simple yet feature-rich WebRTC video streaming app. 

### Project Overview

Our WebRTC video streaming app allows users to initiate a video call, send text messages, mute/unmute audio, toggle video, and share their screen. Here’s a glimpse of what we’ll cover:

1. **Basic WebRTC Setup**
2. **Adding Text Chat**
3. **Audio and Video Controls**
4. **Screen Sharing**
5. **UI/UX Enhancements with Tailwind CSS**

### Setting Up the Project

First, let’s set up our project structure. Create a directory for your project and initialize the necessary files:

```bash
mkdir webrtc-video-streaming
cd webrtc-video-streaming
```

Create the following files:

- `index.html`
- `main.js`
- `styles.css`

Your project structure should look like this:

```
webrtc-video-streaming/
├── css/
│   └── styles.css
├── js/
│   └── main.js
├── index.html
├── tailwind.config.js
├── package.json
└── README.md
```

### HTML Structure

We'll start with the HTML. Here’s a simple structure to display local and remote video streams and control buttons:

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>WebRTC Video Streaming</title>
  <link rel="stylesheet" href="css/tailwind.output.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
</head>
<body class="bg-gray-100 text-gray-900 dark:bg-gray-900 dark:text-gray-200">
  <div class="container mx-auto p-4">
    <h1 class="text-3xl font-bold text-center mb-4">WebRTC Video Streaming</h1>
    <div class="flex justify-center mb-4">
      <video id="localVideo" autoplay playsinline class="border rounded w-1/2"></video>
      <video id="remoteVideo" autoplay playsinline class="border rounded w-1/2"></video>
    </div>
    <div class="flex justify-center mb-4">
      <button id="startButton" class="px-4 py-2 bg-blue-500 text-white rounded">Start</button>
      <button id="callButton" class="px-4 py-2 bg-green-500 text-white rounded ml-2" disabled>Call</button>
      <button id="hangupButton" class="px-4 py-2 bg-red-500 text-white rounded ml-2" disabled>Hang Up</button>
      <button id="muteButton" class="px-4 py-2 bg-yellow-500 text-white rounded ml-2" disabled>Mute</button>
      <button id="videoButton" class="px-4 py-2 bg-purple-500 text-white rounded ml-2" disabled>Toggle Video</button>
      <button id="shareButton" class="px-4 py-2 bg-gray-500 text-white rounded ml-2" disabled>Share Screen</button>
    </div>
    <div class="flex justify-center">
      <div class="w-1/2 p-4 border rounded">
        <div id="chatBox" class="overflow-y-auto h-64 mb-4 p-2 border rounded bg-gray-200 dark:bg-gray-800"></div>
        <div class="flex">
          <input type="text" id="chatInput" class="flex-grow p-2 border rounded" placeholder="Type a message...">
          <button id="sendButton" class="px-4 py-2 bg-blue-500 text-white rounded ml-2">Send</button>
        </div>
      </div>
    </div>
  </div>
  <script src="js/main.js"></script>
</body>
</html>
```

### CSS Styling

We'll use Tailwind CSS for styling. Add the necessary Tailwind CSS directives in your `styles.css` file:

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

### JavaScript Logic

Now, let’s implement the WebRTC logic along with the additional features in `main.js`:

```javascript
let localStream;
let remoteStream;
let localPeerConnection;
let remotePeerConnection;
let dataChannel;

const startButton = document.getElementById('startButton');
const callButton = document.getElementById('callButton');
const hangupButton = document.getElementById('hangupButton');
const muteButton = document.getElementById('muteButton');
const videoButton = document.getElementById('videoButton');
const shareButton = document.getElementById('shareButton');
const sendButton = document.getElementById('sendButton');
const chatInput = document.getElementById('chatInput');
const chatBox = document.getElementById('chatBox');
const localVideo = document.getElementById('localVideo');
const remoteVideo = document.getElementById('remoteVideo');

startButton.onclick = start;
callButton.onclick = call;
hangupButton.onclick = hangup;
muteButton.onclick = toggleMute;
videoButton.onclick = toggleVideo;
shareButton.onclick = shareScreen;
sendButton.onclick = sendMessage;

async function start() {
  try {
    localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
    localVideo.srcObject = localStream;
    callButton.disabled = false;
    muteButton.disabled = false;
    videoButton.disabled = false;
    shareButton.disabled = false;
  } catch (error) {
    console.error('Error accessing media devices.', error);
  }
}

async function call() {
  callButton.disabled = true;
  hangupButton.disabled = false;

  const configuration = {
    iceServers: [
      {
        urls: 'stun:stun.l.google.com:19302'
      }
    ]
  };

  localPeerConnection = new RTCPeerConnection(configuration);
  remotePeerConnection = new RTCPeerConnection(configuration);

  dataChannel = localPeerConnection.createDataChannel('chat');
  dataChannel.onmessage = (event) => {
    const message = document.createElement('div');
    message.textContent = event.data;
    chatBox.appendChild(message);
  };

  remotePeerConnection.ondatachannel = (event) => {
    event.channel.onmessage = (event) => {
      const message = document.createElement('div');
      message.textContent = event.data;
      chatBox.appendChild(message);
    };
  };

  localPeerConnection.addEventListener('icecandidate', event => onIceCandidate(event, remotePeerConnection));
  remotePeerConnection.addEventListener('icecandidate', event => onIceCandidate(event, localPeerConnection));

  remotePeerConnection.addEventListener('track', event => {
    remoteStream = event.streams[0];
    remoteVideo.srcObject = remoteStream;
  });

  localStream.getTracks().forEach(track => localPeerConnection.addTrack(track, localStream));

  try {
    const offer = await localPeerConnection.createOffer();
    await localPeerConnection.setLocalDescription(offer);
    await remotePeerConnection.setRemoteDescription(offer);

    const answer = await remotePeerConnection.createAnswer();
    await remotePeerConnection.setLocalDescription(answer);
    await localPeerConnection.setRemoteDescription(answer);
  } catch (error) {
    console.error('Error creating offer/answer.', error);
  }
}

function onIceCandidate(event, peerConnection) {
  if (event.candidate) {
    peerConnection.addIceCandidate(new RTCIceCandidate(event.candidate))
      .catch(error => console.error('Error adding ICE candidate.', error));
  }
}

function hangup() {
  localPeerConnection.close();
  remotePeerConnection.close();
  localPeerConnection = null;
  remotePeerConnection = null;
  hangupButton.disabled = true;
  callButton.disabled = false;
}

function toggleMute() {
  const audioTrack = localStream.getAudioTracks()[0];
  audioTrack.enabled = !audioTrack.enabled;
  muteButton.textContent = audioTrack.enabled ? 'Mute' : 'Unmute';
}

function toggleVideo() {
  const videoTrack = localStream.getVideoTracks()[0];
  videoTrack.enabled = !videoTrack.enabled;
  videoButton.textContent = videoTrack.enabled ? 'Disable Video' : 'Enable Video';
}

async function shareScreen() {
  try {
    const screenStream = await navigator.mediaDevices.getDisplayMedia({ video: true });
    const screenTrack = screenStream.getTracks()[0];

    localPeerConnection.getSenders().find(sender => sender.track.kind === 'video').replaceTrack(screenTrack);

    screenTrack.onended = () => {
      localPeerConnection.getSenders().find(sender => sender.track.kind === 'video').replaceTrack(localStream.getTracks().find(track => track.kind === 'video'));
    };
  } catch (error) {
    console.error('Error sharing screen.', error);
  }
}

function sendMessage() {
  const message = chatInput.value;
  dataChannel.send(message);
  const messageElement = document.createElement('div');
 

 messageElement.textContent = `You: ${message}`;
  chatBox.appendChild(messageElement);
  chatInput.value = '';
}
```

### Key Features Explained

1. **Text Chat:**
    - Implemented using WebRTC's `RTCDataChannel`.
    - Allows real-time text communication alongside the video call.

2. **Mute/Unmute Audio:**
    - Toggle the audio track’s enabled state.
    - Useful for managing noise and privacy.

3. **Toggle Video:**
    - Enable/disable the video track.
    - Provides control over video visibility.

4. **Screen Sharing:**
    - Utilizes the `getDisplayMedia` API to capture and share the screen.
    - Automatically switches back to the video stream when screen sharing ends.

5. **Enhanced UI/UX:**
    - Tailwind CSS provides a modern, responsive design.
    - Font Awesome icons improve button aesthetics.

### Conclusion

Building a WebRTC video streaming app is a great way to dive into real-time communication technologies. With the additional features like text chat, audio and video controls, and screen sharing, our app becomes more practical and user-friendly. Enhancing the UI/UX with Tailwind CSS ensures that the app is visually appealing and responsive.

Feel free to fork the project on GitHub, experiment with the code, and add more features to make it even more robust. Whether you're a beginner or an experienced developer, working with WebRTC is an exciting way to explore the possibilities of peer-to-peer communication on the web. Happy coding!