Skip to content

Commit

Permalink
Video calling enabled now
Browse files Browse the repository at this point in the history
Added video calling support
  • Loading branch information
tanish35 authored Oct 31, 2024
2 parents f674110 + 5b61587 commit 8e135e2
Show file tree
Hide file tree
Showing 8 changed files with 338 additions and 1 deletion.
8 changes: 8 additions & 0 deletions backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ model User {
sentMessages Message[] @relation("SentMessages")
Like Like[]
Otp Otp[]
Video Video[]
}

model Otp {
Expand Down Expand Up @@ -131,3 +132,10 @@ model Like {
User User @relation(fields: [user_id], references: [user_id])
Post Post @relation(fields: [post_id], references: [post_id])
}

model Video{
video_id String @id @default(cuid())
url String
user_id String
User User @relation(fields: [user_id], references: [user_id])
}
73 changes: 73 additions & 0 deletions backend/src/controllers/videoController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import asyncHandler from "express-async-handler";
import { Request, Response } from "express";
import axios from "axios";
import prisma from "../lib/prisma";

export const createRoom = asyncHandler(async (req: Request, res: Response) => {
//@ts-ignore
const username = req.user.username;
const video = await prisma.video.findUnique({
where: {
video_id: username,
},
});
if (video) {
res.status(205).json({ message: "Room already exists" });
return;
}
const response = await axios.post(
"https://api.daily.co/v1/rooms",
{
properties: { enable_chat: true, enable_screenshare: true },
name: username,
},
{ headers: { Authorization: `Bearer ${process.env.DAILY_API_KEY}` } }
);
if (!response.data) {
res.status(400);
throw new Error("Invalid data");
}
const url = response.data.url;
const video_id = response.data.name;
//@ts-ignore
const user_id = req.user.user_id;
await prisma.video.create({
data: {
video_id,
url,
user_id,
},
});
res.status(201).json(response.data);
});

export const deleteRoom = asyncHandler(async (req: Request, res: Response) => {
const { video_id } = req.body;
if (!video_id) {
res.status(400);
throw new Error("Invalid data");
}
const video = await prisma.video.findUnique({
where: {
video_id,
},
select: {
user_id: true,
},
});
//@ts-ignore
const user_id = req.user.user_id;
if (video?.user_id !== user_id) {
res.status(401).json({ message: "Unauthorized" });
return;
}
await axios.delete(`https://api.daily.co/v1/rooms/${video_id}`, {
headers: { Authorization: `Bearer ${process.env.DAILY_API_KEY}` },
});
await prisma.video.delete({
where: {
video_id,
},
});
res.status(200).json({ message: "Room deleted" });
});
4 changes: 3 additions & 1 deletion backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import reviewRoutes from "./routes/reviewRoutes";
import ratingRoutes from "./routes/ratingRoute";
import postsRoutes from "./routes/postsRoutes";
import roomRouter from "./routes/roomRoutes";
import Otprouter from "./routes/otpRoute";
import Otprouter from "./routes/otpRoute";
import videoRouter from "./routes/videoRoutes";

// import { getCommunities } from "./controllers/postController";

Expand Down Expand Up @@ -41,6 +42,7 @@ app.use("/api/chat", chatRoutes); // Use the chat routes
app.use("/api/post", postsRoutes);
app.use("/api/room", roomRouter);
app.use("/api/otp", Otprouter);
app.use("/api/video", videoRouter);
// app.get("/api/post/communities", getCommunities);
app.get("/api/logout", (req: Request, res: Response) => {
res.clearCookie("Authorization").json({ message: "Logged out successfully" });
Expand Down
11 changes: 11 additions & 0 deletions backend/src/routes/videoRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import express from "express";
import checkAuth from "../middleware/checkAuth";
import { createRoom, deleteRoom } from "../controllers/videoController";
import rateLimiter from "../middleware/rateLimit";

const router = express.Router();

router.post("/createroom", checkAuth, rateLimiter, createRoom);
router.delete("/deleteroom", checkAuth, rateLimiter, deleteRoom);

export default router;
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"dependencies": {
"@chakra-ui/react": "^2.8.2",
"@daily-co/daily-js": "^0.72.1",
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0",
"axios": "^1.7.2",
Expand Down
137 changes: 137 additions & 0 deletions frontend/src/components/StartVideoCall.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { useState, useRef, useEffect } from "react";
import {
Box,
Button,
Center,
Heading,
VStack,
useToast,
Flex,
Input,
} from "@chakra-ui/react";
import { gsap } from "gsap";
import { useNavigate } from "react-router-dom";
import VideoCall from "./VideoCall";
import { useUser } from "../hook/useUser";
import axios from "axios";
import "../styles/loader.css";
// import { useNavigate } from "react-router-dom";

const StartVideoCall = () => {
const toast = useToast();
const navigate = useNavigate();
const { userDetails, loadingUser } = useUser();

const [isLoading, setIsLoading] = useState(loadingUser);
const [roomUrl, setRoomUrl] = useState(""); // URL for the created room
const [joinRoomUrl, setJoinRoomUrl] = useState(""); // URL for joining a room
const buttonRef = useRef();

useEffect(() => {
if (loadingUser) {
setIsLoading(true);
} else {
setIsLoading(false);
if (!userDetails) {
toast({
title: "Please sign in first",
status: "error",
duration: 5000,
isClosable: true,
});
navigate("/login");
}
}
}, [loadingUser, userDetails]);

const createRoom = async () => {
try {
const response = await axios.post("/api/video/createroom");
if (response.status == 205) {
navigate(`/video/${userDetails.username}`);
return;
}
setRoomUrl(response.data.url);
setJoinRoomUrl("");
navigate(`/video/${response.data.url.split("/").pop()}`);
} catch (error) {
console.error("Failed to create room", error);
toast({
title: "Error creating room",
status: "error",
duration: 5000,
isClosable: true,
});
}
};

const handleUrlChange = (e) => {
setJoinRoomUrl(e.target.value);
};

const handleJoinRoom = () => {
if (joinRoomUrl) {
setRoomUrl(joinRoomUrl);
setJoinRoomUrl("");
navigate(`/video/${joinRoomUrl.split("/").pop()}`);
} else {
toast({
title: "Please enter a valid room URL",
status: "warning",
duration: 5000,
isClosable: true,
});
}
};

useEffect(() => {
if (!isLoading) {
gsap.from(buttonRef.current, {
y: -50,
opacity: 0,
duration: 0.5,
ease: "bounce",
});
}
}, [isLoading]);

if (isLoading) {
return (
<Flex minH="100vh" align="center" justify="center" bg="black">
<div className="loader"></div>
</Flex>
);
}

return (
<Center height="100vh" bgGradient="linear(to-r, teal.500, blue.500)">
<VStack spacing={8}>
<Heading color="white" size="2xl">
{roomUrl ? "Join the Video Call" : "Ready to Start a Video Call?"}
</Heading>
<Button
ref={buttonRef}
colorScheme="teal"
size="lg"
onClick={createRoom}
>
Start Video Call
</Button>
<Input
placeholder="Enter Room URL"
value={joinRoomUrl}
onChange={handleUrlChange} // Use handleUrlChange to update state
/>
<Button
colorScheme="teal"
size="lg"
onClick={handleJoinRoom} // Call handleJoinRoom when joining
>
Join Video Call
</Button>
</VStack>
</Center>
);
};

export default StartVideoCall;
95 changes: 95 additions & 0 deletions frontend/src/components/VideoCall.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import DailyIframe from "@daily-co/daily-js";
import { useEffect, useRef, useState } from "react";
import { Box, useToast, Flex } from "@chakra-ui/react";
import { useParams, useNavigate } from "react-router-dom";
import { useUser } from "../hook/useUser";
import axios from "axios";
import "../styles/loader.css";

const VideoCall = () => {
const videoRef = useRef();
const callFrame = useRef();
const { id } = useParams();
const navigate = useNavigate();
const roomUrl = "https://campusify.daily.co/" + id;
const { userDetails, loadingUser } = useUser();
const toast = useToast();
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
if (loadingUser) {
setIsLoading(true);
} else {
setIsLoading(false);
if (!userDetails) {
toast({
title: "Please sign in first",
status: "error",
duration: 5000,
isClosable: true,
});
navigate("/login");
}
}
}, [loadingUser, userDetails, navigate, toast]);

const username = userDetails?.username;

useEffect(() => {
if (!isLoading && username) {
callFrame.current = DailyIframe.createFrame(videoRef.current, {
showLeaveButton: true,
iframeStyle: {
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
borderRadius: "0",
},
});
const handleLeave = async () => {
const videoId = roomUrl.split("/").pop();
try {
const response = await axios.delete("/api/video/deleteroom", {
data: { video_id: videoId },
});
console.log(response.data.message);
} catch (error) {
console.error("Failed to delete room:", error);
}
};
callFrame.current.on("left-meeting", handleLeave);

// console.log(username, roomUrl);
callFrame.current.join({ url: roomUrl, userName: username });

return () => {
callFrame.current.leave();
callFrame.current.destroy();
};
}
}, [roomUrl, username, isLoading]);

if (isLoading) {
return (
<Flex minH="100vh" align="center" justify="center" bg="black">
<div className="loader"></div>
</Flex>
);
}

return (
<Box
ref={videoRef}
width="100vw"
height="100vh"
position="absolute"
top="0"
left="0"
bg="black"
/>
);
};

export default VideoCall;
10 changes: 10 additions & 0 deletions frontend/src/components/routes/mainroute.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { Outlet } from "react-router-dom";
import { chatRoomApi } from "../contexts/chatRoomApi";
import { useState } from "react";
import Posts from "../../components/Posts";
import StartVideoCall from "../StartVideoCall";
import VideoCall from "../VideoCall";

import Register from "../Register";
import SinglePost from "../../components/SinglePost";
Expand Down Expand Up @@ -104,6 +106,14 @@ const Mainrouter = createBrowserRouter([
path: "/edit",
element: <EditDetails />,
},
{
path: "/call",
element: <StartVideoCall />,
},
{
path: "/video/:id",
element: <VideoCall />,
},
{
path: "room",
element: <Mainbuttons />,
Expand Down

0 comments on commit 8e135e2

Please sign in to comment.