Copyright © 2023 World Wide Web Consortium. W3C® liability, trademark and permissive document license rules apply.
本文档介绍了一种 API,用于裁剪从当前标签页的显示捕获所派生出的视频轨道。
本节描述的是本文档在发布时的状态。当前 W3C 出版物列表以及本技术报告的最新修订版可在 W3C 技术 报告索引中找到,网址为 https://www.w3.org/TR/。
这份首次公开工作草案代表了 Web 实时通信工作组打算探索的方向, 以解决浏览上下文的部分捕获用例。工作组特别希望从该 API 的潜在采用者那里 获得反馈,了解这一方向与上述用例的匹配程度。本文档由 Web 实时 通信工作组作为 工作草案发布,使用 推荐标准轨道。
发布为工作草案并不 意味着获得 W3C 及其成员的认可。
这是一份草案文档,可能随时被其他文档更新、替换或废弃。 除了作为正在进行中的工作之外,不应引用本文档。
本文档由一个在 W3C 专利 政策下运作的小组制作。 W3C 维护着一份与该小组交付物相关的 任何专利披露的公开列表; 该页面还包括 披露专利的说明。实际 知晓某项专利,并且该个人认为该专利包含 必要权利要求的个人, 必须按照 W3C 专利政策第 6 节 披露该信息。
本文档受 2021 年 11 月 2 日 W3C 流程文档管辖。
除了标记为非规范性的章节之外,本规范中的所有编写指南、图表、示例和注释都是非规范性的。 本规范中的其他所有内容都是规范性的。
本文档中的关键词 MUST 和 MUST NOT 应按 BCP 14 [RFC2119] [RFC8174] 中的描述来解释,但仅当它们如这里所示全部以大写形式出现时才如此。
本文档使用 [SCREEN-CAPTURE] 中下列概念的定义: display-surface 和 browser display-surface。
本节是非规范性的。
复杂应用通常由位于不同 iframe
中的多个 document 组成,
它们都显示在同一个 浏览
上下文中。考虑这样一个应用。假定其中一个
document,即 CAPTURING-DOC,使用 getDisplayMedia()
或
getViewportMedia 来捕获整个当前 浏览
上下文。如果此
document 随后希望将视频轨道裁剪到协作文档 CAPTURED-DOC 的某个子区段
CAPTURE-TARGET 的坐标,CAPTURING-DOC 如何才能高效且可靠地
做到这一点?尤其要记住,由滚动、缩放或窗口大小调整导致的布局变化会带来额外挑战。
考虑一个由两个主要部分组成的组合应用,它们托管在同一标签页内的不同 iframe 中:
一个视频会议应用和一个生产力套件应用。假定该视频会议应用使用现有/即将推出的 API,
例如
getDisplayMedia()
和/或 getViewportMedia,并捕获
整个标签页。现在它需要裁剪掉生产力套件中某个特定区段之外的所有内容。
在将生成的已裁剪视频远程传输之前,它需要裁剪掉自己的视频会议内容、
任何演讲者备注以及生产力套件中的其他私有和/或无关内容。
此外,考虑到这两个协作应用很可能彼此跨源。它们可以发送消息,但所有通信都是 异步的,并且如果信息在它们之间尽量少传输,会更容易且性能更好。 这就排除了涉及发布整个帧的解决方案,也排除了对布局变化反应过慢的解决方案 (例如滚动、缩放和窗口大小变化)。
值得注意的是,大多数应用在此类场景中可能更倾向于使用
getViewportMedia。然而,在撰写本文时,
getViewportMedia 仍未被规范化,也未被实现。它会有一些
非平凡的要求,其采用需要一些时间和工作。因此,在未来一段时间内,许多
应用可能会结合使用 getDisplayMedia()
和
区域捕获。
区域捕获机制由两部分组成:
Element 标记为
裁剪机制的
潜在目标的机制。
Element 的轮廓,
或停止此类裁剪并将轨道恢复到其未裁剪状态的机制。
我们为视频轨道定义两个裁剪状态:已裁剪和
未裁剪。轨道
初始为未裁剪,并且在对其成功调用
cropTo 时,可能转变为
已裁剪。
CropTarget 是一个有意为空的不透明标识符。其用途是作为输入传递给
cropTo。
WebIDL[Exposed=(Window,Worker), Serializable]
interface CropTarget {
[Exposed=Window, SecureContext] static Promise<CropTarget> fromElement(Element element);
};
关于 fromElement
是否应暴露到
安全上下文之外,目前尚无共识。
fromElement()
用受支持类型的 Element 调用
fromElement,
会将该 Element 与一个 CropTarget 关联。此 CropTarget 可用作
cropTo 的输入。我们将
有效
CropTarget 定义为在仍然
活动的
document 中调用
CropTarget.fromElement()
所返回的对象。
当用给定的 element 调用
fromElement 时,用户
代理以 element 作为输入
创建 CropTarget。
用户代理 MUST 返回一个 Promise p。
用户代理 MUST 只在它已完成与新 CropTarget 相关状态的所有必要内部传播之后,
才 resolve
p;在此时,用户代理 MUST 准备好接收新的 CropTarget 作为
cropTo 的有效参数。
当克隆此前已对其调用过
fromElement 的
Element 时,
克隆不会与任何 CropTarget 关联。如果稍后对该克隆调用
fromElement,
则会为其分配一个新的 CropTarget。
关于生成 CropTarget
是否应通过调用类似
CropTarget.fromElement()
的异步方法来完成,还是通过一个接受
Element 作为输入的
CropTarget 构造函数来完成,
目前尚无共识。这在
议题 #17
中有进一步讨论。
要以 element 作为输入创建 CropTarget,运行以下步骤:
令 cropTarget 为一个新的 CropTarget 类型对象。
令 weakRef 为对 element 的弱引用。
创建 cropTarget.[[Element]], 并初始化为 weakRef。
cropTarget 保持对其所表示元素的弱引用。换句话说, cropTarget 不会阻止其元素被垃圾回收。
CropTarget 对象是可序列化的。给定
value、serialized 和布尔值 forStorage,其序列化
步骤为:
如果 forStorage 为 true,则抛出一个新的 DOMException 对象,
其 name 属性
的值为 "DataCloneError"。
将 serialized.[[CropTargetElement]] 设置为
value.[[Element]]。
给定 serialized 和 value,其反序列化 步骤为:
将 value.[[Element]] 设置为
serialized.[[CropTargetElement]]。
回想一下,按照 [SCREEN-CAPTURE],当调用 getDisplayMedia()
时,
它返回一个 Promise<MediaStream>,
并且此 MediaStream
包含
恰好一个视频轨道,其类型为 MediaStreamTrack。
我们规定,如果用户选择捕获一个 browser display-surface,用户
代理 MUST 将视频轨道实例化为 MediaStreamTrack,
或 MediaStreamTrack
的某个子类,并且 cropTo MUST
暴露在此轨道上。为简单起见,本文档假定用户代理使用一个名为
BrowserCaptureMediaStreamTrack
的子类。
该轨道 MUST 初始为未裁剪。
WebIDL[Exposed = Window]
interface BrowserCaptureMediaStreamTrack : MediaStreamTrack {
Promise<undefined> cropTo(CropTarget? cropTarget);
BrowserCaptureMediaStreamTrack clone();
};
cropTo()
对此方法的调用会指示用户代理开始/停止将视频轨道裁剪到
cropTarget.[[Element]] 的
边界客户端矩形。由于该轨道被限制在
display-surface 的可见视口内,
所捕获区域将是可见视口与元素边界客户端矩形的交集。
每当调用 cropTo 时,
用户代理 MUST
执行以下算法:
如果 cropTarget 既不是有效 CropTarget,也不是 null,
则用户代理 MUST 返回一个以 UnknownError
拒绝的 Promise。
Promise。并行运行以下步骤:
undefined,
也不是有效 CropTarget,
则以 NotAllowedError
拒绝 p,并中止这些步骤。
如果 cropTarget 是 undefined
或有效
CropTarget,
则用户代理 MUST 根据
cropTarget 更新 this 视频轨道的
裁剪状态:
undefined,
则用户代理 MUST 停止
裁剪。This 视频轨道恢复为
未裁剪状态。
CropTarget 所引用元素的
轮廓。这意味着,对于轨道上产生的每个新帧,
用户代理都会计算属于该元素的像素的边界框,并将该帧裁剪到
此边界框的坐标。
将此方法调用之前的轨道状态称为 PRE-STATE, 并将此方法调用之后的状态称为 POST-STATE。用户代理 MUST 在能够保证不会再向应用交付任何根据 PRE-STATE 裁剪(或未裁剪)的帧,并且随后交付给应用的任何其他帧都将 因此按照 POST-STATE 或更后的状态裁剪(或未裁剪)时, resolve p。
cropTo promise 的 resolve 时机以及视频帧实际裁剪的时机, 可通过 JavaScript 中的 MediaStreamTrack 转换观察到。预期第一个新裁剪的视频帧会在 cropTo promise 被 resolve 之后立即入队到 MediaStreamTrack ReadableStream 上。
clone()
当 BrowserCaptureMediaStreamTrack
被克隆时,用户代理 MUST 产生一个
初始为未裁剪的
轨道,而不管原始轨道的裁剪状态如何。
我们将通过调用
fromElement 为其生成了
CropTarget 的
Element 定义为
潜在
裁剪目标。
我们将由一次成功的
cropTo 调用所
定位的潜在裁剪目标定义为
裁剪会话目标。
考虑在已裁剪视频轨道上产生的一帧。 用户代理计算 (i) 顶级 浏览上下文的视口与 (ii) 属于裁剪会话目标的所有像素的边界 框之间的交集。此交集被定义为该裁剪会话目标在该帧中的坐标。
考虑一个被裁剪到给定 裁剪会话目标 TARGET 的视频轨道 VT。我们定义 VT 的裁剪会话在 面对 TARGET 所经历的变化时的行为。
我们将裁剪会话目标附加到 DOM, 但由在顶级 浏览上下文的视口内绘制的零个像素组成的情况,定义为 空裁剪会话目标。
用户代理 MUST NOT 在具有空裁剪会话目标的轨道上产生新帧。 对于这样的轨道,如果该轨道变为未裁剪,或者如果它的 裁剪会话目标不再为 空, 则用户代理 MUST 恢复帧的产生。
我们将已从 DOM 分离的裁剪会话目标 定义为已断开连接的裁剪会话目标。
空裁剪会话目标与
已断开连接的
裁剪会话目标之间的区别在于,已断开连接的目标
可能变得
不可达,
在这种情况下它不会产生任何新帧。尽管如此,用户代理
MUST 以与处理空裁剪会话
目标相同的方式处理已断开连接的裁剪会话目标。
应用可在该轨道上用 undefined 或一个
新的 CropTarget 调用
cropTo,
从而允许该轨道上帧的产生得以
恢复。
捕获目标中的代码:
const mainContentArea = navigator.getElementById('mainContentArea');
const cropTarget = await CropTarget.fromElement(mainContentArea);
sendCropTarget(cropTarget);
function sendCropTarget(cropTarget) {
// 可以使用 postMessage() 或任何其他方式,
// 将该裁剪目标发送给此标签页中的另一个 document。
// 也可能没有其他 document,而只是由本地使用。
}
捕获文档中的代码:
async function startCroppedCapture(cropTarget) {
const stream = await navigator.mediaDevices.getDisplayMedia();
const [track] = stream.getVideoTracks();
if (!!track.cropTo) {
handleError(stream);
return;
}
await track.cropTo(cropTarget);
transmitVideoRemotely(track);
}
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in: