webRTC

OneTwoThree·2022년 12월 4일
0

zoom 클론코딩

목록 보기
3/3

강의 링크

기초

autoplay : 자동 재생, playsinline : 모바일 기기에서 사용할 때 자동으로 전체화면이 되지 않게 함

//http
const http = require(`http`);

//websocket
const WebSocket = require(`ws`);

//socketio
const {Server} = require(`socket.io`);



//express를 이용한 httpserver 
const httpServer = http.createServer(app);
//socketio 서버 
const wsServer = new Server(httpServer);
httpServer.listen(3000, ()=>{
  console.log(`listening on port 3000`);
}); // 서버는 ws, http 프로토콜 모두 이해할 수 있게 된다!

서버에서
기존에 만든 socke.io의 채팅 코드는 지웠다.
http서버랑 socketio서버로 서버를 돌린다.

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//브라우저에서 socketio 연결하기 위해서는 io() 만 해주면 된다.
//io 함수가 알아서 socketio를 돌리는 서버를 찾아서 연결할것임 
const socket = io();

const  myFace = document.getElementById("myFace");

let myStream;

async function getMedia(){
  try { 
    //스트림을 받아온다
    myStream = await navigator.mediaDevices.getUserMedia({
      audio : true,
      video : true,
    });
    console.log(myStream);
    myFace.srcObject = myStream;
  }catch(e){
    console.log(e);
  }
}

getMedia();

브라우저에서
getMedia를 만들고 호출해준다
getMedia에서는 getUserMedia로 스트림을 얻어온다
그리고 해당 스트림을 뷰페이저에 표시해준다.

track

Stream은 track을 제공하고 우리는 track에 접근할 수 있다.

getAudioTracks로 오디오 트랙의 배열을 얻어와 enabled를 반대로 해준다.

비디오에 대해서도 getVideoTracks로 같게 해주면 된다.

getUserMedia()

getUserMedia는 유저의 유저의 카메라와 오디오를 가져온다.

enumerateDevices()

컴퓨터나 모바일에 연결된 모든 장치 목록을 알려준다.

카메라 변경하기

async function getCameras(){
  try{
    //enumerateDevices : pc에 연결된 기기들을 가져옴 
    const devices = await navigator.mediaDevices.enumerateDevices();
    //filter를 이용해 기기들 중 카메라에 해당하는 것만 가져옴  
    const cameras = devices.filter(device=>device.kind==="videoinput");
    cameras.forEach(camera=>{
      //카메라에 해당하는 것들을 이용해 option을 만들고 value는 카메라의 deviceID, innerText는 camer의 label로 지정해준다.
      const option = document.createElement("option")
      option.value = camera.deviceId;
      option.innerText = camera.label;
      
      //뷰페이저에 등록해준다.
      camerasSelect.appendChild(option);
    })
  }catch(e){
    console.log(e);
  }
}

연결된 기기들 중 카메라에 해당하는 것만 filter를 통해 찾는다.
그리고 해당 목록을 이용해서 뷰페이저에 카메라 목록을 선택할 수 있게 추가해줬다.

deviceId를 value로 해주는 이유는 카메라의 deviceId를 이용해야 스트림을 바꿔줄 수 있다.

이제 유저가 원하는 카메라를 선택하면 해당 카메라로 바꿔주기만 하면 된다!

function handleCameraChange(){
  //뷰페이저의 camerasSelect의 value 가져옴. 이것은 사용자가 선택한 카메라의 Id값 
  //이 Id를 사용해서 비디오를 변경해줄 수 있다.
  console.log(camerasSelect.value);
}

//카메라를 선택하면 handleCameraChange 호출 
camerasSelect.addEventListener("input",handleCameraChange);

이렇게 카메라를 선택하면 해당 카메라의 Id를 얻는것까지는 성공

우리가 이미 만들어놓은 getMedia를 호출하면 스트림을 얻어와서 화면에 뿌려준다.
getMedia에서는 getUserMedia를 호출하는데 getUserMedia는 constraints를 인자로 받는다.

그리고 video의 deviceId값을 안다면 이렇게 어떤 비디오의 스트림을 얻어올 지 정해줄 수 있다.

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//브라우저에서 socketio 연결하기 위해서는 io() 만 해주면 된다.
//io 함수가 알아서 socketio를 돌리는 서버를 찾아서 연결할것임 
const socket = io();

