Welcome folks today in this blog post we will be building the zoom clone
in socket.io using angular
at the frontend and we will be having the express
server running at the backend. All the full source code of the application is shown below.
Get Started
In order to get started you need to make a zoom
clone directory and inside it we will be making separate directory
for frontend and backend as shown below
mkdir zoomclone
cd zoomclone
Making the Backend
Now we will be making the backend first as shown below
mkdir backend
npm init -y
npm i express
npm i socket.io
Now you will see the below directory
structure of the express
app as shown below
Now you need to copy paste the below code inside the index.js
file as shown below
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
const express = require('express'); const app = express(); const server = require('http').Server(app); const io = require('socket.io')(server); const path = require('path'); app.use('/static', express.static('public')); app.get('/**', (req, res) => { return res.sendfile(path.join(__dirname + '/public/index.html')); }); io.on('connection', socket => { socket.on('join-room', (roomId, userId) => { socket.join(roomId); socket.to(roomId).broadcast.emit('user-connected', userId); socket.on('disconnect', () => { socket.to(roomId).broadcast.emit('user-disconnected', userId); }) }); }) server.listen(3000); |
As you can see we are importing the express
and socket.io
libraries at the top and then we are making the public
directory as static and then we are listening on for every socket
connection using the connection
event and we are listening on for join-room
event and inside it we are allowing the user
to join the room using the join() method. And we are getting the roomId
and then we are broadcasting the message to all sockets
present in that roomId
that the user is connected. And when the user disconnects
we are listening using the disconnect
event we are broadcasting the message that the user
is disconnected.
Now you can start the backend
express server using the below command as shown below
node index.js
Making the Frontend
Now guys we will be making the angular
frontend. For this we need to make the frontend
directory as shown below
mkdir frontend
cd frontend
ng new sampleapp
cd sampleapp
And now we will be installing the packages
that are needed for this application
npm i ngx-socket-io
Now you will see the below directory
structure of the angular app as shown below
Now we need to make two
components for this frontend zoom clone first one is the home
page and secondly we need to make the room
component as shown below
ng generate component home
ng generate component room
This will create two folders namely home
and room
as shown above in the figure.
Now you need to go to app.module.ts
file and copy paste the following code
app.module.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { RoomComponent } from './room/room.component'; import { HomeComponent } from './home/home.component'; import {SocketIoModule} from "ngx-socket-io"; @NgModule({ declarations: [ AppComponent, RoomComponent, HomeComponent, ], imports: [ BrowserModule, AppRoutingModule, BrowserAnimationsModule, SocketIoModule.forRoot({ url: '/' }) ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } |
As you can see we are importing the ngx-socket-io
module and also the different components
inside the imports
array and the declarations
array.
And now we need to go to home.component.html
file and copy paste the following code
home.component.html
1 2 3 4 5 |
<h1>Welcome to video chat!</h1> <div> Want to chat? <button (click)="createRoom()">Create a new Webrtc video chat room now!</button> </div> |
As you can see we have a simple button to create the room
and join it.
And now we need to copy paste the below code inside the home.component.ts
file as shown below
home.component.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import { Component, OnInit } from '@angular/core'; import uuidv4 from 'uuid/dist/v4'; import {Router} from "@angular/router"; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.scss'] }) export class HomeComponent implements OnInit { constructor( private router: Router, ) { } ngOnInit() { } createRoom() { console.log('createRoom'); this.router.navigate([`/${uuidv4()}`]); } } |
As you can see we are redirecting
the user to the random id
which is actually the roomId. For this we are importing the uuid
package at the very top.
Now guys we will be writing the html
code inside the room.component.html
file as shown below
room.component.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<div class="video-grid"> <div *ngFor="let video of videos"> <span *ngIf="video.userId !== currentUserId; else currentUserVideoLabel">{{ video.userId }}</span> <ng-template #currentUserVideoLabel> <span>You</span> </ng-template> <video [srcObject]="video.srcObject" (loadedmetadata)="onLoadedMetadata($event)" [muted]="video.muted" > </video> </div> </div> |
As you can see inside the div
section we are displaying the grid
section where we will be rendering the webcam
of users which are connecting inside the room. And for this we are using the ngFor
directive to loop through all the user’s `webcam and display it inside the browser.
And now we need to copy paste the code inside the room.component.ts
file as shown below
room.component.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
import { Component, OnInit } from '@angular/core'; import {ActivatedRoute} from "@angular/router"; import { Socket } from "ngx-socket-io"; import { v4 as uuidv4 } from 'uuid'; declare const Peer; interface VideoElement { muted: boolean; srcObject: MediaStream; userId: string; } @Component({ selector: 'app-room', templateUrl: './room.component.html', styleUrls: ['./room.component.scss'] }) export class RoomComponent implements OnInit { currentUserId:string = uuidv4(); videos: VideoElement[] = []; constructor( private route: ActivatedRoute, private socket: Socket, ) { } ngOnInit() { console.log(`Initialize Peer with id ${this.currentUserId}`); const myPeer = new Peer(this.currentUserId, { host: '/', port: 3001, }); this.route.params.subscribe((params) => { console.log(params); myPeer.on('open', userId => { this.socket.emit('join-room', params.roomId, userId); }); }); navigator.mediaDevices.getUserMedia({ audio: true, video: true, }) .catch((err) => { console.error('[Error] Not able to retrieve user media:', err); return null; }) .then((stream: MediaStream | null) => { if (stream) { this.addMyVideo(stream); } myPeer.on('call', (call) => { console.log('receiving call...', call); call.answer(stream); call.on('stream', (otherUserVideoStream: MediaStream) => { console.log('receiving other stream', otherUserVideoStream); this.addOtherUserVideo(call.metadata.userId, otherUserVideoStream); }); call.on('error', (err) => { console.error(err); }) }); this.socket.on('user-connected', (userId) => { console.log('Receiving user-connected event', `Calling ${userId}`); // Let some time for new peers to be able to answer setTimeout(() => { const call = myPeer.call(userId, stream, { metadata: { userId: this.currentUserId }, }); call.on('stream', (otherUserVideoStream: MediaStream) => { console.log('receiving other user stream after his connection'); this.addOtherUserVideo(userId, otherUserVideoStream); }); call.on('close', () => { this.videos = this.videos.filter((video) => video.userId !== userId); }); }, 1000); }); }); this.socket.on('user-disconnected', (userId) => { console.log(`receiving user-disconnected event from ${userId}`) this.videos = this.videos.filter(video => video.userId !== userId); }); } addMyVideo(stream: MediaStream) { this.videos.push({ muted: true, srcObject: stream, userId: this.currentUserId, }); } addOtherUserVideo(userId: string, stream: MediaStream) { const alreadyExisting = this.videos.some(video => video.userId === userId); if (alreadyExisting) { console.log(this.videos, userId); return; } this.videos.push({ muted: false, srcObject: stream, userId, }); } onLoadedMetadata(event: Event) { (event.target as HTMLVideoElement).play(); } } |
As you can see we are using the peerjs
library to connect the different clients
together to build a simple zoom clone video
chat and then we are using the navigator
api in the browser to allow the permission
to use the webcam of the user. And then we are using the socket.io
library in angular to send different events to the backend express
server whenever there is a new client connected inside the application and when socket are joining
different room, And when the user disconnects from the video chat we are removing it from the grid.
And now guys we need to do some routing
to connect these two components together. For this we need to go to app-routing.module.ts
file and copy paste the below code
app-routing.module.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import {HomeComponent} from "./home/home.component"; import {RoomComponent} from "./room/room.component"; const routes: Routes = [ { path: '', component: HomeComponent, }, { path: ':roomId', component: RoomComponent, } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { } |
As you can see we are providing the path
for each home
and room
component. And for this we are importing the built
in angular routing library.
And now we need to go to app.component.html
file and copy paste this line
app.component.html
1 |
<router-outlet></router-outlet> |
Now you also need to start the peerjs
server at port 3001 as shown below
npm i -g peer
peerjs --port 3001
And now you can start the angular
app at port 4200
ng serve