노마드코더의 줌 클론코딩 강의를 보고 정리한 내용입니다.
비디오 가져오기
스트림 : 비디오와 오디오가 결합된 것
사용자의 스트림 가져오기
스트림은 track을 제공해준다. track은 비디오가 될 수 있고, 오디오, 자막등이 될 수 있다.
해당 track에 접근할 수 있다.
const socket = io();
const myFace = document.getElementById("myFace"); //영상 화면
const muteBtn = document.getElementById("mute"); //음소거 설정
const cameraBtn = document.getElementById("camera"); //화면 설정
const cameraSelect = document.getElementById("cameras"); //카메라 종류
let myStream;
let muted = false;
let cameraOff = false;
async function getCameras() {
try {
const devices = await navigator.mediaDevices.enumerateDevices(); //현재 존재하는 device가져오기
const cameras = devices.filter((device) => device.kind === "videoinput"); //videoinput인 device만 추출
const currentCamer = myStream.getVideoTracks()[0]; //현재 사용하는 카메라
cameras.forEach((camera) => {
//존재하는 카메라를 select 옵션으로 추가하기
const option = document.createElement("option");
option.value = camera.deviceId;
option.innerText = camera.label;
if (currentCamer.label === camera.label) {
//현재 사용하는 카메라와 label이 동일하다면 select 를 true로
option.selected = true;
}
cameraSelect.appendChild(option);
});
console.log(cameras);
} catch (e) {
console.log(e);
}
}
async function getMedia(deviceId) {
//deviceid가 없을 때 사용
const initialConstraints = {
audio: true,
video: { facingMode: "user" },
};
// deviceId가 있을 때 사용
const cameraConstraints = {
audio: true,
video: { deviceId: { exact: deviceId } },
};
try {
myStream = await navigator.mediaDevices.getUserMedia(
deviceId ? cameraConstraints : initialConstraints
);
myFace.srcObject = myStream;
if (!deviceId) {
//최초 한번만 실행하도록
await getCameras();
}
} catch (e) {
console.log(e);
}
}
getMedia(); //카메라 setting
//음소거 on/off 함수
function handleMuteClick() {
myStream
.getAudioTracks()
.forEach((track) => (track.enabled = !track.enabled));
if (!muted) {
muteBtn.innerText = "Unmute";
muted = true;
} else {
muteBtn.innerText = "Mute";
muted = false;
}
}
//카메라 on/off
function handleCameraClick() {
myStream
.getVideoTracks()
.forEach((track) => (track.enabled = !track.enabled));
if (cameraOff) {
cameraBtn.innerText = "Turn Camera Off";
cameraOff = false;
} else {
cameraBtn.innerText = "Turn Camera On";
cameraOff = true;
}
}
//카메라 변경시
async function handleCameraChange() {
await getMedia(cameraSelect.value);
}
muteBtn.addEventListener("click", handleMuteClick);
cameraBtn.addEventListener("click", handleCameraClick);
cameraSelect.addEventListener("input", handleCameraChange);
설명은 주석으로 대체한다.
webRTC
peer-to-peer 로 서버를 거치지 않고 사용자끼리 바로 연결되는 것.
그러나 시그널링 서버가 필요하다.
필요한 이유?
내가 A와 p2p연결을 하고 싶을 때 A의 브라우저가 어디에 있고, IP주소가 뭔지, 방화벽이 있는지, 회사 네트워크 안에 있는지 등을 알기 위해서 사용한다. 서버는 상대가 어디에 있는지 알게 하도록 도와준다.
서버는 다른 사람이 어디에있는지, setting 정보 등을 알려준다.
RTC Code
RTC 연결 통로를 생성하고 그 안에 비디오와 오디오 track 넣어주기
// RTC Code
function makeConnection() {
myPeerConnection = new RTCPeerConnection();
myStream
.getTracks()
.forEach((track) => myPeerConnection.addTrack(track, myStream));
}
offer를 주고 받기 위해서는 시그널링 서버가 필요하다
클라이언트와 서버코드가 왔다갔다하기 때문에 헷갈릴 수 있다.
1. 처음에 A에서 B로 offer을 전송한다.offer를 local desc 설정해준다.
app.js - client
socket.on("welcome", async () => {
const offer = await myPeerConnection.createOffer();
myPeerConnection.setLocalDescription(offer); //local desc를 설정
console.log("sent the offer");
//서버에 offer 보내기
socket.emit("offer", offer, roomName);
});
2. 서버에서 offer을 받아서 B에게 offer를 보내준다.
server.js - server
//peer A에서 보낸 offer 받아서 Peer B에게 offer 보내기
socket.on("offer", (offer, roomName) => {
socket.to(roomName).emit("offer", offer);
});
3. B에서 offer을 받아서 set remote desc를 해주고, answer를 생성한다. answer는 set local desc해주고, 서버에 answer를 보내준다.
app.js
//Peer B는 offer 받기
socket.on("offer", async (offer) => {
myPeerConnection.setRemoteDescription(offer); //peer의 remote desc
const answer = await myPeerConnection.createAnswer();
myPeerConnection.setLocalDescription(answer); //local desc를 설정
socket.emit("answer", answer, roomName);
});
4. 서버에서 answer을 받아서 A에게 보내준다.
server.js
//peer B에게 받은 answer을 방에있는 모든 사람들에게 보내기
socket.on("answer", (answer, roomName) => {
socket.to(roomName).emit("answer", answer);
});
5. A는 answer을 받아서 set remote desc를 해준다.
app.js
//peer A에서 answer 받기
socket.on("answer", (answer) => {
myPeerConnection.setRemoteDescription(answer);
});
이러면 offer와 answer를 주고 받는게 끝났고, 이제 IceCandidate를 주고 받아야한다.
IceCandidate
인터넷 연결 생성, webRTC에 필요한 피로토콜들을 의미하는데 멀리 떨어진 장치와 소통할 수 있게 해주는 방법이다.
1. candidate를 서버에 보내기
// RTC Code
function makeConnection() {
myPeerConnection = new RTCPeerConnection();
myPeerConnection.addEventListener("icecandidate", handleIce); //icecandidate 리스너 추가
myPeerConnection.addEventListener("addstream", handleAddStream);
myStream
.getTracks()
.forEach((track) => myPeerConnection.addTrack(track, myStream));
}
app.js
//candidate를 서버에 보내기
function handleIce(data) {
console.log("sent candidate");
socket.emit("ice", data.candidate, roomName);
}
2. 서버에서는 받은 candidate를 roomname을 가지고 브라우저에 보내기
server.js
//받은 ice를 보내기
socket.on("ice", (ice, roomName) => {
socket.to(roomName).emit("ice", ice);
});
3. 브라우저에서는 다른 사용자의 candidate를 받는다.
app.js
//candidate 받기
socket.on("ice", (ice) => {
console.log("receive candidate");
myPeerConnection.addIceCandidate(ice);
});
즉, 본인의 cadidate를 다른 사용자들에게 보내는 동시에, 다른 사용자의 candidate를 받을 수 있도록 한다.
그리고 addstream 리스너를 사용해서 다른 유저의 stream받고, html에 보여주도록 해준다.
app.js
// RTC Code
function makeConnection() {
myPeerConnection = new RTCPeerConnection();
myPeerConnection.addEventListener("icecandidate", handleIce);
myPeerConnection.addEventListener("addstream", handleAddStream); //리스너 추가
myStream
.getTracks()
.forEach((track) => myPeerConnection.addTrack(track, myStream));
}
function handleAddStream(data) {
const peerFace = document.getElementById("peerFace");
peerFace.srcObject = data.stream;
}
'개발 공부 > Web' 카테고리의 다른 글
[Web]SocketIO (0) | 2022.07.27 |
---|---|
[Web] WebSocket 정리 (0) | 2022.07.26 |
라디오 버튼을 이미지로 구현하기 [이전 블로그 게시글] (0) | 2022.02.16 |
[정리] 웹 폰트란? [이전 블로그 게시글] (0) | 2022.02.16 |