系列: Web 端流媒体入门(共 3 篇)
本篇: 一. 概念科普 二. 数据链路图解 三. 声网 Web SDK 上手
建议: 先读一再读本篇
范围: 仅 Web(getUserMedia+ 声网 Web SDKagora-rtc-sdk-ng)
摘要
「本地预览有画面,观众却黑屏」多半是没搞清数据流到了哪一站。本文用一条 8 步流水线(含 join 入会)+ 时序图,讲清 Web 直播从摄像头到观众端的完整路径,并总结 4 个最容易踩的坑。
关键词: getUserMedia、publish、subscribe、WebRTC、SFU、声网、数据链路
一、先建立全局:8 步流水线(含 join)
把 Web 直播想成一条工厂流水线。下面 8 步覆盖从硬件到观众画面的主路径(①② 多在浏览器/OS 内完成,业务代码从 ③ 开始介入):
[摄像头/麦克风]
│
▼
① 操作系统驱动
│
▼
② 浏览器 getUserMedia ← 原始 MediaStreamTrack(YUV / PCM)
│
▼
③ RTC SDK 采集 + 编码 ← LocalTrack(H.264 / AAC 等,见第①篇 §5)
│
▼
④ client.join() ← 带 Token 进入频道(主播/观众都要,在 publish 之前)
│
├─ track.play(容器) ← 仅本地预览(主播侧,≠ 推流)
│
└─ ⑤ client.publish() ← 仅 host 角色:上行开始
│
▼
⑥ 媒体服务器 SFU 转发 ← 按 channelName 分发给已入会用户
│
▼
⑦ subscribe() ← 观众在 user-published 里拉远端轨
│
▼
⑧ remoteTrack.play() ← SDK 在容器内挂载 <video> 并解码播放
一句话记忆: 采集 → 编码 → join 入会 →(主播)publish → SFU →(观众)subscribe → play。
主播 vs 观众:路径分叉
| 角色 | 关键步骤 |
|---|---|
| 主播(host) | ③ 创建轨 → ④ join → 预览 play → ⑤ publish |
| 观众(audience) | ④ join(通常无需本地摄像头)→ 监听 user-published → ⑦ subscribe → ⑧ play |
观众不会也不必调用 publish(除非切换为 host 连麦)。
二、每一步干什么?(附生活比喻)
| 步 | 技术动作 | 在做什么 | 生活比喻 |
|---|---|---|---|
| ② | getUserMedia | 向浏览器申请设备,得到原始轨 | 打开话筒 |
| ③ | createMicrophoneAndCameraTracks 等 | 包装为 LocalTrack + 编码 | 把画面压成可传输的小包 |
| ④ | client.join(appId, channel, token, uid) | 鉴权进入同一频道 | 刷卡进房间 |
| 预览 | track.play(容器) | 仅本地 DOM 渲染,不上传 | 对着镜子练声 |
| ⑤ | client.publish(host) | 编码流经 WebRTC 上行 | 电台开始广播 |
| ⑥ | SFU | 按频道转发多路流,服务器不混画面 | 快递分拣中心 |
| ⑦ | subscribe + user-published | 观众拉取主播远端轨 | 调频收听 |
| ⑧ | remoteTrack.play(容器) | 解码并显示 | 收音机出声 |
和「打电话」对照
| 步骤 | 打电话 | Web 直播 |
|---|---|---|
| 输入 | 你说话 | 摄像头 / 麦克风 |
| 数字化 | 话筒 → 电信号 | getUserMedia |
| 压缩 | 语音/画面编码 | H.264 + AAC/Opus |
| 传输 | 基站、运营商 | WebRTC + SFU |
| 输出 | 对方听到 | 观众看到画面 |
三、第 ② 站详解:getUserMedia
这是 Web 标准 API,所有 RTC SDK 最终都要靠它拿设备(除非你用屏幕共享等特殊源)。
// 最小示例:constraints 为「期望」而非保证值,各浏览器支持不一
const stream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
frameRate: { ideal: 30 },
},
audio: true,
});
// 实际分辨率以 track.getSettings() 为准;直播码率/帧率建议在 SDK encoderConfig 里配
注意:
- 须在安全上下文(HTTPS /
localhost);iframe 嵌入时还受 Permissions Policy 限制。 - 用户拒绝会抛
NotAllowedError,需单独catch并提示去浏览器设置里开启权限。 - 仅做设备检测时,测完对所有轨执行
stream.getTracks().forEach((t) => t.stop()),避免占用摄像头。
四、第 ③~⑤ 步:编码、入会、推流(顺序不能乱)
以声网 Web SDK 为例,主播侧推荐顺序:
createClient → setClientRole('host') → createTracks → join → play(预览) → publish
createMicrophoneAndCameraTracks() 内部会:
- 调用
getUserMedia; - 创建
LocalAudioTrack/LocalVideoTrack; - 按
encoderConfig编码(详见第①篇 §5)。
const client = AgoraRTC.createClient({ mode: 'live', codec: 'h264' });
await client.setClientRole('host');
const [microphoneTrack, cameraTrack] =
await AgoraRTC.createMicrophoneAndCameraTracks();
const token = await fetchTokenFromBackend(channel, uid);
await client.join(appId, channel, token, uid); // ④ 必须先 join
cameraTrack.play(document.getElementById('preview')); // 预览,仍不等于推流
await client.publish([microphoneTrack, cameraTrack]); // ⑤ 此刻才开始上行
为什么要编码?再记一个数
720p 未压缩约 83 MB/s,家庭上行无法承载。编码后视频常见 约 1–2 Mbps 量级,才能稳定直播(见第①篇 H.264 码率表)。
五、第 ⑥ 步:SFU 是什么?
SFU(Selective Forwarding Unit) = 选择性转发:服务器收到主播的多路编码流后,按观众需要分别转发,不在服务器上混成一张大画面(那是 MCU 干的事)。
声网在全球的 SD-RTN™ 网络里部署的就是这类媒体节点。你只需要知道:
- 同一
channelName的用户会在逻辑上进入「同一房间」; - 主播
publish后,SFU 把该用户的音视频轨转发给同一频道内已join且完成subscribe的观众。
本篇不展开 MCU、旁路推流 CDN,Web 入门知道 SFU 即可。
六、完整时序图(主播 + 观众 + Token)
Token 为什么必须经过后端?
它绑定了 appId、频道、用户权限和过期时间。前端只负责「拿着卡进门」,不能自己印卡。
七、四个最易踩的坑
坑 1:track.play() ≠ 已经推流
很多 bug 是:主播预览正常,观众黑屏。
| 调用 | 效果 |
|---|---|
cameraTrack.play(dom) | 仅本地预览 |
client.publish([tracks]) | 才真正上行 |
排查时在 Chrome / Edge 打开 chrome://webrtc-internals,看主播页是否有 outbound-rtp(上行)、观众页是否有 inbound-rtp(下行)。Safari 需用 Web Inspector 的 WebRTC 面板。
坑 2:未 join 就 publish,或频道不一致
publish / subscribe 都要求在 join 成功之后,且主播与观众必须使用相同的 channelName(以及有效 Token)。否则会失败或观众永远收不到 user-published。
坑 3:Token 写死在前端
测试阶段可以临时用控制台生成的 Token,上线必须后端签发,且 channel、uid 要与 join 参数一致,否则会 join 失败或中途被踢。
坑 4:测试流忘记 stop
const test = await navigator.mediaDevices.getUserMedia({ video: true });
test.getTracks().forEach((t) => t.stop()); // 必须释放
否则正式 createMicrophoneAndCameraTracks 时可能遇到设备被占用。
八、直播 RTC vs 点播(再对齐一次)
| 维度 | 直播 RTC(本系列) | 点播 HLS |
|---|---|---|
| 内容从哪来 | 实时采集 | 已存储文件 |
| 关键 API | getUserMedia + join + publish | <video> + HLS 播放器(如 hls.js) |
| 延迟 | 约 1–3 秒 | 通常更长 |
两套链路 不要混读代码:点播页面里不会出现 publish。
九、本篇小结
- 8 步流水线:采集 → 编码 →
join→(host)publish→ SFU →subscribe→play。 - 预览和推流是两件独立的事。
- 观众依赖
user-published,不是只join就能出画。 - Token 走后端;测试采集后要对所有轨
stop()。
下一篇预告
③ 声网 Web SDK 快速上手:8 个 API 搞定推流拉流 — 文档入口、API 对照表、主播/观众五步流程与最小代码骨架。
参考文献
| 资料 | 链接 |
|---|---|
| 第①篇 H.264 / AAC 说明 | https://blog.csdn.net/qq_41860203/article/details/161461471?spm=1011.2415.3001.5331 |
| MDN 安全上下文 | https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts |
| MDN getUserMedia | https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia |
| MDN WebRTC 协议简介 | https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Protocols |
| WebRTC for the Curious | https://webrtcforthecurious.com/ |
| 声网 Web 快速开始 | https://doc.shengwang.cn/doc/rtc/javascript/get-started/quick-start |
| 流媒体技术科普 | https://zhuanlan.zhihu.com/p/1947215285758203669 |
236

被折叠的 条评论
为什么被折叠?



