├── client ├── .env ├── .env.production ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── src │ ├── utils │ │ ├── helpers.js │ │ ├── apiEndpoints.js │ │ └── apiRequests.js │ ├── App.scss │ ├── components │ │ ├── CallPage │ │ │ ├── CallPage.scss │ │ │ └── CallPage.js │ │ ├── NoMatch │ │ │ ├── NoMatch.scss │ │ │ └── NoMatch.js │ │ ├── UI │ │ │ ├── Alert │ │ │ │ ├── Alert.js │ │ │ │ └── Alert.scss │ │ │ ├── Header │ │ │ │ ├── Header.scss │ │ │ │ └── Header.js │ │ │ ├── CallPageHeader │ │ │ │ ├── CallPageHeader.scss │ │ │ │ └── CallPageHeader.js │ │ │ ├── MeetingInfo │ │ │ │ ├── MeetingInfo.js │ │ │ │ └── MeetingInfo.scss │ │ │ ├── Messenger │ │ │ │ ├── Messenger.scss │ │ │ │ └── Messenger.js │ │ │ └── CallPageFooter │ │ │ │ ├── CallPageFooter.js │ │ │ │ └── CallPageFooter.scss │ │ └── HomePage │ │ │ ├── HomePage.js │ │ │ └── HomePage.scss │ ├── setupTests.js │ ├── App.test.js │ ├── reducers │ │ └── MessageListReducer.js │ ├── index.css │ ├── reportWebVitals.js │ ├── index.js │ ├── App.js │ ├── logo.svg │ └── test.js ├── .gitignore ├── package.json └── README.md ├── dump.rdb ├── .env ├── README.md ├── app ├── routes.js ├── socketManager.js ├── config │ └── redis.js ├── controller.js └── model.js ├── package.json ├── server.js └── .gitignore /client/.env: -------------------------------------------------------------------------------- 1 | 2 | REACT_APP_BASE_URL=http://localhost:4000 3 | -------------------------------------------------------------------------------- /client/.env.production: -------------------------------------------------------------------------------- 1 | REACT_APP_BASE_URL= 2 | -------------------------------------------------------------------------------- /dump.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkii922/google-meet-clone/HEAD/dump.rdb -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | 2 | PORT=4000 3 | REDIS_HOST= 4 | REDIS_PORT=18440 5 | REDIS_PASSWORD=test 6 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkii922/google-meet-clone/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkii922/google-meet-clone/HEAD/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkii922/google-meet-clone/HEAD/client/public/logo512.png -------------------------------------------------------------------------------- /client/src/utils/helpers.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | export const formatDate = (timestamp) => { 4 | return moment(timestamp).format("h:mm A"); 5 | } -------------------------------------------------------------------------------- /client/src/utils/apiEndpoints.js: -------------------------------------------------------------------------------- 1 | export const BASE_URL = process.env.REACT_APP_BASE_URL; 2 | export const SAVE_CALL_ID = "/api/save-call-id"; 3 | export const GET_CALL_ID = "/api/get-call-id"; 4 | -------------------------------------------------------------------------------- /client/src/App.scss: -------------------------------------------------------------------------------- 1 | .green { 2 | background: #00796b !important; 3 | color: #fff !important; 4 | font-weight: 500 !important; 5 | &:hover { 6 | background: #00685c !important; 7 | } 8 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # google-meet-clone 2 | 3 | # SETUP 4 | Start redis server "redis-server" 5 | 6 | ## Backend 7 | npm i 8 | 9 | Create .env file 10 | 11 | ## Env File Content 12 | PORT=4000 13 | 14 | ## In Client folder (React app) 15 | npm i 16 | 17 | npm start 18 | -------------------------------------------------------------------------------- /client/src/components/CallPage/CallPage.scss: -------------------------------------------------------------------------------- 1 | .callpage-container { 2 | .video-container { 3 | height: 100vh; 4 | width: 100%; 5 | object-fit: cover; // use "cover" to avoid distortion 6 | position: absolute; 7 | z-index: -1; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /client/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /app/routes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const controller = require('./controller'); 4 | 5 | router.post("/api/save-call-id", controller.saveCallId); 6 | router.get("/api/get-call-id/:id", controller.getCallId); 7 | 8 | module.exports = router; -------------------------------------------------------------------------------- /client/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /app/socketManager.js: -------------------------------------------------------------------------------- 1 | const io = require("./../server").io; 2 | 3 | module.exports = (socket) => { 4 | try { 5 | console.log("Connected"); 6 | socket.on("code", (data, callback) => { 7 | socket.broadcast.emit("code", data); 8 | }); 9 | } catch (ex) { 10 | console.log(ex.message); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /client/src/reducers/MessageListReducer.js: -------------------------------------------------------------------------------- 1 | const MessageListReducer = (state, action) => { 2 | let draftState = [...state]; 3 | switch (action.type) { 4 | case "addMessage": 5 | return [...draftState, action.payload]; 6 | default: 7 | return state; 8 | } 9 | }; 10 | 11 | export default MessageListReducer; -------------------------------------------------------------------------------- /app/config/redis.js: -------------------------------------------------------------------------------- 1 | let redis = require("redis"); 2 | var options = { 3 | host: process.env.REDIS_HOST, 4 | port: process.env.REDIS_PORT, 5 | auth_pass: process.env.REDIS_PASSWORD, 6 | }; 7 | 8 | let client = redis.createClient(options); 9 | 10 | client.on("error", (error) => { 11 | console.log(error); 12 | }); 13 | 14 | module.exports = client; 15 | -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /client/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | .env.production 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /client/src/components/NoMatch/NoMatch.scss: -------------------------------------------------------------------------------- 1 | .no-match { 2 | .no-match__content { 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | justify-content: center; 7 | margin-top: 100px; 8 | h2 { 9 | font-size: 38px; 10 | font-weight: 300; 11 | } 12 | .btn { 13 | cursor: pointer; 14 | font-size: 16px; 15 | padding: 15px; 16 | border: none; 17 | outline: none; 18 | background: none; 19 | border-radius: 5px; 20 | text-decoration: none; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /app/controller.js: -------------------------------------------------------------------------------- 1 | const { saveCallId, getCallId } = require("./model"); 2 | 3 | exports.saveCallId = async (req, res) => { 4 | try { 5 | const { id, signalData } = req.body; 6 | await saveCallId(id, signalData); 7 | res.status(200).send(true); 8 | } catch (ex) { 9 | res.status(400).send(ex.message); 10 | } 11 | }; 12 | 13 | exports.getCallId = async (req, res) => { 14 | try { 15 | const { id } = req.params; 16 | const code = await getCallId(id); 17 | res.status(200).send({ code }); 18 | } catch (ex) { 19 | res.status(400).send(ex.message); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /client/src/components/NoMatch/NoMatch.js: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import "./NoMatch.scss"; 3 | import Header from "../UI/Header/Header"; 4 | 5 | const NoMatch = () => { 6 | return ( 7 |
8 |
9 |
10 |

Invalid video call name.

11 |
12 | 13 | Return to home screen 14 | 15 |
16 |
17 |
18 | ); 19 | }; 20 | export default NoMatch; 21 | -------------------------------------------------------------------------------- /app/model.js: -------------------------------------------------------------------------------- 1 | const redisClient = require("./config/redis"); 2 | 3 | exports.saveCallId = (key, value) => { 4 | return new Promise((resolve, reject) => { 5 | redisClient.SET(key, JSON.stringify(value), "EX", 86400, (err, res) => { 6 | if (err) { 7 | reject(err); 8 | } 9 | resolve(res); 10 | }); 11 | }); 12 | }; 13 | 14 | exports.getCallId = (key) => { 15 | return new Promise((resolve, reject) => { 16 | redisClient.GET(key, (err, res) => { 17 | if (err) { 18 | reject(err); 19 | } 20 | resolve(JSON.parse(res)); 21 | }); 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /client/src/components/UI/Alert/Alert.js: -------------------------------------------------------------------------------- 1 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 2 | import { faCommentAlt } from "@fortawesome/free-solid-svg-icons"; 3 | import "./Alert.scss"; 4 | 5 | const Alert = ({ messageAlert }) => { 6 | return ( 7 |
8 |
9 | 10 |

{messageAlert.payload.user}

11 |
12 |

{messageAlert.payload.msg}

13 |
14 | ); 15 | }; 16 | 17 | export default Alert; 18 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /client/src/components/UI/Header/Header.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | width: 100%; 3 | display: flex; 4 | justify-content: space-between; 5 | padding: 20px; 6 | box-sizing: border-box; 7 | .logo { 8 | display: flex; 9 | align-items: center; 10 | width: 124px; 11 | img { 12 | width: 100%; 13 | } 14 | .help-text { 15 | font-size: 22px; 16 | padding-left: 4px; 17 | color: #5f6368; 18 | } 19 | } 20 | .action-btn { 21 | display: flex; 22 | align-items: center; 23 | .icon-block { 24 | font-size: 20px; 25 | padding: 10px; 26 | color: #5f6368; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google-meet", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "dependencies": { 7 | "body-parser": "^1.19.0", 8 | "cors": "^2.8.5", 9 | "dotenv": "^8.2.0", 10 | "express": "^4.17.1", 11 | "redis": "^3.0.2", 12 | "socket.io": "^3.1.0" 13 | }, 14 | "devDependencies": {}, 15 | "scripts": { 16 | "test": "echo \"Error: no test specified\" && exit 1", 17 | "start": "node server.js", 18 | "heroku-postbuild": "cd client && npm install && npm install --only=dev --no-shrinkwrap && npm run build" 19 | }, 20 | "author": "", 21 | "license": "ISC" 22 | } 23 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; 2 | import "./App.scss"; 3 | import HomePage from "./components/HomePage/HomePage"; 4 | import CallPage from "./components/CallPage/CallPage"; 5 | import NoMatch from "./components/NoMatch/NoMatch"; 6 | 7 | function App() { 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /client/src/components/UI/Alert/Alert.scss: -------------------------------------------------------------------------------- 1 | .message-alert-popup { 2 | position: absolute; 3 | bottom: 100px; 4 | right: 10px; 5 | background: #fff; 6 | border-radius: 5px; 7 | padding: 15px; 8 | min-width: 250px; 9 | max-width: 250px; 10 | .alert-header { 11 | display: flex; 12 | align-items: center; 13 | margin-bottom: 5px; 14 | color: #04796a; 15 | .icon { 16 | margin-right: 10px; 17 | } 18 | h3 { 19 | font-size: 16px; 20 | font-weight: 500; 21 | margin: 0; 22 | } 23 | } 24 | .alert-msg { 25 | font-size: 14px; 26 | margin: 0; 27 | white-space: nowrap; 28 | overflow: hidden; 29 | text-overflow: ellipsis; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /client/src/components/UI/Header/Header.js: -------------------------------------------------------------------------------- 1 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 2 | import { 3 | faQuestionCircle, 4 | faExclamationCircle, 5 | faCog, 6 | } from "@fortawesome/free-solid-svg-icons"; 7 | import './Header.scss'; 8 | 9 | const Header = () => { 10 | return ( 11 |
12 |
13 | 14 | Meet 15 |
16 |
17 | 18 | 19 | 20 |
21 |
22 | ); 23 | }; 24 | export default Header; 25 | -------------------------------------------------------------------------------- /client/src/utils/apiRequests.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export const postRequest = async (url, payload = {}) => { 4 | const data = await axios.post(url, payload) 5 | .then(resp => resp.data) 6 | .catch(err => ( 7 | { error: err.response.data } 8 | )); 9 | return data; 10 | } 11 | 12 | export const putRequest = async (url, payload = {}) => { 13 | const data = await axios.put(url, payload) 14 | .then(resp => resp.data) 15 | .catch(err => ( 16 | { error: err.response.data } 17 | )); 18 | return data; 19 | } 20 | 21 | export const getRequest = async (url) => { 22 | const data = await axios.get(url) 23 | .then(resp => resp.data) 24 | .catch(err => ( 25 | { error: err.response.data } 26 | )); 27 | return data; 28 | } 29 | 30 | export const deleteRequest = async (url) => { 31 | const data = await axios.delete(url) 32 | .then(resp => resp.data) 33 | .catch(err => ( 34 | { error: err.response.data } 35 | )); 36 | return data; 37 | } -------------------------------------------------------------------------------- /client/src/components/UI/CallPageHeader/CallPageHeader.scss: -------------------------------------------------------------------------------- 1 | .frame-header { 2 | display: flex; 3 | justify-content: space-around; 4 | align-items: center; 5 | width: 350px; 6 | position: absolute; 7 | top: 0; 8 | right: 0; 9 | background: #fff; 10 | border-radius: 0 0 0 10px; 11 | overflow: hidden; 12 | .header-items { 13 | display: flex; 14 | align-items: center; 15 | justify-content: center; 16 | height: 50px; 17 | flex: 1; 18 | border-right: 1px solid rgb(245, 245, 245); 19 | position: relative; 20 | .alert-circle-icon { 21 | position: absolute; 22 | background: #04796a; 23 | width: 10px; 24 | height: 10px; 25 | border-radius: 50%; 26 | border: 2px solid #fff; 27 | top: 10px; 28 | right: 20px; 29 | } 30 | &.date-block { 31 | font-size: 22px; 32 | color: #5f6368; 33 | min-width: 120px; 34 | font-weight: 400; 35 | } 36 | &.icon-block { 37 | .icon { 38 | color: #505357; 39 | font-size: 22px; 40 | &.profile { 41 | font-size: 32px; 42 | color: #04796a; 43 | } 44 | } 45 | &:hover { 46 | cursor: pointer; 47 | background: #eee; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const express = require("express"); 3 | const http = require("http"); 4 | const bodyParser = require("body-parser"); 5 | const cors = require("cors"); 6 | const port = process.env.PORT; 7 | const app = express(); 8 | const server = http.createServer(app); 9 | const Routes = require("./app/routes"); 10 | const path = require('path'); 11 | 12 | app.use([ 13 | cors(), 14 | bodyParser.json(), 15 | bodyParser.urlencoded({ extended: false }), 16 | Routes, 17 | ]); 18 | 19 | // const io = (module.exports.io = require("socket.io")(server)); 20 | // This is missing in the video. 21 | const io = (module.exports.io = require('socket.io')(server, { 22 | cors: { 23 | origin: '*', 24 | } 25 | })); 26 | const socketManager = require("./app/socketManager"); 27 | io.on("connection", socketManager); 28 | 29 | if (process.env.NODE_ENV === 'production') { 30 | // Serve any static files 31 | app.use(express.static(path.join(__dirname, 'client/build'))); 32 | // Handle React routing, return all requests to React app 33 | app.get('*', function(req, res) { 34 | res.sendFile(path.join(__dirname, 'client/build', 'index.html')); 35 | }); 36 | } 37 | 38 | server.listen(port, () => { 39 | console.log(`Server is running on port ${port}`); 40 | }); 41 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@fortawesome/fontawesome-svg-core": "^1.2.34", 7 | "@fortawesome/free-solid-svg-icons": "^5.15.2", 8 | "@fortawesome/react-fontawesome": "^0.1.14", 9 | "@testing-library/jest-dom": "^5.11.4", 10 | "@testing-library/react": "^11.1.0", 11 | "@testing-library/user-event": "^12.1.10", 12 | "axios": "^0.21.1", 13 | "dotenv": "^10.0.0", 14 | "moment": "^2.29.1", 15 | "node-sass": "^4.14.1", 16 | "react": "^17.0.1", 17 | "react-dom": "^17.0.1", 18 | "react-router-dom": "^5.2.0", 19 | "react-scripts": "4.0.1", 20 | "shortid": "^2.2.16", 21 | "simple-peer": "^9.9.3", 22 | "socket.io-client": "^3.1.1", 23 | "web-vitals": "^0.2.4" 24 | }, 25 | "scripts": { 26 | "start": "react-scripts start", 27 | "build": "react-scripts build", 28 | "test": "react-scripts test", 29 | "eject": "react-scripts eject" 30 | }, 31 | "eslintConfig": { 32 | "extends": [ 33 | "react-app", 34 | "react-app/jest" 35 | ] 36 | }, 37 | "browserslist": { 38 | "production": [ 39 | ">0.2%", 40 | "not dead", 41 | "not op_mini all" 42 | ], 43 | "development": [ 44 | "last 1 chrome version", 45 | "last 1 firefox version", 46 | "last 1 safari version" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /client/src/components/UI/MeetingInfo/MeetingInfo.js: -------------------------------------------------------------------------------- 1 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 2 | import { 3 | faCopy, 4 | faTimes, 5 | faUser, 6 | faShieldAlt, 7 | } from "@fortawesome/free-solid-svg-icons"; 8 | import "./MeetingInfo.scss"; 9 | 10 | const MeetingInfo = ({ setMeetInfoPopup, url }) => { 11 | return ( 12 |
13 |
14 |

Your meeting's ready

15 | { 19 | setMeetInfoPopup(false); 20 | }} 21 | /> 22 |
23 | 27 |

28 | Or share this meeting link with others you want in the meeting 29 |

30 |
31 | {url} 32 | navigator.clipboard.writeText(url)} 36 | /> 37 |
38 |
39 | 40 |

41 | People who use this meeting link must get your permission before they 42 | can join. 43 |

44 |
45 |

Joined as akshay@gmail.com

46 |
47 | ); 48 | }; 49 | 50 | export default MeetingInfo; 51 | -------------------------------------------------------------------------------- /client/src/components/UI/CallPageHeader/CallPageHeader.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 3 | import { 4 | faUserFriends, 5 | faCommentAlt, 6 | faUserCircle, 7 | } from "@fortawesome/free-solid-svg-icons"; 8 | import "./CallPageHeader.scss"; 9 | import { formatDate } from "./../../../utils/helpers"; 10 | 11 | const CallPageHeader = ({ 12 | isMessenger, 13 | setIsMessenger, 14 | messageAlert, 15 | setMessageAlert, 16 | }) => { 17 | let interval = null; 18 | const [currentTime, setCurrentTime] = useState(() => { 19 | return formatDate(); 20 | }); 21 | 22 | useEffect(() => { 23 | interval = setInterval(() => setCurrentTime(formatDate()), 1000); 24 | return () => { 25 | clearInterval(interval); 26 | }; 27 | }, []); 28 | 29 | return ( 30 |
31 |
32 | 33 |
34 |
{ 37 | setIsMessenger(true); 38 | setMessageAlert({}); 39 | }} 40 | > 41 | 42 | {!isMessenger && messageAlert.alert && ( 43 | 44 | )} 45 |
46 |
{currentTime}
47 |
48 | 49 |
50 |
51 | ); 52 | }; 53 | 54 | export default CallPageHeader; 55 | -------------------------------------------------------------------------------- /client/src/components/UI/MeetingInfo/MeetingInfo.scss: -------------------------------------------------------------------------------- 1 | .meeting-info-block { 2 | position: absolute; 3 | top: 50px; 4 | left: 50px; 5 | background: #fff; 6 | border-radius: 10px; 7 | padding: 25px; 8 | width: 310px; 9 | .meeting-header { 10 | display: flex; 11 | align-items: center; 12 | justify-content: space-between; 13 | color: #222; 14 | h3 { 15 | margin: 0; 16 | font-size: 18px; 17 | font-weight: 400; 18 | } 19 | .icon { 20 | cursor: pointer; 21 | font-size: 20px; 22 | } 23 | } 24 | .add-people-btn { 25 | display: flex; 26 | align-items: center; 27 | justify-content: center; 28 | background: #04796a; 29 | color: #fff; 30 | font-size: 16px; 31 | padding: 10px; 32 | border-radius: 5px; 33 | outline: none; 34 | border: none; 35 | margin-top: 20px; 36 | .icon { 37 | font-size: 12px; 38 | margin-right: 10px; 39 | } 40 | } 41 | .info-text { 42 | color: #222; 43 | font-size: 14px; 44 | } 45 | .meet-link { 46 | display: flex; 47 | align-items: center; 48 | justify-content: space-between; 49 | background: #f1f3f5; 50 | padding: 15px; 51 | border-radius: 5px; 52 | span { 53 | font-size: 14px; 54 | font-weight: 600; 55 | border: none; 56 | outline: none; 57 | background: none; 58 | flex: 1; 59 | color: #555; 60 | } 61 | .icon { 62 | cursor: pointer; 63 | color: #555; 64 | } 65 | } 66 | .permission-text { 67 | display: flex; 68 | align-items: center; 69 | justify-content: center; 70 | .icon { 71 | color: #4385f4; 72 | font-size: 20px; 73 | margin-right: 10px; 74 | } 75 | } 76 | .small-text { 77 | font-size: 13px; 78 | color: #222; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /client/src/components/HomePage/HomePage.js: -------------------------------------------------------------------------------- 1 | import { useHistory } from "react-router-dom"; 2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 3 | import { faVideo, faKeyboard } from "@fortawesome/free-solid-svg-icons"; 4 | import shortid from "shortid"; 5 | import "./HomePage.scss"; 6 | import Header from "../UI/Header/Header"; 7 | 8 | const HomePage = () => { 9 | const history = useHistory(); 10 | 11 | const startCall = () => { 12 | const uid = shortid.generate(); 13 | history.push(`/${uid}#init`); 14 | }; 15 | 16 | return ( 17 |
18 |
19 |
20 |
21 |
22 |

Premium video meetings. Now free for everyone.

23 |

24 | We re-engineered the service we built for secure business 25 | meetings, Google Meet, to make it free and available for all. 26 |

27 |
28 | 32 |
33 |
34 | 35 | 36 |
37 | 38 |
39 |
40 |
41 |
42 | Learn more about Google Meet 43 |
44 |
45 |
46 |
47 | 48 |
49 |
50 |
51 |
52 | ); 53 | }; 54 | export default HomePage; 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | # .env 73 | # .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /client/src/components/UI/Messenger/Messenger.scss: -------------------------------------------------------------------------------- 1 | .messenger-container { 2 | position: absolute; 3 | top: 0; 4 | right: 0; 5 | background: #fff; 6 | height: calc(100vh - 90px); 7 | width: 350px; 8 | box-sizing: border-box; 9 | display: flex; 10 | justify-content: space-between; 11 | flex-direction: column; 12 | .messenger-header { 13 | padding: 20px; 14 | display: flex; 15 | align-items: center; 16 | justify-content: space-between; 17 | margin: 10px 0; 18 | h3 { 19 | margin: 0; 20 | font-weight: 400; 21 | } 22 | .icon { 23 | cursor: pointer; 24 | font-size: 20px; 25 | } 26 | } 27 | .messenger-header-tabs { 28 | display: flex; 29 | align-items: center; 30 | border-bottom: 1px solid #eee; 31 | .tab { 32 | display: flex; 33 | align-items: center; 34 | justify-content: center; 35 | width: 100%; 36 | color: #555; 37 | p { 38 | margin-left: 10px; 39 | } 40 | &.active { 41 | border-bottom: 2px solid #04796a; 42 | color: #04796a; 43 | } 44 | &:hover { 45 | cursor: pointer; 46 | background: rgba(0, 121, 107, 0.039); 47 | } 48 | } 49 | } 50 | .chat-section { 51 | padding: 20px; 52 | flex: 1; 53 | overflow-y: scroll; 54 | .chat-block { 55 | margin-bottom: 30px; 56 | .sender { 57 | font-weight: 500; 58 | font-size: 14px; 59 | small { 60 | margin-left: 5px; 61 | font-weight: 300; 62 | } 63 | } 64 | .msg { 65 | margin: 0; 66 | padding-top: 5px; 67 | color: #555; 68 | font-size: 14px; 69 | } 70 | } 71 | } 72 | .send-msg-section { 73 | padding: 20px; 74 | border-top: 1px solid #eee; 75 | border-bottom: 1px solid #eee; 76 | display: flex; 77 | align-items: center; 78 | justify-content: space-between; 79 | color: #555; 80 | input { 81 | padding: 5px; 82 | border: none; 83 | outline: none; 84 | border-bottom: 1px solid #eee; 85 | width: 80%; 86 | } 87 | .icon { 88 | cursor: pointer; 89 | &:hover { 90 | color: #04796a; 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /client/src/components/UI/CallPageFooter/CallPageFooter.js: -------------------------------------------------------------------------------- 1 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 2 | import { 3 | faVideo, 4 | faMicrophone, 5 | faPhone, 6 | faAngleUp, 7 | faClosedCaptioning, 8 | faDesktop, 9 | faMicrophoneSlash, 10 | } from "@fortawesome/free-solid-svg-icons"; 11 | import "./CallPageFooter.scss"; 12 | 13 | const CallPageFooter = ({ 14 | isPresenting, 15 | stopScreenShare, 16 | screenShare, 17 | isAudio, 18 | toggleAudio, 19 | disconnectCall, 20 | }) => { 21 | return ( 22 |
23 |
24 |
25 | Meeting details 26 | 27 |
28 |
29 |
30 |
toggleAudio(!isAudio)} 33 | > 34 | 38 |
39 |
40 | 41 |
42 |
43 | 44 |
45 |
46 |
47 |
48 | 49 |

