From 5ab104adbae5feb7ce82073e1d38557156ab5931 Mon Sep 17 00:00:00 2001 From: sooyeon-kr Date: Tue, 10 Sep 2024 20:08:25 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20get=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/consultingController.js | 6 +- src/public/js/consultingRoom.js | 942 ------------------------ src/routes/consultingRoutes.js | 3 +- 3 files changed, 4 insertions(+), 947 deletions(-) diff --git a/src/controllers/consultingController.js b/src/controllers/consultingController.js index b43f976..d7fba14 100644 --- a/src/controllers/consultingController.js +++ b/src/controllers/consultingController.js @@ -1,9 +1,7 @@ import { getRoomList as getRoomListFromRedis } from '../redis_client.js'; export function getConsultingRoom(req, res) { const { roomId } = req.params; - const { userName, userId } = req.body; - - console.log('디버깅'); + const { userName, userId } = req.query; if (!roomId || !userName || !userId) { return res.status(400).json({ message: '모든 필드가 필요합니다.' }); @@ -12,7 +10,7 @@ export function getConsultingRoom(req, res) { console.log(roomId, userName, userId); try { - res.render('../views/session.ejs', { roomId, userName, userId }); + res.render('session', { roomId, userName, userId }); } catch (error) { console.error('View 렌더링 오류:', error); res.status(500).json({ message: '서버 오류가 발생했습니다.' }); diff --git a/src/public/js/consultingRoom.js b/src/public/js/consultingRoom.js index 1763b36..8b13789 100644 --- a/src/public/js/consultingRoom.js +++ b/src/public/js/consultingRoom.js @@ -1,943 +1 @@ -const socket = io(); -const myFace = document.getElementById('myFace'); -const sharedScreen = document.getElementById('sharedScreen'); -const peerFace = document.getElementById('peerFace'); -const muteBtn = document.getElementById('mute'); -const cameraBtn = document.getElementById('camera'); -const shareScreenBtn = document.getElementById('shareScreen'); - -const camerasSelect = document.getElementById('cameras'); -const microphonesSelect = document.getElementById('microphones'); -const chatToggle = document.getElementById('chatToggle'); -const sendBtn = document.getElementById('sendButton'); -const chatBox = document.getElementById('chatBox'); -const messageInput = document.getElementById('messageInput'); -const videoContainer = document.getElementById('videoContainer'); -videoContainer.classList.add('video-container'); -myFace.classList.add('video'); -peerFace.classList.add('video'); -let myStream; -let screenStream; -let muted = false; -let cameraOff = false; -let myPeerConnection; -let myDataChannel; -let isScreenSharing = false; -let isPeerScreenSharing = false; -let targetRoomId; -const { roomId, userId, userName } = window.ROOM_DATA; -const roomTitle = document.getElementById('roomTitle'); -// 페이지 로드 시 자동으로 상담방 입장 -window.addEventListener('load', () => { - targetRoomId = roomId; - joinConsultingRoom(roomId); - roomTitle.innerHTML = `${userName}님의 상담룸`; -}); - -const DEVICE_TYPES = { - AUDIO: 'audioinput', - VIDEO: 'videoinput', -}; -STREAM_TYPES = { - MY_FACE: 'myFace', - PEER_FACE: 'peerFace', - SHARED_SCHREEN: 'sharedScreen', -}; -const ACTION_STREAM_TYPES = { - PEERFACE: 'peerface', - SCREENSHARE: 'screenshare', - STOP_PEERFACE: 'stopPeerface', - STOP_SCREENSHARE: 'stopScreenshare', -}; -const MESSAGE_TYPES = { METADATA: 'metadata', CHAT: 'chat', SYSTEM: 'system' }; - -window.addEventListener('beforeunload', () => { - console.log('페이지 떠남, 소켓 연결 해제'); - socket.disconnect(); -}); -const leaveRoomBtn = document.getElementById('leaveRoom'); -/** - * 상담룸 입장하기 버튼을 눌렀을 때, 소켓연결을 통해 해당하는 상담룸 정보를 가져오는 함수 - * @param {string} roomId 상담룸 고유 번호 - */ -function joinConsultingRoom(roomId) { - console.log(`입장 - 방 ID: ${roomId}`); - socket.emit('joinConsultingRoom', { userId, roomId, userName }); - socket.on('joinedConsultingRoom', async (roomInfo) => { - try { - console.log(roomInfo); - await initializeConnectionProcess(socket); - socket.on('newUserJoined', ({ userName, message }) => { - makeConnection(socket); - }); - leaveRoomBtn.addEventListener('click', () => handleLeaveRoom(socket)); - } catch (error) { - console.error('상담룸 입장 중 에러 발생:', error); - } - }); -} -/** - * 연결 로직들에 필요한 함수들을 소켓으로 초기화하는 함수 - * @param {socket} socket 연결된 소켓 - */ -async function initializeConnectionProcess(socket) { - try { - await initConsultingRoom(socket); - await makeAndSendOffer(socket); - await receiveOfferMakeAnswer(socket); - await finallyReceiveAnswer(socket); - await receiveIce(socket); - } catch (error) { - console.error('연결 초기화 중 에러 발생:', error); - } -} -/** - * 상담룸을 초기화하는 함수들을 관리하는 함수 - * @param {socket} socket 연결된 소켓 - */ -async function initConsultingRoom(socket) { - try { - await initCall(); - await makeConnection(socket); - } catch (error) { - console.error('상담룸 초기화 중 에러 발생:', error); - } -} -/** - * 필요없는 UI, 필요한 UI 관리하면서 내 영상을 가져옴 - */ -async function initCall() { - call.hidden = false; - await getMedia(); - updateShareScreenButton(); -} - -/** - * 초기에 스트림을 가져오는 함수 - * @param {string} deviceId 디바이스 고유 ID - */ -async function getMedia(deviceId) { - const constraints = { - audio: true, - video: deviceId ? { deviceId: { exact: deviceId } } : { facingMode: 'user' }, - }; - - try { - myStream = await navigator.mediaDevices.getUserMedia(constraints); - myFace.srcObject = myStream; - if (!deviceId) { - await getCameras(); - await getMicrophones(); - } - } catch (error) { - console.error('getMedia error:', error); - handleGetMediaError(error); - } -} -/** - * 카메라 목록 가져오는 함수 - */ -async function getCameras() { - try { - const devices = await navigator.mediaDevices.enumerateDevices(); - const cameras = devices.filter((device) => device.kind == 'videoinput'); - const currentCamera = myStream.getVideoTracks()[0]; - cameras.forEach((camera) => { - const option = document.createElement('option'); - option.value = camera.deviceId; - option.innerText = camera.label; - if (currentCamera.label === camera.label) { - option.selected = true; - } - camerasSelect.appendChild(option); - }); - - camerasSelect.addEventListener('change', async () => { - const selectedDeviceId = camerasSelect.value; - await changeDevice(DEVICE_TYPES.VIDEO, selectedDeviceId); - }); - } catch (error) { - console.log(error); - } -} -/** - * 마이크 목록 가져오는 함수 - */ -async function getMicrophones() { - try { - const devices = await navigator.mediaDevices.enumerateDevices(); - const audios = devices.filter((device) => device.kind == 'audioinput'); - - const currentAudio = myStream.getAudioTracks()[0]; - - microphonesSelect.innerHTML = ''; - - audios.forEach((audio) => { - const option = document.createElement('option'); - option.value = audio.deviceId; - option.innerText = audio.label; - - if (currentAudio && currentAudio.label === audio.label) { - option.selected = true; - } - - microphonesSelect.appendChild(option); - }); - - microphonesSelect.addEventListener('change', async () => { - const selectedDeviceId = microphonesSelect.value; - await changeDevice(DEVICE_TYPES.AUDIO, selectedDeviceId); - }); - } catch (error) { - console.log('마이크 장치 가져오기 오류:', error); - } -} - -/** - * 디바이스가 바꼈을 때, 스트림에 새롭게 적용하는 함수 - * @param {DEVICE_TYPES} deviceType audioinput, videoinput - * @param {string} deviceId 디바이스 고유 아이디 - */ -async function changeDevice(deviceType, deviceId) { - try { - const constraints = {}; - constraints[deviceType === DEVICE_TYPES.AUDIO ? 'audio' : 'video'] = { deviceId: { exact: deviceId } }; - const newStream = await navigator.mediaDevices.getUserMedia(constraints); - - updateTrack(newStream, deviceType); - - if (deviceType === DEVICE_TYPES.AUDIO) { - updateMuteButton(); - } else if (deviceType === DEVICE_TYPES.VIDEO) { - updateCameraButton(); - } - } catch (error) { - console.error(`${deviceType === DEVICE_TYPES.AUDIO ? '마이크' : '카메라'} 변경 중 오류 발생:`, error); - } -} -/** - * 마이크 켜기/끄기 상태에 따라 버튼 내용을 바꿔주는 함수 - */ -function updateMuteButton() { - const audioTrack = myStream.getAudioTracks()[0]; - if (audioTrack) { - muted = !audioTrack.enabled; - muteBtn.innerHTML = muted ? '' : ''; - } -} - -/** - * 바디오 켜기/끄기 상태에 따라 버튼 내용을 바꿔주는 함수 - */ -function updateCameraButton() { - const videoTrack = myStream.getVideoTracks()[0]; - if (videoTrack) { - cameraOff = !videoTrack.enabled; - cameraBtn.innerHTML = cameraOff ? '' : ''; - } -} - -/** - * 트랙의 타입에 따라 트랙을 업데이트 하는 함수 - * @param {MediaStream} newStream - * @param {DEVICE_TYPES} deviceType audioinput, videoinput - */ -function updateTrack(newStream, deviceType) { - const trackType = deviceType === DEVICE_TYPES.AUDIO ? 'audio' : 'video'; - const sender = myPeerConnection.getSenders().find((sender) => sender.track.kind === trackType); - if (sender) sender.replaceTrack(newStream.getTracks().find((track) => track.kind === trackType)); - - const oldTracks = trackType === 'audio' ? myStream.getAudioTracks() : myStream.getVideoTracks(); - oldTracks.forEach((track) => track.stop()); - myStream.removeTrack(oldTracks[0]); - myStream.addTrack(newStream.getTracks().find((track) => track.kind === trackType)); - - console.log(`${trackType} 트랙이 업데이트되었습니다.`); -} -/** - * 미디어 에러 발생 시 처리 하는 함수 - * @param {Error} error 에러내용 - */ -function handleGetMediaError(error) { - console.log(error.name); - if (error.name === 'NotFoundError' || error.name === 'DevicesNotFoundError') { - alert('카메라 장치를 찾을 수 없습니다. 음성만으로 연결합니다.'); - getAudioOnly(); - } else { - alert('미디어 장치를 가져오는 도중 오류가 발생했습니다.'); - console.error('미디어 오류:', error); - } -} - -/** - * 오디오만 가져오는 함수 - */ -async function getAudioOnly() { - try { - myStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false }); - console.log('오디오 스트림 초기화 완료:', myStream); - } catch (error) { - console.error('오디오 스트림 가져오는 중 오류 발생:', error); - alert('오디오 장치를 가져오는 도중 오류가 발생했습니다.'); - } -} - -/** - * connection 연결 설정 함수 - * @param {socket} socket 연결된 소켓 - */ -async function makeConnection(socket) { - myPeerConnection = new RTCPeerConnection({ - iceServers: [ - { - urls: [ - 'stun:stun.l.google.com:19302', - 'stun:stun1.l.google.com:19302', - 'stun:stun2.l.google.com:19302', - 'stun:stun3.l.google.com:19302', - 'stun:stun4.l.google.com:19302', - ], - }, - ], - }); - myPeerConnection.addEventListener('icecandidate', (event) => handleIceEvent(event, socket)); - myPeerConnection.addEventListener('track', handleTrackEvent); - myPeerConnection.addEventListener('iceconnectionstatechange', (event) => handleICEConnectionStateChange(event)); - myPeerConnection.onnegotiationneeded = async () => { - console.log('Signaling State:', myPeerConnection.signalingState); - try { - if (myPeerConnection.signalingState != 'stable') { - console.log('현재 연결 상태가 안정적이지 않아 재협상을 보류합니다.'); - return; // 협상 중이면 협상 요청을 보류 - } - console.log('Negotiation needed event fired'); - await makeAndSendOffer(socket); - } catch (err) { - console.error('협상 중 에러 발생:', err); - } - }; - myStream.getTracks().forEach((track) => myPeerConnection.addTrack(track, myStream)); -} - -function handleICEConnectionStateChange(event) { - console.log('ICE 연결 상태:', myPeerConnection.iceConnectionState); - switch (myPeerConnection.iceConnectionState) { - case 'disconnected': - case 'failed': - case 'closed': - handlePeerDisconnection(); - break; - case 'connected': - console.log('Peer 연결 성공'); - // TODO: 상대방 CUSTOM - addSystemMessageToChat(`상대방이 입장했습니다.`); - break; - } -} -/** - * 상대방과 연결이 끊어졌을 때 실행하는 함수 - */ -function handlePeerDisconnection() { - console.log('Peer 연결이 끊어졌습니다.'); - if (isScreenSharing) stopScreenShare(); - - // 채팅창에 메시지 추가 - // TODO: 상대방 CUSTOM - addSystemMessageToChat(`상대방이 퇴장했습니다.`); - - // 비디오 스트림 중지 - stopStream(peerFace.srcObject, 'peerFace', ACTION_STREAM_TYPES.STOP_PEERFACE); - stopStream(sharedScreen.srcObject, 'sharedScreen', ACTION_STREAM_TYPES.STOP_SCREENSHARE); - - // myPeerConnection.disconnect(); - isPeerScreenSharing = false; - isScreenSharing = false; - myDataChannel.close(); - myDataChannel = null; - // UI 업데이트 - updateUIForDisconnection(); -} -function updateUIForConnection() { - peerFace.style.display = 'block'; - updateVideoLayout(); -} -/** - * 상대방 비디오 요소를 숨기는 홤수 - */ -function updateUIForDisconnection() { - peerFace.srcObject = null; - sharedScreen.srcObject = null; - peerFace.style.display = 'none'; - sharedScreen.style.display = 'none'; - updateVideoLayout(); -} -/** - * 시스템 상에서 알려주는 메시지 - * @param {string} message 메시지 - */ -function addSystemMessageToChat(message) { - const messageElement = document.createElement('p'); - messageElement.innerHTML = `Pickle: ${message}`; - messageElement.style.color = 'blue'; - chatBox.appendChild(messageElement); - chatBox.scrollTop = chatBox.scrollHeight; -} - -/** - * sdp 수립 시, 자동적으로 실행되는 함수 - * @param {Event} event 이벤트 - * @param {socket} socket 연결된 소켓 - */ -function handleIceEvent(event, socket) { - console.log('4-1. offer - answer 연결 성공하여 ice 시작'); - const ice = event.candidate; - console.log('handleIceEvent ice'); - if (socket) socket.emit('ice', { ice, roomId: targetRoomId }); - console.log('4-2. ice'); -} - -function handleTrackEvent(event) { - const [stream] = event.streams; - console.log('handleTrackEvent 실행중'); - if (event.track.kind === 'video') { - if (isPeerScreenSharing) { - sharedScreen.srcObject = stream; - sharedScreen.style.display = 'block'; - console.log(isScreenSharing); - console.log('수신된 화면 공유 스트림을 sharedScreen에 추가했습니다.'); - } else { - peerFace.srcObject = stream; - console.log('수신된 웹캠 스트림을 peerFace에 추가했습니다.'); - } - } - updateVideoLayout(); -} - -async function receiveIce(socket) { - socket.on('ice', (ice) => { - console.log('5-1. candidate 수신'); - myPeerConnection.addIceCandidate(ice); - console.log('5-2. ICE 후보 추가 '); - }); -} -/** - * SDP를 수립하기 위해 첫 번째로 offer를 생성하여 전송하는 함수 - * 이 때, DataChannel도 생성함 - * @param {socket} socket 연결된 소켓 - */ -async function makeAndSendOffer(socket) { - try { - if (!socket || !socket.connected) { - console.error('Socket is not connected'); - return; - } - myDataChannel = myPeerConnection.createDataChannel('communication'); - myDataChannel.addEventListener('message', (event) => { - const message = JSON.parse(event.data); - const { type } = message; - - switch (type) { - case MESSAGE_TYPES.METADATA: - handleStreamMetadata(message.streamType); - break; - case MESSAGE_TYPES.CHAT: - addMessageToChat('상대방', message.content.content); - break; - case MESSAGE_TYPES.SYSTEM: - addSystemMessageToChat(message.content.content); - break; - default: - console.log('알 수 없는 메시지 유형:', type); - } - }); - - console.log('made data channel'); - const offer = await myPeerConnection.createOffer(); - await myPeerConnection.setLocalDescription(offer); - const roomId = targetRoomId; - socket.emit('offer', { offer, roomId }); - console.log('1. offer 생성 -> 전송'); - } catch (error) { - console.error('offer 생성 및 전송 중 에러 발생:', error); - } -} -/** - * 상대편에서 offer를 생성하여 보냈을 경우, 실행되는 함수 - * offer를 수신하여, 상대편의 정보를 인지함 - * answer를 생성하여 전송 - * 상대편에서 받은 datachannel도 연결함 - * @param {socket} socket 연결된 소켓 - */ -async function receiveOfferMakeAnswer(socket) { - try { - socket.on('offer', async (offer) => { - myPeerConnection.addEventListener('datachannel', (event) => { - myDataChannel = event.channel; - myDataChannel.addEventListener('message', (event) => { - const message = JSON.parse(event.data); - const { type } = message; - - switch (type) { - case MESSAGE_TYPES.METADATA: - handleStreamMetadata(message.streamType); - break; - case MESSAGE_TYPES.CHAT: - addMessageToChat('상대방', message.content.content); - break; - case MESSAGE_TYPES.SYSTEM: - addSystemMessageToChat(message.content.content); - break; - default: - console.log('알 수 없는 메시지 유형:', type); - } - }); - }); - console.log('2-1. offer 수신 '); - await myPeerConnection.setRemoteDescription(offer); - const answer = await myPeerConnection.createAnswer(); - await myPeerConnection.setLocalDescription(answer); - console.log('2-2. answer 생성 및 전송 '); - const roomId = targetRoomId; - socket.emit('answer', { answer, roomId }); - }); - } catch (error) { - console.error('offer 수신 및 answer 생성과 전송 중 에러 발생:', error); - } -} -/** - * 첫번째로 offer를 생성했던 쪽에서 answer를 받게되면 실행되는 함수 - * answer로 상대편의 정보를 인지 - * @param {socket} socket 연결된 소켓 - */ -async function finallyReceiveAnswer(socket) { - try { - socket.on('answer', (answer) => { - console.log('3. answer 수신'); - myPeerConnection.setRemoteDescription(answer); - }); - } catch (error) { - console.error('answer 수신 중 에러 발생:', error); - } -} - -muteBtn.addEventListener('click', handleMuteClick); -/** - * 마이크 끄기/켜기 버튼 클릭 시, 오디오 트랙 음소거/해제하는 함수 - */ -function handleMuteClick() { - myStream.getAudioTracks().forEach((track) => (track.enabled = !track.enabled)); - updateMuteButton(); -} - -cameraBtn.addEventListener('click', handleCameraClick); -/** - * 카메라 끄기/켜기 버튼 클릭 시, 비디오 트랙 활성화 및 비활성화하는 함수 - */ -function handleCameraClick() { - myStream.getVideoTracks().forEach((track) => (track.enabled = !track.enabled)); - updateCameraButton(); -} - -sendBtn.addEventListener('click', handleSendClick); -/** - * 보내기 버튼을 누르면, 상대방에게도 전달되고 나의 채팅창에도 표시되도록 함 - */ -function handleSendClick() { - const message = messageInput.value; - if (message) { - sendChatMessage(message); - addMessageToChat('나', message); - messageInput.value = ''; - } -} - -/** - * 채팅창에 보내는 사람과 메시지를 표시하는 함수 - * @param {string} sender 보내는 사람 - * @param {string} message 메시지 - */ - -function addMessageToChat(sender, message) { - const messageElement = document.createElement('p'); - messageElement.innerHTML = `${sender}: ${message}`; - chatBox.appendChild(messageElement); - chatBox.scrollTop = chatBox.scrollHeight; - if (!isChatVisible) { - chatToggle.classList.add('new-message'); - } -} - -/** - * 데이터 채널로 타입과 내용을 전송하는 함수 - * @param {MESSAGE_TYPES} type 메시지 타입 - * @param {string} content 내용 - */ -function sendDataChannelMessage(type, content) { - if (myDataChannel && myDataChannel.readyState === 'open') { - const message = JSON.stringify({ type, content }); - myDataChannel.send(message); - console.log(`메시지 전송: ${message}`); - } else { - console.log('데이터 채널이 열려 있지 않습니다.'); - } -} - -/** - * 스트림에 액션과, 어떤 스트림인지를 전송(웹캠 혹은 화면공유) - * @param {MESSAGE_TYPES} actionType 웹캠 혹은 화면공유를 시작한다 혹은 중지한다 - */ -function sendStreamMetadata(actionType) { - sendDataChannelMessage(MESSAGE_TYPES.METADATA, { action: actionType }); -} - -/** - * 메시지를 상대방에게 전달하는 함수 - * @param {string} message 메시지 - */ -function sendChatMessage(message) { - sendDataChannelMessage(MESSAGE_TYPES.CHAT, { content: message }); -} - -/** - * 화면공유를 포함한 영상이 나오지 않도록 스트림을 중지처리하는 함수 - * @param {MideaStream} stream - * @param {STREAM_TYPES} elementId myFace, peerFace, sharedScreen 중 하나 - * @param {ACTION_STREAM_TYPES} actionType stopScreenshare, stopPeerface 중 하나 - */ -function stopStream(stream, elementId, actionType) { - if (stream) { - console.log('stopStream'); - console.log(stream); - stream.getTracks().forEach((track) => track.stop()); - sendStreamMetadata(actionType); - console.log(`${actionType} 중지`); - document.getElementById(elementId).srcObject = null; - } -} - -/** - * 데이터 채널로 받은 streamType에 따라 어떤 스트림을 보여주거나 중지해야할지 결정하는 함수 - * @param {ACTION_STREAM_TYPES} streamType - */ -function handleStreamMetadata(streamType) { - console.log(streamType); - switch (streamType) { - case ACTION_STREAM_TYPES.PEERFACE: - console.log('상대방이 웹캠 스트림을 전송 중입니다.'); - break; - case ACTION_STREAM_TYPES.SCREENSHARE: - console.log('상대방이 화면 공유 스트림을 전송 중입니다.'); - isPeerScreenSharing = true; - updateShareScreenButton(); - break; - case ACTION_STREAM_TYPES.STOP_PEERFACE: - console.log('상대방이 웹캠 스트림을 중지했습니다.'); - peerFace.srcObject = null; - break; - case ACTION_STREAM_TYPES.STOP_SCREENSHARE: - console.log('상대방이 화면 공유 스트림을 중지했습니다.'); - isPeerScreenSharing = false; - sharedScreen.srcObject = null; - updateVideoLayout(); - updateShareScreenButton(); - break; - default: - console.log('알 수 없는 메타데이터 액션:', streamType); - } -} - -shareScreenBtn.addEventListener('click', handleShareClick); -/** - * 화면 공유 및 중지 버튼 클릭시 실행되는 함수 - */ -async function handleShareClick() { - if (!isScreenSharing) { - await startScreenShare(); - } else { - stopScreenShare(); - } -} - -/** - * 화면 공유 시작 함수 - */ -async function startScreenShare() { - try { - screenStream = await navigator.mediaDevices.getDisplayMedia({ - video: true, - audio: false, - }); - console.log('screenStream'); - console.log(screenStream); - screenStream.getTracks().forEach((track) => { - myPeerConnection.addTrack(track, screenStream); - }); - - const sharedScreen = document.getElementById('sharedScreen'); - sharedScreen.srcObject = screenStream; - sharedScreen.style.display = 'block'; - - sendStreamMetadata(ACTION_STREAM_TYPES.SCREENSHARE); - - console.log('화면 공유 시작'); - updateScreenSharingStatus(); - - screenStream.getVideoTracks()[0].addEventListener('ended', () => { - stopScreenShare(); - }); - updateVideoLayout(); - } catch (error) { - console.error('화면 공유 오류 발생:', error); - } -} - -/** - * 화면 공유 중지 함수 - */ -function stopScreenShare() { - if (screenStream) { - screenStream.getTracks().forEach((track) => track.stop()); - screenStream = null; - } - sendStreamMetadata(ACTION_STREAM_TYPES.STOP_SCREENSHARE); - const senders = myPeerConnection.getSenders(); - senders.forEach((sender) => { - if (sender.track && sender.track.kind === 'video') { - if (sender.track.label.includes('screen')) { - myPeerConnection.removeTrack(sender); - } - } - }); - updateVideoLayout(); - updateScreenSharingStatus(); - console.log('화면 공유 중지'); -} -/** - * 화면 공유 시, 버튼의 상태와 화면 공유 상태를 저장하는 변수 업데이트 - */ -function updateScreenSharingStatus() { - if (isScreenSharing) { - isScreenSharing = false; - shareScreenBtn.innerHTML = ''; - sharedScreen.style.display = 'none'; - } else { - isScreenSharing = true; - shareScreenBtn.innerHTML = ''; - sharedScreen.style.display = 'block'; - } - updateVideoLayout(); - updateShareScreenButton(); -} -function updateShareScreenButton() { - if (isPeerScreenSharing) { - shareScreenBtn.disabled = true; - shareScreenBtn.style.opacity = '0.5'; - shareScreenBtn.title = '상대방이 화면을 공유 중입니다'; - } else { - shareScreenBtn.disabled = false; - shareScreenBtn.style.opacity = '1'; - shareScreenBtn.title = isScreenSharing ? '화면 공유 중지' : '화면 공유 시작'; - } -} -/** - * 메타데이터(어떤 스트림에 어떤 액션을 취할지)를 데이터채널을 통해 전송하는 함수 - * @param {streamType} streamType peerface, screenshare, stopPeerface, stopScreenshare - */ -function sendStreamMetadata(streamType) { - if (myDataChannel.readyState === 'open') { - const metadata = JSON.stringify({ type: 'metadata', streamType }); - myDataChannel.send(metadata); - console.log(`메타데이터 전송: ${metadata}`); - } else { - console.log('데이터 채널이 열려 있지 않습니다.'); - } -} -/** - * 나가기 버튼을 눌렀을 때 실행되는 함수 - * @param {socket} socket 연결된 소켓 - */ -async function handleLeaveRoom(socket) { - console.log('방 나가기 시작'); - - if (socket && socket.connected) { - console.log('서버에 leaveRoom 이벤트 전송'); - socket.emit('leaveRoom', roomId); - } - await cleanupResources(); - - socket.disconnect(); - console.log('방 나가기 완료'); - - // 창 닫기 - window.close(); - - // 브라우저가 window.close()를 차단할 경우를 대비한 대체 방법 - if (!window.closed) { - alert('창을 닫을 수 없습니다. 수동으로 창을 닫아주세요.'); - window.location.href = '/'; // 메인 페이지로 리다이렉트 - } -} -async function cleanupResources(socket) { - console.log('자원 정리 시작'); - - // 모든 스트림의 트랙 중지 - function stopAllTracks(stream) { - if (stream) { - stream.getTracks().forEach((track) => { - track.stop(); - console.log(`${track.kind} 트랙 중지됨`); - }); - } - const senders = myPeerConnection.getSenders(); - senders.forEach((sender) => { - myPeerConnection.removeTrack(sender); - }); - } - - stopAllTracks(myStream); - stopAllTracks(screenStream); - - // 비디오 요소 초기화 - myFace.srcObject = null; - peerFace.srcObject = null; - sharedScreen.srcObject = null; - - // PeerConnection 정리 - if (myPeerConnection) { - myPeerConnection.ontrack = null; - myPeerConnection.onicecandidate = null; - myPeerConnection.oniceconnectionstatechange = null; - myPeerConnection.onsignalingstatechange = null; - myPeerConnection.onicegatheringstatechange = null; - myPeerConnection.onnegotiationneeded = null; - - // DataChannel 정리 - if (myDataChannel) { - myDataChannel.close(); - myDataChannel = null; - } - - // PeerConnection 종료 - myPeerConnection.close(); - myPeerConnection = null; - console.log('PeerConnection 종료됨'); - } - - // 변수 초기화 - - muted = false; - cameraOff = false; - - updateScreenSharingStatus(); - - cleanupChatBox(); - - console.log('자원 정리 완료'); -} -function showInitialScreen() { - call.hidden = true; - if (isScreenSharing) updateScreenSharingStatus(); - isPeerScreenSharing = false; -} - -function cleanupChatBox() { - chatBox.innerHTML = ''; // 모든 채팅 메시지 삭제 - console.log('채팅창이 초기화되었습니다.'); -} - -const chatContainer = document.getElementById('chatContainer'); - -let isChatVisible = false; - -chatToggle.addEventListener('click', toggleChat); - -function toggleChat() { - console.log('DDDD'); - isChatVisible = !isChatVisible; - chatContainer.style.display = isChatVisible ? 'block' : 'none'; - chatToggle.innerHTML = isChatVisible ? '' : ''; - - if (isChatVisible) { - chatBox.scrollTop = chatBox.scrollHeight; - chatToggle.classList.remove('new-message'); - } -} -function updateVideoLayout() { - if (isScreenSharing || isPeerScreenSharing) { - videoContainer.classList.add('screen-share-active'); - sharedScreen.style.display = 'block'; - - myFace.style.left = '20px'; - myFace.style.bottom = '20px'; - myFace.style.top = 'auto'; - myFace.style.right = 'auto'; - - peerFace.style.right = '20px'; - peerFace.style.bottom = '20px'; - peerFace.style.top = 'auto'; - peerFace.style.left = 'auto'; - - makeDraggable(myFace); - makeDraggable(peerFace); - } else { - videoContainer.classList.remove('screen-share-active'); - sharedScreen.style.display = 'none'; - - myFace.style = ''; - peerFace.style = ''; - - removeDraggable(myFace); - removeDraggable(peerFace); - } -} -function makeDraggable(element) { - let pos1 = 0, - pos2 = 0, - pos3 = 0, - pos4 = 0; - element.onmousedown = dragMouseDown; - - function dragMouseDown(e) { - e = e || window.event; - e.preventDefault(); - pos3 = e.clientX; - pos4 = e.clientY; - document.onmouseup = closeDragElement; - document.onmousemove = elementDrag; - } - - function elementDrag(e) { - e = e || window.event; - e.preventDefault(); - pos1 = pos3 - e.clientX; - pos2 = pos4 - e.clientY; - pos3 = e.clientX; - pos4 = e.clientY; - - const newTop = element.offsetTop - pos2; - const newLeft = element.offsetLeft - pos1; - - // 비디오 컨테이너 경계 내에서만 이동하도록 제한 - const container = videoContainer.getBoundingClientRect(); - const elementRect = element.getBoundingClientRect(); - - if (newTop >= 0 && newTop + elementRect.height <= container.height) { - element.style.top = newTop + 'px'; - element.style.bottom = 'auto'; - } - if (newLeft >= 0 && newLeft + elementRect.width <= container.width) { - element.style.left = newLeft + 'px'; - element.style.right = 'auto'; - } - } - - function closeDragElement() { - document.onmouseup = null; - document.onmousemove = null; - } -} - -function removeDraggable(element) { - element.onmousedown = null; -} diff --git a/src/routes/consultingRoutes.js b/src/routes/consultingRoutes.js index a971c12..7ebd32b 100644 --- a/src/routes/consultingRoutes.js +++ b/src/routes/consultingRoutes.js @@ -7,6 +7,7 @@ import { const router = express.Router(); router.get('/consulting-room', getRoomList); -router.post('/consulting-room/:roomId', getConsultingRoom); +router.get('/consulting-room-test/:roomId', getConsultingRoom); +router.get('/consulting-room/:roomId', getConsultingRoom); export default router;