WebRTC 实战:用即构 SDK 搭建 Web 端 1v1 视频通话(含完整流程与 Demo)

随着 WebRTC 技术的成熟,实时音视频已经从"原生 App 的专属能力",慢慢变成 Web 端的常见能力:在线教育小班课、视频客服、在线面试、远程问诊......很多业务都希望做到 "用户打开一个网页,就能直接视频通话",无需下载任何客户端。

如果完全基于原生 WebRTC 自己搭建一套系统,往往会遇到大量复杂问题:SDP 信令流程、ICE/候选收集、STUN/TURN 服务器部署、弱网重传策略、音视频编解码兼容等等,这些对普通前端或业务开发来说门槛不低。

因此,越来越多团队会选择使用商业级实时音视频服务,例如即构提供的 Web 实时音视频 SDK。SDK 在 WebRTC 之上封装了房间管理、推拉流、弱网优化、跨网加速等能力,开发者可以用非常少的代码实现稳定的 1v1 视频通话。

这篇文章将基于即构国内官网文档,完整实现一个 Web 端 1v1 视频通话 Demo,并对每一步做文字说明,帮助读者真正理解背后的原理与流程。


一、应用场景与整体思路

在开始写代码之前,先明确一下这个 Demo 打算解决什么问题,以及整体方案长什么样。

1. 典型应用场景

  • 视频客服:在企业后台或 SaaS 平台中嵌入视频窗口,客服和用户一键通话。
  • 在线面试:在 HR 系统中内置视频面试,避免跳转到第三方会议工具。
  • 在线问诊:医生通过网页和患者进行实时视频沟通。
  • 在线小班课 / 1v1 辅导:老师和学生直接在 Web 中上课。

这些场景的共同需求是:

  • 无需安装应用
  • 浏览器即可进入
  • 通话稳定、延迟可接受
  • 容易集成到现有 Web 系统中

2. 技术方案整体思路

本 Demo 的整体技术思路可以概括为四步:

  1. 初始化即构 Web SDK:根据 appID 和 server 创建引擎实例。
  2. 登录房间:用 userID / roomID / token 加入一个"通话房间"。
  3. 本地推流 + 远端拉流:本地采集摄像头/麦克风并推流,对端收到流后拉取并播放。
  4. 挂断清理:停止推流/拉流,退出房间,释放资源。

底层复杂的 WebRTC 信令、网络策略、编解码细节,都由即构云和 SDK 处理,我们只需要关注「何时推流、何时拉流、何时退出」。


二、集成即构 Web SDK(对齐官方文档)

官方文档地址(国内):
https://doc-zh.zego.im/real-time-video-web/quick-start/integrating-sdk

即构 Web SDK 支持多种集成方式,常见的是 npm 与 script 两种方式。

1. 使用 npm(适合工程化项目)

如果项目使用 Vue、React、Vite 或 Webpack,这种方式最适合。

安装 SDK:

bash 复制代码
npm install zego-express-engine-webrtc

在代码中引入:

js 复制代码
import { ZegoExpressEngine } from "zego-express-engine-webrtc";

这种方式的优势是:

  • 版本号可控,方便统一升级/锁定版本;
  • 更适合 TS / 模块化工程;
  • 方便与打包工具集成,减小最终产物体积。

2. 通过 script 引入(适合快速 Demo)

如果只是想快速跑通 Demo,或者当前项目没有构建工具,也可以直接在 HTML 中通过 <script> 标签引入 SDK。

从即构官网(国内)下载 Web SDK,解压后会看到 dist_js 目录,里面有打包好的 JS 文件,例如:

html 复制代码
<script src="./dist_js/ZegoExpressWebRTC-3.5.0.js"></script>

这种方式的特点是:

  • 上手非常快,只需一个 HTML + JS 文件即可;
  • 适合做实验、PoC、内部 Demo;
  • 后续需要接入工程化项目时,再切换到 npm 方式即可。

三、1v1 视频通话整体流程拆解

在具体写代码之前,先用一个简单的表格把整个流程拆开,方便在脑子里建立大致模型:

步骤 功能 描述
步骤 1 初始化引擎 创建 ZegoExpressEngine 实例,与即构云服务建立基础连接
步骤 2 注册回调 监听房间状态变化、用户变化、流新增/删除等事件
步骤 3 登录房间 使用 loginRoom 进入一个 roomID 对应的房间
步骤 4 创建本地流 打开摄像头/麦克风,获取本地媒体流
步骤 5 本地预览 在页面上渲染本地画面,确认设备正常
步骤 6 推流 将本地流推送到云端,让其他人可以拉取
步骤 7 监听并拉取远端流 收到对端推流事件后拉流并显示
步骤 8 挂断清理 停止推流/拉流,退出房间,释放资源

