diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..93b3654 --- /dev/null +++ b/.env.development @@ -0,0 +1,2 @@ +REACT_APP_API_URL = http://localhost:8081 +REACT_APP_CHAT_URL = ws://localhost:8082/chat diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..3cf66e1 --- /dev/null +++ b/.env.production @@ -0,0 +1,2 @@ +REACT_APP_API_URL = https://api.lemonair.me +REACT_APP_CHAT_URL = wss://chat.lemonair.me/chat diff --git a/package-lock.json b/package-lock.json index f92f04d..10631c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,9 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.6.2", + "dotenv": "^16.3.1", "hls.js": "^1.4.14", + "http-proxy-middleware": "^2.0.6", "react": "^18.2.0", "react-dom": "^18.2.0", "react-player": "^2.14.0", @@ -7257,11 +7259,14 @@ } }, "node_modules/dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, "node_modules/dotenv-expand": { @@ -15224,6 +15229,14 @@ } } }, + "node_modules/react-scripts/node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "engines": { + "node": ">=10" + } + }, "node_modules/react-stacked-center-carousel": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/react-stacked-center-carousel/-/react-stacked-center-carousel-1.0.13.tgz", diff --git a/package.json b/package.json index 97672b7..52bbf6d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,9 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.6.2", + "dotenv": "^16.3.1", "hls.js": "^1.4.14", + "http-proxy-middleware": "^2.0.6", "react": "^18.2.0", "react-dom": "^18.2.0", "react-player": "^2.14.0", diff --git a/src/App.js b/src/App.js index d7043e8..2e7f0f4 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,5 @@ -import "./App.css"; -import Router from "./shared/Router"; +import './App.css'; +import Router from './shared/Router'; const App = () => { return ; diff --git a/src/components/Chat.js b/src/components/Chat.js index a1b4205..22d2beb 100644 --- a/src/components/Chat.js +++ b/src/components/Chat.js @@ -41,6 +41,15 @@ const InputContainer = styled.div` justify-content: center; `; +const NotLoginInputContainer = styled.div` + margin-top: auto; + border-top: 1px solid #333333; + height: 8vh; + display: flex; + align-items: center; + justify-content: center; +`; + const TextInput = styled.input` border-radius: 10px; width: 70%; @@ -65,19 +74,29 @@ const ChatComponent = ({ chattingRoomId }) => { const [messages, setMessages] = useState([]); const [inputMessage, setInputMessage] = useState(''); const [socket, setSocket] = useState(null); + const [socketIntervalId, setSocketIntervalId] = useState(null); + const [isLoggedIn, setIsLoggedIn] = useState(false); const messagesEndRef = useRef(null); const chattingRoomIdString = chattingRoomId; const accessToken = localStorage.getItem('accessToken'); + useEffect(() => { + if (accessToken) { + setIsLoggedIn(true); + } + }, [accessToken]); const fetchToken = useCallback(async () => { try { console.log(accessToken); - const response = await fetch('https://api.lemonair.me/api/auth/chat', { - method: 'POST', - headers: { - Authorization: accessToken, - }, - }); + const response = await fetch( + `${process.env.REACT_APP_API_URL}/api/auth/chat`, + { + method: 'POST', + headers: { + Authorization: accessToken, + }, + } + ); if (!response.ok) { throw new Error('Network response was not ok.'); } @@ -99,15 +118,20 @@ const ChatComponent = ({ chattingRoomId }) => { chatToken = 'notlogin'; // 로그인하지 않은 사용자의 경우 토큰 정보를 notlogin으로 요청한다. } const newSocket = new WebSocket( - `wss://chat.lemonair.me/chat/${chattingRoomIdString}/${chatToken}` + `${process.env.REACT_APP_CHAT_URL}/${chattingRoomIdString}/${chatToken}` ); setSocket(newSocket); newSocket.onopen = () => { console.log('웹소켓 연결됨'); + const heartbeatInterval = setInterval(() => { + newSocket.send('heartbeat'); + }, 30000); + + setSocketIntervalId(heartbeatInterval); }; newSocket.onmessage = (event) => { - console.log(event.data); + // console.log(event.data); const receiveData = event.data.split(':'); const from = receiveData[0]; const message = receiveData.slice(1).join(':').trim(); @@ -116,6 +140,8 @@ const ChatComponent = ({ chattingRoomId }) => { newSocket.onclose = () => { console.log('웹소켓 연결 종료'); + clearInterval(socketIntervalId); + // 연결 종료 시 재연결 시도 setTimeout(async () => { if (accessToken) { @@ -124,26 +150,36 @@ const ChatComponent = ({ chattingRoomId }) => { chatToken = 'notlogin'; // 로그인하지 않은 사용자의 경우 토큰 정보를 notlogin으로 요청한다. } const reconnectSocket = new WebSocket( - `wss://chat.lemonair.me/chat/${chattingRoomIdString}/${chatToken}` + `${process.env.REACT_APP_CHAT_URL}/${chattingRoomIdString}/${chatToken}` ); setSocket(reconnectSocket); reconnectSocket.onopen = () => { console.log('웹소켓 재연결됨'); setSocket(reconnectSocket); + const heartbeatInterval = setInterval(() => { + newSocket.send('heartbeat'); + }, 30000); + setSocketIntervalId(heartbeatInterval); }; reconnectSocket.onmessage = (event) => { - console.log(event.data); + // console.log(event.data); const receiveData = event.data.split(':'); const from = receiveData[0]; const message = receiveData.slice(1).join(':').trim(); setMessages((prevMessages) => [...prevMessages, { from, message }]); }; + + reconnectSocket.onclose = () => { + console.log('웹소켓 연결 종료'); + clearInterval(socketIntervalId); + }; }, 1000); // 1초 후 재연결 시도 }; return () => { newSocket.close(); + clearInterval(socketIntervalId); }; }; @@ -164,6 +200,11 @@ const ChatComponent = ({ chattingRoomId }) => { }; const handleKeyDown = (event) => { + if (!isLoggedIn && event.key === 'Enter') { + setInputMessage(''); + return; + } + if (event.key === 'Enter') { event.preventDefault(); sendMessage(); @@ -181,15 +222,26 @@ const ChatComponent = ({ chattingRoomId }) => { ))}
- - setInputMessage(e.target.value)} - onKeyDown={handleKeyDown} - /> - 전송 - + {isLoggedIn ? ( + + setInputMessage(e.target.value)} + onKeyDown={handleKeyDown} + /> + + 전송 + + + ) : ( + + 로그인한 사용자만 채팅을 입력할 수 있습니다. + + )} ); }; diff --git a/src/components/HlsPlayer.js b/src/components/HlsPlayer.js index 6231803..8d6097b 100644 --- a/src/components/HlsPlayer.js +++ b/src/components/HlsPlayer.js @@ -1,5 +1,5 @@ import React, { useEffect, useRef } from 'react'; -import Hls from 'hls.js'; +import Hls from 'hls.js/dist/hls.min'; const HlsVideoPlayer = ({ videoUrl }) => { const videoRef = useRef(null); @@ -14,21 +14,24 @@ const HlsVideoPlayer = ({ videoUrl }) => { console.log('initialize 실행'); // 자동재생 console.log('play 실행 전'); - videoElement.play(); + + // videoElement.play(); console.log('play 실행 후ㅡ'); // 마지막 청크의 재생시간으로 이동 console.log('hls', hls); + // videoElement.muted = false; // console.log("hls.media.current.segments", hls.media.current.segments); - console.log(data.lastSegment); - console.log(hls.media.segments); - const lastSegment = hls.media.segments[hls.media.segments.length - 1]; - console.log('lastSegment', lastSegment); + + let myFragments = data.levels[0].details.fragments; + // console.log(hls.media.segments); + const lastSegment = myFragments[myFragments.length - 1]; + + // console.log("lastSegment", lastSegment); if (lastSegment) { - videoElement.currentTime = lastSegment.end; + videoElement.currentTime = lastSegment.start - 0.5; } }; - // Hls 지원 여부 확인 if (Hls.isSupported()) { console.log('hls를 지원한다.'); var config = { @@ -130,11 +133,6 @@ const HlsVideoPlayer = ({ videoUrl }) => { }; hls = new Hls(config); - // 이벤트 리스너 등록 - // hls.on(Hls.Events.MEDIA_ATTACHED, (event, data) => { - // console.log("event listener 등록"); - // initializeHls(data); - // }); hls.on(Hls.Events.MEDIA_ATTACHED, function () { console.log('video and hls.js are now bound together !'); @@ -159,10 +157,20 @@ const HlsVideoPlayer = ({ videoUrl }) => { console.log('컴포넌트 unmount시에 destroy'); hls.destroy(); } + if (videoElement) { + console.log('video pause'); + videoElement.pause(); + } }; - }, [videoUrl]); + }, [videoUrl, videoRef.current]); - return