diff --git a/api-server/test.js b/api-server/test.js deleted file mode 100644 index ec5b490..0000000 --- a/api-server/test.js +++ /dev/null @@ -1,20 +0,0 @@ -import { io } from 'socket.io-client'; - -const socket = io('http://127.0.0.1:5000'); - -// Join a room with a specific meeting ID -socket.emit('join', { 'username': 'teet', meeting_id: 'e45f73db-9973-4acd-9467-c48f56b26804' }); - -// Emit the SDP offer immediately -socket.emit('offer', { meeting_id: 'e45f73db-9973-4acd-9467-c48f56b26804', sdp: '...' }); - -// Simulate receiving the offer and sending back an SDP answer after 1 second -socket.emit('answer', { meeting_id: 'e45f73db-9973-4acd-9467-c48f56b26804', sdp: 'v=0\r\no=- 25678 753849 IN IP4 192.0.2.2\r\ns=-\r\nc=IN IP4 192.0.2.2\r\nt=0 0\r\na=recvonly\r\nm=video 49170 RTP/AVP 98\r\na=rtpmap:98 H264/90000\r\n' }); - -// Simulate sending ICE candidates after 2 seconds -socket.emit('ice_candidate', { meeting_id: 'e45f73db-9973-4acd-9467-c48f56b2', candidate: 'candidate:842163049 1 udp 1677729535 192.0.2.1 3478 typ srflx raddr 192.0.2.1 rport 3478' }); - -// Listen for events -socket.on('offer', (data) => console.log('Received offer:', data)); -socket.on('answer', (data) => console.log('Received answer:', data)); -socket.on('ice_candidate', (data) => console.log('Received ICE candidate:', data)); diff --git a/client/src/components/Chat.js b/client/src/components/Chat.js new file mode 100644 index 0000000..091154a --- /dev/null +++ b/client/src/components/Chat.js @@ -0,0 +1,75 @@ +// src/components/Chat.js +import React, { useState, useRef, useCallback, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import toast from 'react-hot-toast'; +import Button from './Button'; +import ChatHandler from '../services/chatHandler'; +import ChatInput from './ChatInput'; + + +const Chat = ({ meeting_id, username, socket }) => { + const [chatHistory, setChatHistory] = useState([]); + const chatHandler = useRef(null); + + const initializeChat = () => { + if (!username) { + toast.error('Please enter a username before joining a meeting.'); + return; + } + + const errorHandler = (error) => { + console.error(error); + toast.error(error.message); + } + + // Assuming ChatHandler is a utility to handle the chat logic + chatHandler.current = new ChatHandler(meeting_id, username, socket, setChatHistory, errorHandler); + chatHandler.current.initialize(); + }; + + useEffect(() => { + initializeChat(); + }, [meeting_id, username, socket]); + + const handleSendMessage = useCallback((message) => { + if (!message.trim()) { + toast.error('Please enter a message before sending.'); + return; + } + chatHandler.current.sendMessage(message); + setChatHistory(prevHistory => [...prevHistory, { sender: username, text: message }]); + }, [username]); + + const getProfilePicture = (name) => { + return `https://ui-avatars.com/api/?name=${encodeURIComponent(name)}&background=random`; + }; + + return ( +
+

Chat

