必须在 rtcpeerconnection 初始化时启用 encodedinsertablestreams: true,否则 createencodedstreams() 返回 undefined 或抛出 notsupportederror;该选项不兼容 safari,firefox 需手动开启,且仅 https/localhost 有效。

必须在 RTCPeerConnection 初始化时启用 encodedInsertableStreams: true
不加这句,createEncodedStreams() 会直接返回 undefined 或抛出 NotSupportedError——这是 90% 的人卡住的起点。它不是可选配置,而是硬性开关,影响后续所有新创建的 RTCRtpSender 和 RTCRtpReceiver。
常见错误现象:
- 控制台无报错,但
sender.createEncodedStreams()返回undefined - 调用成功却收不到任何帧(
readable.getReader().read()永远 pending)
正确写法示例:
const pc = new RTCPeerConnection({
encodedInsertableStreams: true,
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});
注意:该选项不兼容 Safari(截至 2026 年 5 月),Firefox 需手动开启 media.webrtc.internals.insertable-streams.enabled;且仅在 HTTPS 或 localhost 下生效,HTTP 环境下静默失效。
发送端加密必须操作 chunk.data 并重新赋值
RTCEncodedVideoFrame 的 data 是只读 ArrayBuffer,不能直接修改其内容。你得用 new Uint8Array(chunk.data) 构造视图,加密后必须把新 ArrayBuffer 赋回 chunk.data,否则 WebRTC 仍发送原始帧。
关键点:
- H.264 关键帧以
00 00 00 01(BE)开头,AV1 帧需检查chunk.obu_type,不同 codec 加密逻辑要分支处理 - 加密必须在 Worker 中完成,主线程做 AES-GCM 会导致帧堆积、卡顿甚至断流
- 每次加密必须用唯一 IV,并随帧一起传出去(可塞进
chunk.data前 12 字节,或用chunk.metadata扩展字段)
简短示意(Worker 内):
const iv = crypto.getRandomValues(new Uint8Array(12));
const key = await importKey(...); // 密钥由主线程导入
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
new Uint8Array(chunk.data)
);
chunk.data = encrypted; // 必须重赋值
接收端解密前必须做帧重组,不能假设每个 chunk 是一帧
receiver.createEncodedStreams() 拿到的 chunk.data 是裸 RTP payload,不含 header,且可能被分片(如 VP8 partitioning)、FEC 补充或 NACK 重传。同一视频帧可能拆成多个 chunk 到达,也可能因丢包重传而重复送达。
安全做法:
- 依赖
chunk.timestamp+ 时间窗口(如 ±50ms)聚合属于同一帧的 chunks - 用
chunk.type === 'key'标记关键帧起始,配合chunk.dependency(Chrome 支持不稳定,慎用) - 解密前先校验帧完整性(例如检查 H.264 NALU 起始码是否对齐,AV1 OBU 是否完整)
切忌直接对每个 chunk 单独解密后扔给解码器——大概率触发解码器崩溃或花屏。
createEncodedStreams() 返回的是二进制流对,不是 MediaStream
它返回的是 { readable: ReadableStream, writable: WritableStream },每个 chunk 是 RTCEncodedVideoFrame 实例,含 data、timestamp、type、codec 等字段。不是 MediaStream,也不是 TransformStream,不能接 MediaRecorder 或直接喂给 <video></video>。
容易踩的坑:
- 试图用
stream.pipeTo(anotherStream)转发未加密帧——会失败,因为writable不接受任意Uint8Array,只认RTCEncodedVideoFrame - 在
readable上用for await却没处理背压,导致内存暴涨 - 忽略
chunk.codec直接硬解——H.264、VP8、AV1 的帧结构差异极大,IV 长度、NALLU 边界、OBU 类型都不同
真正能落地的路径是:Worker 解密 → 封装为自定义事件 → 主线程用 pc.addTransceiver() 注册 receiver → 保持原链路不变。加密只是插入在编码输出与网络发送之间的一环,其余流程照旧。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











