当前位置: 首页 > news >正文

一对一 WebRTC 视频聊天

“最小可运行”的一对一 WebRTC 视频聊天示例(前端+信令服务器),完全按照你给出的 8 条信令流程图实现。
技术栈:

  • 前端:原生 JavaScript + socket.io-client
  • 信令服务器:Node.js + socket.io + Express(80 端口同时提供静态文件和信令)
  • STUN/TURN:Google 公共 STUN 即可,生产环境请换成自己的 TURN

代码可直接复制到同一目录运行,局域网两台浏览器打开 http://localhost 即可互拨。


  1. 目录结构
    webrtc-1v1/
    ├─ server.js // 信令服务器
    ├─ public/
    │ ├─ index.html // 页面
    │ └─ client.js // 前端逻辑
    └─ package.json

  1. 安装依赖
npm init -y
npm i express socket.io

  1. 信令服务器 server.js
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const path = require('path');const app = express();
const server = http.createServer(app);
const io = socketIo(server, { cors: { origin: '*' } });// 房间 => Set<socketId>
const rooms = new Map();app.use(express.static(path.join(__dirname, 'public')));io.on('connection', socket => {console.log('connect', socket.id);/* 1. join 加入房间 */socket.on('join', room => {socket.join(room);socket.room = room;const others = rooms.get(room) || new Set();if (others.size >= 1) {// 2. resp-join 返回另一个人的 uidsocket.emit('resp-join', [...others][0]);}others.add(socket.id);rooms.set(room, others);// 4. new-peer 通知房间里其他人socket.to(room).emit('new-peer', socket.id);});/* 3. leave 离开房间 */socket.on('leave', () => leaveRoom(socket));socket.on('disconnect', () => leaveRoom(socket));/* 6. offer 转发 */socket.on('offer', ({ target, sdp }) => {socket.to(target).emit('offer', { from: socket.id, sdp });});/* 7. answer 转发 */socket.on('answer', ({ target, sdp }) => {socket.to(target).emit('answer', { from: socket.id, sdp });});/* 8. candidate 转发 */socket.on('candidate', ({ target, candidate }) => {socket.to(target).emit('candidate', { from: socket.id, candidate });});function leaveRoom(sock) {if (!sock.room) return;const room = sock.room;const others = rooms.get(room);if (others) {others.delete(sock.id);if (others.size === 0) rooms.delete(room);else {// 5. peer-leave 通知剩余的人sock.to(room).emit('peer-leave', sock.id);}}sock.leave(room);delete sock.room;}
});server.listen(80, () => console.log('HTTP+Socket.io on 80'));

  1. 前端 public/index.html
<!doctype html>
<html>
<head><meta charset="utf-8"/><title>WebRTC 1v1</title><style>video{width:240px;height:180px;background:#000;margin:6px}</style>
</head>
<body><h3>WebRTC 一对一</h3><input id="room" placeholder="房间名"/><button id="btnJoin">加入</button><button id="btnLeave" disabled>离开</button><br/><video id="local" autoplay muted></video><video id="remote" autoplay></video><script src="/socket.io/socket.io.js"></script><script src="client.js"></script>
</body>
</html>

  1. 前端 public/client.js
const localVideo = document.getElementById('local');
const remoteVideo = document.getElementById('remote');
const roomInput = document.getElementById('room');
const btnJoin = document.getElementById('btnJoin');
const btnLeave = document.getElementById('btnLeave');let localStream;
let pc;                 // RTCPeerConnection
let socket;
let roomName;
let otherId = null;     // 对端 socket.idbtnJoin.onclick = async () => {roomName = roomInput.value.trim();if (!roomName) return;socket = io();/* 1. join */socket.emit('join', roomName);/* 2. resp-join:如果房间已有人,立即发起 offer */socket.on('resp-join', async uid => {otherId = uid;await createPeerConnection();const offer = await pc.createOffer();await pc.setLocalDescription(offer);socket.emit('offer', { target: otherId, sdp: offer });});/* 4. new-peer:有人后进来,我作为“被叫” */socket.on('new-peer', async uid => {otherId = uid;await createPeerConnection();});/* 5. peer-leave:对方离开 */socket.on('peer-leave', () => {closeCall();});/* 6. offer */socket.on('offer', async ({ from, sdp }) => {if (!pc) await createPeerConnection();await pc.setRemoteDescription(sdp);const answer = await pc.createAnswer();await pc.setLocalDescription(answer);socket.emit('answer', { target: from, sdp: answer });});/* 7. answer */socket.on('answer', async ({ from, sdp }) => {await pc.setRemoteDescription(sdp);});/* 8. candidate */socket.on('candidate', async ({ from, candidate }) => {await pc.addIceCandidate(candidate);});socket.on('connect', async () => {localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });localVideo.srcObject = localStream;});btnJoin.disabled = true;btnLeave.disabled = false;
};btnLeave.onclick = () => {if (socket) {socket.emit('leave');socket.close();}closeCall();btnJoin.disabled = false;btnLeave.disabled = true;
};async function createPeerConnection() {pc = new RTCPeerConnection({iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]});localStream.getTracks().forEach(t => pc.addTrack(t, localStream));pc.ontrack = e => {remoteVideo.srcObject = e.streams[0];};pc.onicecandidate = e => {if (e.candidate && otherId) {socket.emit('candidate', { target: otherId, candidate: e.candidate });}};
}function closeCall() {if (pc) {pc.close();pc = null;}remoteVideo.srcObject = null;otherId = null;
}

  1. 运行
sudo node server.js   # 监听 80 端口需 sudo

浏览器打开 http://localhost,A/B 两台机器输入相同房间名即可互拨。
控制台可看到 8 条信令按顺序打印,完全符合题目流程图。


  1. 生产环境注意
  2. 把 Google STUN 换成自建 TURN(coturn),否则 NAT 穿墙失败率很高。
  3. 信令服务器务必加 HTTPS/WSS,否则最新浏览器禁止 getUserMedia。
  4. 增加异常处理、断线重连、摄像头/麦克风权限错误提示。
  5. 若要做移动端,需加 playsinline、自动播放策略处理。
http://www.jsqmd.com/news/42116/

相关文章:

  • 2025年11月载冷剂厂家推荐榜:五强真实数据与场景化选型指南
  • 2025年11月载冷剂厂家榜单:性能参数与口碑综合评测
  • 20232313 2025-2026-1 《网络与系统攻防技术》实验五实验报告 - 20232313
  • 【第7章 I/O编程与异常】Python文件操作与上下文管理器的深度解析(避坑指南)
  • 2025年11月乙二醇厂家对比榜:五家主流厂商真实数据与选型要点
  • 2025年11月乙二醇厂家对比榜:五强产品性能与合规资质全盘点
  • 工业级时序数据库选型指南:技巧架构与场景化实践
  • springboot生成前后端接口文档 - f
  • 20232429 2025-2026-1 《网络与系统攻防技术》实验五实验报告
  • P5797 [SEERC 2019] Max or Min
  • make
  • Spring Cloud - Spring Cloud 注册中心与服务提供者(Spring Cloud Eureka 概述、微服务高效入门、微服务应用实例)
  • DateUtil
  • (链表)找单链表倒数第k个结点
  • (链表)判断是否回文
  • 和为定值的子集数 25-11-16
  • (链表)判断两个单链表是否存在交点
  • (链表)逆置
  • (链表)任意删除一个结点
  • 在抖音直播推广开源作品的可行性?
  • 分布式监控体系:从指标采集到智能告警的完整之道 - 实践
  • hot 100 (1)—— 两数之和(哈希) - 指南
  • DLSS Swapper商业模式:开源软件商业化探索 - 指南
  • 性能优化体系化建设:BI平台的深度优化实践
  • AT_jsc2019_qual_e Card Collector题解
  • 20251115ACC
  • Day40(10)-F:\硕士阶段\Java\课程代码\后端\web-ai-code\web-ai-project01\springboot-mybatis-quickstart
  • 还能回到原先吗 绞尽脑汁翻阅文献 这名为爱的实验 被等号连接
  • irm steam.work|iex 风险分析
  • 2025年11月手动旗杆厂家口碑推荐榜单及选购指南