webRTC

webRTC


webRTC๋ž€

WebRTC (Web Real-Time Communication)๋Š” ์›น ๋ธŒ๋ผ์šฐ์ € ๊ฐ„์— ํ”Œ๋Ÿฌ๊ทธ์ธ์˜ ๋„์›€ ์—†์ด ์„œ๋กœ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋œ API์ด๋‹ค.
W3C์—์„œ ์ œ์‹œ๋œ ์ดˆ์•ˆ์ด๋ฉฐ, ์Œ์„ฑ ํ†ตํ™”, ์˜์ƒ ํ†ตํ™”, P2P ํŒŒ์ผ ๊ณต์œ  ๋“ฑ์œผ๋กœ ํ™œ์šฉ๋  ์ˆ˜ ์žˆ๋‹ค.

https://ko.wikipedia.org/wiki/WebRTC

์œ„์™€ ๊ฐ™์ด webRTC ๊ธฐ์ˆ ์€ ์Œ์„ฑ , ์˜์ƒ p2p ํŒŒ์ผ๊ณต์œ ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ „์†กํ•˜๋Š” web ๊ธฐ์ˆ ์ด๋‹ค.
์•„์ง๋„ ๊ณ„์† ๋ฐœ์ „๋˜๊ฐ€๊ณ  ์žˆ์œผ๋ฉฐ webRTC๋Š” ํ™”์ƒ,์Œ์„ฑ ๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ ๊ตฌํ˜„์„ ์ œ๊ณตํ•˜๋ฉฐ
p2p์™€ ๊ฐ™์€ ํด๋ผ์ด์–ธํŠธ์˜ ์—ฐ๊ฒฐ์€ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

p2p ์—ฐ๊ฒฐ์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์กด์žฌํ•˜๋ฉฐ ์•„์ง ๊ธฐ๋ณธ์ ์œผ๋กœ ํ™•๋ฆฝ๋œ ๋ฐฉ๋ฒ•์€ ์—†์œผ๋ฉฐ Stun , turn server๊ฐ€ ์กด์žฌํ•˜๋ฉฐ ์ด ๋ถ€๋ถ„์€ node.js์— peerJs๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•˜๋ ค๊ณ  ํ•œ๋‹ค.

  1. webRTC๋ฅผ ์ด์šฉํ•œ ๊ธฐ๋Šฅ ๊ตฌํ˜„
  2. https ์ธ์ฆ (ssl ์ธ์ฆํ‚ค ๋ฐœ๊ธ‰)
  3. p2p ์—ฐ๊ฒฐ (peerJs)
  4. ์•ฑ ๋ฐฐํฌ (heroku)

๊ตฌํ˜„ํ•˜๋Š” ์ˆœ์„œ๋กœ ์œ„์™€ ๊ฐ™์ด ์žˆ์œผ๋ฉฐ webRTC๋Š” ์„œ๋กœ๊ฐ€ ์„œ๋กœ๋ฅผ ์‹๋ณ„ํ•˜๋Š” ๊ณ ์œ ์˜ id๊ฐ€ ์กด์žฌํ•ด์•ผํ•˜๋ฉฐ ์„œ๋ฒ„๊ฐ„ p2p ๊ณต์œ ์‹œ ์ธ์ฆ๋œ https ํ”„๋กœํ† ์ฝœ์„ ์ด์šฉํ•˜์—ฌ์•ผ๋งŒ ์„œ๋กœ ๊ณต์œ ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค. domain๊ณผ ssl์ธ์ฆ์„ ๋ฐ›์•„์•ผ๋งŒ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

์—ฌ๊ธฐ์„œ๋Š” ๋ฌด๋ฃŒ๋กœ ํ˜ธ์ŠคํŒ… heroku๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ„๋‹จํ•˜๊ฒŒ webRTC๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค.

webRTC

package.json

์„ค์น˜ํ•œ npm ๋ชจ๋“ˆ

{"engines":{
    "node":"^16.3.0",
    "npm":"^7.15.1"},
"scripts":{
    "start":"node index.js"},
"dependencies":{
    "ejs":"^3.1.6",
    "express":"^4.17.1",
    "mongoose":"^5.12.15",
    "peer":"^0.6.1",
    "socket.io":"^4.1.2",
    "uuid":"^3.4.0"}}

index.js

node Server๋ฅผ ๋Œ๋ฆฌ๋Š” ๋ฉ”์ธ ์Šคํฌ๋ฆฝํŠธ์ด๋‹ค.

const express = require("express");
const app = express();
const PORT = process.env.PORT||3030;
const server = require("http").Server(app);
const io = require("socket.io")(server);
const { v4: uuidV4 } = require("uuid");
const { ExpressPeerServer } = require('peer');
const peerServer = ExpressPeerServer(server, { 
  debug : true
});

app.set("view engine", "ejs");
app.use(express.static("public"));
app.use("/peerjs", peerServer);

app.get("/", (req, res) => {
  res.redirect(`/${uuidV4()}`);
});

app.get("/:room", (req, res) => {
  res.render("room", { roomId: req.params.room });
});

io.on("connection", (socket) => {
  socket.on("join-room", (roomId, userId) => {
    socket.join(roomId);
    socket.to(roomId).emit('uesr-connected', userId);
    
    socket.on('disconnect', ()=>{
      socket.to(roomId).emit('user-disconnected',userId);
    })
  });
});

