Welcome folks today in this blog post we will be building a zoom clone video chat
and watch youtube videos together
in node.js express
and socket.io using peerjs
. All the full source code of the application is shown below.
Get Started
In order to get started you need to read the below
blog post where I have shown how to build a basic zoom clone
video chat in node.js express and socket.io
Build Zoom Clone Video Chat Web App in Node.js Express and Socket.io Using WebRTC and PeerJS Library
And after you watch that tutorial you need to clone the github
repo of that tutorial as shown below
git clone https://github.com/WebDevSimplified/Zoom-Clone-With-WebRTC.git
After you clone the repository using git
you can go into the directory and install the node
packages as shown below
cd zoom-clone-with-webrtc
npm i
And now your directory structure will look like this
Installing the Peerjs Library
For this application you need the peerjs
library globally installed on my machine. You can install it using the npm
command
npm i -g peerjs
Now you can start the peerjs
server at port 3001
as shown below
peerjs --port 3001
And now you can start the express
server using the below commnad
nodemon server.js
This will start the app at port 3000
And now you need to little bit modify
and tweak
this app to allow users to watch
their youtube videos together. Now first of all you need to go room.ejs
file and copy paste the following code
views/room.ejs
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 |
<!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"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"> <script> const ROOM_ID = "<%= roomId %>" </script> <script defer src="https://unpkg.com/peerjs@1.2.0/dist/peerjs.min.js"></script> <script src="/socket.io/socket.io.js" defer></script> <script src="https://www.youtube.com/iframe_api"></script> <script src="script.js" defer></script> <title>Document</title> <style> #video-grid { display: grid; grid-template-columns: repeat(auto-fill, 300px); grid-auto-rows: 300px; } video { width: 100%; height: 100%; object-fit: cover; } </style> </head> <body> <div class="container"> <div id="video-grid"></div> <br><br> <!-- 1. The <iframe> (and video player) will replace this <div> tag. --> <div style="display: none;" id="player-container" class="container text-center"> <div class="embed-responsive-item" id="player"></div><br> </div> <br> <div class="form-group"> <input class="form-control" type="url" id="url" placeholder="Enter Youtube Video URL" required> </div> <button onclick="changeVideo()" class="btn btn-danger">Watch Youtube Video Together</button> <button onclick="pauseVideo()" class="btn btn-primary">Pause</button> <button onclick="playVideo()" class="btn btn-success">Play</button> <br><br><br><br><br> </div> </body> </html> |
As you can see we are importing the bootstrap
css cdn and also we are also importing the youtube iframe
api script tag and then we are adding the iframe player
div tag where we will be showing the youtube video. And then we have the input field
where we will be taking the url
of the video and three buttons for displaying the video
and then two buttons for playback
. And also we have attached the onclick
event listeners to all the buttons.
And now we need to modify the script.js
file which is the javascript
code at the client side inside the public
folder
public/script.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 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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
const socket = io('/') const videoGrid = document.getElementById('video-grid') let url = "" let player let videoId = "muR3W2EP3W8" const myPeer = new Peer(undefined, { host: '/', port: '3001' }) const myVideo = document.createElement('video') myVideo.muted = true const peers = {} navigator.mediaDevices.getUserMedia({ video: true, audio: false }).then(stream => { addVideoStream(myVideo, stream) myPeer.on('call', call => { call.answer(stream) const video = document.createElement('video') call.on('stream', userVideoStream => { addVideoStream(video, userVideoStream) }) }) socket.on('changevideo',(videoId) => { document.getElementById('player-container').style.display = "block" youTubePlayerChangeVideoId(videoId); }) socket.on('play',() => { player.playVideo() }) socket.on('pause',() => { player.pauseVideo() }) socket.on('user-connected', userId => { connectToNewUser(userId, stream) }) }) function playVideo(){ socket.emit("play") } function pauseVideo(){ socket.emit("pause") } function changeVideo(){ document.getElementById("player").innerHTML = ""; url = document.getElementById('url').value videoId = YouTubeGetID(url) socket.emit("changevideo",videoId) document.getElementById('url').value="" } function YouTubeGetID(url) { var ID = ""; url = url .replace(/(>|<)/gi, "") .split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/); if (url[2] !== undefined) { ID = url[2].split(/[^0-9a-z_\-]/i); ID = ID[0]; } else { ID = url; } return ID; } socket.on('user-disconnected', userId => { if (peers[userId]) peers[userId].close() }) myPeer.on('open', id => { socket.emit('join-room', ROOM_ID, id) }) function connectToNewUser(userId, stream) { const call = myPeer.call(userId, stream) const video = document.createElement('video') call.on('stream', userVideoStream => { addVideoStream(video, userVideoStream) }) call.on('close', () => { video.remove() }) peers[userId] = call } function addVideoStream(video, stream) { video.srcObject = stream video.addEventListener('loadedmetadata', () => { video.play() }) videoGrid.append(video) } function youTubePlayerChangeVideoId(videoId) { player.cueVideoById({ suggestedQuality: "tiny", videoId: videoId }); player.pauseVideo(); } function onYouTubeIframeAPIReady() { player = new YT.Player("player", { height: 500, width: "100%", videoId: videoId, playerVars: { playsinline: 1, autoplay: 0, controls: 1, }, events: { onReady: onPlayerReady, onStateChange: onPlayerStateChange, }, }); } function onPlayerReady() { console.log(true); } var done = false; function onPlayerStateChange(event) { if (event.data == YT.PlayerState.PLAYING && !done) { done = true; } } |
As you can see we have the added the lifecycle
methods of the youtube iframe embed
api where we are initializing the video player
and setting the width and height
and then we have the methods which execute when we click the button to change
video inside which we are extracting the video id
from the youtube url using the custom function where we are using the regular expressions
and then we are emitting the event to the server side
to change the video. And then the same thing we are doing for play
and pause
buttons. Here also we are sending the events
to server side. And also we have the events which we are listening
from the server. Based upon that we are calling playVideo()
and pauseVideo()
to play and pause the video.
And lastly we need to edit the server.js
file at the backend and copy paste the below code
server.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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
const express = require('express') const app = express() const server = require('http').Server(app) const io = require('socket.io')(server) const { v4: uuidV4 } = require('uuid') app.set('view engine', 'ejs') app.use(express.static('public')) app.get('/', (req, res) => { res.redirect(`/${uuidV4()}`) }) app.get('/:room', (req, res) => { res.render('room', { roomId: req.params.room }) }) 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) }) socket.on('changevideo',(videoId) => { io.emit('changevideo',videoId) }) socket.on('play',() =>{ io.sockets.in(roomId).emit("play") }) socket.on('pause',() => { io.sockets.in(roomId).emit("pause") }) }) }) server.listen(4000) |
As you can see we are listening
for the events coming from the client side
using the on()
method and inside which we are emitting
the events
to the clients
connected to the same roomId
using the io.sockets.in(roomId)
method. Basically it will only send events to all clients connected to the same room. And we are doing it the same thing for play and pause buttons
And now if you start the express app
the result will look like this as shown below