WebRTC 란 무엇입니까?
WebRTC (Web Real-Time Communications)는 HTML5 사양으로, 타사 플러그인없이 브라우저간에 실시간으로 직접 통신 할 수 있습니다. WebRTC는 파일 공유와 같은 여러 작업에 사용될 수 있지만 실시간 피어 투 피어 오디오 및 비디오 통신은 분명히 주요 기능이므로 이 기사의 내용에 중점을 둘 것입니다.
WebRTC의 기능은 장치에 대한 액세스를 허용하는 것입니다. 마이크, 카메라를 사용하고 WebRTC의 도움으로 화면을 공유하고 실시간으로 모든 작업을 수행 할 수 있습니다! 가장 간단한 방법으로
https://tsh.io/blog/how-to-write-video-chat-app-using-webrtc-and-nodejs/
WebRTC를 사용하면 웹 페이지 내에서 오디오 및 비디오 통신을 수행 할 수 있습니다.
WebRTC JavaScript API
WebRTC는 많은 기술이 관련된 복잡한 주제입니다. 그러나 연결 설정, 통신 및 데이터 전송은 일련의 JS API를 통해 구현됩니다. 기본 API는 다음과 같습니다.
왜 Node.js인가?
둘 이상의 장치를 원격으로 연결하려면 서버가 필요합니다. 이 경우 실시간 통신을 처리하는 서버가 필요합니다. Node.js는 실시간으로 확장 가능한 응용 프로그램을 위해 제작되었다는 것을 알고 있습니다. 무료 데이터 교환이 가능한 양방향 연결 앱을 개발하려면 클라이언트와 서버 간의 통신 세션을 열 수 있는 WebSocket을 사용하는 것이 좋습니다. 클라이언트의 요청은 보다 정확하게 루프로 처리됩니다. 이벤트 루프는 Node.js가 요청을 처리하기 위해 "비 차단"접근 방식을 취하여 낮은 대기 시간과 높은 처리량을 달성하기 때문에 좋은 옵션으로 만듭니다.
데모 아이디어 : 여기서 무엇을 만들 예정입니까?
우리는 오디오 및 비디오를 기본 비디오 채팅 앱인 연결된 장치로 스트리밍 할 수 있는 매우 간단한 응용 프로그램을 만들 것입니다. 우리는 다음을 사용할 것입니다 :
화상 채팅 구현
가장 먼저 할 일은 응용 프로그램의 UI로 작동하는 HTML 파일을 제공하는 것입니다. npm init를 실행하여 새 node.js 프로젝트를 초기화하겠습니다. 그런 다음 npm i -D typescript ts-node nodemon @ types / express @ types / socket.io를 실행하여 몇 가지 dev 종속성을 설치하고 npm i express socket.io를 실행하여 프로덕션 종속성을 설치해야 합니다.
이제 package.json 파일에서 프로젝트를 실행할 스크립트를 정의 할 수 있습니다.
{ "scripts": { "start": "ts-node src/index.ts", "dev": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts" }, "devDependencies": { "@types/express": "^4.17.2", "@types/socket.io": "^2.1.4", "nodemon": "^1.19.4", "ts-node": "^8.4.1", "typescript": "^3.7.2" }, "dependencies": { "express": "^4.17.1", "socket.io": "^2.3.0" } }
npm run dev command를 실행하면 nodemon은 .ts 확장자로 끝나는 모든 파일에 대한 src 폴더의 변경 사항을 확인합니다. 이제 src 폴더를 만들고 이 폴더 안에 index.ts와 server.ts라는 두 가지 유형 스크립트 파일을 만듭니다.
server.ts 내부에서 서버 클래스를 만들고 express 및 socket.io와 작동하도록 합니다.
import express, { Application } from "express"; import socketIO, { Server as SocketIOServer } from "socket.io"; import { createServer, Server as HTTPServer } from "http"; export class Server { private httpServer: HTTPServer; private app: Application; private io: SocketIOServer; private readonly DEFAULT_PORT = 5000; constructor() { this.initialize(); this.handleRoutes(); this.handleSocketConnection(); } private initialize(): void { this.app = express(); this.httpServer = createServer(this.app); this.io = socketIO(this.httpServer); } private handleRoutes(): void { this.app.get("/", (req, res) => { res.send(`<h1>Hello World</h1>`); }); } private handleSocketConnection(): void { this.io.on("connection", socket => { console.log("Socket connected."); }); } public listen(callback: (port: number) => void): void { this.httpServer.listen(this.DEFAULT_PORT, () => callback(this.DEFAULT_PORT) ); } }
서버를 실행하려면 Server 클래스의 새 인스턴스를 작성하고 listen 메소드를 호출해야 합니다. index.ts 파일 내에서 작성합니다.
import { Server } from "./server"; const server = new Server(); server.listen(port => { console.log(`Server is listening on http://localhost:${port}`); });
이제 npm run dev를 실행하면 다음을 볼 수 있습니다.
브라우저를 열고 http : // localhost : 5000을 입력하면 "Hello World"메시지가 나타납니다.
이제 public / index.html 안에 새로운 HTML 파일을 만들 것입니다 :
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Dogeller</title> <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,500,700&display=swap" rel="stylesheet" /> <link rel="stylesheet" href="./styles.css" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js"></script> </head> <body> <div class="container"> <header class="header"> <div class="logo-container"> <img src="./img/doge.png" alt="doge logo" class="logo-img" /> <h1 class="logo-text"> Doge<span class="logo-highlight">ller</span> </h1> </div> </header> <div class="content-container"> <div class="active-users-panel" id="active-user-container"> <h3 class="panel-title">Active Users:</h3> </div> <div class="video-chat-container"> <h2 class="talk-info" id="talking-with-info"> Select active user on the left menu. </h2> <div class="video-container"> <video autoplay class="remote-video" id="remote-video"></video> <video autoplay muted class="local-video" id="local-video"></video> </div> </div> </div> </div> <script src="./scripts/index.js"></script> </body> </html>
이 파일에서 우리는 두 가지 비디오 요소를 선언했습니다. 하나는 원격 비디오 연결 용이고 다른 하나는 로컬 비디오 용입니다. 알다시피, 로컬 스크립트도 가져오고 있으므로 scripts라는 새 폴더를 만들고 이 디렉토리에 index.js 파일을 만듭니다. 스타일은 GitHub 리포지토리에서 다운로드 할 수 있습니다.
이제 index.html을 브라우저에 제공해야 합니다. 먼저, 어떤 정적 파일을 제공 할 것인지 명시해야 합니다. 이를 위해 Server 클래스 내에 새로운 메소드를 구현할 것입니다.
private configureApp(): void { this.app.use(express.static(path.join(__dirname, "../public"))); }
initialize 메소드 안에서 configureApp 메소드를 호출하는 것을 잊지 마십시오 :
private initialize(): void { this.app = express(); this.httpServer = createServer(this.app); this.io = socketIO(this.httpServer); this.configureApp(); this.handleSocketConnection(); }
이제 http : // localhost : 5000을 입력하면 index.html 파일이 작동하는 것을 볼 수 있습니다.
다음으로 구현하려는 것은 카메라 및 비디오 액세스이며 로컬 비디오 요소로 스트리밍 합니다. 이를 수행하려면 public / scripts / index.js 파일을 열고 다음을 사용하여 구현해야 합니다.
navigator.getUserMedia( { video: true, audio: true }, stream => { const localVideo = document.getElementById("local-video"); if (localVideo) { localVideo.srcObject = stream; } }, error => { console.warn(error.message); } );
브라우저로 돌아 가면 미디어 장치에 액세스하라는 메시지가 표시되며 이 프롬프트를 수락하면 카메라가 작동하는 것을 볼 수 있습니다!
소켓 연결을 처리하는 방법?
이제 소켓 연결 처리에 중점을 둘 것입니다. 클라이언트를 서버와 연결해야 하므로 socket.io를 사용합니다. public / scripts / index.js 안에 다음을 추가하십시오.
this.io.on("connection", socket => { const existingSocket = this.activeSockets.find( existingSocket => existingSocket === socket.id ); if (!existingSocket) { this.activeSockets.push(socket.id); socket.emit("update-user-list", { users: this.activeSockets.filter( existingSocket => existingSocket !== socket.id ) }); socket.broadcast.emit("update-user-list", { users: [socket.id] }); } }
페이지를 새로 고치면 터미널에 "소켓이 연결되었습니다"라는 메시지가 나타납니다.
이제 server.ts로 돌아가서 연결된 소켓을 메모리에 저장하여 고유 한 연결 만 유지합니다. 따라서 Server 클래스에서 새 개인 필드를 추가하십시오.
private activeSockets: string[] = [];
소켓 연결시 소켓이 이미 존재하는지 확인하십시오. 그렇지 않은 경우 새 소켓을 메모리에 넣고 연결된 사용자에게 데이터를 내 보냅니다.
this.io.on("connection", socket => { const existingSocket = this.activeSockets.find( existingSocket => existingSocket === socket.id ); if (!existingSocket) { this.activeSockets.push(socket.id); socket.emit("update-user-list", { users: this.activeSockets.filter( existingSocket => existingSocket !== socket.id ) }); socket.broadcast.emit("update-user-list", { users: [socket.id] }); } }
소켓 연결 해제에도 응답해야 하므로 소켓 연결 내부에 다음을 추가해야 합니다.
socket.on("disconnect", () => { this.activeSockets = this.activeSockets.filter( existingSocket => existingSocket !== socket.id ); socket.broadcast.emit("remove-user", { socketId: socket.id }); });
클라이언트 측 (public / scripts / index.js를 의미)에서 해당 메시지에 대해 올바른 동작을 구현해야 합니다.
socket.on("update-user-list", ({ users }) => { updateUserList(users); }); socket.on("remove-user", ({ socketId }) => { const elToRemove = document.getElementById(socketId); if (elToRemove) { elToRemove.remove(); } });
다음은 updateUserList 함수입니다.
function updateUserList(socketIds) { const activeUserContainer = document.getElementById("active-user-container"); socketIds.forEach(socketId => { const alreadyExistingUser = document.getElementById(socketId); if (!alreadyExistingUser) { const userContainerEl = createUserItemContainer(socketId); activeUserContainer.appendChild(userContainerEl); } }); }
그리고 createUserItemContainer 함수 :
function createUserItemContainer(socketId) { const userContainerEl = document.createElement("div"); const usernameEl = document.createElement("p"); userContainerEl.setAttribute("class", "active-user"); userContainerEl.setAttribute("id", socketId); usernameEl.setAttribute("class", "username"); usernameEl.innerHTML = `Socket: ${socketId}`; userContainerEl.appendChild(usernameEl); userContainerEl.addEventListener("click", () => { unselectUsersFromList(); userContainerEl.setAttribute("class", "active-user active-user--selected"); const talkingWithInfo = document.getElementById("talking-with-info"); talkingWithInfo.innerHTML = `Talking with: "Socket: ${socketId}"`; callUser(socketId); }); return userContainerEl; }
목록에서 활성 사용자를 클릭 한 후 callUser 함수를 호출하려고 합니다. 그러나 그것을 구현하기 전에 window 객체에서 두 개의 클래스를 선언해야 합니다.
const { RTCPeerConnection, RTCSessionDescription } = window;
callUser 함수에서 사용할 것입니다 :
async function callUser(socketId) { const offer = await peerConnection.createOffer(); await peerConnection.setLocalDescription(new RTCSessionDescription(offer)); socket.emit("call-user", { offer, to: socketId }); }
여기에서 로컬 오퍼를 작성하고 선택된 사용자에게 보냅니다. 서버는 호출 사용자라는 이벤트를 수신하고 오퍼를 인터셉트하여 선택된 사용자에게 전달합니다. server.ts에서 구현해 봅시다 :
socket.on("call-user", data => { socket.to(data.to).emit("call-made", { offer: data.offer, socket: socket.id }); });
이제 클라이언트 쪽에서 call-made event에 반응해야 합니다.
socket.on("call-made", async data => { await peerConnection.setRemoteDescription( new RTCSessionDescription(data.offer) ); const answer = await peerConnection.createAnswer(); await peerConnection.setLocalDescription(new RTCSessionDescription(answer)); socket.emit("make-answer", { answer, to: data.socket }); });
그런 다음 서버에서 제공 한 오퍼에 대한 원격 설명을 설정하고 이 오퍼에 대한 답변을 작성하십시오. 서버 측에서는 선택한 사용자에게 적절한 데이터를 전달하기 만하면 됩니다. server.ts 내부에 다른 리스너를 추가하겠습니다.
socket.on("make-answer", data => { socket.to(data.to).emit("answer-made", { socket: socket.id, answer: data.answer }); });
고객의 입장에서는 다음과 같은 정답 이벤트를 처리해야 합니다.
socket.on("answer-made", async data => { await peerConnection.setRemoteDescription( new RTCSessionDescription(data.answer) ); if (!isAlreadyCalling) { callUser(data.socket); isAlreadyCalling = true; } });
유용한 플래그 인 isAlreadyCalling을 사용하여 사용자에게 한 번만 전화하도록 합니다.
마지막으로 해야 할 일은 로컬 트랙 (오디오 및 비디오를 피어 연결에 추가하는 것)입니다. 덕분에 연결된 사용자와 비디오 및 오디오를 공유 할 수 있습니다. 이를 위해 navigator.getMediaDevice 콜백에서 peerConnection 객체에서 addTrack 함수를 호출해야 합니다.
navigator.getUserMedia( { video: true, audio: true }, stream => { const localVideo = document.getElementById("local-video"); if (localVideo) { localVideo.srcObject = stream; } stream.getTracks().forEach(track => peerConnection.addTrack(track, stream)); }, error => { console.warn(error.message); } );
그리고 ontrack 이벤트를 위한 적절한 핸들러를 추가해야 합니다 :
peerConnection.ontrack = function({ streams: [stream] }) { const remoteVideo = document.getElementById("remote-video"); if (remoteVideo) { remoteVideo.srcObject = stream; } };
보시다시피, 전달 된 객체에서 스트림을 가져오고 수신 된 스트림을 사용하도록 원격 비디오에서 srcObject를 변경했습니다. 이제 활성 사용자를 클릭 한 후 다음과 같이 비디오 및 오디오 연결을 만들어야 합니다.
이제 화상 채팅 앱을 작성하는 방법을 알게 되었습니다!
WebRTC는 방대한 주제입니다. 특히 후드에서 어떻게 작동하는지 알고 싶은 경우에 특히 그렇습니다. 다행히도 사용하기 쉬운 JavaScript API에 액세스 할 수 있습니다. 예를 들어 매우 깔끔한 앱을 만들 수 있습니다. 비디오 공유, 채팅 응용 프로그램 및 훨씬 더!
등록된 댓글이 없습니다.