Turn on captions

50 |
51 | 52 | {isPresenting ? ( 53 |
54 | 55 |

Stop presenting

56 |
57 | ) : ( 58 |
59 | 60 |

Present now

61 |
62 | )} 63 |
64 |
65 | ); 66 | }; 67 | 68 | export default CallPageFooter; 69 | -------------------------------------------------------------------------------- /client/src/components/UI/CallPageFooter/CallPageFooter.scss: -------------------------------------------------------------------------------- 1 | .footer-item { 2 | position: absolute; 3 | bottom: 0; 4 | left: 0; 5 | width: 100%; 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | height: 90px; 10 | background: #ffffff; 11 | z-index: 1; 12 | .left-item { 13 | display: flex; 14 | align-items: center; 15 | height: 100%; 16 | width: 350px; 17 | .icon-block { 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | height: 100%; 22 | padding: 0 20px; 23 | .icon { 24 | margin-left: 10px; 25 | font-size: 20px; 26 | } 27 | &:hover { 28 | cursor: pointer; 29 | background: #eee; 30 | } 31 | } 32 | } 33 | .center-item { 34 | width: 100%; 35 | display: flex; 36 | align-items: center; 37 | justify-content: center; 38 | flex: 1; 39 | .icon-block { 40 | background: #fff; 41 | border: 1px solid rgb(223, 223, 223); 42 | width: 50px; 43 | height: 50px; 44 | border-radius: 50%; 45 | display: flex; 46 | align-items: center; 47 | justify-content: center; 48 | margin: 0 10px; 49 | .icon { 50 | color: #555; 51 | font-size: 20px; 52 | &.red { 53 | color: #d93025; 54 | } 55 | } 56 | &:hover { 57 | cursor: pointer; 58 | box-shadow: 0 0 10px #ddd; 59 | } 60 | &.red-bg{ 61 | background: #d93025; 62 | .icon{ 63 | color: #fff; 64 | } 65 | } 66 | } 67 | } 68 | .right-item { 69 | display: flex; 70 | align-items: center; 71 | justify-content: flex-end; 72 | height: 100%; 73 | width: 350px; 74 | .icon-block { 75 | display: flex; 76 | align-items: center; 77 | justify-content: center; 78 | flex-direction: column; 79 | height: 100%; 80 | width: 146px; 81 | color: #555; 82 | .icon { 83 | font-size: 20px; 84 | margin-bottom: 10px; 85 | } 86 | .title { 87 | margin: 0; 88 | font-size: 14px; 89 | font-weight: 500; 90 | } 91 | &:hover { 92 | cursor: pointer; 93 | background: #eee; 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /client/src/components/UI/Messenger/Messenger.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import "./Messenger.scss"; 3 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 4 | import { 5 | faTimes, 6 | faUserFriends, 7 | faCommentAlt, 8 | faPaperPlane, 9 | } from "@fortawesome/free-solid-svg-icons"; 10 | import { formatDate } from "./../../../utils/helpers"; 11 | 12 | const Messenger = ({ setIsMessenger, sendMsg, messageList }) => { 13 | const [msg, setMsg] = useState(""); 14 | 15 | const handleChangeMsg = (e) => { 16 | setMsg(e.target.value); 17 | }; 18 | 19 | const handleKeyDown = (e) => { 20 | if (e.key === "Enter") { 21 | sendMsg(msg); 22 | setMsg(""); 23 | } 24 | }; 25 | 26 | const handleSendMsg = () => { 27 | sendMsg(msg); 28 | setMsg(""); 29 | }; 30 | 31 | return ( 32 |
33 |
34 |

Meeting details

35 | { 39 | setIsMessenger(false); 40 | }} 41 | /> 42 |
43 | 44 |
45 |
46 | 47 |