const  myFace = document.getElementById("myFace");
const camerasSelect = document.getElementById("cameras");

let myStream;

async function getCameras(){
  try{
    //enumerateDevices : pc에 연결된 기기들을 가져옴 
    const devices = await navigator.mediaDevices.enumerateDevices();
    //filter를 이용해 기기들 중 카메라에 해당하는 것만 가져옴  
    const cameras = devices.filter(device=>device.kind==="videoinput");
    cameras.forEach(camera=>{
      //카메라에 해당하는 것들을 이용해 option을 만들고 value는 카메라의 deviceID, innerText는 camer의 label로 지정해준다.
      const option = document.createElement("option");
      option.value = camera.deviceId;
      option.innerText = camera.label;
      
      //뷰페이저에 등록해준다.
      camerasSelect.appendChild(option);
    })
  }catch(e){
    console.log(e);
  }
}


async function getMedia(deviceId){
  //deviceId가 없을 때, 즉 카메라를 선택한 것이 아닌 맨 처음에 접속할 때 
  const initialConstraints = {
    audio : true,
    video : {facingMode:"user"},
  };
  const cameraConstraints = {
    audio : true,
    video : {deviceId : {exact : deviceId}},
  }
  try { 
    //getUserMedia : 유저의 비디오, 오디오 스트림을 가져옴 
    myStream = await navigator.mediaDevices.getUserMedia(
      //맨처음에는 initial 아닐경우 camera
      deviceId ? cameraConstraints : initialConstraints
    );
      
    //뷰페이저에 설정 
    myFace.srcObject = myStream;
    
    if (!deviceId){ //deviceId가 없는 맨 처음에만 카메라 목록 가져옴 
      //카메라 목록 얻어옴
    await getCameras();
    }
    

  }catch(e){
    console.log(e);
  }
}

//미디어 스트림 가져와서 화면에 띄우기 
getMedia();


async function handleCameraChange(){
  //뷰페이저의 camerasSelect의 value 가져옴. 이것은 사용자가 선택한 카메라의 Id값 
  //이 Id를 사용해서 비디오를 변경해줄 수 있다.
  await getMedia(camerasSelect.value);
}

//카메라를 선택하면 handleCameraChange 호출 
camerasSelect.addEventListener("input",handleCameraChange);

이렇게 하면 카메라를 변경하고 그것이 반영된다!

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//브라우저에서 socketio 연결하기 위해서는 io() 만 해주면 된다.
//io 함수가 알아서 socketio를 돌리는 서버를 찾아서 연결할것임 
const socket = io();

const  myFace = document.getElementById("myFace");
const camerasSelect = document.getElementById("cameras");

let myStream;

async function getCameras(){
  try{
    //enumerateDevices : pc에 연결된 기기들을 가져옴 
    const devices = await navigator.mediaDevices.enumerateDevices();
    //filter를 이용해 기기들 중 카메라에 해당하는 것만 가져옴  
    const cameras = devices.filter(device=>device.kind==="videoinput");


    //getVideoTracks로 비디오 트랙의 첫번째를 가져옴 
    const currentCamera = myStream.getVideoTracks()[0];

    cameras.forEach(camera=>{
      //카메라에 해당하는 것들을 이용해 option을 만들고 value는 카메라의 deviceID, innerText는 camer의 label로 지정해준다.
      const option = document.createElement("option");
      option.value = camera.deviceId;
      option.innerText = camera.label;

      //처음에 현재 카메라가 내가 선택한 카메라와 같으면 자동으로 선택되게 한다 
      if (currentCamera.label === camera.label){
        option.selected=true;
      }
      
      //뷰페이저에 등록해준다.
      camerasSelect.appendChild(option);
    })
  }catch(e){
    console.log(e);
  }
}




async function getMedia(deviceId){
  //deviceId가 없을 때, 즉 카메라를 선택한 것이 아닌 맨 처음에 접속할 때 
  const initialConstraints = {
    audio : true,
    video : {facingMode:"user"},
  };
  const cameraConstraints = {
    audio : true,
    video : {deviceId : {exact : deviceId}},
  }
  try { 
    //getUserMedia : 유저의 비디오, 오디오 스트림을 가져옴 
    myStream = await navigator.mediaDevices.getUserMedia(
      //맨처음에는 initial 아닐경우 camera
      deviceId ? cameraConstraints : initialConstraints
    );
      
    //뷰페이저에 설정 
    myFace.srcObject = myStream;
    
    if (!deviceId){ //deviceId가 없는 맨 처음에만 카메라 목록 가져옴 
      //카메라 목록 얻어옴
    await getCameras();
    }
    

  }catch(e){
    console.log(e);
  }
}



