1. 引言
[WEBRTC-NV-USE-CASES] 文档描述了若干 只有通过访问媒体才能实现的功能(需求 N20-N22), 包括但不限于:
-
趣味帽子
-
机器学习
-
虚拟现实游戏
这些用例进一步要求处理能够在 worker 线程中完成(需求 N23-N24)。
此规范给出了一个基于 [WEBCODECS] 和 [STREAMS] 的接口, 以提供对这类功能的访问。
此规范提供对原始媒体的访问, 原始媒体是诸如摄像头、麦克风、屏幕捕获等媒体源的输出, 或编解码器解码器部分的输出,以及编解码器 解码器部分的输入。处理后的媒体可由任何 能接收 MediaStreamTrack 的目标消费,包括 HTML <video> 标签、 RTCPeerConnection、canvas 或 MediaRecorder。
此规范明确旨在支持以下用例:
-
视频处理:这是“趣味帽子”用例,其中输入是单个视频轨道, 输出是经转换的视频轨道。
-
自定义视频接收器:在此用例中,目的不是产生一个已处理的
MediaStreamTrack, 而是以不同方式消费媒体。例如,应用程序可以使用 [WEBCODECS] 和 [WEBTRANSPORT] 来 创建一个类似RTCPeerConnection的 接收器,但使用不同的编解码器配置和联网协议。 -
多源处理:在此用例中,两个或多个轨道被合并为一个。 例如,可以将包含实时天气地图的演示文稿和带有演讲者的摄像头轨道 合并,以生成天气预报应用。
注: 关于是否应支持音频 用例,WG 尚无共识。
注: WG 预期 Streams 规范将采纳 相关 解释文档中概述的解决方案,以解决当前 Streams 规范的一些问题。
2. 规范
此规范展示了 [MEDIACAPTURE-STREAMS] 的 IDL 扩展。
它定义了一些继承 MediaStreamTrack
接口的新对象,并且
可以从 MediaStreamTrack
构造。
此 API 由两个元素组成。一个是轨道接收器, 能够将来自轨道的未编码媒体帧暴露给 ReadableStream。 另一个则是它的反向形式:它提供一个以 媒体帧为输入的轨道源。
2.1. MediaStreamTrackProcessor
MediaStreamTrackProcessor
允许创建一个
ReadableStream,
该流可以暴露流经
给定 MediaStreamTrack
的媒体。
如果该 MediaStreamTrack
是视频轨道,
则由流暴露的块将是 VideoFrame
对象。
这使 MediaStreamTrackProcessor
实际上成为
MediaStream 模型中的接收器。
MediaStreamTrackProcessor
内部包含一个循环队列,
该队列允许缓冲由其所连接轨道传入的媒体帧。
这种缓冲允许 MediaStreamTrackProcessor
临时保存等待从其关联 ReadableStream
中读取的帧。
应用程序可以通过在 MediaStreamTrackProcessor
构造函数中提供的参数来影响队列的最大大小。然而,
队列的最大大小由 UA 决定并且可以动态改变,
但不会超过应用程序请求的大小。
如果应用程序未提供最大大小参数,则 UA 可自由
决定队列的最大大小。
当一个新帧到达
MediaStreamTrackProcessor
时,
如果队列已达到其最大大小,
最旧的帧将从队列中移除,新帧将被
添加到队列中。这意味着,对于最大大小为 1 的队列这一特殊情况,
如果存在已排队的帧,它将始终是
最新的那个。
UA 也可在任何时候自由地从队列中移除任何帧。UA
可以为了节省资源或在特定情况下提高性能而移除帧。
在这种情况下,[[numDiscardedFrames]] 必须相应递增。
在所有情况下,未被丢弃的帧都必须按照其到达
MediaStreamTrackProcessor
的顺序提供给 ReadableStream。
MediaStreamTrackProcessor
只在已对流发出读取请求时,
才会向其关联的 ReadableStream
提供帧。其想法是避免流的内部缓冲,
因为该内部缓冲不能给 UA 足够的灵活性来选择缓冲策略。
鼓励作者在不再需要帧时立即对输出 VideoFrames
调用 close()。
底层媒体资源由 MediaStreamTrack
的
源拥有。
不释放它们(或等待垃圾回收)可能导致源停止发出新的 VideoFrames。
2.1.1. 接口定义
[Exposed =(Window ,DedicatedWorker ),Transferable ]interface {MediaStreamTrackHandle constructor (MediaStreamTrack ); }; [track Exposed =DedicatedWorker ]interface {MediaStreamTrackProcessor constructor (MediaStreamTrackProcessorInit );init readonly attribute ReadableStream readable ;readonly attribute unsigned long long discardedFrames ;readonly attribute unsigned long long totalFrames ; };typedef (MediaStreamTrack or MediaStreamTrackHandle );MediaStreamTrackOrHandle dictionary {MediaStreamTrackProcessorInit required MediaStreamTrackOrHandle ; [track EnforceRange ]unsigned short ; };maxBufferSize
注: WG 已达成共识,该接口应 暴露在 DedicatedWorker 上。 关于该接口是否不应暴露在 Window 上,WG 尚无共识。
注: WG 已达成共识,应存在从 kind 为 "video" 的 MediaStreamTrack 创建 MediaStreamTrackProcessor 的能力。 关于是否应支持从 kind 为 "audio" 的 MediaStreamTrack 创建 MediaStreamTrackProcessor, WG 尚无共识。
2.1.2. 内部槽
[[track]]- 其原始数据将由
MediaStreamTrackProcessor暴露的轨道。 [[maxBufferSize]]- 由
MediaStreamTrackProcessor缓冲的媒体帧最大数量, 如应用程序所指定。如果应用程序没有提供它, 它可以没有值。其最小有效值为 1。 [[queue]]- 一个队列, 用于缓冲尚未由应用程序读取的媒体帧
[[numPendingReads]]- 一个整数,其值表示应用程序已发出但尚未处理的 读取请求数量。
[[isClosed]]- 一个布尔值,其值指示该
MediaStreamTrackProcessor是否已关闭。 [[numDiscardedFrames]]- 此
MediaStreamTrackProcessor丢弃的帧数。 [[numTotalFrames]]- 此
MediaStreamTrackProcessor接收的帧数,包括被丢弃的帧。
2.1.3. 构造函数
MediaStreamTrackProcessor(init)
-
如果 init.
track是一个无效的MediaStreamTrack, 则抛出TypeError。 -
如果 init.
track是一个MediaStreamTrackHandle, 则运行以下子步骤:-
如果 init.
track.[[track]]正在引用一个无效的MediaStreamTrack, 则抛出TypeError。
-
令 maxBufferSize 为 1。
-
如果 init.
maxBufferSize的整数值大于 1,则运行以下子步骤:-
将 maxBufferSize 设置为 init.
maxBufferSize。 -
用户代理可以决定将 maxBufferSize 钳制为更低的值,但不得低于 1。
钳制 maxBufferSize 对某些源(例如摄像头)可能很有用, 例如在它们在任意给定时间只能使用有限数量的 VideoFrames 的情况下。
-
-
令 processor 为一个新的
MediaStreamTrackProcessor对象。 -
将 processor.
[[track]]设置为 init.track。 -
将 processor.
[[maxBufferSize]]设置为 maxBufferSize。 -
将 processor.
[[queue]]设置为空队列。 -
将 processor.
[[numPendingReads]]设置为 0。 -
将 processor.
[[isClosed]]设置为 false。 -
将 processor.
[[numDiscardedFrames]]设置为 0。 -
将 processor.
[[numTotalFrames]]设置为 0。 -
返回 processor。
2.1.4. 属性
readable, 类型为 ReadableStream,只读-
允许读取由存储在
[[track]]内部槽中的MediaStreamTrack或MediaStreamTrackHandle递送的帧。此属性在第一次被调用时 按照以下步骤创建:-
将 this.
readable初始化为一个新的ReadableStream。 -
设置 this.
readable, 将其 pullAlgorithm 设置为以 this 为参数的 processorPull, 将 cancelAlgorithm 设置为以 this 为参数的 processorCancel, 并将 highWaterMark 设置为 0。
processorPull 算法以 processor 作为输入。其定义为以下步骤:
-
将 processor.
[[numPendingReads]]的值递增 1。 -
排队一个任务,以 processor 为参数运行 maybeReadFrame 算法。
-
返回一个用 undefined 兑现的 promise。
maybeReadFrame 算法以 processor 作为输入。 其定义为以下步骤:
-
如果 processor.
[[queue]]为空, 则中止这些步骤。 -
如果 processor.
[[numPendingReads]]等于零,则中止这些步骤。 -
令 frame 为从 processor.
[[queue]]出队一个帧媒体数据的结果。 -
将 processor.
[[numPendingReads]]递减 1。 -
转到步骤 1。
processorCancel 算法以 processor 作为输入。 它通过运行以下步骤定义:
-
以 processor 为参数运行 processorClose 算法。
-
返回一个用 undefined 兑现的 promise。
processorClose 算法以 processor 作为输入。 它通过运行以下步骤定义:
-
如果 processor.
[[isClosed]]为 true,则中止这些步骤。 -
将 processor 从 processor.
[[track]]断开。 执行此操作的机制是 UA 特定的,其结果是 processor 不再是 processor.[[track]]的接收器,或不再是其引用的MediaStreamTrack的接收器。 -
将 processor.
[[track]]设置为 null。 -
关闭 processor.
readable.[[controller]]。 -
清空 processor.
[[queue]]。 -
将 processor.
[[isClosed]]设置为 true。
-
discardedFrames, 类型为 unsigned long long,只读- 返回 processor.
[[numDiscardedFrames]]的值。 totalFrames, 类型为 unsigned long long,只读- 返回 processor.
[[numTotalFrames]]的值。
2.1.5. 处理与轨道的交互
当MediaStreamTrackProcessor
processor 的 [[track]] 向 processor 递送
帧时,UA 必须以 processor 为参数执行 handleNewFrame 算法。
handleNewFrame 算法以 processor 作为输入。 它通过运行以下步骤定义:
-
如果 processor.
[[queue]]有 processor.[[maxBufferSize]]个元素,则运行以下步骤:-
令 droppedFrame 为从 processor.
[[queue]]出队的结果。 -
以 droppedFrame 运行 Close VideoFrame 算法。
-
将 processor.
[[numDiscardedFrames]]递增 1。
-
-
将 processor.
[[numTotalFrames]]递增 1。 -
将新的帧媒体数据入队到 processor.
[[queue]]。 -
排队一个任务,以 processor 为参数运行 maybeReadFrame 算法。
在任何时候,UA 都可以从 processor.[[queue]]
移除任何帧。
UA 可以决定从 processor.[[queue]] 移除帧,例如,
为了防止资源耗尽或在某些情况下提高性能。
在这种情况下,processor.[[numDiscardedFrames]] 必须相应递增。
应用程序可以通过注意帧的时间戳中存在间隙,或通过查看
discardedFrames
来检测帧已被丢弃。
当 MediaStreamTrackProcessor
processor 的 [[track]]
结束时,
必须以 processor 为参数执行 processorClose 算法。
2.1.6. MediaStreamTrackHandle
MediaStreamTrackHandle
接口表示到 MediaStreamTrack
对象的代理,该代理可用于使 DedicatedWorkerGlobalScope 中的 MediaStreamTrackProcessor
成为存在于另一个上下文中的 MediaStreamTrack
的接收器。
MediaStreamTrackHandle
具有以下槽:
-
一个
[[transferable]]内部槽,用于确保一旦 handle 对象实例被转移, 该实例就不能再次被转移。 -
一个
[[track]]内部槽,它是到被代理的MediaStreamTrack对象的弱引用。
2.1.7. 构造函数
MediaStreamTrackHandle(track)
-
令 handle 为一个新的
MediaStreamTrackHandle对象。 -
将 handle.
[[transferable]]设置为 true。 -
将 handle.
[[track]]设置为到 track 的内部引用。 -
返回 handle。
给定 value 和 dataHolder,
MediaStreamTrackHandle
的转移步骤为:
-
如果 value.
[[transferable]]为 false,则抛出DataCloneErrorDOMException。 -
将 value.
[[transferable]]设置为 false。 -
将 dataHolder.
[[track]]设置为 handle.[[track]]。 -
将 value.
[[track]]设置为 null。
给定 dataHolder 和
handle,MediaStreamTrackHandle
的转移接收步骤为:
-
将 handle.
[[track]]设置为 dataHolder.[[track]]。
MediaStreamTrackHandle
可以延长其所代理 MediaStreamTrack
的生命周期。
从垃圾回收的角度看,就好像 MediaStreamTrackHandle
持有对其 MediaStreamTrack
的强引用。
此外,如果某个 MediaStreamTrack
被一个正在转移的数据持有者引用,则不得对其进行垃圾回收。
2.2. VideoTrackGenerator
VideoTrackGenerator
允许在
MediaStream 模型中为
MediaStreamTrack
创建一个视频源,该视频源从由 VideoFrame
对象组成的 Stream 生成其帧。它有两个 readonly
属性:一个 writable
WritableStream
和一个
track
MediaStreamTrack。
VideoTrackGenerator
是其
writable
属性的底层接收器]。track
属性
是输出。可以在
track
属性上使用
clone
方法,创建连接到同一个 VideoTrackGenerator
的更多轨道。
WritableStream
接受 VideoFrame
对象。
当一个 VideoFrame
被写入 writable
时,
会自动调用该帧的 close() 方法,使其内部
资源不再可从 JavaScript 访问。
注: WG 已达成共识,应存在能够 生成 kind 为 "video" 的 MediaStreamTrack 的源。 关于是否应存在能够生成 kind 为 "audio" 的 MediaStreamTrack 的源,WG 尚无共识。
2.2.1. 接口定义
[Exposed =DedicatedWorker ]interface {VideoTrackGenerator constructor ();readonly attribute WritableStream writable ;attribute boolean muted ;readonly attribute MediaStreamTrack track ; };
注: WG 已达成共识,该接口应 暴露在 DedicatedWorker 上。 关于它是否应暴露在 Window 上,WG 尚无共识。
2.2.2. 内部槽
[[track]]- 此源输出的
MediaStreamTrack [[isMuted]]- 一个布尔值,其值指示此源以及它所源出的所有
MediaStreamTrack当前是否被muted。
2.2.3. 构造函数
VideoTrackGenerator()
-
令 generator 为一个新的
VideoTrackGenerator对象。 -
令 track 为一个新创建的
MediaStreamTrack, 其 source 设置为 generator,tieSourceToContext 设置为false。 -
将 generator.
track初始化为 track。 -
返回 generator。
2.2.4. 属性
writable, 类型为 WritableStream,只读-
允许向
VideoTrackGenerator写入视频帧。 当此属性 第一次被访问时,它必须按照以下步骤初始化:-
将 this.
writable初始化为一个新的WritableStream。 -
设置 this.
writable, 将其 writeAlgorithm 设置为以 this 为参数的 writeFrame, 将 closeAlgorithm 设置为以 this 为参数的 closeWritable, 并将 abortAlgorithm 设置为以 this 为参数的 closeWritable。
writeFrame 算法以 generator 和 frame 作为输入。它通过运行 以下步骤定义:
-
如果 frame 不是
VideoFrame对象,则返回一个用TypeError拒绝的 promise。 -
如果 frame 的
[[Detached]]内部槽的值为 true,则返回一个用TypeError拒绝的 promise。 -
如果 generator.
[[isMuted]]为 false,则对于每个源自 generator 的活动轨道(命名为 track),运行以下步骤:-
令 clone 为以 frame 运行 Clone videoFrame 算法的结果。
-
将 clone 发送到 track。
-
-
以 frame 运行 Close VideoFrame 算法。
-
返回一个用 undefined 兑现的 promise。
closeWritable 算法以 generator 作为输入。 它通过运行以下步骤定义。
-
当媒体数据发送到轨道时,UA 可以应用处理 (例如裁剪和缩小),以确保发送到轨道的媒体数据 满足轨道的约束。每个轨道可以根据其约束接收 不同版本的媒体数据。
muted, 类型为 boolean-
将
VideoTrackGenerator静音。 getter 步骤为返回 this.[[isMuted]]。给定值 newValue 的 setter 步骤如下: track, 类型为 MediaStreamTrack,只读MediaStreamTrack输出。getter 步骤为返回 this.[[track]]。
2.2.5. MediaStreamTrack 行为的特化
VideoTrackGenerator
充当一个或多个 MediaStreamTrack
的源。
本节增加了对源自
VideoTrackGenerator
的 MediaStreamTrack
如何表现的说明。
2.2.5.1. stop
stop
方法停止轨道。当源自 VideoTrackGenerator
的最后一个轨道结束时,该 VideoTrackGenerator
的
writable
会被关闭。
2.2.5.2. 可约束属性
为任何源自
VideoTrackGenerator
的 MediaStreamTrack
定义以下可约束属性:
| 属性名称 | 值 | 注释 |
|---|---|---|
| width |
ConstrainULong
|
作为设置,这是轨道收到的最新帧的宽度,
以像素为单位。
作为能力,max 必须反映
VideoFrame
可以具有的最大宽度,min
必须反映 VideoFrame
可以具有的最小宽度。
|
| height |
ConstrainULong
|
作为设置,这是轨道收到的最新帧的高度,
以像素为单位。
作为能力,max 必须反映
VideoFrame
可以具有的最大高度,min 必须反映
VideoFrame
可以具有的最小高度。
|
| frameRate |
ConstrainDouble
|
作为设置,这是基于轨道最近收到的帧估算出的帧率。
作为能力,min 必须为零,并且
max 必须为系统支持的最大帧率。
|
| aspectRatio |
ConstrainDouble
|
作为设置,这是轨道递送的最新帧的纵横比;
这是以像素为单位的宽度除以高度所得的 double,
四舍五入到小数点后第十位。作为能力,
min 必须为一个 VideoFrame
支持的最小纵横比,
max 必须为一个 VideoFrame
支持的最大纵横比。
|
| resizeMode |
ConstrainDOMString
|
作为设置,此字符串应为
VideoResizeModeEnum
的成员之一。
值 "none"
表示 MediaStreamTrack 输出的帧是写入到
支撑该轨道的
writable
的帧的未修改版本,
无论存在任何约束。
值 "crop-and-scale"
表示
MediaStreamTrack 输出的帧可以是源帧的裁剪和/或
缩小版本,
具体基于轨道的 width、height 和
aspectRatio 约束的值。
作为能力,值 "none"
和
"crop-and-scale"
都必须存在。
|
应用于源自 VideoTrackGenerator
的视频 MediaStreamTrack
的 applyConstraints
方法支持上面定义的属性。
例如,它可用于调整帧大小或调整轨道的帧率。
请注意,这些约束不会影响写入到
VideoTrackGenerator
的 writable
的 VideoFrame
对象,
只会影响已应用约束的轨道的输出。
还请注意,由于 VideoTrackGenerator
原则上可以为受支持的可约束属性产生
具有任意设置的媒体数据,
对由 VideoTrackGenerator
支撑的轨道调用
applyConstraints
通常不会因
OverconstrainedError
而失败,除非给定约束超出由
getCapabilities
报告的系统支持范围。
2.2.5.3. 事件和属性
事件和属性的工作方式与任何MediaStreamTrack
相同。
需要注意的是,如果 VideoTrackGenerator
的 writable
流被关闭,则所有与之连接的活动
轨道都会结束,并在其上触发 ended 事件。
3. 示例
3.1. 视频处理
考虑一个面部识别函数detectFace(videoFrame),它返回面部位置
(以某种格式),以及一个操作函数 blurBackground(videoFrame, facePosition),
它返回一个与给定 videoFrame 类似的新 VideoFrame,但将
非面部部分模糊化。该示例还展示了在 video 元素上应用效果前后的
视频。
// main.js const stream= await navigator. mediaDevices. getUserMedia({ video: true }); const videoBefore= document. getElementById( 'video-before' ); const videoAfter= document. getElementById( 'video-after' ); videoBefore. srcObject= stream; const trackHandle= new MediaStreamTrackHandle( stream. getVideoTracks()[ 0 ]); const worker= new Worker( 'worker.js' ); worker. postMessage({ trackHandle}, [ trackHandle]); const { data} = await new Promise( r=> worker. onmessage); videoAfter. srcObject= new MediaStream([ data. track]); // worker.js self. onmessage= async ({ data: { trackHandle}}) => { const source= new VideoTrackGenerator(); parent. postMessage({ track: source. track}, [ source. track]); const { readable} = new MediaStreamTrackProcessor({ track: trackHandle}); const transformer= new TransformStream({ async transform( frame, controller) { const facePosition= await detectFace( frame); const newFrame= blurBackground( frame, facePosition); frame. close(); controller. enqueue( newFrame); } }); await readable. pipeThrough( transformer). pipeTo( source. writable); };
3.2. 带约束的多消费者后处理
一个常见用例是从馈送到视频会议的实时摄像头视频中移除背景, 同时显示结果的实时自我预览。即使由于带宽约束造成的反压导致 实际发送所用的帧率可能下降,也希望自我预览具有高帧率。 这可以通过对轨道克隆应用约束来实现,从而避免必须处理 两次。// main.js const stream= await navigator. mediaDevices. getUserMedia({ video: true }); const [ track] = stream. getVideoTracks(); const worker= new Worker( 'worker.js' ); worker. postMessage({ track}, [ track]); const { data} = await new Promise( r=> worker. onmessage); const selfView= document. getElementById( 'video-self' ); selfView. srcObject= new MediaStream([ data. track. clone()]); // 60 fps await data. track. applyConstraints({ width: 320 , height: 200 , frameRate: 30 }); const pc= new RTCPeerConnection( config); pc. addTrack( data. track); // 30 fps // worker.js self. onmessage= async ({ data: { track}}) => { const source= new VideoTrackGenerator(); parent. postMessage({ track: source. track}, [ source. track]); const { readable} = new MediaStreamTrackProcessor({ track}); const transformer= new TransformStream({ transform: myRemoveBackgroundFromVideo}); await readable. pipeThrough( transformer). pipeTo( source. writable); };
3.3. worker 中带约束的多消费者后处理
在 worker 中通过 WebTransport 发送视频帧时,能够显示更高帧率的自我预览 也同样相关。这里可以使用上面的相同技术, 区别在于约束是在 worker 中应用到轨道克隆上的。// main.js const stream= await navigator. mediaDevices. getUserMedia({ video: true }); const [ track] = stream. getVideoTracks(); const worker= new Worker( 'worker.js' ); worker. postMessage({ track}, [ track]); const { data} = await new Promise( r=> worker. onmessage); const selfView= document. getElementById( 'video-self' ); selfView. srcObject= new MediaStream([ data. track]); // 60 fps // worker.js self. onmessage= async ({ data: { track}}) => { const source= new VideoTrackGenerator(); const sendTrack= source. track. clone(); parent. postMessage({ track: source. track}, [ source. track]); await sendTrack. applyConstraints({ width: 320 , height: 200 , frameRate: 30 }); const wt= new WebTransport( "https://webtransport.org:8080/up" ); const { readable} = new MediaStreamTrackProcessor({ track}); const transformer= new TransformStream({ transform: myRemoveBackgroundFromVideo}); await readable. pipeThrough( transformer) . pipeThrough({ writable: source. writable, readable: sendTrack. readable}), . pipeThrough( createMyEncodeVideoStream({ codec: "vp8" , width: 640 , height: 480 , bitrate: 1000000 , })) . pipeThrough( new TransformStream({ transform: mySerializer})); . pipeTo( wt. createUnidirectionalStream()); // 30 fps };
上面的示例避免使用 tee() 函数来服务多个
消费者,因为它对实时流存在问题。
为简洁起见,该示例还过度简化了使用 WebCodecs 包装器 对视频帧进行编码并通过单个 WebTransport 流发送(会造成 队头阻塞)的过程。
4. 实现建议
本节为信息性内容。
4.1. 与多个消费者一起使用
存在一些用例,程序员可能希望单个帧流 被多个消费者消费。
示例包括背景模糊函数的结果既应显示在自我预览中,
又应使用 VideoEncoder
进行编码的情况。
对于两个消费者都消费未处理帧且不希望同步的情况,
实例化多个 MediaStreamTrackProcessor
对象是一种稳健的解决方案。
对于两个消费者都打算使用 VideoTrackGenerator
将处理步骤的结果转换为
MediaStreamTrack
的情况,
例如在将处理后的流馈送到 <video> 标签和 RTCPeerConnection
时,
将生成的 MediaStreamTrack
附加到多个接收器可能是最合适的机制。
对于下游处理接收帧而不是流的情况,可以根据需要克隆帧 并将其发送到下游处理;"clone" 是一种廉价操作。
当流是某些处理的输出,并且两个分支都需要 Stream 对象 以进行进一步处理时,需要一个从一个流产生两个流的函数。
然而,标准 tee() 操作在此上下文中 存在问题:
-
它破坏了防止过度排队的反压机制
-
它会创建到同一缓冲区的多个链接,这意味着哪个 消费者有权 destroy() 缓冲区这一问题难以处理
因此,只有在完全理解其影响时, 才应对包含媒体的 Streams 使用 tee()。相反,应使用 更适合用例的自定义元素来分割流。
-
如果两个分支都需要能够处置帧,则 clone() 该帧, 并将不同副本入队到两个队列中。这对应于函数 ReadableStreamTee(stream, cloneForBranch2=true)。然后选择下面的 备选方案之一。
-
如果一个分支需要所有帧,而另一个分支容忍丢帧, 则将缓冲区入队到需要所有帧的流中,并使用该流的反压信号 来停止从源读取。如果另一个流的反压信号表明有空间, 也将同一帧入队到该队列中。
-
如果两个流都不容忍丢帧,则使用组合反压信号 停止从源读取。在这种情况下,如果缓冲区大小都为 1, 帧将以锁步方式处理。
-
如果只在分配给该进程的底层 缓冲池耗尽时才阻塞传入流是可以接受的,则可以使用标准 tee()。
注: Streams 规范中已提交了一些 issue, 其解决方案可能会影响本节:https://github.com/whatwg/streams/issues/1157, https://github.com/whatwg/streams/issues/1156, https://github.com/whatwg/streams/issues/401, https://github.com/whatwg/streams/issues/1186
5. 安全和隐私注意事项
此 API 定义了一个 MediaStreamTrack
源和一个 MediaStreamTrack
接收器。
该源(VideoTrackGenerator)的安全性和隐私性
依赖于同源策略。也就是说,VideoTrackGenerator
能以 MediaStreamTrack
形式提供的数据,在 VideoFrame
对象被构造并推入
VideoTrackGenerator
之前,必须对文档可见。
任何试图使用跨源数据创建
VideoFrame
对象的尝试都会失败。
因此,VideoTrackGenerator
不会引入任何新的
指纹识别表面。
此 API 引入的 MediaStreamTrack
接收器(MediaStreamTrackProcessor)
将与其他
MediaStreamTrack
接收器(如 WebRTC 对等连接和媒体元素)暴露的相同数据
从 MediaStreamTrack
暴露出来。MediaStreamTrackProcessor
的安全性和隐私性依赖于
MediaStreamTrackProcessor
所连接轨道的 MediaStreamTrack
源的安全性和隐私性。例如,摄像头、麦克风和屏幕捕获轨道
依赖通过权限对话框进行的显式使用授权(参见
[MEDIACAPTURE-STREAMS] 和 [SCREEN-CAPTURE]),
而元素捕获和 VideoTrackGenerator
依赖同源策略。
MediaStreamTrackProcessor
的一个潜在问题是资源耗尽。
例如,站点可能持有过多打开的 VideoFrame
对象,
并耗尽由 GPU 内存支持的系统范围帧池。UA 可以
通过限制站点可持有的池支持帧数量来缓解这一风险。
这可以通过减少缓冲帧的最大数量,
并在达到预算限制后拒绝向 readable
递送更多帧来实现。意外耗尽也通过在
VideoFrame
对象写入 VideoTrackGenerator
后自动关闭它们来缓解。
6. 与早期提案的向后兼容性
本节为信息性内容。
此接口以前的提案具有如下 API:
[Exposed =Window ,DedicatedWorker ]interface MediaStreamTrackGenerator :MediaStreamTrack {constructor (MediaStreamTrackGeneratorInit init );attribute WritableStream writable ; // VideoFrame or AudioData };dictionary MediaStreamTrackGeneratorInit {required DOMString kind ; };
VideoTrackGenerator 可以像这样在 MediaStreamTrackGenerator 之上进行 shim:
// 未测试,不太可能按所写内容工作!
class VideoTrackGenerator {
constructor() {
this.innerGenerator = new MediaStreamTrackGenerator({kind: 'video'});
this.writable = this.innerGenerator.writable;
this.track = this.innerGenerator.clone();
}
// 缺失:用于设置 "muted" 属性的 shim。
};
关于以前提案的进一步说明,包括涉及音频处理的考虑事项, 可在此文档的较早版本中找到。
注: 当我们完成仓库移动后, 此处将放置一个指向 chrome-96 分支的链接。