server.listen(PORT, () => {
  console.log("listening on * : 3000");
});

script.js

ํด๋ผ์ด์–ธํŠธ peer , socket์„ ์—ฐ๊ฒฐํ•˜๊ณ  webRtc ๊ตฌํ˜„ ์Šคํฌ๋ฆฝํŠธ์ด๋‹ค.

const socket = io("/");
const videoGrid = document.getElementById("video-grid");
const myVideo = document.createElement("video");
const peer = new Peer(undefined, {
  path: "/peerjs",
  host: "/",
  port: "443",
});
myVideo.muted = true;
const peers = {};

// 1. ์‚ฌ์šฉ์ž ๋น„๋””์˜ค ์˜ค๋””์˜ค ๊ถŒํ•œ ์š”์ฒญ
let myVideoStream;
navigator.mediaDevices
  .getUserMedia({
    video: true,
    audio: false,
  })
  .then((stream) => {
    // ๋‚ด๊ฐ€ ์ ‘์†์‹œ ๋น„๋””์˜ค ํ‘œ์‹œ
    myVideoStream = stream;
    addVideoStream(myVideo, stream);

    // ์ ‘์†์‹œ ์ ‘์†ํ•œ ์œ ์ €๋“ค ๋น„๋””์˜ค ํ‘œ์‹œ
    peer.on("call", (call) => {
      console.log("์ ‘์† ์œ ์ €๋“ค" + call);
      call.answer(stream);
      const video = document.createElement("video");
      call.on("stream", (userVideoStream) => {
        addVideoStream(video, userVideoStream);
      });
    });

    // ๋‹ค๋ฅธ ์œ ์ €๋“ค ์ ‘์†์‹œ ๋น„๋””์˜ค ํ‘œ์‹œ
    socket.on("uesr-connected", (userId) => {
      console.log("๋‹ค๋ฅธ์œ ์ € ์ ‘์†์‹œ" + userId);
      connectToNewUser(userId, stream);
    });
  });

socket.on("user-disconnected", (userId) => {
  if (peers[userId]) peers[userId].close();
});

peer.on("open", (id) => {
  socket.emit("join-room", ROOM_ID, id);
});

// ์œ ์ € ์ ‘์†์‹œ ์กฐ์ธ์‹œ ์‹คํ–‰
const connectToNewUser = (userId, stream) => {
  const call = peer.call(userId, stream);
  const video = document.createElement("video");
  call.on("stream", (userVideoStream) => {
    addVideoStream(video, userVideoStream);
  });
  call.on("close", () => {
    video.remove();
  });

  peers[userId] = call;
}

// ๋น„๋””์˜ค ์ƒ์„ฑ
const addVideoStream = (video, stream) => {
  video.srcObject = stream; // ๋น„๋””์˜ค ์„ค์ • ์…‹ํŒ…??
  video.addEventListener("loadedmetadata", () => {
    video.play();
  });
  videoGrid.appendChild(video);
}

room.ejs

html ์—”์ง„์ค‘ํ•˜๋‚˜์ธ ejs๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ™”๋ฉด์„ ๊ตฌ์„ฑํ–ˆ๋‹ค.

<head>
    <title>Document</title>
    <script>
        const ROOM_ID = "<%= roomId%>";
    </script>
       <link rel="stylesheet" href="style.css">

    <script defer src="https://unpkg.com/peerjs@1.3.1/dist/peerjs.min.js"></script>
    <script src="/socket.io/socket.io.js" defer></script>
    <script src="script.js" defer></script>
    <style>
        #video-grid {
            display: flex;
            justify-content: center;
            flex-wrap: wrap;
        }

        video {
            width: 400px;
            height: 300px;
            object-fit: cover;
            padding: 8px;
        }
    </style>
</head>

<body>
    <div class="main">
        <div class="main__left">
            <div class="main__videos">
                <div id="video-grid">

                </div>
            </div>
            <div class="main__controls">
                <div class="main__controls__block">
                    <div onclick="muteUnmute()" class="main__controls__button main__mute_button">
                        <i class="fas fa-microphone"></i>
                        <span>Mute</span>
                    </div>
                    <div onclick="playStop()" class="main__controls__button main__video_button">
                        <i class="fas fa-video"></i>
                        <span>Stop Video</span>
                    </div>
                </div>
                <div class="main__controls__block">
                    <div class="main__controls__button">
                        <i class="fas fa-shield-alt"></i>
                        <span>Security</span>
                    </div>
                    <div class="main__controls__button">
                        <i class="fas fa-user-friends"></i>
                        <span>Participants</span>
                    </div>
                    <div class="main__controls__button">
                        <i class="fas fa-comment-alt"></i>
                        <span>Chat</span>
                    </div>
                </div>
                <div class="main__controls__block">
                    <div class="main__controls__button">
                        <span class="leave_meeting">Leave Meeting</span>
                    </div>
                </div>
            </div>
        </div>
        <div class="main__right">
            <div class="main__header">
                <h6>Chat</h6>
            </div>
            <div class="main__chat_window">
                <ul class="messages">

                </ul>

            </div>
            <div class="main__message_container">
                <input id="chat_message" type="text" placeholder="Type message here...">
            </div>
        </div>
    </div>
</body>

</html>

Categories:

Updated:

Leave a comment