Question

In: Computer Science

Create a simple video conferencing web app (P2P or WebRTC). Needs to handle more than one-on-one...

Create a simple video conferencing web app (P2P or WebRTC).

Needs to handle more than one-on-one communication and should have option to screen share as well.

(prefer not to use sockets if possible. But if needed then that works too)

Solutions

Expert Solution

Video Chat implementation

The first thing we’re gonna do is to serve an HTML file that will work as a UI for our application. Let’s initialize new node.js project by running: npm init. After that we need to install a few dev dependencies by running: npm i -D typescript ts-node nodemon @types/express @types/socket.io and production dependencies by running: npm i express socket.io.

Now we can define scripts to run our project in package.json file:

{
"scripts": {
"start": "ts-node src/index.ts",
"dev": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts"
},
"devDependencies": {
"@types/express": "^4.17.2",
"@types/socket.io": "^2.1.4",
"nodemon": "^1.19.4",
"ts-node": "^8.4.1",
"typescript": "^3.7.2"
},
"dependencies": {
"express": "^4.17.1",
"socket.io": "^2.3.0"
}
}

When we run npm run dev command, then nodemon will be looking at any changes in src folder for every file which ends with the .ts extension. Now we are going to create an src folder and inside this folder, we will create two typescript files: index.ts and server.ts.

Inside server.ts we will create server class and we will make it work with express and socket.io:

import express, { Application } from "express";
import socketIO, { Server as SocketIOServer } from "socket.io";
import { createServer, Server as HTTPServer } from "http";
export class Server {
private httpServer: HTTPServer;
private app: Application;
private io: SocketIOServer;
private readonly DEFAULT_PORT = 5000;
constructor() {
this.initialize();
this.handleRoutes();
this.handleSocketConnection();
}
private initialize(): void {
this.app = express();
this.httpServer = createServer(this.app);
this.io = socketIO(this.httpServer);
}
private handleRoutes(): void {
this.app.get("/", (req, res) => {
res.send(`<h1>Hello World</h1>`);
});
}
private handleSocketConnection(): void {
this.io.on("connection", socket => {
console.log("Socket connected.");
});
}
public listen(callback: (port: number) => void): void {
this.httpServer.listen(this.DEFAULT_PORT, () =>
callback(this.DEFAULT_PORT)
);
}
}

To run our server, we need to make a new instance of Server class and invoke listen method, we will make it inside index.ts file:

import { Server } from "./server";
const server = new Server();
server.listen(port => {
console.log(`Server is listening on http://localhost:${port}`);
});

Now, when we run: npm run dev, we should see:

And when we open the browser and enter on http://localhost:5000 we should notice our “Hello World” message:

Now we are going to create a new HTML file inside public/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Dogeller</title>
<link
href="https://fonts.googleapis.com/css?family=Montserrat:300,400,500,700&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="./styles.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js"></script>
</head>
<body>
<div class="container">
<header class="header">
<div class="logo-container">
<img src="./img/doge.png" class="logo-img" />
<h1 class="logo-text">
Doge<span class="logo-highlight">ller</span>
</h1>
</div>
</header>
<div class="content-container">
<div class="active-users-panel" id="active-user-container">
<h3 class="panel-title">Active Users:</h3>
</div>
<div class="video-chat-container">
<h2 class="talk-info" id="talking-with-info">
Select active user on the left menu.
</h2>
<div class="video-container">
<video autoplay class="remote-video" id="remote-video"></video>
<video autoplay muted class="local-video" id="local-video"></video>
</div>
</div>
</div>
</div>
<script src="./scripts/index.js"></script>
</body>
</html>

In this file, we declared two video elements: one for remote video connection and another for local video. As you’ve probably noticed, we are also importing local script, so let’s create a new folder – called scripts and create index.js file inside this directory. As for styles, you can download them from the GitHub repository.

Now, you need to serve index.html to the browser. First, you need to tell express, which static files you want to serve. In order to do it, we will implement a new method inside the Server class:

private configureApp(): void {
this.app.use(express.static(path.join(__dirname, "../public")));
}

Don’t forget to invoke configureApp method inside initialize method:

private initialize(): void {
this.app = express();
this.httpServer = createServer(this.app);
this.io = socketIO(this.httpServer);
this.configureApp();
this.handleSocketConnection();
}

Now, when you enter http://localhost:5000, you should see your index.html file in action:

The next thing you want to implement is the camera and video access, and stream it to the local-video element. To do it, you need to open public/scripts/index.js file and implement it with:

navigator.getUserMedia(
{ video: true, audio: true },
stream => {
const localVideo = document.getElementById("local-video");
if (localVideo) {
localVideo.srcObject = stream;
}
},
error => {
console.warn(error.message);
}
);

When you go back to the browser, you should notice a prompt that asks you to access your media devices, and after accepting this prompt, you should see your camera in action!

How to handle socket connections?

Now we will focus on handling socket connections – we need to connect our client with the server and for that, we will use socket.io. Inside public/scripts/index.js, add:

this.io.on("connection", socket => {
const existingSocket = this.activeSockets.find(
existingSocket => existingSocket === socket.id
);
if (!existingSocket) {
this.activeSockets.push(socket.id);
socket.emit("update-user-list", {
users: this.activeSockets.filter(
existingSocket => existingSocket !== socket.id
)
});
socket.broadcast.emit("update-user-list", {
users: [socket.id]
});
}
}

After page refresh, you should notice a message: “Socket connected” in our terminal.

Now we will go back to server.ts and store connected sockets in memory, just to keep only unique connections. So, add a new private field in the Server class:

private activeSockets: string[] = [];

And on the socket connection check if socket already exists. If it doesn’t, push new socket to memory and emit data to connected users:

this.io.on("connection", socket => {
const existingSocket = this.activeSockets.find(
existingSocket => existingSocket === socket.id
);
if (!existingSocket) {
this.activeSockets.push(socket.id);
socket.emit("update-user-list", {
users: this.activeSockets.filter(
existingSocket => existingSocket !== socket.id
)
});
socket.broadcast.emit("update-user-list", {
users: [socket.id]
});
}
}

You also need to respond on socket disconnect, so inside socket connection, you need to add:

socket.on("disconnect", () => {
this.activeSockets = this.activeSockets.filter(
existingSocket => existingSocket !== socket.id
);
socket.broadcast.emit("remove-user", {
socketId: socket.id
});
});

On the client-side (meaning public/scripts/index.js), you need to implement proper behaviour on those messages:

socket.on("update-user-list", ({ users }) => {
updateUserList(users);
});
socket.on("remove-user", ({ socketId }) => {
const elToRemove = document.getElementById(socketId);
if (elToRemove) {
elToRemove.remove();
}
});

Here is the updateUserList function:

function updateUserList(socketIds) {
const activeUserContainer = document.getElementById("active-user-container");
socketIds.forEach(socketId => {
const alreadyExistingUser = document.getElementById(socketId);
if (!alreadyExistingUser) {
const userContainerEl = createUserItemContainer(socketId);
activeUserContainer.appendChild(userContainerEl);
}
});
}

And createUserItemContainer function:

function createUserItemContainer(socketId) {
const userContainerEl = document.createElement("div");
const usernameEl = document.createElement("p");
userContainerEl.setAttribute("class", "active-user");
userContainerEl.setAttribute("id", socketId);
usernameEl.setAttribute("class", "username");
usernameEl.innerHTML = `Socket: ${socketId}`;
userContainerEl.appendChild(usernameEl);
userContainerEl.addEventListener("click", () => {
unselectUsersFromList();
userContainerEl.setAttribute("class", "active-user active-user--selected");
const talkingWithInfo = document.getElementById("talking-with-info");
talkingWithInfo.innerHTML = `Talking with: "Socket: ${socketId}"`;
callUser(socketId);
});
return userContainerEl;
}

Please notice that we add a click listener to a user container element, which invokes callUser function – for now, it can be an empty function. Now when you run two browser windows (one as a private window), you should notice two connected sockets in your web app:

After clicking the active user from the list, we want to invoke callUser function. But before you implement it, you need to declare two classes from the window object.

const { RTCPeerConnection, RTCSessionDescription } = window;

We will use them in callUser function:

async function callUser(socketId) {
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(new RTCSessionDescription(offer));
socket.emit("call-user", {
offer,
to: socketId
});
}

Here we create a local offer and send to the selected user. The server listens to an event called call-user, intercepts the offer and forwards it to the selected user. Let’s implement it in server.ts:

socket.on("call-user", data => {
socket.to(data.to).emit("call-made", {
offer: data.offer,
socket: socket.id
});
});

Now on the client side, you need to react on call-made event:

socket.on("call-made", async data => {
await peerConnection.setRemoteDescription(
new RTCSessionDescription(data.offer)
);
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(new RTCSessionDescription(answer));
socket.emit("make-answer", {
answer,
to: data.socket
});
});

Then set a remote description on the offer you’ve got from the server and create an answer for this offer. On the server-side, you need to just pass proper data to the selected user. Inside server.ts, let’s add another listener:

socket.on("make-answer", data => {
socket.to(data.to).emit("answer-made", {
socket: socket.id,
answer: data.answer
});
});

On the client’s side we need to handle answer-made event:

socket.on("answer-made", async data => {
await peerConnection.setRemoteDescription(
new RTCSessionDescription(data.answer)
);
if (!isAlreadyCalling) {
callUser(data.socket);
isAlreadyCalling = true;
}
});

We use the helpful flag – isAlreadyCalling – just to make sure we call only the user only once.

The last thing you need to do is to add local tracks – audio and video to your peer connection, Thanks to this, we will be able to share video and audio with connected users. To do this, in the navigator.getMediaDevice callback we need to call the addTrack function on the peerConnection object.

navigator.getUserMedia(
{ video: true, audio: true },
stream => {
const localVideo = document.getElementById("local-video");
if (localVideo) {
localVideo.srcObject = stream;
}
stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
},
error => {
console.warn(error.message);
}
);

And we need to add a proper handler for ontrack event:

peerConnection.ontrack = function({ streams: [stream] }) {
const remoteVideo = document.getElementById("remote-video");
if (remoteVideo) {
remoteVideo.srcObject = stream;
}
};

As you can see, we’ve taken stream from the passed object and changed srcObject in remote-video to use received stream. So now after you click on the active user, you should make a video and audio connection, just like below:


Related Solutions

Create a simple video conferencing web app (P2P or WebRTC). Needs to handle more than one-on-one...
Create a simple video conferencing web app (P2P or WebRTC). Needs to handle more than one-on-one communication and should have option to screen share as well. (prefer not to use sockets if possible. But if needed then that works too)
Create a simple video conferencing web app (P2P or WebRTC). Needs to handle more than one-on-one...
Create a simple video conferencing web app (P2P or WebRTC). Needs to handle more than one-on-one communication and should have option to screen share as well. (prefer not to use sockets if possible. But if needed then that works too)
Create a simple video conferencing web app (P2P or WebRTC). Needs to handle more than one-on-one...
Create a simple video conferencing web app (P2P or WebRTC). Needs to handle more than one-on-one communication and should have option to screen share as well. (prefer not to use sockets if possible. But if needed then that works too)
Create a simple python app that allows the user to create a roster of students and...
Create a simple python app that allows the user to create a roster of students and their grade on CUS-1166. Moreover the app needs to calculate the average grade of students added to the roster. 1-To begin with, create a new file n the same working folder as part A (i.e. cus1166_lab1) and name it app.py. Moreover, create a subfolder and name it mymodules. 2-Within mymodules create the files __init__.py , models.py , math_utils.py . 3-In the models.py file define...
Summary Develop a Spring Boot Web App which allows user to create a list of the...
Summary Develop a Spring Boot Web App which allows user to create a list of the destinations they have visited during vacations. Your application should allow user to search for a destination based on year of visit. Form View The initial page should have links to either search for a destination , add a new destination, display a list of all destinations that they have visited. The search destination page will have a text field to search by year. The...
Suppose a video conferencing provider, Soom, is planning to pay its next dividend in one year....
Suppose a video conferencing provider, Soom, is planning to pay its next dividend in one year. The dividend will be $1 per share, and Soom expects dividends to grow at 10% per year over the next three years. After that, they expect dividends to grow at 2% per year indefinitely (that is, forever). Suppose that investors have the alternative of buying corporate bonds that will earn 5% per year. Assume the company will next pay a dividend of $1 per...
Find one video or cartoon example of one of these fallacies on the Web. The example...
Find one video or cartoon example of one of these fallacies on the Web. The example must take the form of a video/cartoon (check out YouTube, for instance, or search for the fallacies in cartoon banks). The Cartoon Network, Family Guy, Stephen Colbert/Jon Stewart, or even just plain old cartoon websites are good places to look. Once you've located the example, paste in a link to the file (embed it) in your Discussion Board thread. Finally, explain why you think...
Part 1; a simple slide show: Create a Web document called l5p1.html that displays one of...
Part 1; a simple slide show: Create a Web document called l5p1.html that displays one of the images from lab 2 in approximately the center of the page; centering need not be exact. Below the image, place three button elements labeled with the names of items from lab 2. (One of them must be the name of the item you chose to display.) Pressing a button should change the displayed image to that of the item named on the button....
Needs to be written in C# Console App! Create a base class BankAccount. Decide what characteristics...
Needs to be written in C# Console App! Create a base class BankAccount. Decide what characteristics are common for checking and saving accounts and include these characteristics in the base class. Define derived classes for checking and savings. In your design , do not allow the banking base account to be instantiated --only the checking and saving classes. Use a Program class to test your design.
There can be more than one correct answer! Thanks 7. How is glucose reabsorbed? A. simple...
There can be more than one correct answer! Thanks 7. How is glucose reabsorbed? A. simple diffusion B. co-transported with Na+ C. counter-transported with H+ D. actively transported E. none of the above 8. The mechanism for the reabsorption of Na+ is similar in the A. proximal and distal convoluted tubules B. loop of Henle and distal convoluted tubules C. proximal convoluted tubules and cortical collecting ducts D. cortical and medullary collecting ducts E. Loop of Henle and proximal convoluted...
ADVERTISEMENT
ADVERTISEMENT
ADVERTISEMENT