by @thanbv1510
Mục lục
1. Giới thiệu về WebRTC
WebRTC là gì?
WebRTC (Web Real-Time Communication) là một công nghệ mã nguồn mở cho phép truyền thông theo thời gian thực (real-time) giữa các trình duyệt web và ứng dụng di động thông qua giao thức peer-to-peer (P2P), không cần cài đặt plugin hay phần mềm bổ sung.
Kiến trúc WebRTC bao gồm các thành phần chính sau:
- Signaling Server
- Đóng vai trò trung gian kết nối giữa các peer
- Hỗ trợ trao đổi thông tin về kết nối như địa chỉ IP, ports, và khả năng media
- Không trực tiếp xử lý dữ liệu media
- NAT (Network Address Translation)
- Chuyển đổi địa chỉ IP private thành public để có thể giao tiếp qua Internet
- Là một trong những thách thức chính trong việc thiết lập kết nối P2P
- STUN/TURN Server
- STUN (Session Traversal Utilities for NAT): Giúp peer xác định địa chỉ IP public và loại NAT
- TURN (Traversal Using Relays around NAT): Làm relay server khi không thể thiết lập kết nối P2P trực tiếp
- Peers (End-points)
- Các thiết bị đầu cuối (máy tính, điện thoại) tham gia vào cuộc gọi
- Trực tiếp trao đổi media streams (audio, video, data) qua kết nối P2P
Các thành phần kỹ thuật chính của WebRTC
- MediaStream (getUserMedia)
- Cho phép truy cập vào camera và microphone của thiết bị
- Tạo luồng media (audio/video) để truyền tải
- Hỗ trợ các constraints để cấu hình chất lượng media
- RTCPeerConnection
- Xử lý việc thiết lập kết nối P2P giữa các clients
- Quản lý việc truyền tải media streams
- Xử lý mã hóa và giải mã dữ liệu
- Quản lý băng thông và chất lượng kết nối
- RTCDataChannel
- Cho phép truyền tải dữ liệu tùy ý giữa các peers
- Hỗ trợ cả reliable và unreliable data transmission
- Thích hợp cho chat, file sharing, game data, etc.
2. Sequence Diagram
3. Hướng dẫn code chi tiết
3.1 Cấu trúc project
Đầu tiên, chúng ta sẽ tạo một project Spring boot 3 với 2 depencency là Spring Web
và WebSocket
src/
├── main/
│ ├── java/
│ │ └── com/roninhub/webrtc/
│ │ ├── WebrtcApplication.java
│ │ ├── WebSocketConfiguration.java
│ │ └── SocketHandler.java
│ ├── resources/
│ │ └── static/
│ │ ├── index.html
│ │ └── main.js
3.2 Backend với Spring Boot
WebrtcApplication.java
@SpringBootApplication
public class WebrtcApplication {
public static void main(String[] args) {
SpringApplication.run(WebrtcApplication.class, args);
}
}
WebSocketConfiguration.java
@Configuration
@EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new SocketHandler(), "/socket")
.setAllowedOrigins("*");
}
}
Mục đích của file WebSocketConfiguration.java
là:
@EnableWebSocket
: Kích hoạt hỗ trợ WebSocket trong ứng dụngregisterWebSocketHandlers
: Đăng ký endpoint "/socket" và cho phép CORS
SocketHandler.java
@Component
public class SocketHandler extends TextWebSocketHandler {
List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message)
throws InterruptedException, IOException {
for (WebSocketSession webSocketSession : sessions) {
if (webSocketSession.isOpen() && !session.getId().equals(webSocketSession.getId())) {
webSocketSession.sendMessage(message);
}
}
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
}
}
Trong file SocketHandler.java
sẽ thực hiện các nhiệm vụ:
- Quản lý danh sách các WebSocket sessions
- Xử lý các message và chuyển tiếp tới các clients khác
- Thêm session mới khi có kết nối được thiết lập
3.3 Frontend với HTML và JavaScript
index.html
<!DOCTYPE html>
<html>
<head>
<title>WebRTC Video Call</title>
<style>
video {
width: 300px;
height: 200px;
margin: 10px;
background: #ddd;
}
</style>
</head>
<body>
<h1>WebRTC Video Call</h1>
<div>
<h3>Remote video</h3>
<video id="remoteVideo" autoplay playsinline></video>
</div>
<div>
<h3>Local video</h3>
<video id="localVideo" autoplay playsinline></video>
</div>
<button onclick="makeCall()">Make Call</button>
<script src="main.js"></script>
</body>
</html>
main.js
const websocket = new WebSocket('ws://localhost:8080/socket');
let peerConnection;
let localStream;
let remoteStream;
const localVideo = document.getElementById('localVideo');
const remoteVideo = document.getElementById('remoteVideo');
websocket.onopen = function () {
const configuration = null;
peerConnection = new RTCPeerConnection(configuration);
const constraints = {audio: true, video: {width: 100, height: 70}};
navigator.mediaDevices.getUserMedia(constraints)
.then(stream => {
localStream = stream;
localVideo.srcObject = stream;
stream.getTracks().forEach(track => {
peerConnection.addTrack(track, stream);
});
localVideo.onloadedmetadata = () => {
localVideo.play();
};
})
.catch(error => {
console.error('Error accessing media devices.', error);
});
peerConnection.onicecandidate = function (event) {
if (event.candidate) {
send({
event: "candidate",
data: event.candidate
});
}
};
peerConnection.ontrack = function (event) {
if (!remoteStream) {
remoteStream = new MediaStream();
remoteVideo.srcObject = remoteStream;
}
remoteStream.addTrack(event.track);
remoteVideo.onloadedmetadata = () => {
remoteVideo.play();
};
};
};
websocket.onmessage = function (msg) {
const content = JSON.parse(msg.data);
const data = content.data;
switch (content.event) {
case "offer":
handleOffer(data);
break;
case "answer":
handleAnswer(data);
break;
case "candidate":
handleCandidate(data);
break;
default:
break;
}
};
function handleOffer(offer) {
peerConnection.setRemoteDescription(new RTCSessionDescription(offer))
.then(() => peerConnection.createAnswer())
.then(answer => peerConnection.setLocalDescription(answer))
.then(() => send({event: "answer", data: peerConnection.localDescription}))
.catch(error => console.error('Error handling offer:', error));
}
function handleAnswer(answer) {
peerConnection.setRemoteDescription(new RTCSessionDescription(answer))
.then(() => console.log("Connection established successfully!"));
}
function handleCandidate(candidate) {
peerConnection.addIceCandidate(new RTCIceCandidate(candidate))
.then(() => console.log("Candidate added successfully!"));
}
function send(message) {
websocket.send(JSON.stringify(message));
}
function makeCall() {
peerConnection.createOffer()
.then(offer => peerConnection.setLocalDescription(offer))
.then(() => send({event: "offer", data: peerConnection.localDescription}))
.catch(error => console.error('Error creating offer:', error));
}
Giải thích code:
- Khởi tạo WebSocket connection tới server
- Thiết lập RTCPeerConnection và lấy media stream từ camera/mic
- Xử lý các events:
onicecandidate
: Gửi ICE candidates tới peer khácontrack
: Xử lý media stream nhận được từ peer khác
- Xử lý signaling:
handleOffer
: Xử lý offer nhận đượchandleAnswer
: Xử lý answer nhận đượchandleCandidate
: Xử lý ICE candidate nhận được
- Function
makeCall()
: Tạo offer để bắt đầu cuộc gọi
4. Demo
5. Tổng kết và hướng phát triển
Bài viết này giúp các bạn xây dựng được ứng dụng gọi video cơ bản với WebRTC bằng cách sử dụng Spring Boot làm signaling server, thực hiện kết nối P2P giữa hai clients và truyền tải luồng audio/video. Các bạn có thể tiếp tục phát triển thêm các tính năng nâng cao như:
- Quản lý phòng (Room Management)
- Tạo và quản lý các phòng chat
- Cho phép nhiều người tham gia cùng một phòng
- Bảo mật
- Thêm xác thực người dùng
- Mã hóa đầu cuối (end-to-end encryption)
- Quản lý phiên đăng nhập
- Tính năng chat
- Chat text song song với video call
- Chia sẻ file
- Emoji và stickers
- Xử lý lỗi và phục hồi
- Tự động kết nối lại khi mất kết nối
- Xử lý các trường hợp lỗi
- Tính năng nâng cao
- Chia sẻ màn hình
- Ghi âm/video
</> Code tham khảo: https://github.com/ronin-engineer-88/webrtc-demo
✏️ System Design VN: https://fb.com/groups/systemdesign.vn
📚 Đọc thêm tài liệu khác: https://roninhub.com/tai-lieu
🎬 Youtube: https://youtube.com/@ronin-engineer