+
+ {chatHistory.map((msg, index) => ( +
+ {`${msg.sender}'s +
+ {msg.sender}: {msg.text} +
+
+ ))} +
+ +
+ ); +}; + +Chat.propTypes = { + meeting_id: PropTypes.string.isRequired, + username: PropTypes.string.isRequired, + socket: PropTypes.object.isRequired, +}; + +export default Chat; diff --git a/client/src/components/ChatInput.js b/client/src/components/ChatInput.js new file mode 100644 index 0000000..6c9beb3 --- /dev/null +++ b/client/src/components/ChatInput.js @@ -0,0 +1,34 @@ +import React, { useRef } from 'react'; +import Button from '../components/Button'; +import PropTypes from 'prop-types'; + +const ChatInputSection = ({ onSend }) => { + const inputRef = useRef(); + + const handleSendMessage = (e) => { + e.preventDefault(); // Prevent the form from causing a page reload + const message = inputRef.current.value.trim(); + if (message) { + onSend(message); + inputRef.current.value = ''; // Clear the input after sending + } + }; + + return ( +
+ + +
+ ); +}; + +ChatInputSection.propTypes = { + onSend: PropTypes.func.isRequired, +}; + +export default ChatInputSection; diff --git a/client/src/pages/MeetingPage.js b/client/src/pages/MeetingPage.js index d5af21c..f1bc1e8 100644 --- a/client/src/pages/MeetingPage.js +++ b/client/src/pages/MeetingPage.js @@ -1,8 +1,8 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef, useCallback } from 'react'; import { useParams, useLocation, useNavigate } from 'react-router-dom'; import io from 'socket.io-client'; -import ChatHandler from '../services/chatHandler'; import RTCHandler from '../services/rtcHandler'; +import Chat from '../components/Chat'; import Button from '../components/Button'; import toast, { Toaster } from 'react-hot-toast'; @@ -21,11 +21,7 @@ const MeetingPage = () => { const [isMuted, setIsMuted] = useState(false); const [isVideoOff, setIsVideoOff] = useState(false); - const chatHandler = useRef(null); - const [chatHistory, setChatHistory] = useState([]); - const [message, setMessage] = useState(''); - - useEffect(() => { + const initializeMeeting = () => { if (!username) { toast.error('Please enter a username before joining a meeting.'); navigate('/'); @@ -47,25 +43,26 @@ const MeetingPage = () => { console.error(error); navigate('/'); toast.error(error.message); - } + }; - chatHandler.current = new ChatHandler(meeting_id, username, socketRef.current, setChatHistory, errorHandler); - chatHandler.current.initialize(); rtcHandler.current = new RTCHandler(meeting_id, username, socketRef.current, setPeers, errorHandler); rtcHandler.current.initialize(); + }; + + useEffect(() => { + initializeMeeting(); + return () => { rtcHandler.current.cleanup(); socketRef.current.disconnect(); - } + }; }, []); const toggleMute = () => { setIsMuted(prevState => { const newMutedState = !prevState; - // Mute or unmute the audio track in the local stream if (rtcHandler.current && rtcHandler.current.localStream) { rtcHandler.current.localStream.getAudioTracks().forEach(track => { - console.log(track); track.enabled = !newMutedState; }); } @@ -76,10 +73,8 @@ const MeetingPage = () => { const toggleVideo = () => { setIsVideoOff(prevState => { const newVideoState = !prevState; - // Turn on or off the video track in the local stream if (rtcHandler.current && rtcHandler.current.localStream) { rtcHandler.current.localStream.getVideoTracks().forEach(track => { - console.log(track); track.enabled = !newVideoState; }); } @@ -87,24 +82,6 @@ const MeetingPage = () => { }); }; - const sendMessage = (e) => { - e.preventDefault(); - if (!message.trim()) { - toast.error('Please enter a message before sending.'); - return; - } - chatHandler.current.sendMessage(message); - setMessage(''); - }; - - const getProfilePicture = (name) => { - return `https://ui-avatars.com/api/?name=${encodeURIComponent(name)}&background=random`; - }; - - if (!username || !rtcHandler.current) { - return null; - } - const VideoElement = React.memo(({ stream, muted, peerName }) => { const videoRef = useRef(); @@ -116,12 +93,16 @@ const MeetingPage = () => { return (
-
); }); + if (!username || !rtcHandler.current) { + return null; + } + return (
@@ -131,45 +112,21 @@ const MeetingPage = () => {
- + {rtcHandler.current.localStream && ( + + )} {Object.entries(peers).map(([peerUsername, peer]) => ( peerUsername !== username && ( - ) ))}
-
-

Chat

-
- {chatHistory.map((msg, index) => ( -
- {`${msg.sender}'s -
- {msg.sender}: {msg.text} -
-
- ))} -
-
- setMessage(e.target.value)} - className="flex-1 px-2 py-1 border rounded-l" - placeholder="Type a message..." - /> - -
-
+