People (1)

48 |
49 |
50 | 51 |

Chat

52 |
53 |
54 | 55 |
56 | {messageList.map((item) => ( 57 |
58 |
59 | {item.user} {formatDate(item.time)} 60 |
61 |

{item.msg}

62 |
63 | ))} 64 |
65 | 66 |
67 | handleChangeMsg(e)} 71 | onKeyDown={(e) => handleKeyDown(e)} 72 | /> 73 | 78 |
79 |
80 | ); 81 | }; 82 | 83 | export default Messenger; 84 | -------------------------------------------------------------------------------- /client/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/components/HomePage/HomePage.scss: -------------------------------------------------------------------------------- 1 | .home-page { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100vh; 5 | .body { 6 | flex: 1; 7 | display: flex; 8 | align-items: center; 9 | justify-content: space-around; 10 | .left-side { 11 | max-width: 500px; 12 | .content { 13 | h2 { 14 | font-size: 44px; 15 | font-weight: 400; 16 | margin: 0; 17 | } 18 | p { 19 | font-size: 18px; 20 | font-weight: 300; 21 | } 22 | .action-btn { 23 | margin-top: 60px; 24 | display: flex; 25 | align-items: center; 26 | justify-content: space-between; 27 | .btn { 28 | cursor: pointer; 29 | display: flex; 30 | align-items: center; 31 | justify-content: center; 32 | font-size: 16px; 33 | padding: 15px; 34 | border: none; 35 | outline: none; 36 | background: none; 37 | border-radius: 5px; 38 | color: #00796b; 39 | text-decoration: none; 40 | .icon-block { 41 | padding-right: 10px; 42 | } 43 | &:hover { 44 | background: #f6faf9; 45 | } 46 | } 47 | .input-block { 48 | margin-left: 20px; 49 | display: flex; 50 | flex: 1; 51 | .input-section { 52 | position: relative; 53 | margin-right: 10px; 54 | .icon-block { 55 | position: absolute; 56 | top: 50%; 57 | transform: translate(10px, -50%); 58 | color: #5f6368; 59 | } 60 | input { 61 | font-size: 16px; 62 | height: 48px; 63 | border: 1px solid #ccc; 64 | border-radius: 5px; 65 | padding-left: 35px; 66 | width: 100%; 67 | box-sizing: border-box; 68 | outline: none; 69 | } 70 | } 71 | } 72 | } 73 | } 74 | .help-text { 75 | margin-top: 30px; 76 | padding-top: 25px; 77 | border-top: 1px solid #ddd; 78 | a { 79 | text-decoration: none; 80 | color: #00796b; 81 | } 82 | } 83 | } 84 | .right-side { 85 | .content { 86 | border: 1px solid #eee; 87 | padding: 30px; 88 | width: 100%; 89 | max-width: 650px; 90 | border-radius: 5px; 91 | box-sizing: border-box; 92 | box-shadow: 0 1px 2px 0 rgba(60, 64, 67, 0.302), 93 | 0 2px 6px 2px rgba(60, 64, 67, 0.149); 94 | img { 95 | width: 100%; 96 | border-radius: 5px; 97 | } 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `yarn build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /client/src/test.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, useRef } from "react"; 2 | import Peer from "simple-peer"; 3 | import "./App.css"; 4 | let peer = null; 5 | 6 | function App() { 7 | const [incoming, setIncoming] = useState(null); 8 | const [outgoing, setOutgoing] = useState(null); 9 | const [msg, setMsg] = useState(""); 10 | const [stream, setStream] = useState(); 11 | const [screenCastStream, setScreenCastStream] = useState(); 12 | // const userVideo = useRef(); 13 | 14 | useEffect(() => { 15 | initWebRTC(); 16 | }, []); 17 | 18 | const initWebRTC = () => { 19 | navigator.mediaDevices 20 | .getUserMedia({ 21 | video: true, 22 | audio: false, 23 | }) 24 | .then((stream) => { 25 | setStream(stream); 26 | // if (userVideo.current) { 27 | // userVideo.current.srcObject = stream; 28 | // } 29 | 30 | peer = new Peer({ 31 | initiator: window.location.hash === "#admin", 32 | trickle: false, 33 | stream: stream, 34 | }); 35 | 36 | peer.on("signal", (data) => { 37 | console.log(data); 38 | // when peer has signaling data, give it to peer2 somehow 39 | // peer2.signal(data) 40 | setOutgoing(data); 41 | }); 42 | 43 | peer.on("connect", () => { 44 | // wait for 'connect' event before using the data channel 45 | // peer.send('hey peer2, how is it going?') 46 | }); 47 | 48 | peer.on("data", (data) => { 49 | // got a data channel message 50 | console.log("got a message from peer2: " + data); 51 | }); 52 | 53 | peer.on("stream", (stream) => { 54 | console.log("stream **** ", stream); 55 | // got remote video stream, now let's show it in a video tag 56 | var video = document.querySelector("video"); 57 | 58 | if ("srcObject" in video) { 59 | video.srcObject = stream; 60 | } else { 61 | video.src = window.URL.createObjectURL(stream); // for older browsers 62 | } 63 | 64 | video.play(); 65 | }); 66 | }) 67 | .catch(() => {}); 68 | }; 69 | 70 | const handleChange = (e) => { 71 | console.log(e.target.value); 72 | setIncoming(e.target.value); 73 | }; 74 | 75 | const handleSubmit = () => { 76 | peer.signal(JSON.parse(incoming)); 77 | }; 78 | 79 | const handleChangeMsg = (e) => { 80 | setMsg(e.target.value); 81 | }; 82 | 83 | const sendMsg = () => { 84 | peer.send(`${msg}`); 85 | }; 86 | 87 | const screenShare = () => { 88 | navigator.mediaDevices 89 | .getDisplayMedia({ cursor: true }) 90 | .then((screenStream) => { 91 | peer.replaceTrack( 92 | stream.getVideoTracks()[0], 93 | screenStream.getVideoTracks()[0], 94 | stream 95 | ); 96 | setScreenCastStream(screenStream); 97 | // userVideo.current.srcObject = screenStream; 98 | screenStream.getTracks()[0].onended = () => { 99 | peer.replaceTrack( 100 | screenStream.getVideoTracks()[0], 101 | stream.getVideoTracks()[0], 102 | stream 103 | ); 104 | // userVideo.current.srcObject = stream; 105 | }; 106 | }); 107 | }; 108 | 109 | const stopScreenShare = () => { 110 | screenCastStream.getVideoTracks().forEach(function (track) { 111 | track.stop(); 112 | }); 113 | peer.replaceTrack( 114 | screenCastStream.getVideoTracks()[0], 115 | stream.getVideoTracks()[0], 116 | stream 117 | ); 118 | }; 119 | 120 | return ( 121 |
122 | 123 | handleChange(e)} /> 124 |
125 | 126 |
127 |
128 |
129 |
130 | 131 | 132 |
133 |
134 |
135 |
136 |
137 | handleChangeMsg(e)} /> 138 | 139 | 140 | 141 |
142 | ); 143 | } 144 | 145 | export default App; 146 | -------------------------------------------------------------------------------- /client/src/components/CallPage/CallPage.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useReducer, useState } from "react"; 2 | import { useParams, useHistory } from "react-router-dom"; 3 | import { getRequest, postRequest } from "./../../utils/apiRequests"; 4 | import { 5 | BASE_URL, 6 | GET_CALL_ID, 7 | SAVE_CALL_ID, 8 | } from "./../../utils/apiEndpoints"; 9 | import io from "socket.io-client"; 10 | import Peer from "simple-peer"; 11 | import "./CallPage.scss"; 12 | import Messenger from "./../UI/Messenger/Messenger"; 13 | import MessageListReducer from "../../reducers/MessageListReducer"; 14 | import Alert from "../UI/Alert/Alert"; 15 | import MeetingInfo from "../UI/MeetingInfo/MeetingInfo"; 16 | import CallPageFooter from "../UI/CallPageFooter/CallPageFooter"; 17 | import CallPageHeader from "../UI/CallPageHeader/CallPageHeader"; 18 | 19 | let peer = null; 20 | const socket = io.connect(process.env.REACT_APP_BASE_URL); 21 | const initialState = []; 22 | 23 | const CallPage = () => { 24 | const history = useHistory(); 25 | let { id } = useParams(); 26 | const isAdmin = window.location.hash == "#init" ? true : false; 27 | const url = `${window.location.origin}${window.location.pathname}`; 28 | let alertTimeout = null; 29 | 30 | const [messageList, messageListReducer] = useReducer( 31 | MessageListReducer, 32 | initialState 33 | ); 34 | 35 | const [streamObj, setStreamObj] = useState(); 36 | const [screenCastStream, setScreenCastStream] = useState(); 37 | const [meetInfoPopup, setMeetInfoPopup] = useState(false); 38 | const [isPresenting, setIsPresenting] = useState(false); 39 | const [isMessenger, setIsMessenger] = useState(false); 40 | const [messageAlert, setMessageAlert] = useState({}); 41 | const [isAudio, setIsAudio] = useState(true); 42 | 43 | useEffect(() => { 44 | if (isAdmin) { 45 | setMeetInfoPopup(true); 46 | } 47 | initWebRTC(); 48 | socket.on("code", (data) => { 49 | if (data.url === url) { 50 | peer.signal(data.code); 51 | } 52 | }); 53 | }, []); 54 | 55 | const getRecieverCode = async () => { 56 | const response = await getRequest(`${BASE_URL}${GET_CALL_ID}/${id}`); 57 | if (response.code) { 58 | peer.signal(response.code); 59 | } 60 | }; 61 | 62 | const initWebRTC = () => { 63 | navigator.mediaDevices 64 | .getUserMedia({ 65 | video: true, 66 | audio: true, 67 | }) 68 | .then((stream) => { 69 | setStreamObj(stream); 70 | 71 | peer = new Peer({ 72 | initiator: isAdmin, 73 | trickle: false, 74 | stream: stream, 75 | }); 76 | 77 | if (!isAdmin) { 78 | getRecieverCode(); 79 | } 80 | 81 | peer.on("signal", async (data) => { 82 | if (isAdmin) { 83 | let payload = { 84 | id, 85 | signalData: data, 86 | }; 87 | await postRequest(`${BASE_URL}${SAVE_CALL_ID}`, payload); 88 | } else { 89 | socket.emit("code", { code: data, url }, (cbData) => { 90 | console.log("code sent"); 91 | }); 92 | 93 | } 94 | }); 95 | 96 | peer.on("connect", () => { 97 | // wait for 'connect' event before using the data channel 98 | }); 99 | 100 | peer.on("data", (data) => { 101 | clearTimeout(alertTimeout); 102 | messageListReducer({ 103 | type: "addMessage", 104 | payload: { 105 | user: "other", 106 | msg: data.toString(), 107 | time: Date.now(), 108 | }, 109 | }); 110 | 111 | setMessageAlert({ 112 | alert: true, 113 | isPopup: true, 114 | payload: { 115 | user: "other", 116 | msg: data.toString(), 117 | }, 118 | }); 119 | 120 | alertTimeout = setTimeout(() => { 121 | setMessageAlert({ 122 | ...messageAlert, 123 | isPopup: false, 124 | payload: {}, 125 | }); 126 | }, 10000); 127 | }); 128 | 129 | peer.on("stream", (stream) => { 130 | // got remote video stream, now let's show it in a video tag 131 | let video = document.querySelector("video"); 132 | 133 | if ("srcObject" in video) { 134 | video.srcObject = stream; 135 | } else { 136 | video.src = window.URL.createObjectURL(stream); // for older browsers 137 | } 138 | 139 | video.play(); 140 | }); 141 | 142 | }) 143 | .catch(() => { }); 144 | }; 145 | 146 | const sendMsg = (msg) => { 147 | peer.send(msg); 148 | messageListReducer({ 149 | type: "addMessage", 150 | payload: { 151 | user: "you", 152 | msg: msg, 153 | time: Date.now(), 154 | }, 155 | }); 156 | }; 157 | 158 | const screenShare = () => { 159 | navigator.mediaDevices 160 | .getDisplayMedia({ cursor: true }) 161 | .then((screenStream) => { 162 | peer.replaceTrack( 163 | streamObj.getVideoTracks()[0], 164 | screenStream.getVideoTracks()[0], 165 | streamObj 166 | ); 167 | setScreenCastStream(screenStream); 168 | screenStream.getTracks()[0].onended = () => { 169 | peer.replaceTrack( 170 | screenStream.getVideoTracks()[0], 171 | streamObj.getVideoTracks()[0], 172 | streamObj 173 | ); 174 | }; 175 | setIsPresenting(true); 176 | }); 177 | }; 178 | 179 | const stopScreenShare = () => { 180 | screenCastStream.getVideoTracks().forEach(function (track) { 181 | track.stop(); 182 | }); 183 | peer.replaceTrack( 184 | screenCastStream.getVideoTracks()[0], 185 | streamObj.getVideoTracks()[0], 186 | streamObj 187 | ); 188 | setIsPresenting(false); 189 | }; 190 | 191 | const toggleAudio = (value) => { 192 | streamObj.getAudioTracks()[0].enabled = value; 193 | setIsAudio(value); 194 | }; 195 | 196 | const disconnectCall = () => { 197 | peer.destroy(); 198 | history.push("/"); 199 | window.location.reload(); 200 | }; 201 | 202 | return ( 203 |
204 | 205 | 206 | 212 | 220 | 221 | {isAdmin && meetInfoPopup && ( 222 | 223 | )} 224 | {isMessenger ? ( 225 | 230 | ) : ( 231 | messageAlert.isPopup && 232 | )} 233 |
234 | ); 235 | }; 236 | export default CallPage; 237 | --------------------------------------------------------------------------------