JS实现视频通话主要基于WebRTC技术,通过浏览器原生的音视频采集能力和点对点传输协议,无需安装额外插件即可完成两端的实时音视频通信。整个实现过程需要前端完成音视频采集、连接建立,同时需要后端信令服务器辅助交换连接信息。

核心前置知识
实现JS视频通话需要了解几个核心概念和API:
- WebRTC:浏览器内置的实时通信标准,支持音视频采集、点对点传输、数据通道等功能。
- getUserMedia:浏览器提供的媒体设备采集API,用于获取本地摄像头、麦克风的音视频流。
- RTCPeerConnection:WebRTC核心接口,用于创建和管理点对点的连接,处理音视频流的传输。
- 信令服务器:用于交换两端的连接信息(如SDP、ICE候选),常用的实现方案有Socket.IO、WebSocket等。
环境准备
首先需要准备两部分环境:
1. 前端环境
现代浏览器(Chrome、Firefox、Edge等)都原生支持WebRTC相关API,无需额外引入第三方库,只需要保证页面在HTTPS环境下运行(本地localhost环境除外),否则浏览器会禁止获取媒体设备权限。
2. 信令服务器
这里选择Node.js搭配Socket.IO实现简单的信令服务器,先初始化项目并安装依赖:
# 初始化项目 npm init -y # 安装依赖 npm install express socket.io
信令服务器实现
信令服务器的核心作用是转发两端的连接信息,代码实现如下:
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, {
cors: {
origin: '*',
methods: ['GET', 'POST']
}
});
// 存储房间内的用户
const rooms = {};
io.on('connection', (socket) => {
console.log('新用户连接:', socket.id);
// 用户加入房间
socket.on('join_room', (roomId) => {
if (!rooms[roomId]) {
rooms[roomId] = [];
}
rooms[roomId].push(socket.id);
socket.join(roomId);
// 通知房间内其他用户有新用户加入
socket.to(roomId).emit('user_joined', socket.id);
console.log(`用户${socket.id}加入房间${roomId}`);
});
// 转发offer
socket.on('offer', (data) => {
socket.to(data.roomId).emit('offer', {
offer: data.offer,
senderId: socket.id
});
});
// 转发answer
socket.on('answer', (data) => {
socket.to(data.roomId).emit('answer', {
answer: data.answer,
senderId: socket.id
});
});
// 转发ICE候选
socket.on('ice_candidate', (data) => {
socket.to(data.roomId).emit('ice_candidate', {
candidate: data.candidate,
senderId: socket.id
});
});
// 用户断开连接
socket.on('disconnect', () => {
for (const roomId in rooms) {
const index = rooms[roomId].indexOf(socket.id);
if (index !== -1) {
rooms[roomId].splice(index, 1);
if (rooms[roomId].length === 0) {
delete rooms[roomId];
}
// 通知房间内其他用户有用户离开
socket.to(roomId).emit('user_left', socket.id);
}
}
console.log('用户断开连接:', socket.id);
});
});
server.listen(3000, () => {
console.log('信令服务器运行在 http://localhost:3000');
});
前端页面实现
前端页面需要包含本地视频预览、远端视频展示区域,以及加入房间的操作按钮,HTML结构如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS视频通话</title>
<style>
.video-container {
display: flex;
gap: 20px;
margin-top: 20px;
}
video {
width: 400px;
height: 300px;
border: 1px solid #ccc;
background-color: #000;
}
.controls {
margin-top: 20px;
}
input, button {
padding: 8px 16px;
margin-right: 10px;
}
</style>
</head>
<body>
<h1>JS视频通话示例</h1>
<div class="controls">
<input type="text" id="roomId" placeholder="输入房间号">
<button id="joinBtn">加入房间</button>
</div>
<div class="video-container">
<div>
<h3>本地视频</h3>
<video id="localVideo" autoplay muted playsinline></video>
</div>
<div>
<h3>远端视频</h3>
<video id="remoteVideo" autoplay playsinline></video>
</div>
</div>
<!-- 引入Socket.IO客户端 -->
<script src="https://ipipp.com/socket.io/socket.io.js"></script>
<script src="client.js"></script>
</body>
</html>
前端核心逻辑实现
前端核心逻辑放在client.js文件中,主要步骤如下:
1. 初始化变量和连接信令服务器
// 获取DOM元素
const localVideo = document.getElementById('localVideo');
const remoteVideo = document.getElementById('remoteVideo');
const roomIdInput = document.getElementById('roomId');
const joinBtn = document.getElementById('joinBtn');
// 连接信令服务器
const socket = io('http://localhost:3000');
// 本地音视频流
let localStream = null;
// 点对点连接实例
let peerConnection = null;
// 当前房间号
let currentRoomId = null;
// ICE服务器配置,用于穿透NAT
const iceServers = [
{ urls: 'stun:stun.l.google.com:19302' }
];
2. 获取本地音视频流
点击加入房间按钮时,先获取本地摄像头和麦克风的权限,得到音视频流后赋值给本地视频元素:
joinBtn.addEventListener('click', async () => {
const roomId = roomIdInput.value.trim();
if (!roomId) {
alert('请输入房间号');
return;
}
currentRoomId = roomId;
try {
// 获取本地音视频流,要求视频和音频
localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
// 将本地流赋值给本地视频元素
localVideo.srcObject = localStream;
// 加入房间
socket.emit('join_room', roomId);
} catch (err) {
console.error('获取媒体设备失败:', err);
alert('获取摄像头或麦克风权限失败,请检查设备权限设置');
}
});
3. 处理用户加入和连接建立
当房间内已有其他用户时,新加入的用户需要创建offer发送给对方,已有用户则响应answer:
// 监听有新用户加入
socket.on('user_joined', async (userId) => {
console.log('有新用户加入:', userId);
// 创建点对点连接
createPeerConnection();
// 将本地流添加到连接中
localStream.getTracks().forEach(track => {
peerConnection.addTrack(track, localStream);
});
// 创建offer
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
// 发送offer给新加入的用户
socket.emit('offer', {
roomId: currentRoomId,
offer: offer
});
});
// 监听收到的offer
socket.on('offer', async (data) => {
console.log('收到offer:', data);
createPeerConnection();
// 将本地流添加到连接中
localStream.getTracks().forEach(track => {
peerConnection.addTrack(track, localStream);
});
// 设置远端描述
await peerConnection.setRemoteDescription(new RTCSessionDescription(data.offer));
// 创建answer
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
// 发送answer给offer发送者
socket.emit('answer', {
roomId: currentRoomId,
answer: answer
});
});
// 监听收到的answer
socket.on('answer', async (data) => {
console.log('收到answer:', data);
if (peerConnection) {
await peerConnection.setRemoteDescription(new RTCSessionDescription(data.answer));
}
});
4. 处理ICE候选和远端流
ICE候选用于协商两端的网络地址,远端流到达后赋值给远端视频元素:
// 创建点对点连接的函数
function createPeerConnection() {
peerConnection = new RTCPeerConnection({ iceServers });
// 监听ICE候选
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
socket.emit('ice_candidate', {
roomId: currentRoomId,
candidate: event.candidate
});
}
};
// 监听远端流到达
peerConnection.ontrack = (event) => {
remoteVideo.srcObject = event.streams[0];
};
}
// 监听收到的ICE候选
socket.on('ice_candidate', async (data) => {
console.log('收到ICE候选:', data);
if (peerConnection) {
await peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
}
});
// 监听用户离开
socket.on('user_left', (userId) => {
console.log('用户离开:', userId);
if (remoteVideo.srcObject) {
remoteVideo.srcObject = null;
}
if (peerConnection) {
peerConnection.close();
peerConnection = null;
}
});
测试流程
完成上述代码后,测试步骤如下:
- 启动信令服务器:在服务器目录执行
node index.js,看到运行提示说明服务器启动成功。 - 打开两个浏览器窗口(或不同浏览器),访问前端页面(如果是本地文件,需要用本地服务运行,比如用
live-server等工具)。 - 两个窗口输入相同的房间号,依次点击加入房间按钮。
- 允许浏览器获取摄像头和麦克风权限后,即可看到本地和远端的视频画面,实现视频通话。
常见问题处理
- 如果无法获取媒体设备,检查页面是否在HTTPS环境(localhost除外),以及浏览器是否授予了摄像头和麦克风权限。
- 如果无法建立连接,检查信令服务器是否正常运行,前端连接的服务器地址是否正确。
- 如果视频卡顿,可尝试更换更稳定的ICE服务器,或降低视频分辨率,在
getUserMedia的参数中配置video: { width: 640, height: 480 }即可。
WebRTCgetUserMediaRTCPeerConnectionSocket_IO修改时间:2026-06-21 22:03:52