Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add P2P Video Call Application #1776

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions projects/Video-Call-App/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
35 changes: 35 additions & 0 deletions projects/Video-Call-App/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
98 changes: 98 additions & 0 deletions projects/Video-Call-App/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<h1 align='center'><b>💥 Video Call App 💥</b></h1>

<!-- -------------------------------------------------------------------------------------------------------------- -->

<h3 align='center'>Tech Stack Used 🎮</h3>

<p align='center'>
<img src="https://img.shields.io/badge/Next-black?style=for-the-badge&logo=next.js&logoColor=white" alt="Next JS" />
<img src="https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB" alt="React" />
<img src="https://img.shields.io/badge/tailwindcss-%2338B2AC.svg?style=for-the-badge&logo=tailwind-css&logoColor=white" alt="TailwindCSS" />
<img src="https://img.shields.io/badge/socket.io-black?style=for-the-badge&logo=socket.io&logoColor=white" alt="Socket.IO" />
<img src="https://img.shields.io/badge/peerjs-%23000000.svg?style=for-the-badge&logo=peerjs&logoColor=white" alt="PeerJS" />
</p>

<!-- -------------------------------------------------------------------------------------------------------------- -->

## :zap: Description 📃

<div>
<p>This is a video call application built with Next.js, React, and Tailwind CSS. It allows users to create and join video call rooms with ease.</p>
</div>

<!-- -------------------------------------------------------------------------------------------------------------- -->

## :zap: How to run it? 🕹️

### Prerequisites

Ensure you have the following installed:

- Node.js
- npm or yarn

### Installation

1. Clone the repository

2. Install dependencies:

```bash
npm install
# or
yarn install
```

### Running the Application

To start the development server:

```bash
npm run dev
# or
yarn dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the application.

### Building for Production

To create a production build:

```bash
npm run build
# or
yarn build
```

To start the production server:

```bash
npm start
# or
yarn start
```

<!-- -------------------------------------------------------------------------------------------------------------- -->

## :zap: Screenshots 📸

<p align='center'>
<img src="screenshot.webp" alt="App Screenshot" />
</p>

<!-- -------------------------------------------------------------------------------------------------------------- -->

<h4 align='center'>Developed By <b><i>Amit Singh Bora</i></b> 👦</h4>
<p align='center'>
<a href='https://www.linkedin.com/in/amitbora1/'>
<img src='https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white' />
</a>
<a href='https://github.com/amitb0ra'>
<img src='https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white' />
</a>
</p>

<h4 align='center'>Happy Coding 🧑‍💻</h4>

<h3 align="center">Show some &nbsp;❤️&nbsp; by &nbsp;🌟&nbsp; this repository!</h3>
34 changes: 34 additions & 0 deletions projects/Video-Call-App/component/Bottom/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import cx from "classnames";
import { Mic, Video, PhoneOff, MicOff, VideoOff } from "lucide-react";

import styles from "@/component/Bottom/index.module.css";

const Bottom = (props) => {
const { muted, playing, toggleAudio, toggleVideo, leaveRoom } = props;

return (
<div className={styles.bottomMenu}>
{muted ? (
<MicOff
className={cx(styles.icon, styles.active)}
size={55}
onClick={toggleAudio}
/>
) : (
<Mic className={styles.icon} size={55} onClick={toggleAudio} />
)}
{playing ? (
<Video className={styles.icon} size={55} onClick={toggleVideo} />
) : (
<VideoOff
className={cx(styles.icon, styles.active)}
size={55}
onClick={toggleVideo}
/>
)}
<PhoneOff size={55} className={cx(styles.icon)} onClick={leaveRoom}/>
</div>
);
};

export default Bottom;
13 changes: 13 additions & 0 deletions projects/Video-Call-App/component/Bottom/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.bottomMenu {
@apply absolute flex justify-between bottom-5 left-0 right-0 mx-auto;
width: 300px;
.icon {
@apply p-4 rounded-full text-white cursor-pointer;
background-color: theme("colors.secondary");

&:hover,
&.active {
background-color: theme("colors.buttonPrimary");
}
}
}
23 changes: 23 additions & 0 deletions projects/Video-Call-App/component/CopySection/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { CopyToClipboard } from "react-copy-to-clipboard";
import { Copy } from "lucide-react";

import styles from "@/component/CopySection/index.module.css";

const CopySection = (props) => {
const { roomId } = props;

return (
<div className={styles.copyContainer}>
<div className={styles.copyHeading}>Copy Room ID:</div>
<hr />
<div className={styles.copyDescription}>
<span>{roomId}</span>
<CopyToClipboard text={roomId}>
<Copy className="ml-3 cursor-pointer" />
</CopyToClipboard>
</div>
</div>
);
};

export default CopySection;
16 changes: 16 additions & 0 deletions projects/Video-Call-App/component/CopySection/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.copyContainer {
@apply flex flex-col absolute text-white border border-white rounded p-2;
left: 30px;
bottom: 100px;

hr {
@apply my-1;
}
.copyHeading {
@apply text-base;
}

.copyDescription {
@apply flex items-center text-sm;
}
}
40 changes: 40 additions & 0 deletions projects/Video-Call-App/component/Player/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import ReactPlayer from "react-player";
import cx from "classnames";
import { Mic, MicOff, UserSquare2 } from "lucide-react";

import styles from "@/component/Player/index.module.css";

const Player = (props) => {
const { url, muted, playing, isActive } = props;
return (
<div
className={cx(styles.playerContainer, {
[styles.notActive]: !isActive,
[styles.active]: isActive,
[styles.notPlaying]: !playing,
})}
>
{playing ? (
<ReactPlayer
url={url}
muted={muted}
playing={playing}
width="100%"
height="100%"
/>
) : (
<UserSquare2 className={styles.user} size={isActive ? 400 : 150} />
)}

{!isActive ? (
muted ? (
<MicOff className={styles.icon} size={20} />
) : (
<Mic className={styles.icon} size={20} />
)
) : undefined}
</div>
);
};

export default Player;
27 changes: 27 additions & 0 deletions projects/Video-Call-App/component/Player/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.playerContainer {
@apply relative overflow-hidden mb-5 h-full;
}

.active {
@apply rounded-lg;
}

.notActive {
@apply rounded-md h-min;
width: 200px;
-webkit-box-shadow: 0px 0px 11px -1px rgba(0, 0, 0, 0.75);
-moz-box-shadow: 0px 0px 11px -1px rgba(0, 0, 0, 0.75);
box-shadow: 0px 0px 11px -1px rgba(0, 0, 0, 0.75);
}

.icon {
@apply text-white absolute right-2 bottom-2;
}

.user {
@apply text-white;
}

.notPlaying {
@apply flex items-center justify-center;
}
29 changes: 29 additions & 0 deletions projects/Video-Call-App/context/socket.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { createContext, useContext, useEffect, useState } from "react";
import { io } from "socket.io-client";

const SocketContext = createContext(null);

export const useSocket = () => {
const socket = useContext(SocketContext)
return socket
}

export const SocketProvider = (props) => {
const { children } = props;
const [socket, setSocket] = useState(null);

useEffect(() => {
const connection = io();
console.log("socket connection", connection)
setSocket(connection);
}, []);

socket?.on('connect_error', async (err) => {
console.log("Error establishing socket", err)
await fetch('/api/socket')
})

return (
<SocketContext.Provider value={socket}>{children}</SocketContext.Provider>
);
};
30 changes: 30 additions & 0 deletions projects/Video-Call-App/hooks/useMediaStream.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {useState, useEffect, useRef} from 'react'


const useMediaStream = () => {
const [state, setState] = useState(null)
const isStreamSet = useRef(false)

useEffect(() => {
if (isStreamSet.current) return;
isStreamSet.current = true;
(async function initStream() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
})
console.log("setting your stream")
setState(stream)
} catch (e) {
console.log("Error in media navigator", e)
}
})()
}, [])

return {
stream: state
}
}

export default useMediaStream
35 changes: 35 additions & 0 deletions projects/Video-Call-App/hooks/usePeer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useSocket } from "@/context/socket"
import { useRouter } from "next/router"

const { useState, useEffect, useRef } = require("react")

const usePeer = () => {
const socket = useSocket()
const roomId = useRouter().query.roomId;
const [peer, setPeer] = useState(null)
const [myId, setMyId] = useState('')
const isPeerSet = useRef(false)

useEffect(() => {
if (isPeerSet.current || !roomId || !socket) return;
isPeerSet.current = true;
let myPeer;
(async function initPeer() {
myPeer = new (await import('peerjs')).default()
setPeer(myPeer)

myPeer.on('open', (id) => {
console.log(`your peer id is ${id}`)
setMyId(id)
socket?.emit('join-room', roomId, id)
})
})()
}, [roomId, socket])

return {
peer,
myId
}
}

export default usePeer;
Loading