接下来按照这个顺序,一步步在代码中实现。


四、初始化 SDK 与注册事件

这一步的目的是:
创建引擎实例,并提前把房间、用户、流相关的事件监听好。

1. 初始化引擎

js 复制代码
// 使用从即构控制台获取的 appID 和 server 参数
const zg = new ZegoExpressEngine(appID, server);

// 可选:关闭调试日志
zg.setDebugVerbose(false);

解释:

  • appIDserver 通常在即构控制台创建项目后获得。
  • 初始化引擎相当于告诉 SDK:后续所有操作都基于这一个应用和环境来进行。
  • setDebugVerbose(false) 是可选配置,用于关闭浏览器中的调试日志输出,在生产环境中可以保持关闭。

2. 监听房间状态

js 复制代码
zg.on("roomStateChanged", (roomID, reason, errorCode) => {
  if (reason === "LOGINED") {
    console.log("已成功进入房间:", roomID);
  } else if (reason === "CONNECTING") {
    console.log("正在连接房间...");
  } else if (reason === "DISCONNECTED") {
    console.log("已从房间断开,错误码:", errorCode);
  }
});

作用:

  • 该回调会在房间连接成功、重连、断开时触发。
  • 可以用来在页面上展示当前房间状态,例如"已连接""重连中"等。

3. 监听房间内用户变化(可选但推荐)

js 复制代码
zg.on("roomUserUpdate", (roomID, updateType, userList) => {
  console.log("房间用户变化:", updateType, userList);
});

说明:

  • 当有用户进入或离开房间时,会触发该事件。
  • 在 1v1 场景中,可以用来判断对端是否在线,做一些 UI 提示。

4. 监听流新增 / 删除(这是 1v1 核心)

js 复制代码
zg.on("roomStreamUpdate", async (roomID, updateType, streamList) => {
  if (updateType === "ADD") {
    // 有新的流发布(对端开始推流)
    const streamID = streamList[0].streamID;

    const remoteStream = await zg.startPlayingStream(streamID);
    const remoteView = zg.createRemoteStreamView(remoteStream);

    remoteView.play("remote-video", {
      enableAutoplayDialog: true,
    });
  } else if (updateType === "DELETE") {
    // 有流被移除(对端停止推流或离开房间)
    const streamID = streamList[0].streamID;
    zg.stopPlayingStream(streamID);
  }
});

解释:

  • 这是接收远端视频的关键步骤。
  • 当对端推流后,本地会收到 ADD 类型的 roomStreamUpdate 事件,从 streamList 中取出 streamID 即可开始拉流。
  • enableAutoplayDialog: true 是为了解决浏览器的自动播放策略问题,避免某些环境下视频不自动播放。
  • 当对端停止推流或离开房间时,会触发 DELETE,此时需要停止拉流并做 UI 清理。

五、登录房间:建立通话的"房间上下文"

下一步是进入一个具体房间,只有进入同一个房间的用户才能互相看到彼此的流。

js 复制代码
const userID = "user_" + Date.now();
const userName = userID;
const roomID = "room_1";

const success = await zg.loginRoom(
  roomID,
  token,
  { userID, userName },
  { userUpdate: true }
);

if (!success) {
  console.error("登录房间失败");
  // 这里可以提示用户"网络异常,请稍后再试"等
  return;
}

说明:

  • userID 通常需要保持在应用层唯一,这里用时间戳拼接是简单做法,实际项目中建议使用业务系统中的用户 ID。
  • userName 主要用于展示,不要求严格唯一。
  • token 一般由业务服务端生成,用于鉴权;在开发阶段可以使用即构控制台提供的测试 token。
  • 第四个参数 { userUpdate: true } 非常重要,如果不传这个选项,将收不到 roomUserUpdate 回调。

到这一步为止,当前用户已经加入了一个房间,可以开始创建和推送自己的音视频流。


六、创建本地音视频流并在页面预览

一旦房间登录成功,就可以通过 SDK 创建本地流并开始预览。

js 复制代码
const localStream = await zg.createZegoStream();

localStream.playVideo(
  document.getElementById("local-video"),
  { enableAutoplayDialog: true }
);