//미디어 스트림 가져와서 화면에 띄우기 
getMedia();


async function handleCameraChange(){
  //뷰페이저의 camerasSelect의 value 가져옴. 이것은 사용자가 선택한 카메라의 Id값 
  //이 Id를 사용해서 비디오를 변경해줄 수 있다.
  await getMedia(camerasSelect.value);
}

//카메라를 선택하면 handleCameraChange 호출 
camerasSelect.addEventListener("input",handleCameraChange);

맨 처음에 선택된 카메라도 정상작동하게 하기 위해 약간의 수정


webRTC

web realtime communication
p2p

앞에서 webSocket으로 만든 방식은 p2p가 아니다
서버에 많은 socket 연결이 있고 내가 서버에게 메세지를 보내면 서버는 연결된 다른 socket들에게 메세지를 보내는 것이다.

p2p 방식은 서버가 필요없다
webRTC를 이용하면 서버를 거치지 않고 직접 소통 가능

signaling

p2p 연결을 하기 위해서는 상대의 브라우저가 어디 있는지 알아야함
어떻게 알까?
이것을 알기 위해 시그널링 서버를 사용한다

크롬브라우저가 서버에게 내 IP 주소 등 정보를 알려준다
= 브라우저가 서버에게 configuration, 브라우저의 위치 전달

FireFox도 마찬가지로 서버에게 configuration, 위치 전달

서버는 영상, 오디오를 처리하지 않는다
이렇게 전달받은 정보를 가지고 다른 브라우저에게 내 위치를 알려준다.

브라우저는 서버에게 위치, setting, configuration, 방화벽이 있는지 등의 정보를 전달하고 서버는 그 정보를 다른 사람에게 전달해줌

--> 브라우저는 서로를 찾을 수 있게 됨

시그널링을 통해 서로의 위치를 알면 브라우저끼리 연결하고 이때 영상, 음성 정보를 전달한다.

시그널링 위해 socketio를 사용하고 위치 정보를 보내기 위해 webSocket을 사용할것임

webRTC로 비디오, 오디오, 텍스트까지 보낼 수 있다.

p2p 채팅방을 만들 수 있다.

rooms

video call을 처리하려면 마찬가지로 room이 필요함

기존에는 getMedia 브라우저에서 바로 호출해서 모든 걸 시작했다.


브라우저에서
사용자가 방에 들어가면 join_room 을 emit하도록 하자.
여기서 socket은 백엔드 즉 시그널링 서버를 의미한다.


서버에서
join_room 을 처리해준다.
payload로 받은 roomName에 대해 방에 참가한다.


브라우저에서
다음 함수를 정의해준다
방에 참가하면 방 이름 입력창을 지우고 화면을 표시해준 뒤
getMedia로 스트림을 가져오기 위함이다.

이 함수를 join_room에 emit할 때 담아서 보내고 서버에서 호출하도록 한다.

그리고 브라우저에서 내가 속한 roomName을 알아야 하므로 welcomeHandleSubmit에서 입력받은 값을 변수에 저장해놓음

지금까지 한게 socketio를 이용해서 방에 입장 -> 서버에 join_room 을 emit하고 연결한 브라우저의 미디어 스트림을 얻어와서 본인의 화면에 표시하는것임.
즉 socketio를 이용해 시그널링을 해서 서로 같은 room에 들어온것임

이제 webRTC연결을 만들어주면 된다.


RTC 연결 만들기

각 브라우저가 방에 들어가면 startMedia를 호출하고
getMedai로 스트림을 가져와서 자기 브라우저에 보이게한다
그리고 여기서 RTC 연결을 만들기 위한 makeConnection을 정의해주자

makeConnection은 다음과 같이 RTCPeerConnection을 만든다
myPeerConnection은 Connection을 어디서든 접근할 수 있게 함수 밖에 정의해줬다.

그리고 내 영상 스트림의 트랙을 가져와서 내가 만든 RTC 연결에 트랙을 추가해준다.

그리고 누군가 내 방에 들어오면 나는 offer를 만든다.
offer는 다른 브라우저가 참가할 수 있도록 하는 초대장이다.

0개의 댓글