0:00
Now this is a bit of a different intro than usual, and that's because I'm going to show you how to build a Zoom clone
0:05
where you can communicate on video chat with anyone you want. As you can see, I have my phone and my computer
0:11
I'm talking with both of them, and this could be used to call anyone that you want
0:15
So let's get started now. Welcome back to WebDev simplified times four
0:24
My name's Kyle, and my job is to simplify the web for you so you can start building your dream project sooner
0:30
So if that sounds interesting, make sure you subscribe to the channel for more videos just like this
0:35
Now to get started, I just have a blank project open a Visual Studio code and a working demo of the project on the right
0:40
As you can see in this top browser, I am calling myself in this chat room with this title
0:45
And then also down here, I have that same chat room open. And if I were to join that chat room with another tab, you can see that now all three of these windows have the three different calls inside of them
0:56
And if I were to leave, it'll exit that person out of the other chats. So as you can see, this is a very flexible and useful video calling platform
1:03
and it's automatically going to create rooms for us so that we can chat in specific rooms
1:07
instead of calling every single person on the application. So to get started with this, the very first thing that I want to do is to actually set up our
1:15
project, because we're going to need some form of server to help us communicate the rooms
1:19
that we have for our different users, and then we're going to need, obviously, our front-end
1:23
code for talking with the different users in the video chat. So in order to get our server setup, let's just run NPM init dash y
1:31
That's going to create for us this boilerplate package.jason. And now we need to install our dependencies
1:37
So we can say NPMI. And the first dependency that we're going to need is express, because that's going to be the server we're going to use
1:43
And we're going to use EJS as our templating language, so we'll use that dependency. And then we're going to need something called socket.io, which is going to allow us to communicate back and forth with the actual server easily
1:54
Now another dependency that we're going to need is going to be UUID, so we can just say NPMI
1:59
UUID, and this dependency is what allows us to create these dynamic URLs right here
2:05
with these different room numbers inside of them, so we can just have dynamic, unique UUIDs
2:10
for all of our different rooms, so the users can chat in specific rooms. Lastly, we're going to need one dev dependency, so NPMI dash-s save dev, and this is going to be called
2:20
Nodemann, and this allows us to quickly refresh our application every single single
2:24
time we make changes so we don't have to manually restart our server each and every time
2:28
And speaking of our server, let's create a server.js file where all of our server code will go
2:34
And now in our package JSON, let's create a script. We're going to call it Dev start
2:39
And we're just going to inside it here put Nodemon server.js. And now if we come down here and we were to run NPM, run Dev, start, it's going to run this server file and every single time we make changes
2:52
For example, console.log high and rerun it, you can see it's going to log that out and restart our server
2:58
Now inside of our server, I just want to set up the very basic code for actually getting our server running
3:04
So we can just create an express server by saying Const Express is equal to require Express
3:10
And then we can create an app variable, which is equal to running that express function
3:15
And we're also going to need to get a server, which is useful for socket I.O
3:19
socket i.o so we can say server is equal to require of htp and we just need to pass in here
3:26
dot server and then pass our actual app object here. This allows us to create a server to be used
3:32
with socket i.o. And speaking of socket i.o, let's bring that in. So we'll say i.o is equal to
3:38
require of socket.io. And we need to pass in this server to the return of this require function
3:44
So this actually creates a server for us based on our Express server, and then passes that to Socket I.O
3:50
So that socket I.O actually knows what server we're actually using and how to actually interact with that
3:55
And then what we can do is we can say server dot listen on port 3,000, and that's actually going to start up our server on port 3,000
4:03
But in order to get that to work, I need to shut down my existing server on port 3,000
4:08
So there we go. I've shut down that other server, but you'll still notice these videos are actually working
4:12
and that's because the video chat does not communicate through the server
4:16
It actually communicates directly with the person's computer, so we don't have to worry about sending our traffic through the server
4:21
The server is purely just for setting up our rooms. And now, if we just get rid of this, go to Localhost 3,000
4:27
this is going to be where our application is when we actually start it up. So if we save over here, and if we refresh, you should see Cannot Get Slash
4:34
and that's exactly what we want. Now the next step is to actually set up our Express server
4:38
so we actually have a route at this homepage here. We can just say app.set, and this is going to be for our view engine
4:45
We need to set up how we're going to render our views. In our case, we downloaded the EJS library, so we're going to use EJS
4:53
Next, we want to make sure we set up our static folder. So we can say app.us, express. static
4:58
Whoops, static. And this is going to be called the public. So we're going to put all of our JavaScript and CSS all in this public folder here
5:06
So now that we have that setup, we can actually work on this Git route here. So we can just say app.git of slash, and this is going to take in a request and a response
5:16
Let's just say request and response. And then with that request and response, all that we want to do is create a brand new room and redirect the user to that room
5:26
Because we don't have a home page for this application. So if you go to the homepage, it's just going to create a brand new room for you
5:32
So let's create a route for our rooms. We can say app.git slash colon room
5:37
and this is going to be a dynamic parameter that we pass into the URL. And it's going to again take request and a response
5:44
And inside of here, what we can do is we can actually get our room from this room parameter here
5:49
So we can say res. render, room, and we want to pass down our room ID
5:55
And this room ID is just request. Dotparams. And that's coming right here from our URL
6:02
Now up here, we can take our response and we can redirect our user to
6:07
slash room, but we want to get a dynamic room. So we can come in here slash, and then we want to have some form of room ID here that we pass in
6:15
And that's what that UUID library we download is going to do. So we can say const, and we need to say require here
6:22
It's going to be equal to UUID. And inside of here, what we're going to do is we're going to take a function called V4
6:28
and we're just going to rename it to UUID V4. That way it's more self-explanatory what it does
6:33
And instead of passing this room ID here, we can call the UUUUI. UID V4 function, and that's going to give us a dynamic URL
6:41
So now if we save and we go to Local Host 3000, we should actually get a random UUID being
6:46
appended to the end of this. So I hit Enter, you can see down here we have this random UUID that looks just like this
6:53
being appended to the end of our URL. If we come down here, same thing, we should get another random UUID
6:59
If I just copy that in, you can see we have two completely random rooms that are generated
7:03
every single time we go to our home page. It saying that we don have any view essentially that it rendering We are trying to render this view called room but we don have a view called room yet So let work on creating that view We can create a new folder called views
7:18
This is by default where your views are going to go. We'll create a new file called room. EJS
7:24
And that's because we're using the EJS view engine. Now inside of here, if we hit exclamation point and hit tab, you can see it generates boilerplate
7:32
HTML code for us. And inside of here, we can actually go through and render all the code we need for our room view
7:38
Now luckily for us, this is actually really simple code. We're going to need to have some grid, which we're going to put our videos
7:44
We're going to call this video grid. And then we're going to have some really simple styles for styling out this video grid
7:50
So we could say video grid. And this video grid is going to be a display of grid
7:56
We're going to have grid template columns, which we're going to say repeat on auto fill
8:02
And we want to have 300 pixels. So essentially we're going to have a 300 pixel wide video
8:08
And then we're going to say grid auto rows, and we want to have 300 pixels
8:13
And what this is saying is that every single one of our rows should be 300 pixels tall
8:17
and every single one of our columns should be 300 pixels wide. So essentially we're going to get a square video
8:23
Now in order to make sure our video fills that whole space, we can select all of our video elements
8:28
We can change the width to 100%, the height to 100%. And in order to account for aspect ratios
8:35
For example, if you have a really skinny video or a really wide video, you want to make sure it still only is a square
8:41
So we can say object, fit, and set that to cover. Essentially, what that's going to do is it's going to zoom in the video until it fills the entire square and then cut off everything around the edges
8:51
It's just like if you did background sizing and you did covering that way. It's the same thing
8:56
And that right there is all of the code that we need to set up our styles. As I said, it's a really simple styles
9:01
It's just a grid of video objects. So now if we save that, and we save that. save over here and we just do it quick back to our homepage and refresh you can see we have a
9:10
random room ID generated and it's just going to be a blank page which has that one single empty
9:15
video grid div on it now the next thing that I want to work on before we start diving into the front
9:20
end is going to be thinking about what we want to handle on our server with socket i.o so if we just
9:26
come down here we can say i.o.on connection this is going to run any time someone connects to our
9:33
web page. We want to just admit socket. That's the actual socket that the user is connecting
9:38
through. And here we can actually set up events to listen to. So the first event I want to listen
9:43
to is going to be when someone connects to a room. So we can say socket.on, join room. And inside of
9:50
here, we're going to pass in the room ID as well as the user ID. So what this essentially does
9:56
is, whenever we connect with socket. I.O., we're going to set up this event listener that says
10:00
whenever we join a room, we're going to pass in the room ID and the user ID. So on the front end, what happens is as soon as everything is set up and we have a room
10:07
and we have a user, we're going to call this join room event here, and then it's going to call all the code inside of this socket.on
10:14
For now, I'm just going to console log room ID and user ID so we can see exactly what's happening
10:19
and we'll just save that for now. Now instead of our room EJS, what we need to do is we need to take the room ID that we pass in here
10:26
and we need to give it to our front end code somehow. And the easiest way to do that is just simply with a script tag inside of here, what we can do is we can say const, room ID is going to be equal to
10:38
And we want to have this syntax here with the less than sign that present and equal
10:42
That's going to actually render code on our server. So we know we have room ID being passed down from our server here
10:49
So we can access that inside of here just like this, close that off. And we need to wrap this inside of quotes because it's actually going to be a string
10:57
So now in our JavaScript, we're going to have this room underscore ID variable
11:02
We can actually see that by if I just refresh here and inspect the page
11:06
Go over to our console and type in room ID. You can see that this room ID here matches exactly the URL right here, 48FA, 48FA
11:15
So we're actually getting the room ID that we're currently in, and we're passing that down through this room underscore ID variable
11:21
Now the next thing we need to do after that is make sure we can use socket I.O
11:25
inside of the front end. And luckily, socketio does all of this automatically
11:30
We can create a script tag here, and it's going to have a source of slash
11:34
We want to go to socket.io slash socket. ops, socket. Dotio. Dot.j
11:43
And let's just make sure we defer that. And what this essentially is doing is loading all of the socket I.O
11:48
JavaScript code into our front end, and it's serving it from our own server here
11:52
That's what happened when we set up our I.O. with our server like this. So now we have that socketio code
11:58
And the last thing to do is to set up our own custom script. We'll just call it script.js
12:03
And we'll make for sure to defer this. So now we have access to some script file
12:07
which is going to come from a public folder, which we need to make sure is at the root level
12:12
And we'll create a file in here called script.js. Now inside of here, we have access to all of our socketio code
12:18
We have access to our room ID. And with that, we can actually call that socketio join room event
12:24
right here. So inside of our script, let's first get a reference to Socket. We can just say
12:30
socket is going to be equal to I.O. And then we need to pass in the path that we want to call
12:35
In our case, we're just going to use the root path, because that's where our server is set up at the root path. So we have Socket is going to connect to our route path of our application at Local
12:44
Host 3,000. And then what we can do is say socket.emit. And this is going to send an event to our
12:50
server. We're going to call it Join Room. And inside of here, we need to pass
12:54
our different arguments. So we have our room ID, and let's just pass a user ID of 10 for now so that we can see
12:59
if this works. So now if we save and we refresh over here, you should see that we get past our room ID
13:05
as well as our user ID, and that's because here we're printing that out whenever a user
13:09
joins the room. If we join this other room down here with a different browser, you can see that we get
13:14
that room ID as well as that hard-coded user ID of 10
13:18
So now the next thing to work on is actually telling all of the other users in the same room
13:23
that we have joined, that we have a new user that just connected because we need to set up the
13:27
video connections. So to do that, first we want this current socket to join a room. So we can say
13:32
socket. Dot join room ID. So now we're joining this new room that we passed up here with this
13:39
current user. So anytime something happens on this room, we will send it to this socket or essentially
13:44
this user. And then we can say socket.2 room ID, which means we're going to send a message to the
13:51
room that we are currently in. We want to make sure this is a broadcast message. All this does
13:56
is says send it to everyone else in the same room, but don't send it back to me. Because we
14:00
already know that we connected. We don't need to send ourselves a message saying we connected
14:05
Then we can just say emit here, and this is going to be our event, which is user connected
14:09
We're going to pass in the ID of that user that just connected. So now we can go back into our script
14:15
and we can actually listen for this event So we can say socket user connected And then This is going to take in a user ID And then what we can do is you just say console user connected and we going to pass at the user ID for now just to see what this is actually doing
14:34
So if we save and we just inspect our page here, go to our console, and if we refresh
14:40
you'll notice nothing's actually happening. And if we refresh down here, nothing's happening. But if we have this user down here at the bottom, join the exact same room as the user at the top
14:49
you'll see user connected 10 which is the user ID that we have hard coded so now when people are in
14:55
the exact same room we're sending messages to other people in the same room saying hey someone else joined
15:00
here's the ID that they have so you can try to connect to them but so far all we've done is just hard
15:06
code these IDs we just have 10 hard coded here so how exactly we're going to handle this ID connection
15:12
well there are a few different ways we could do it one way would be to hard code and write out all of the
15:17
code by hand that we need to do to set up these fancy connections, or we could take advantage
15:22
of a library that's army that's already built that does a lot of the hard work for us
15:26
And this library is called Purejs. They have the ability to create connections between
15:31
different users using WebRTC, and most importantly, they have a server setup that you can use
15:37
that allows you to create these dynamic IDs for connecting between different users
15:41
And to set up that server is incredibly easy. Just open up a new tab in your terminal, and we're going
15:47
to run npm i dash g and we want to call peer what this is going to do is globally install this
15:53
peer library which allows us to actually run a peer server and then we can just say peer j s in a
16:00
port we'll say port 3 000 and 1 now what happens is we have a peer server running on port 3 0001
16:07
and what this peer server allows us to do is to connect different users and it'll give us an
16:11
idea of a user which we can use here instead of this hard-coded number 10 but we also
16:17
We also need to make sure that we include this script tag here inside of our code
16:21
So let's copy this script tag, come over to our room EJS, we'll just put it way at the top
16:25
of our code, and we'll just make sure that we set this to defer so it loads in the correct
16:30
order from top all the way down to bottom. Now if we save that, come into our script, we have access to that peer library, and we can
16:37
create a new peer. But since we're connecting to our own server, we need to pass some parameters to this new peer
16:44
So let's create a peer, we'll call it MyPier. it's going to be equal to a new peer
16:48
and this first parameter to peer is going to be an ID. We'll just pass an undefined
16:53
because we're going to let the server take care of generating our own ID. Now the next thing that we pass to this is going to be our host
17:00
In our case, our host is just this slash here. We just want it to be our root host
17:05
and then a port, which is in our case going to be 3,001
17:09
We're just going to put that inside of a string here. So now we have a connection to this peer server
17:14
And if I were to just save this, and I refresh, my browser, you can see client connected, and it's giving us an ID for our client
17:21
If we come over here, you should see the same thing, client connected, and a new random ID
17:25
for that user. So now we're actually connecting to this peer server. And all this peer server does is it takes all of the WebRTC information for a user, and
17:33
it turns it into this really easy to use ID, which we can pass between different places and
17:39
use with this peer library to actually connect with other peers on the network
17:43
So now with that setup, what we can use is we can say, my, peer dot on open and what this is saying is as soon as we actually connect with our peer server and
17:52
get back an ID we actually want to run this code and this is actually going to pass us the ID of our
17:57
user and inside of here let's move up our join room code and we'll pass in that user ID whenever
18:03
we actually join so now if we save come back over here and we go over to our other terminals
18:09
so we can see what our user information is we refresh here and we refresh down here and we
18:15
inspect the console, you can see that it says user connected and it actually has an ID of a user
18:21
And this is a unique ID. If I were to refresh this page, you can see I get a brand new ID because it's a new user
18:27
being connected to this exact room. So now we actually have IDs that we can use to connect between our different users and actually
18:34
make calls between them. So now let's get into the fun part of this application, which is setting up that video call connection
18:40
And the first step is to render our own video on the screen. So that's actually fairly straightforward to do
18:45
The first thing I want to do is get a reference to that grid. So we can just say document
18:50
Dot get element by ID. We just call it video grid. This is going to have the place where we place all of our new videos
18:57
And then we also want to get reference to a video. So we'll say const my video is equal to document
19:02
Dot create element. And we want to create a video element. But most importantly, we want to make sure that we take my video and we want to mute this
19:12
So we're going to say muted is equal to true. Because obviously we don't want to listen to our video. own video that really doesn't make sense so we're going to make sure we mute ourselves
19:19
and this isn't going to mute us for other people it just mutes the video for ourselves so we
19:24
don't have to hear our own microphone play back to us because obviously that would be really really
19:29
annoying now the next step once we have that done is to actually try to connect our video so we can
19:35
just say navigator dot media devices dot get user media and what this is going to do is take a options
19:43
parameter here, this object, and we need to say that we want to get video, so we'll say
19:47
video true, and we want to get audio, so we'll say audio true, because we want to get our
19:51
video and audio to send to other people. And this is a promise, so it's going to say .then, and it's going to pass us a stream
19:58
and this stream is going to be our video and our audio. And we want to tell our video object that we've created here to actually use that stream
20:06
So let's create a function to do that. We'll just come all the way down to the bottom here, and we'll say add, video, stream
20:13
And this is going to take in a video, and it's going to take in a stream. And now all this is going to do is going to take our video
20:20
We're going to set our source object equal to that stream. This will allow us to play our video
20:25
And then we want to add an event listener to our video. So I'll say add event listener
20:32
This is going to be on loaded meta data, just like that
20:36
And this is going to be a function. And all we want to do when this is done, whoops, there we go, is we just want to say video
20:43
play. Essentially what's saying is once it loads this stream and this video is actually loaded
20:47
on our page, we want to play that video. Then what we want to do is we want to take our video grade
20:52
and we want to just append our video onto the grid of videos we already have. Now if I save this
20:58
come up here and make sure we call that add video stream function with my video and a stream
21:04
we should actually see our video being appended to our page. And as you can see that video just
21:09
popped up and you may need to actually approve the camera permissions as you
21:13
As you can see here, I have it allowing my camera to be used. And the first time you load the page, it'll ask for permission to use your camera and microphone
21:20
but I've already given myself permission on Local Host 3000, so I don't need to see that pop-up
21:25
but you most likely will see that, so don't be alarmed. That's perfectly normal and just browser behavior
21:30
Now the next thing we need to do is once our stream and video is set up we need to allow ourselves to be connected to by other users We need to again use socketio whoops socket and we need to do this user event
21:46
the one that we have down here where we're just printing out our user, and it's going to take in a user ID
21:51
So we already have this event set up on our server working properly, so we can get rid of this test code down here
21:56
And when a new user connects, we're going to call a function called connect to new user
22:02
and we're going to pass in our user ID. as well as our video stream that we want to send to that user that we're trying to connect to
22:08
Because a new user has joined our room, so we have to send our current video stream to that user
22:13
So let's just come down here, we'll create that function. Function connect to new user, it's taking a user ID, and a stream
22:22
And now inside of this function, we're going to create a variable called call
22:26
And this call variable is coming from our peer object, so our peer up here, we're going to call a function called call
22:33
And what this function does is essentially it's going to call a user that we give a certain ID to
22:37
So we can say our user ID, and we're going to pass it the stream we want to send to that user
22:42
So we're calling this user with this ID and sending them our video and audio stream
22:47
And then we want to listen to the event called stream. And what this event is saying is when we call this user, we're going to send them our video stream
22:55
And then when they send us back their video stream, we're going to get this event here called stream
23:00
which is going to take in their video stream. So it's going to say, you know, user video stream
23:06
In this user video stream, we want to just add to our list of videos
23:10
So we can say add video stream. We want to add our user video stream as our stream
23:15
and we're going to create a new video object. So we'll say const. Video is equal to document
23:21
Dot create element of video. So we can pass that video instead of here
23:26
So now what's happening is we're taking the stream from the other user that we're calling
23:30
in and we're adding it into our own custom video element on our page
23:35
And lastly, we're going to have to listen to on close. And this close event, all we want to do is just remove the video
23:41
So we can say video. And what this close is listening for is essentially whenever someone leaves the video call
23:47
they're going to close that call and we want to make sure that we remove their video so we don't just have random videos laying around from people that aren't connected anymore
23:55
So now if we save, I'll refresh this and everything should still work. We have our video
23:59
And if we go to the same room down here, refresh, you should see we get the video here
24:04
But as you can see, even though we're calling the other user, we don't have their videos showing up
24:09
And the reason for that is that we're not actually listening to when we get called
24:13
What we need to do is listen to once someone tries to call us on this my peer object
24:18
So we can do that inside of here. You can say my peer dot on call
24:24
So once someone tries to call us, we're going to get this call object we can work with
24:28
All we want to do is answer their call, and we're going to send them our current stream
24:33
So we're going to answer their call and send them our stream. So now let's see what happens
24:37
We refresh this top one, refresh this bottom one. You can see this top browser is getting sent the video from the bottom browser up because
24:44
a user connected, but our bottom browser is not having any information about the top browser
24:50
And the reason for that is we answered the call from our one peer, but we're not actually responding to any video streams that come in
24:57
So we need to say call.on stream, and we need to do pretty much the same exact thing
25:03
This is going to be our other user video stream. And then this, we need to first come up here, create a new video object to place this
25:12
So we'll say document. That create element, which is a video. And we just need to make sure we add that video stream, which is going to be our video element
25:21
as well as our user video stream. Now, everything should work. If we refresh this browser, you can see we get a video stream
25:27
just ourselves, we refresh down here, and now we have both of these browsers communicating
25:31
with one another. So what's happening is we're able to receive calls by listening to our on-call
25:36
event, and we're also able to make calls when new users connect to our room. And if this user was
25:41
for example, in a different room called room, you can see that no matter how many times I refresh
25:45
this, they're not added up here. But as you can see by this frozen video on the right
25:50
we're not actually closing our connection as well as we should be. So we need to make sure we try to
25:54
handle our video closes so that it works a little bit quicker than as you can see it took quite a
25:59
while to disappear so in order to do this we need to go back to our server and inside of this socket
26:04
on join room what we want to do is say socket dot on disconnect we want to run another function
26:13
and this function is going to be whenever our user disconnects from the server so this is handled by socket
26:17
io so essentially if i were to click the x button and leave completely or go to a different URL or whatever
26:22
it is, socketio.on disconnect is going to get called. And inside of here, I just want to send an event
26:28
down to our room. So just like before, we have socket.2, room ID. I want to make a broadcast event
26:35
and I want to emit a new event called user disconnected. I'm going to pass in the ID of the user
26:42
that disconnected. So now, if I just come into my script, we can just listen to that event. We'll come
26:47
all the way up here, socket.on user disconnected, we should get a user ID. I just want to console
26:56
that log out the user ID. So now if we put both of these users in the exact same room
27:02
refresh everything, we should get two videos. Now if we inspect, come over to our console
27:06
and we have this bottom person leave by going to a different room. So we'll go to room again
27:11
You can see we get that ID being printed out of the user that just left. So now we can use that
27:16
ID to try to remove the video call of that person since we no longer are connected to them anymore
27:21
So in order to do that, let's come into our script here, and we need to keep track of which
27:26
people we have contacted and what call they have set up. So here, when we connect to a new user, we're going to need to save some form of variable that
27:34
tells us what the call is that we just made to that user so that we can remove it
27:38
So what we can do is create a variable. We'll just come up here. We can say const. Here's is going to be equal to an empty object
27:44
And then all the way down here when we connect to a user, we can just say peers of that user
27:50
ID is going to be equal to the call that we just created
27:55
So now essentially every user ID is directly linked to a call that we make
27:59
So now whenever we disconnect from a user, what we can do is we can say peers of the user
28:05
ID dot close. And this is going to close our connection. And we just want to make sure that we only do this if we have a user
28:12
So we could say if peers of user ID exist. So if this is true, then what we want to do is close the connection
28:19
Otherwise, if this is not true, then we don't actually have a connection to close. So it doesn't make sense to call close
28:25
So now refresh down here, refresh down here, and make sure that they're in the same exact room
28:30
You can see we have both users. And now if this user leaves the room, you can see that immediately the video was completely removed from up here
28:37
and there was no long delay that we had to wait for. And that's all it takes to create your own Zoom clone
28:42
If you enjoyed this video, make sure check on my other project-based videos linked over here
28:46
and subscribe to the channel for more videos just like this. Thank you very much for watching and have a good day