프로그래밍/Js

방법1 WebRTC Socket.io + Node.js + turn 임대 서버 이용방법 (1차완료)

소행성왕자 2023. 2. 22. 17:04

 

WebRTC 를 이용하여 화상 채팅을 만들어 보도록 하겠습니다.

본 포스트는 turn 서버를 임대하여 내부 네트워크 와 외부 네트워크 간의 화상 채팅 성공 코드 입니다.

모든 기본 소스와 설명은 아래 링크를 참조했습니다.

https://levelup.gitconnected.com/building-a-video-chat-app-with-node-js-socket-io-webrtc-26f46b213017

 

Building a Video Chat App with Node.js + Socket.io + WebRTC

This tutorial will show you how to build a video chat app using JavaScript and NodeJS. It will also show you how to use PeerJS, WebRTC…

levelup.gitconnected.com

 

SSL 을 이용해야 되기때문에 node 서버에 인증서 경로를 추가해준다.

이미 codetest.coforward.com 은 https 적용되어 있다.

// Dependencies
const fs = require('fs');
const http = require('http');
const https = require('https');
const express = require('express');

const app = express();

// Certificate 인증서 경로
const privateKey = fs.readFileSync('/etc/httpd/ssl/codetest.coforward.com_2022.key', 'utf8');
const certificate = fs.readFileSync('/etc/httpd/ssl/codetest.coforward.com_2022.crt', 'utf8');

const credentials = {
        key: privateKey,
        cert: certificate,
};

app.use((req, res) => {
        res.send('Hello there 222222!');
});

// Starting both http & https servers
const httpServer = http.createServer(app);
const httpsServer = https.createServer(credentials, app);