说明:

  • createZegoStream 会调用浏览器的 getUserMedia 接口,因此用户第一次调用时浏览器会弹出"是否允许访问摄像头/麦克风"的权限弹窗。
  • 建议在 UI 上提前给出提示,例如"即将打开摄像头,请允许浏览器访问设备"。
  • playVideo 会将本地流渲染到指定的 DOM 元素中,通常是一个 <div><video> 元素。
  • 再次强调 enableAutoplayDialog: true 的必要性,这是解决浏览器自动播放限制的关键配置。

这一步的目标是:
让用户在"发起通话之前",先确认本地画面和声音都是正常的。


七、推送本地流(推流):让对端可以看到你

当本地预览正常后,就可以把本地流推送到即构云,让房间里的其他用户通过"拉流"获取。

js 复制代码
const publishStreamID = String(Date.now());

zg.startPublishingStream(publishStreamID, localStream);

说明:

  • publishStreamID 需要在房间中保持唯一,常见做法是使用时间戳、UUID 或 "前缀 + userID" 等。
  • 一旦成功推流,房间内其他用户会收到 roomStreamUpdate 事件,此时就可以开始拉你的流并播放。
  • 推流失败的情况可以通过引擎事件或返回值做错误处理和重试。

八、接收并播放对端视频(拉流)

这个流程前面在注册 roomStreamUpdate 时已经实现,这里再整体梳理一下逻辑:

  1. 对端推流后,本地会收到 roomStreamUpdate 回调,updateType"ADD"
  2. streamList[0].streamID 取出对端流 ID;
  3. 调用 startPlayingStream(streamID) 拉流;
  4. 调用 createRemoteStreamView(remoteStream).play("remote-video") 渲染到远端容器。

在 1v1 场景中,每一侧的逻辑是完全对称的:
你推流 → 对端收到流并拉流 → 对端推流 → 你收到流并拉流。


九、挂断通话与资源释放

通话结束时,需要做三件事:

  1. 停止推流;
  2. 停止拉流(如果有);
  3. 退出房间,并清理页面 UI。

示例代码:

js 复制代码
// 1. 停止推流
zg.stopPublishingStream(publishStreamID);

// 2. 停止拉流(如果已经记录了 remoteStreamID,可以这里一起停止)
if (remoteStreamID) {
  zg.stopPlayingStream(remoteStreamID);
}

// 3. 退出房间
zg.logoutRoom(roomID);

// 4. 可选:清理本地和远端视频区域的 DOM 内容
document.getElementById("local-video").innerHTML = "";
document.getElementById("remote-video").innerHTML = "";

说明:

  • 在实际项目中,可以将这些操作封装在"挂断"按钮点击事件中。
  • 挂断时最好同时做 UI 状态重置,比如按钮置灰、提示"通话已结束"等。
  • 如果有录制、统计、日志上报等需求,也可以在挂断时触发对应逻辑。

十、总结与扩展方向

通过这一篇文章,从集成 SDK 到初始化引擎、登录房间、创建本地流、推流、拉流,再到挂断通话,已经完整走完了一个 Web 端 1v1 视频通话的闭环。

可以看到,在即构 SDK 的封装下,开发者无需接触复杂的 WebRTC 信令、ICE 协商、弱网优化算法等底层细节,就可以用相对简洁的代码实现稳定的实时音视频功能。

在此基础上,可以进一步扩展出很多功能,例如:

  • 多人房间 / 小班课场景(管理多路流)
  • 屏幕共享(用于远程协助或在线演示)
  • 美颜、虚拟背景等视频增强效果
  • 云录制与回放
  • 与 IM / 白板 / 文档协同结合的完整实时互动应用

对于已经在做 Web 端产品的团队来说,把实时音视频作为基础能力集成进去,正在变成一个越来越常见、也越来越必要的选择。而借助即构这类 SDK,可以帮助团队 显著降低实现成本,缩短交付周期,同时保证体验质量

相关推荐
爆浆麻花1 小时前
为什么有些人边框不用border属性
前端·css
uhakadotcom1 小时前
Next.js 从入门到精通(1):项目架构与 App Router—— 文件系统路由与目录结构全解析
前端·面试·github
LFly_ice2 小时前
学习React-22-Zustand
前端·学习·react.js
东华帝君2 小时前
vue3自定义v-model
前端
fruge3 小时前
搭建个人博客 / 简历网站:从设计到部署的全流程(含响应式适配)
前端
光影少年3 小时前
css影响性能及优化方案都有哪些
前端·css
呆呆敲代码的小Y3 小时前
2025年多家海外代理IP实战案例横向测评,挑选适合自己的
前端·产品
q***3753 小时前
爬虫学习 01 Web Scraper的使用
前端·爬虫·学习
v***5653 小时前
Spring Cloud Gateway
android·前端·后端