WenRTC p2p 통신을 이용하여 다대다 통신연결을 위한 코드를 작성중입니다!
현재 offer 를 주고받는데 까지 성공하였고
내일은 answer와 candidate 송수신까지 구현을 목표로 하고있습니다!
오늘 새로 알게된 내용으로는 객체의 키값을 변수로 불러올때의 문법입니다
pcs[`${allUsers[i]}`];
위의 코드는 pcs 객체에서 allUsers[i]의 값을 키로 값을 불러오는 코드입니다!
아래의 코드에서 예시 내용을 확인할 수 있습니다 :)
정말 많은시간을 잡아먹고 있는 RTC 통신.... 내일은 꼭 끝이 났으면 좋겠습니다 ㅠㅠㅠ
// 외부모듈
import styled from 'styled-components';
import React, { useRef, useEffect, useState, Children } from 'react';
import { useParams, Link, useNavigate } from 'react-router-dom';
import * as SockJS from 'sockjs-client';
import * as StompJs from '@stomp/stompjs';
import { useCookies } from 'react-cookie';
// 내부모듈
import { instance } from '../../../../api/core/axios';
import GameRoomChoice from './GameRoomChoice';
import { getNicknameCookie } from '../../../../utils/cookies';
import ChatBox from './ChatBox';
function GameRoomRTC() {
const myNickName = getNicknameCookie('nickname');
console.log(myNickName);
const navigate = useNavigate();
const socketRef = useRef();
const videoRef = useRef(null);
const anotherVideoRef = useRef(null);
const muteBtn = useRef(null);
const cameraBtn = useRef(null);
const camerasSelect = useRef(null);
const cameraOption = useRef(null);
const client = useRef({});
const param = useParams();
const [users, setUsers] = useState([]);
let pcs = {};
let muted = false;
let cameraOff = false;
let stream;
let myPeerConnection;
function createPeerConnection(socketID, socket, peerConnectionLocalStream) {
const pc = new RTCPeerConnection({
iceServers: [
{
urls: 'stun:stun.l.google.com:19302',
},
],
});
// add pc to peerConnections object
console.log(socketID);
console.log(socket);
console.log(peerConnectionLocalStream);
const keyName = socketID;
pcs = { ...pcs, [`${keyName}`]: pc };
console.log(pcs);
pc.onicecandidate = (e) => {
if (e.candidate) {
console.log('onicecandidate');
socket.send(
JSON.stringify({
type: 'candidate',
candidate: e.candidate,
receiver: socketID,
roomId: param.roomId,
}),
);
}
};
pc.oniceconnectionstatechange = (e) => {
// console.log(e);
};
pc.ontrack = (e) => {
console.log('ontrack success');
setUsers((oldUsers) => oldUsers.filter((user) => user.id !== socketID));
setUsers((oldUsers) => [
...oldUsers,
{
id: socketID,
stream: e.streams[0],
},
]);
};
if (peerConnectionLocalStream) {
console.log('localstream add');
peerConnectionLocalStream.getTracks().forEach((track) => {
pc.addTrack(track, peerConnectionLocalStream);
});
} else {
console.log('no local stream');
console.log(peerConnectionLocalStream);
}
return pc;
}
function onClickCameraOffHandler() {
stream.getVideoTracks().forEach((track) => {
track.enabled = !track.enabled;
});
if (!cameraOff) {
cameraBtn.current.innerText = 'OFF';
cameraOff = !cameraOff;
} else {
cameraBtn.current.innerText = 'ON';
cameraOff = !cameraOff;
}
}
function onClickMuteHandler() {
stream.getAudioTracks().forEach((track) => {
track.enabled = !track.enabled;
});
if (!muted) {
muteBtn.current.innerText = 'Unmute';
muted = !muted;
} else {
muteBtn.current.innerText = 'Mute';
muted = !muted;
}
}
async function getCameras() {
try {
// 유저의 장치를 얻어옵니다
const devices = await navigator.mediaDevices.enumerateDevices();
// 얻어온 유저의 장치들에서 카메라장치만 필터링 합니다
const cameras = devices.filter((device) => device.kind === 'videoinput');
// 현재내가 사용중인 카메라의 label명을 셀렉트란에 보여주기위한 과정입니다.
// 아래의 if문과 이어서 확인 해주세요
const currentCamera = stream.getVideoTracks()[0];
cameras.forEach((camera) => {
cameraOption.current.value = camera.deviceId;
cameraOption.current.innerText = camera.label;
if (currentCamera.label === camera.label) {
cameraOption.current.selected = true;
}
camerasSelect.current.appendChild(cameraOption.current);
});
} catch (error) {
console.log(error);
}
}
async function getUserMedia(deviceId) {
const initialConstrains = {
video: { facingMode: 'user' },
audio: true,
};
const cameraConstrains = {
audio: true,
video: { deviceId: { exact: deviceId } },
};
try {
stream = await navigator.mediaDevices.getUserMedia(
deviceId ? cameraConstrains : initialConstrains,
);
videoRef.current.srcObject = stream;
if (!deviceId) {
await getCameras();
}
} catch (err) {
console.log(err);
}
}
useEffect(() => {
socketRef.current = new SockJS(`http://13.209.84.31:8080/signal`);
socketRef.current.onopen = () => {
getUserMedia();
navigator.mediaDevices
.getUserMedia({
video: true,
audio: true,
})
.then((stream) => {
if (videoRef.current) {
videoRef.current.srcObject = stream;
console.log(stream);
}
// eslint-disable-next-line no-self-assign
stream = stream;
socketRef.current?.send(
JSON.stringify({
type: 'join_room',
roomId: param.roomId,
}),
);
})
.catch((error) => {
console.log(`getUserMedia error: ${error}`);
});
};
socketRef.current.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log(data.type);
switch (data.type) {
case 'all_users': {
console.log('all_user recieve');
console.log(data.allUsers);
const { allUsers } = data;
for (let i = 0; i < allUsers.length; i += 1) {
console.log(stream);
createPeerConnection(allUsers[i], socketRef.current, stream);
console.log(pcs);
const allUsersEachPc = pcs[`${allUsers[i]}`];
if (allUsersEachPc) {
allUsersEachPc
.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: false,
})
.then((offer) => {
console.log('create offer success');
allUsersEachPc.setLocalDescription(
new RTCSessionDescription(offer),
);
socketRef.current?.send(
JSON.stringify({
type: 'offer',
offer,
receiver: allUsers[i],
roomId: param.roomId,
}),
);
})
.catch((error) => {
console.log(error);
});
}
}
break;
}
case 'offer': {
console.log('get offer');
createPeerConnection(data.senderId, socketRef.current, stream);
break;
}
default: {
break;
}
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const disconnect = () => {
// socketRef.current.deactivate();
};
const leaveRoom = async () => {
console.log(1);
disconnect();
console.log(2);
await instance
.delete(`rooms/${param.roomId}/exit`)
.then(async (res) => {
console.log('res', res);
await navigate('/rooms');
console.log(4);
})
.catch(async (error) => {
// alert(error.data.message);
await navigate('/rooms');
console.log('5', error);
});
console.log(6);
};
async function onInputCameraChange() {
await getUserMedia(camerasSelect.current.value);
if (myPeerConnection) {
const videoTrack = stream.getVideoTracks()[0];
const videoSender = myPeerConnection
.getSenders()
.find((sender) => sender.track.kind === 'video');
videoSender.replaceTrack(videoTrack);
}
}
return (
<StGameRoomOuter>
<StGameRoomHeader>
<Link to="/rooms">
<button>뒤로가기</button>
</Link>
<Link to="/rooms">
<button
onClick={() => {
leaveRoom();
}}
>
방나가기
</button>
</Link>
<button>설정</button>
</StGameRoomHeader>
<GameRoomChoice props={param} />
<StGameRoomMain>
<StGameTitleAndUserCards>
<StTitle>
<h1>주제</h1>
</StTitle>
<StUserCards>
<StCard>
{' '}
Card
<h4>키워드</h4>
<span>OOO님</span>
<div>
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<video
ref={videoRef}
id="myFace"
autoPlay
playsInline
width={200}
height={200}
>
비디오
</video>
<button ref={muteBtn} onClick={onClickMuteHandler}>
mute
</button>
<button ref={cameraBtn} onClick={onClickCameraOffHandler}>
camera OFF
</button>
<select ref={camerasSelect} onInput={onInputCameraChange}>
<option>기본</option>
{/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
<option ref={cameraOption} value="device" />
</select>
</div>
<button>방장일 경우 시작버튼?</button>
</StCard>
<StCard>
Card
<h4>키워드</h4>
<span>OOO님</span>
<div>
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<video
ref={anotherVideoRef}
id="myFace"
autoPlay
playsInline
width={200}
height={200}
>
비디오
</video>
</div>
</StCard>
</StUserCards>
</StGameTitleAndUserCards>
<StTimer>타이머:남은시간20초</StTimer>
<ChatBox />
</StGameRoomMain>
</StGameRoomOuter>
);
}
const StGameRoomOuter = styled.div`
border: 5px solid black;
display: grid;
grid-template-rows: 100px 1fr;
`;
const StGameRoomHeader = styled.div`
border: 3px solid red;
`;
const StGameRoomMain = styled.div`
margin-top: 30px;
border: 3px solid blue;
display: grid;
grid-template-columns: 1fr 150px 1fr;
`;
const StGameTitleAndUserCards = styled.div`
border: 2px solid black;
`;
const StTimer = styled.div`
border: 2px solid black;
`;
const StChatBox = styled.div`
border: 2px solid black;
display: grid;
grid-template-rows: 30px 1fr 30px;
`;
const StTitle = styled.div`
border: 1px solid black;
display: grid;
grid-template-rows: 120px 1fr;
`;
const StUserCards = styled.div`
border: 1px solid black;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
`;
const StCard = styled.div`
border: 1px solid black;
`;
const StNotice = styled.div`
border: 1px solid black;
`;
const StUserChatBox = styled.div`
border: 1px solid black;
`;
const StSendChat = styled.div`
border: 1px solid black;
`;
export default GameRoomRTC;