요번 프로그램을 진행하면서 배우며 익힌 방법들에대해 개인적으로 정리하는 한주입니다
WEBRTC 와 SOCKET.IO 를 사용하여 화상채팅 구현해보기 ( node.js, javascript, pug )
- 클라이언트 ( 프론트엔드 ) : app.js , home.pug
- 서버 ( 백엔드 ) : server.js
완성된 화면



기본 css 를 사용하지않고 작게나마 꾸며진 css 를 적용한
Map.css
Header 에 link(rel="stylesheet",href="https://unpkg.com/mvp.css”) 추가 해주면 기본 css가 잡힘
home.pug ( HTML 화면을 보여주는 공간)
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(http-equiv="X-UA-Compatible", content="IE=edge")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title Noom
link(rel="stylesheet",href="https://unpkg.com/mvp.css")
body
header
h1 Noom
main
div#welcome // 사이트에 접속시 맨처음 보여주는 화면
form
input(placehorder="room name", required, type="text")
button Enter room
h4 Open Rooms :
ul
div#call // 방이 만들어지면 사용자의 카메라 사용여부를 물어보고 화면이 나오고 채팅이 가능하게 만들어줌
div#myStream
video#myFace(autoplay,playsinline,width="400",height="400")
button#mute Mute
button#camera Trun Camera OFF
select#cameras
video#peerFace(autoplay,playsinline, width="400",height="400")
div#room
h3
ul
form#name
input(placeholder="nickname", required, type="text")
button Save
form#msg
input(placeholder="message", required, type="text")
button Send
script(src="/socket.io/socket.io.js")
script(src="/public/js/app.js")
서버
Javascript를 서버에서 사용하려면 구글의 V8 엔진을 기반으로 한 nodeJS 프레임워크를 사용해야 합니다
nodeJS를 사용한 REST 서버를 편리하게 구현하게 해주는 프레임워크로는 Koa, Hapi, express 등이 있고 이번 프로젝트에서는 express 사용하여 웹소켓에 대해 배워보았습니다
import 를 하여 package.json 의 모듈을 불러오는
import http from "http";
import SocketIO from "socket.io";
import express from "express";
app이라는 변수에 express 함수의 변환 값을 저장하여 app이라는 변수로 REST End Point들을 생성하게 해주는
const app = express();
REST API의 종류 (get, post, update, delete 등등)을 사용하여 End Point를 작성하는
app.set("view engine", "pug");
app.set("views", __dirname + "/public/views");
app.use("/public", express.static(__dirname + "/public"));
app.get("/", (reg, res) => res.render("home"));
app.get("/*", (req, res) => res.redirect("/"));
서버를 만들어 저장하고 그 서버위에 웹소켓 서버를 덮어 사용하는
const httpServer = http.createServer(app);
const wsServer = SocketIO(httpServer);
공용된 방을 만들고 화상채팅과 실시간 채팅서버를 만들어주는 함수
wsServer.on("connection", (socket) => {
socket.on("join_room", (roomName) => {
socket.join(roomName);
socket.to(roomName).emit("welcome");
});
// join_room 이라는 이름으로 이름을 받아오고 해당 방으로 입장하며 클라이언트에 welcome 이라는 메세지를 보내준다
socket.on("offer", (offer, roomName) => {
socket.to(roomName).emit("offer", offer);
});
// offer는 roomName 에 해당하는 정보를 받아오는곳
socket.on("answer", (answer, roomName) => {
socket.to(roomName).emit("answer", answer);
});
// answer은 roomName 에 해당하는 정보의 결과를 보내주는것
socket.on("ice", (ice, roomName) => {
socket.to(roomName).emit("ice", ice);
});
// ice는 이벤트는 로컬 ICE (en-US) 에이전트가 signaling 서버를 통해 원격 피어에게 메세지를 전달 할 필요가 있을때 마다 발생합니다
socket["nickname"] = "Anon";
// 입장시 기본값으로 Anon 이라는 이름으로 설정해줍니다
socket.onAny((event) => {
console.log(`Socket Event : ${event}`);
});
// 소켓 연결 되었을때 서버의 콘솔에 연결됬다는걸 알려줌
socket.on("enter_room", (roomName, done) => {
socket.join(roomName);
done();
socket.to(roomName).emit("welcome", socket.nickname, countRoom(roomName));
wsServer.sockets.emit("room_change", publicRooms());
});
// enter_room 을 눌르면 해당하는 roomName의 방에 입장되고
// done() <- 성공 콜백함수
// welcome 이라는 약속으로 클라이언트에게 사용자의 이름과 방의 정보를 넘겨준다
socket.on("disconnecting", () => {
socket.rooms.forEach((room) =>
socket.to(room).emit("bye", socket.nickname, countRoom(room) - 1)
);
});
// 연결을 끊는동안 클라이언트에게 bye 라는 이름으로 사용자의 이름과 룸의 갯수를 줄여준다
socket.on("disconnect", () => {
wsServer.sockets.emit("room_change", publicRooms());
});
// 연결이 끊기면 room_change 라는 타입으로 공용방을 보여준다
socket.on("new_message", (msg, room, done) => {
socket.to(room).emit("new_message", `${socket.nickname}: ${msg}`);
done();
});
// 메세지가 오면 new_message 라는 타입으로 msg와 방의 정보와 성공여부를 받고
// 방에다가 new_message 라는 타입으로 사용자의 이름 누구누구가 메세지를 보내왔다 넘겨준다
socket.on("nickname", (nickname) => (socket["nickname"] = nickname));
});
Node.js를 이용하여 서버를 구축할 때는 http라는 모듈을 이용하여 listen으로 등록해주는
// handleListen 함수를 만들어 콘솔에 서버 어디를 만들어 보여주고있다 라고 보여주는 함수
const handleListen = () => console.log(`Listening on http://localhost:3000`);
httpServer.listen(3000, handleListen);
클라이언트
서버와 소켓 연결을 하는
const socket = io();
html 의 문자열과 일치하는 정보들을 가져와서 변수에 담아 저장하는 곳
const myFace = document.getElementById("myFace");
// 카메라의 정보를 넣기위한 변수
const muteBtn = document.getElementById("mute");
// 마이크 음소거를 위한 변수
const cameraBtn = document.getElementById("camera");
// 카메라를 키고 끄고 하기위한 변수
const cameraSelect = document.getElementById("cameras");
// 카메라를 어떤카메라를 선택할지를 쓰기위한 변수
const call = document.getElementById("call");
// 방생성을 하기위한 변수
const welcomes = document.getElementById("welcome");
// 채팅을 하기위한 변수
const form = welcomes.querySelector("form");
// 채팅을 하기위한 변수를 가져와 form 으로 변화하는 변수
WebRTC ( 동영상 및 오디오 )
방 생성 전 및 생성후 의 기본값들
call.hidden = true;
room.hidden = true;
// 방을 생성하기 전에는 hidden 을 사용해 가려준다
let myStream;
let muted = false;
let cameraOff = false;
let roomName;
let myPeerConnection;
let myDataChannel;
카메라의 정보를 가져와 사용하는
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;
}
cameraSelect.appendChild(option);
});
} catch (e) {
console.log(e);
}
}
getCameras 성공할경우
navigator.mediaDevices.enumerateDevices() devices에 변수에 담는다
navigator.mediaDevices.enumerateDevices() = >
메서드는 사용(또는 접근)이 가능한 미디어 입력장치나 출력장치들의 리스트를 가져옵니다.
예를 들면 마이크, 카메라, 헤드셋 등의 미디어 입/출력 장치 리스트를 불러오는 것
비디오 장치와 카메라 트렉등 카메라의 정보들을 불러와 선택하게해주는 함수
오디오의 정보를 가져와 사용하는
async function getMedia(deviceId) {
const initialConstrains = {
audio: true,
video: { facingMode: "user" },
};
const cameraConstraints = {
audio: true,
video: { deviceId: { exact: deviceId } },
};
try {
myStream = await navigator.mediaDevices.getUserMedia(
deviceId ? cameraConstraints : initialConstrains
);
myFace.srcObject = myStream;
if (!deviceId) {
await getCameras();
}
} catch (e) {
console.log(e);
}
}
getMedia 를 성공할경우
myStream 에
메서드는 사용자에게 미디어 입력 장치 사용 권한을 요청하며,
사용자가 수락하면 요청한 미디어 종류의 트랙을 포함한 MediaStream을 반환합니다
실패할경우 콘솔에 에러가 나오게된다
마이크를 키고 끄고가 가능하게 해주는 함수
function hanleMuteClick() {
myStream
.getAudioTracks()
.forEach((track) => (track.enabled = !track.enabled));
if (!muted) {
muteBtn.innerText = "Unmute";
muted = true;
} else {
muteBtn.innerText = "Mute";
muted = false;
}
}
myStream 의 정보들을 가져와 키거나 끌수있게 만들어주는 함수
조건문을 걸어 muteBtn 이 true 면 화면에 Unmute 가 버튼으로 보이고 muted는 true로 상태로있으며
muteBtn이 true 면 화면에 보이는 버튼이 Mute 로 바뀌고 muted 는 false로 바뀐다
카메라를 키고 끄고가 가능하게 해주는 함수
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;
}
}
myStream 의 정보들을 가져와 키거나 끌수있게 만들어주는 함수
cameraOff가 false 면 화면에 버튼이 Turn Camera Off 가 보이고
cameraOff가 true 면 화면에 버튼이 Turn Camera On 이 보이게된다
카메라를 장치를 변경하게 해주는 함수
async function handleCameraChange() {
await getMedia(cameraSelect.value);
if (myPeerConnection) {
const videoTrack = myStream.getVideoTracks()[0];
const videoSender = myPeerConnection
.getSenders()
.finde((sender) => sender.track.kind === "video");
videoSender.replaceTrack(videoTrack);
}
}
videoSender 와 videoTrack 장치를 찾아 변경가능하게 해주는 함수입니다
실시간 채팅 ( 메세지 보내기, 메세지 받기 , 방참가 )
채팅 메세지 받기
function addMessage(message) {
const ul = room.querySelector("ul");
const li = document.createElement("li");
li.innerText = message;
ul.appendChild(li);
}
메세지를 받아 html 안의 ul 에 추가해주는 함수입니다
채팅 메세지 보내기
function handleMessageSubmit(event) {
event.preventDefault();
const input = room.querySelector("#msg input");
const value = input.value;
socket.emit("new_message", input.value, roomName, () => {
addMessage(`You : ${value}`);
});
input.value = "";
}
Input 박스안에 사용자가 메세지를 입력하면 서버로 보내지게되고 작성을 누를시에 빈공간으로 남겨주는 함수
채팅 메세지 보내는 이름 보내주기
function handleNicknameSubmit(event) {
event.preventDefault();
const input = room.querySelector("#name input");
const value = input.value;
socket.emit("nickname", input.value);
input.value = "";
}
사용자가 이름을 만들어서 그 이름으로 사용자가 입력하는 입력값을 같이 서버로 보내주는 함수
채팅 방 만드는 함수
function showRoom() {
welcome.hidden = true;
room.hidden = false;
const h3 = room.querySelector("h3");
h3.innerText = `Room ${roomName}`;
const msgForm = room.querySelector("#msg");
const nameForm = room.querySelector("#name");
msgForm.addEventListener("submit", handleMessageSubmit);
nameForm.addEventListener("submit", handleNicknameSubmit);
}
채팅방을 만들어주면 보여주던 welcome 을 숨김처리 해준다
방의 숨김처리 되어있던 룸을 숨김처리를 풀어준다
제목을 활용하여 방의 이름을 설정해주고 메세지와 이름을 서버로 보내준다
채팅 방만드는 함수
function handleRoomSubmit(event) {
event.preventDefault();
const input = form.querySelector("input");
socket.emit("enter_room", input.value, showRoom);
roomName = input.value;
input.value = "";
}
방을 만들면 서버로 보내주는 함수
버튼을 눌렀을시 해당 이벤트들을 실행해주는 라인
form.addEventListener("submit", handleRoomSubmit);
muteBtn.addEventListener("click", hanleMuteClick);
cameraBtn.addEventListener("click", handleCameraClick);
cameraSelect.addEventListener("input", handleCameraChange);'프로그램 시작후 각 주차 정리' 카테고리의 다른 글
| 공부 13주차 - part 1 ( ApexCharts , Recoil ) (0) | 2022.06.04 |
|---|---|
| 공부 12주차 - part 2 ( React-Query ) (0) | 2022.05.29 |
| 공부 10주차 (0) | 2022.05.15 |
| 공부 9주차 (0) | 2022.05.07 |
| 프로그램 8주차 (0) | 2022.05.01 |