必须在 RTCPeerConnection 初始化时启用 encodedInsertableStreams: true,否则 createEncodedStreams() 返回 undefined 或抛出 NotSupportedError;该选项不兼容 Safari,Firefox 需手动开启,且仅 HTTPS/localhost 有效。
必须在 RTCPeerConnection 初始化时启用
不加这句,
会直接返回
或抛出
——这是 90% 的人卡住的起点。它不是可选配置,而是硬性开关,影响后续所有新创建的
和
。
常见错误现象:
控制台无报错,但
返回
调用成功却收不到任何帧(
永远 pending)
正确写法示例:
注意:该选项不兼容 Safari(截至 2026 年 5 月),Firefox 需手动开启
;且仅在 HTTPS 或
下生效,HTTP 环境下静默失效。
立即学习
“
前端免费学习笔记(深入)
”;
发送端加密必须操作
并重新赋值
的
是只读
,不能直接修改其内容。你得用
构造视图,加密后必须把新
赋回
,否则 WebRTC 仍发送原始帧。
关键点:
H.264 关键帧以
(BE)开头,AV1 帧需检查
,不同 codec 加密逻辑要分支处理
加密必须在 Worker 中完成,主线程做 AES-GCM 会导致帧堆积、卡顿甚至断流
每次加密必须用唯一 IV,并随帧一起传出去(可塞进
前 12 字节,或用
扩展字段)
简短示意(Worker 内):
使用HTML,CSS,JavaScript开发Android应用程序 英文文字pdf版附源文件
如果你了解HTML,CSS和JavaScript,您已经拥有所需的工具开发Android应用程序。本动手本书展示了如何使用这些开源web标准设计和建造,可适应任何Android设备的应用程序 - 无需使用Java。您将学习如何创建一个在您选择的平台的Android友好的网络应用程序,然后转换与自由PhoneGap框架到一个原生的Android应用程序。了解为什么设备无关的移动应用是未来的潮流,并开始构建应用程序,提供更
下载
接收端解密前必须做帧重组,不能假设每个
是一帧
拿到的
是裸 RTP payload,不含 header,且可能被分片(如 VP8 partitioning)、FEC 补充或 NACK 重传。同一视频帧可能拆成多个
到达,也可能因丢包重传而重复送达。
安全做法:
依赖
+ 时间窗口(如 ±50ms)聚合属于同一帧的 chunks
用
标记关键帧起始,配合
(Chrome 支持不稳定,慎用)
解密前先校验帧完整性(例如检查 H.264 NALU 起始码是否对齐,AV1 OBU 是否完整)
切忌直接对每个
单独解密后扔给解码器——大概率触发解码器崩溃或花屏。
返回的是二进制流对,不是 MediaStream
它返回的是
,每个
是
实例,含
、
、
、
等字段。不是
,也不是
,不能接
或直接喂给
。
容易踩的坑:
试图用
转发未加密帧——会失败,因为
不接受任意
,只认
在
上用
却没处理背压,导致内存暴涨
忽略
直接硬解——H.264、VP8、AV1 的帧结构差异极大,IV 长度、NALLU 边界、OBU 类型都不同
真正能落地的路径是:Worker 解密 → 封装为自定义事件 → 主线程用
注册 receiver → 保持原链路不变。加密只是插入在编码输出与网络发送之间的一环,其余流程照旧。
encodedInsertableStreams: truecreateEncodedStreams()undefinedNotSupportedErrorRTCRtpSenderRTCRtpReceiversender.createEncodedStreams()undefinedreadable.getReader().read()const pc = new RTCPeerConnection({
encodedInsertableStreams: true,
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});
media.webrtc.internals.insertable-streams.enabledlocalhostchunk.dataRTCEncodedVideoFramedataArrayBuffernew Uint8Array(chunk.data)ArrayBufferchunk.data00 00 00 01chunk.obu_typechunk.datachunk.metadataconst 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; // 必须重赋值
chunkreceiver.createEncodedStreams()chunk.datachunkchunk.timestampchunk.type === 'key'chunk.dependencychunkcreateEncodedStreams(){ readable: ReadableStream, writable: WritableStream }chunkRTCEncodedVideoFramedatatimestamptypecodecMediaStreamTransformStreamMediaRecorderstream.pipeTo(anotherStream)writableUint8ArrayRTCEncodedVideoFramereadablefor awaitchunk.codecpc.addTransceiver()