[project] WebRTC signaling [ 3 ] // using SokcJS

😎·2023년 1월 10일
1

PROJECT

목록 보기
12/26
post-thumbnail

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;
profile
개발 블로그

0개의 댓글