Xây dựng web call video cơ bản với WebRTC [P2]

5 min read

Trong phần 1 của series xây dựng tính năng call video, chúng ta đã thiết lập dự án Next.js và tích hợp WebRTC cơ bản để hiển thị video call giữa hai người dùng. Tuy nhiên, để WebRTC hoạt động, hai người dùng cần trao đổi thông tin kết nối với nhau thông qua một thành phần quan trọng: signaling server.

Trong bài viết này, chúng ta sẽ tìm hiểu cách xây dựng signaling server bằng Socket.io và cách trao đổi các thông tin kết nối (SDP, ICE Candidates) để hoàn thiện tính năng kết nối video call giữa hai người dùng.


1. Giới thiệu về Signaling Server

Signaling server là thành phần trung gian dùng để trao đổi các thông điệp điều khiển giữa hai người dùng trước khi kết nối trực tiếp qua WebRTC. Những thông tin được trao đổi qua signaling server bao gồm:

  • SDP (Session Description Protocol): Thông tin mô tả về kết nối giữa hai thiết bị, bao gồm thông tin về codec video, âm thanh, địa chỉ IP, và các thông số khác.
  • ICE Candidates: Các địa chỉ IP và cổng kết nối có thể sử dụng để thiết lập đường truyền giữa các người dùng.

WebRTC tự thân không cung cấp cơ chế signaling, do đó chúng ta cần tạo một server để trao đổi thông tin này giữa các client.

2. Cài đặt Signaling Server với Socket.io

Trong phần này, chúng ta sẽ xây dựng một signaling server đơn giản bằng Node.js và Socket.io. Server này sẽ lắng nghe các yêu cầu kết nối từ client và chuyển tiếp thông tin giữa các peer.

2.1. Khởi tạo signaling server

Tạo một thư mục mới cho signaling server và cài đặt các gói cần thiết:

mkdir signaling-server
cd signaling-server
npm init -y
npm install express socket.io

Sau khi cài đặt hoàn tất, tạo một file server.js và thêm mã sau:

const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = new Server(server);

io.on('connection', (socket) => {
  console.log('New user connected:', socket.id);

  // Nhận tín hiệu và gửi đến người dùng khác
  socket.on('call-user', (data) => {
    console.log('Call user event:', data);
    socket.broadcast.emit('call-made', data);
  });

  socket.on('answer-call', (data) => {
    socket.broadcast.emit('call-answered', data);
  });

  socket.on('disconnect', () => {
    console.log('User disconnected:', socket.id);
  });
});

server.listen(5000, () => {
  console.log('Signaling server is running on port 5000');
});

2.2. Giải thích mã nguồn

  • Express: Được sử dụng để tạo server cơ bản. Tuy nhiên, vai trò chính của server này là sử dụng Socket.io để xử lý kết nối thời gian thực.
  • Socket.io: Kết nối giữa các client thông qua WebSocket, một giao thức truyền tải dữ liệu thời gian thực. Ở đây, server lắng nghe sự kiện từ client và phát tán thông tin kết nối đến các peer.

Trong signaling server này, chúng ta xử lý hai sự kiện chính:

  • call-user: Được phát ra từ client khi người dùng bắt đầu gọi video.
  • answer-call: Được phát ra khi người dùng nhận cuộc gọi và trả lời.

3. Tích hợp Signaling Server vào Client

Tiếp theo, chúng ta cần tích hợp signaling server vào ứng dụng client của mình để thực hiện việc trao đổi thông tin giữa các peer.

3.1. Cập nhật component VideoCall.js

Trong file VideoCall.js, chúng ta sẽ điều chỉnh mã để client có thể gửi và nhận thông tin từ signaling server.

import React, { useRef, useEffect, useState } from 'react';
import Peer from 'simple-peer';
import io from 'socket.io-client';

const socket = io('http://localhost:5000'); // Kết nối đến signaling server

export default function VideoCall() {
  const [stream, setStream] = useState(null);
  const [peer, setPeer] = useState(null);
  const userVideo = useRef();
  const partnerVideo = useRef();

  useEffect(() => {
    // Lấy luồng video/âm thanh từ camera
    navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then((stream) => {
      setStream(stream);
      if (userVideo.current) {
        userVideo.current.srcObject = stream;
      }
    });

    socket.on('call-made', (signal) => {
      const newPeer = new Peer({ initiator: false, trickle: false, stream });
      newPeer.signal(signal);
      newPeer.on('stream', (stream) => {
        partnerVideo.current.srcObject = stream;
      });
      setPeer(newPeer);
    });

    socket.on('call-answered', (signal) => {
      peer.signal(signal);
    });
  }, [peer]);

  const initiateCall = () => {
    const newPeer = new Peer({ initiator: true, trickle: false, stream });
    newPeer.on('signal', (signal) => {
      socket.emit('call-user', signal);
    });
    newPeer.on('stream', (stream) => {
      partnerVideo.current.srcObject = stream;
    });
    setPeer(newPeer);
  };

  const answerCall = () => {
    peer.on('signal', (signal) => {
      socket.emit('answer-call', signal);
    });
  };

  return (
    <div>
      <div>
        <h2>Your Video</h2>
        <video playsInline muted ref={userVideo} autoPlay style={{ width: '300px' }} />
      </div>
      <div>
        <h2>Partner's Video</h2>
        <video playsInline ref={partnerVideo} autoPlay style={{ width: '300px' }} />
      </div>
      <button onClick={initiateCall}>Gọi video</button>
      <button onClick={answerCall}>Trả lời cuộc gọi</button>
    </div>
  );
}

3.2. Giải thích mã nguồn

  • socket.emit(‘call-user’): Khi người dùng khởi tạo cuộc gọi, thông tin kết nối sẽ được gửi đến signaling server.
  • socket.on(‘call-made’): Khi server phát tín hiệu đến người nhận, thông tin kết nối từ peer khác sẽ được nhận và sử dụng để thiết lập kết nối WebRTC.
  • socket.emit(‘answer-call’): Khi người dùng nhấn trả lời cuộc gọi, client sẽ gửi tín hiệu ngược lại đến server, hoàn tất quá trình kết nối.

4. Xử lý các sự kiện mạng

Một phần quan trọng trong quá trình kết nối qua WebRTC là xử lý các sự kiện về mạng, ví dụ như mất kết nối hoặc thay đổi đường truyền. Bạn có thể mở rộng logic trong signaling server để gửi các thông báo khi một peer bị mất kết nối hoặc khi cần khởi tạo lại kết nối mới.

5. Kết luận

Chúng ta đã hoàn thiện việc tích hợp signaling server vào ứng dụng và có thể trao đổi thông tin giữa hai người dùng để thiết lập kết nối WebRTC. Bài viết tiếp theo sẽ tiếp tục với việc thêm các tính năng nâng cao như chia sẻ màn hình, bật/tắt camera, và quản lý trạng thái kết nối trong ứng dụng.

Đón xem phần 3 để tối ưu và bổ sung các tính năng cần thiết cho một ứng dụng video call hoàn chỉnh nhé!

Avatar photo

Leave a Reply

Your email address will not be published. Required fields are marked *