httpsServer.listen(8443, () => {
        console.log('HTTPS Server running on port 8443');

TURN 서버 미적용 : 같은 네트워크상에서는 정상작동한다.

#vi public/script.js

...
...

var peer = new Peer({
  host: 'codetest.coforward.com',
  port: 8443,
  path: '/peerjs',
  config: {

    'iceServers': [
      { url: 'stun4.l.google.com:19302' },
    ]

/*
    iceServers: [
    {
      urls: "stun:relay.metered.ca:80",
    },
    {
      urls: "turn:relay.metered.ca:80",
      username: "f75c5c089422654fc7c61437",
      credential: "dkLa3Am6b79Sizb9",
    },
    {
      urls: "turn:relay.metered.ca:443",
      username: "f75c5c089422654fc7c61437",
      credential: "dkLa3Am6b79Sizb9",
    },
    {
      urls: "turn:relay.metered.ca:443?transport=tcp",
      username: "f75c5c089422654fc7c61437",
      credential: "dkLa3Am6b79Sizb9",
    },
    ],
*/
  },

  debug: 3
});

TURN 서버 적용 : 같은 네트워크상에서는 정상작동한다.

#vi public/script.js

...
...

var peer = new Peer({
  host: 'codetest.coforward.com',
  port: 8443,
  path: '/peerjs',
  config: {
    /*
    'iceServers': [
      { url: 'stun4.l.google.com:19302' },
    ]
    */

    iceServers: [
    {
      urls: "stun:relay.metered.ca:80",
    },
    {
      urls: "turn:relay.metered.ca:80",
      username: "f75c5c089422654fc7c61437",
      credential: "dkLa3Am6b79Sizb9",
    },
    {
      urls: "turn:relay.metered.ca:443",
      username: "f75c5c089422654fc7c61437",
      credential: "dkLa3Am6b79Sizb9",
    },
    {
      urls: "turn:relay.metered.ca:443?transport=tcp",
      username: "f75c5c089422654fc7c61437",
      credential: "dkLa3Am6b79Sizb9",
    },
    ],

  },

  debug: 3
});

 

TURN 서버 무료 이용 방법 

https://dashboard.metered.ca/

 

Metered - Dashboard

 

dashboard.metered.ca

월 0원에 50G 무료로 이용할수 있다.

https://dashboard.metered.ca/signup?tool=turnserver
mimas@ruu.kr
mimaspw
mimas.metered.live

 

더보기

적용서버 : 121.78.88.131 

경로 : /home/codetest/base/video-chat-v1

서버실행

[codetest@servera131 video-chat-v1]$ pwd
/home/codetest/base/video-chat-v1
[codetest@servera131 video-chat-v1]$ node server.js

브라우져 접속

https://codetest.coforward.com:8443/aaaa

 

전체소스

package.json

{
  "name": "zoom",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "ejs": "^3.1.3",
    "express": "^4.17.1",
    "nodemon": "^2.0.4",
    "peer": "^0.5.3",
    "socket.io": "^2.3.0",
    "uuid": "^8.3.0"
  }
}
$ npm install

$ vi server.js

const fs = require('fs');
const https = require('https');
const express = require("express");
const app = express();

const { v4: uuidv4 } = require("uuid");
app.set("view engine", "ejs");

// Certificate 인증서 경로
const privateKey = fs.readFileSync('/etc/httpd/ssl/codetest.coforward.com_2022.key', 'utf8');
const certificate = fs.readFileSync('/etc/httpd/ssl/codetest.coforward.com_2022.crt', 'utf8');

const credentials = {
        key: privateKey,
        cert: certificate,
};

const server = https.createServer(credentials, app);

const io = require("socket.io")(server, {
  cors: {
    origin: '*'
  }
});
const { ExpressPeerServer } = require("peer");
const opinions = {
  debug: true,
}

app.use("/peerjs", ExpressPeerServer(server, opinions));
app.use(express.static("public"));



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

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

io.on("connection", (socket) => {
  socket.on("join-room", (roomId, userId, userName) => {
    socket.join(roomId);
    setTimeout(()=>{
      socket.to(roomId).broadcast.emit("user-connected", userId);
    }, 1000)
    socket.on("message", (message) => {
      io.to(roomId).emit("createMessage", message, userName);
    });
  });
});

server.listen(process.env.PORT || 8443);

vi public/script.js

const socket = io("/");
const videoGrid = document.getElementById("video-grid");
const myVideo = document.createElement("video");
const showChat = document.querySelector("#showChat");
const backBtn = document.querySelector(".header__back");
myVideo.muted = true;

backBtn.addEventListener("click", () => {
  document.querySelector(".main__left").style.display = "flex";
  document.querySelector(".main__left").style.flex = "1";
  document.querySelector(".main__right").style.display = "none";
  document.querySelector(".header__back").style.display = "none";
});

showChat.addEventListener("click", () => {
  document.querySelector(".main__right").style.display = "flex";
  document.querySelector(".main__right").style.flex = "1";
  document.querySelector(".main__left").style.display = "none";
  document.querySelector(".header__back").style.display = "block";
});

const user = prompt("Enter your name");

var peer = new Peer({
  host: 'codetest.coforward.com',
  port: 8443,
  path: '/peerjs',
  config: {

    'iceServers': [
      { url: 'stun4.l.google.com:19302' },
    ]

/*
    iceServers: [
    {
      urls: "stun:relay.metered.ca:80",
    },
    {
      urls: "turn:relay.metered.ca:80",
      username: "f75c5c089422654fc7c61437",
      credential: "dkLa3Am6b79Sizb9",
    },
    {
      urls: "turn:relay.metered.ca:443",
      username: "f75c5c089422654fc7c61437",
      credential: "dkLa3Am6b79Sizb9",
    },
    {
      urls: "turn:relay.metered.ca:443?transport=tcp",
      username: "f75c5c089422654fc7c61437",
      credential: "dkLa3Am6b79Sizb9",
    },
    ],
*/
  },

  debug: 3
});

let myVideoStream;
navigator.mediaDevices
  .getUserMedia({
    audio: false,
    video: true,
  })
  .then((stream) => {
    myVideoStream = stream;
    addVideoStream(myVideo, stream);

    peer.on("call", (call) => {
      console.log('someone call me');
      call.answer(stream);
      const video = document.createElement("video");
      call.on("stream", (userVideoStream) => {
        addVideoStream(video, userVideoStream);
      });
    });

    socket.on("user-connected", (userId) => {
      connectToNewUser(userId, stream);
    });
  });

const connectToNewUser = (userId, stream) => {
  console.log('I call someone' + userId);
  const call = peer.call(userId, stream);
  const video = document.createElement("video");
  call.on("stream", (userVideoStream) => {
    addVideoStream(video, userVideoStream);
  });
};

peer.on("open", (id) => {
  console.log('my id is' + id);
  socket.emit("join-room", ROOM_ID, id, user);
});

const addVideoStream = (video, stream) => {
  video.srcObject = stream;
  video.addEventListener("loadedmetadata", () => {
    video.play();
    videoGrid.append(video);
  });
};

let text = document.querySelector("#chat_message");
let send = document.getElementById("send");
let messages = document.querySelector(".messages");

send.addEventListener("click", (e) => {
  if (text.value.length !== 0) {
    socket.emit("message", text.value);
    text.value = "";
  }
});

text.addEventListener("keydown", (e) => {
  if (e.key === "Enter" && text.value.length !== 0) {
    socket.emit("message", text.value);
    text.value = "";
  }
});

const inviteButton = document.querySelector("#inviteButton");
const muteButton = document.querySelector("#muteButton");
const stopVideo = document.querySelector("#stopVideo");
muteButton.addEventListener("click", () => {
  const enabled = myVideoStream.getAudioTracks()[0].enabled;
  if (enabled) {
    myVideoStream.getAudioTracks()[0].enabled = false;
    html = `<i class="fas fa-microphone-slash"></i>`;
    muteButton.classList.toggle("background__red");
    muteButton.innerHTML = html;
  } else {
    myVideoStream.getAudioTracks()[0].enabled = true;
    html = `<i class="fas fa-microphone"></i>`;
    muteButton.classList.toggle("background__red");
    muteButton.innerHTML = html;
  }
});

stopVideo.addEventListener("click", () => {
  const enabled = myVideoStream.getVideoTracks()[0].enabled;
  if (enabled) {
    myVideoStream.getVideoTracks()[0].enabled = false;
    html = `<i class="fas fa-video-slash"></i>`;
    stopVideo.classList.toggle("background__red");
    stopVideo.innerHTML = html;
  } else {
    myVideoStream.getVideoTracks()[0].enabled = true;
    html = `<i class="fas fa-video"></i>`;
    stopVideo.classList.toggle("background__red");
    stopVideo.innerHTML = html;
  }
});

inviteButton.addEventListener("click", (e) => {
  prompt(
    "Copy this link and send it to people you want to meet with",
    window.location.href
  );
});

socket.on("createMessage", (message, userName) => {
  messages.innerHTML =
    messages.innerHTML +
    `<div class="message">
        <b><i class="far fa-user-circle"></i> <span> ${userName === user ? "me" : userName
    }</span> </b>
        <span>${message}</span>
    </div>`;
});