1. 引言
开放屏幕应用协议将浏览器连接到能够为共享受众 渲染 Web 内容的设备。通常,这些设备包括 连接互联网的电视、HDMI 加密狗和智能扬声器。
开放屏幕应用协议旨在由浏览器和设备之间的多种 连接技术支持。它对允许浏览器和设备发现彼此并向彼此进行认证有特定 需求,但这些需求可以由多种可能的实现来满足。 为了最大化互操作性,浏览器和设备应支持开放 屏幕网络协议,该协议为浏览器和设备在局域网上 发现、连接并认证彼此提供了一种方式。
开放屏幕应用协议允许浏览器呈现 URL,发起 HTML media element 的远程播放,并将媒体 数据流式传输到另一台设备。
开放屏幕应用协议旨在具有可扩展性,以便 随时间添加额外能力。这可以包括对 现有 Web API 的扩展或新的 Web API。
随附的 解释文档 提供了关于该协议的更多背景信息。
1.1. 术语
Open Screen Protocol agent(或 OSP agent)是此协议的任何实现(浏览器、 显示器、扬声器或其他软件)。
某些 OSP agent 支持 Presentation API。该 API 允许 controlling user agent 在另一台设备上发起 Web 内容的呈现。 我们简称此 agent 为 controller。receiving user agent 负责渲染 Web 内容,我们将其简称为 receiver。Web 内容本身称为 presentation。
某些 OSP agent 还支持 Remote Playback API。该 API 允许 agent 在 media element 上的 remote playback device 渲染媒体。在本文档中,我们将其称为 receiver, 因为它更短,并使 presentation 与 remote playback 之间的术语保持一致。类似地,我们使用术语 controller 来指代 启动、终止并控制远程播放的 agent。
对于媒体流式传输,我们将发送媒体流的 OSP agent 称为 media sender,将接收媒体流的 agent 称为 media receiver。请注意, 一个 agent 可以同时是 media sender 和 media receiver。
对于特定于 Presentation API 或 Remote Playback API 的其他术语和惯用语, 请查阅相应规范。
2. 需求
2.1. Agent 发现需求
定义发现 需求 [Issue #342]
2.2. 传输层需求
定义传输层 需求 [Issue #342]
2.3. Presentation API 需求
-
controller 必须能够 确定 receiver 是否 合理地 能够渲染特定的 presentation request URL。
-
给定 presentation request URL 和 presentation identifier,controller 必须能够在 receiver 上启动新的 presentation。
-
给定 receiver 上现有 presentation 的 presentation request URL 和 presentation identifier,controller 必须能够创建新的
PresentationConnection来连接到该 presentation。 -
必须能够关闭 controller 与 presentation 之间的
PresentationConnection, 并向双方发出连接关闭原因的信号。 -
多个 controller 必须能够同时连接到单个 presentation。
-
controller 发送的消息必须以可靠且有序的方式传递给 presentation(或 反之亦然)。
-
如果消息无法传递,则 controller 必须能够 向 receiver(或反之)发出信号,表示连接应以原因
error关闭。 -
controller 和 presentation 必须能够发送和接收
DOMString消息(在 ECMAScript 中表示为string类型)。 -
controller 和 presentation 必须能够发送和接收二进制 消息(在 HTML5 中表示为
Blob对象,或在 ECMAScript 中表示为ArrayBuffer或ArrayBufferView类型)。 -
给定 presentation request URL 和 presentation identifier,controller 必须能够向 receiver 发出信号 以终止 presentation。
-
当 presentation 被终止时,receiver 必须能够向所有已连接的 controller 发出信号。
2.4. Remote Playback API 需求
-
controller 必须能够 立即且持续地查明,给定
HTMLMediaElement是否至少存在一个兼容的 receiver。 -
controller 必须能够将
HTMLMediaElement的 initiate remote playback 发起到兼容 receiver。 -
controller 必须能够将来自
HTMLMediaElement的媒体源作为 URL,以及文本轨道 发送到兼容 receiver。 -
controller 必须能够将来自
HTMLMediaElement的媒体数据发送到 兼容 receiver。 -
在远程播放期间,controller 和 remote playback device 必须能够同步
HTMLMediaElement的 media element state。 -
在远程播放期间,controller 或 receiver 中任一方必须能够 与另一方断开连接。
-
controller 应能够将语言区域和文本方向信息传递给 receiver,以辅助在远程播放期间渲染文本。
2.5. 非功能性需求
-
应可以使用适度的硬件需求实现 OSP agent, 类似于低端智能手机、 智能电视或流媒体设备中的硬件。关于 agent 硬件规格,请参阅 设备 规格 文档。
-
当协议操作失败时,agent 应向用户呈现合理的信息。 例如,如果 controller 无法启动 presentation,则应可以在 controller 界面中报告这是 网络错误、认证错误,还是 presentation 内容 加载失败。
-
agent 之间的消息延迟应被最小化,以允许交互式 使用。例如,在一个 agent 中的表单里输入文字,并让文本实时显示在 presentation 中,应该是舒适的。 游戏或鼠标使用的实时 延迟是理想状态,但不是需求。
-
发起 presentation 或远程播放的 controller 应 将其首选语言区域传达给 receiver,以便它可以用该语言区域渲染 内容。
-
应可以使用规范未显式定义的可选 特性来扩展应用协议,以便利 基础 API 的实验和增强。
3. 元数据发现
为了了解进一步的元数据,agent 可以发送 agent-info-request 消息, 并接收返回的 agent-info-response 消息。任何 agent 都可以随时发送此 请求,以了解另一台设备的状态和能力, 这些状态和能力由 agent-info 消息在 agent-info-response 中描述。
如果 agent 更改其 agent-info 消息中的任何信息,则它应 向所有其他已连接的 agent 发送带有新 agent-info 的 agent-info-event 消息(无需等待 agent-info-request)。
agent-info 消息包含以下字段:
- display-name(必需)
-
agent 的显示名称,旨在由请求方显示给用户。 如果响应方未经认证,或显示名称发生变化,请求方应通过 UI 指明。
- model-name(可选)
-
如果 agent 是硬件设备,则为 该设备的型号名称。这主要用于调试目的,但也可以 显示给请求 agent 的用户。
- capabilities(必需)
-
agent 支持的控制协议、角色和媒体类型。 存在表示具有某项能力,缺失表示缺少某项 能力。能力应影响 agent 如何呈现给用户,例如根据其是否接收音频、视频或两者 绘制不同的图标。
- state-token(必需)
-
由 [0-9A-Za-z] 范围内的 8 个字符组成的随机字母数字值。 此值在 agent 建立首次连接之前设置, 并且当 agent 被重置或以其他方式丢失了与此协议相关的所有 状态时,必须设置为新值。
- locales(必需)
-
agent 用于显示本地化内容的首选语言区域,按用户偏好 顺序排列。每个条目都是 RFC5646 language tag。
各种能力具有以下含义:
- receive-audio
-
agent 可以通过其支持的其他协议渲染音频。这些其他 协议可以报告更具体的能力,例如在流式传输协议中对 某些音频编解码器的支持。
- receive-video
-
agent 可以通过其支持的其他协议接收视频。这些其他 协议可以报告更具体的能力,例如在流式传输协议中对 某些视频编解码器的支持。
- receive-presentation
-
agent 可以使用 presentation 协议接收 presentation。
- control-presentation
-
agent 可以使用 presentation 协议控制 presentation。
- receive-remote-playback
-
agent 可以使用 remote playback 协议接收远程播放。
- control-remote-playback
-
agent 可以使用 remote playback 协议控制远程播放。
- receive-streaming
-
agent 可以使用 streaming 协议接收流式传输。
- send-streaming
-
agent 可以使用 streaming 协议发送流式传输。
NOTE: 有关所有已知能力的列表,请参阅 能力注册表 (包括由本规范定义的能力,以及通过 § 10 协议扩展定义的能力)。
重写以不依赖 QUIC,或将 agent-status 消息移至网络规范 [Issue #343]
如果监听 agent 希望从广播 agent 接收消息,或 广播 agent 希望向监听 agent 发送消息,它可能希望 保持 QUIC 连接活动。一旦双方都不再需要为发送或接收消息而保持 连接活动,连接应 以错误码 5139 关闭。为了保持 QUIC 连接活动, agent 可以发送 agent-status-request 消息,任何接收到 agent-status-request 消息的 agent 都应发送 agent-status-response 消息。此类 消息应比 QUIC idle_timeout 传输 参数(参见 QUIC 中的传输参数编码)更频繁地发送,并且不应使用 QUIC PING 帧。建议使用 25 秒的 idle_timeout 传输参数。 agent 应表现得像是每当在 QUIC 流上发送消息时, 一个短于 idle_timeout 的计时器都会被重置。如果 计时器到期,则应发送 agent-status-request 消息。
如果监听 agent 希望向广播 agent 发送消息, 监听 agent 可以“按需”连接到广播 agent;它不 需要保持连接活动。
如果 OSP agent 挂起其网络连接(例如出于省电
原因),则一旦网络连接恢复,它应尝试恢复与此前已连接的 OSP agent 的 QUIC 连接。
重新连接后,它应向这些 agent 发送 agent-status-request 消息。
agent-info 和 agent-status-response 消息可以扩展为 包含本规范未定义的额外信息,如 § 10.1 协议扩展字段所述。
4. 使用 CBOR 传递消息
重写以不依赖 QUIC [Issue #343]
消息使用 CBOR 序列化。要按 顺序发送一组消息,该组消息必须在一个 QUIC 流中发送。 独立的 消息组(组之间没有排序依赖)应在 不同的 QUIC 流中发送。为了将多个 CBOR 序列化消息放入 同一个 QUIC 流,使用以下方式。
对于每条消息,OSP agent 必须向单向 QUIC 流写入 以下内容:
-
表示消息类型的类型键,编码为 variable-length integer(有关类型键,请参阅 附录 A:消息)
-
编码为 CBOR 的消息。
如果 agent 接收到一条其不识别类型键的消息,则它 必须以应用错误码 404 关闭 QUIC 连接,并且应 在 CONNECTION_CLOSE 帧的 reason phrase 中包含该未知类型键。
可变长度整数使用 Variable-Length Integer Encoding 进行编码,该编码由 QUIC 使用。
许多消息是请求和响应,因此为 它们定义了通用格式。请求和响应包含 request ID, 它是请求方选择的无符号 整数。响应必须包含与其关联的 请求的 request ID。
4.1. 类型键向后兼容性
随着消息随时间被修改或扩展,必须遵循某些规则 以维持与理解较旧版本 消息的 agent 的向后兼容性。
-
如果向消息添加或从消息移除必需字段(无论是直接对 消息,还是间接通过某个字段的字段),必须为该消息分配新的类型键。 这实际上是新消息,且不得 发送,除非已知接收 agent 理解该新类型键。
-
如果向消息添加可选字段(无论是直接添加到消息中, 还是间接通过某个字段的字段),如果不理解该新增字段的旧版接收 agent 的行为 与包含该字段的新发送 agent 兼容, 则类型键可以保持不变。 否则,必须分配新的类型键。
-
如果从消息中移除可选字段(无论是直接从消息中移除, 还是间接通过某个字段的字段),如果不理解该被移除字段的新接收 agent 的行为 与包含该字段的旧发送 agent 兼容, 则类型键可以保持不变。 否则,必须分配新的类型键。
-
不得向基于数组的消息(如 audio-frame)添加或移除 必需字段。
5. Presentation 协议
本节定义开放屏幕协议用于启动、 终止和控制由 Presentation API 定义的 presentation。§ 5.1 Presentation API 定义 Presentation API 中的 API 如何映射到本节定义的 协议消息。
为了了解哪些 receiver 对于特定 presentation request URL 或 URL 集合是 available presentation displays,controller 可以发送 presentation-url-availability-request 消息, 其值如下:
- urls
-
presentation URL 列表。不得为空。
- watch-duration
-
如果这些 URL 的可用性发生变化,controller 有兴趣接收其更新的时间段。
- watch-id
-
receiver 在发送 URL 可用性更新时必须使用的标识符,以便 controller 知道 receiver 指的是哪些 URL。
作为响应,receiver 应发送一个 presentation-url-availability-response 消息,其值如下:
- url-availabilities
-
URL 可用性状态列表。每个状态必须按列表索引对应于请求中的匹配 URL。
当 watch 仍然有效(watch-duration 尚未过期)时,如果 URL 可用性发生变化,receiver 应发送 presentation-url-availability-event 消息。 此类事件包含以下值:
- watch-id
-
presentation-url-availability-response 中给出的 watch-id, 用于指代可用性已改变的 presentation URL。
- url-availabilities
-
URL 可用性状态列表。每个状态必须对应于 watch-id 所引用的请求中的 URL。
请注意,这些消息不会广播给所有 controller。它们会 单独发送给已请求那些 URL 的可用性、且这些 URL 在原始 可用性请求的 watch duration 内发生可用性状态变化的 controller。
将省电作为 传输需求添加,并移除以下内容。[Issue #342]
为了省电,controller 可以断开 QUIC 连接,并 稍后重新连接以发送可用性请求并接收可用性 响应和更新。重新连接时 QUIC 连接 ID 可以相同,也可以不同。
为了启动 presentation,controller 可以向 receiver 发送 presentation-start-request 消息,其 值如下:
- presentation-id
- url
-
选定的 presentation URL
- headers
-
receiver 应使用这些 header 来获取 presentation URL。例如, Presentation API 的 第 6.6.1 节 表示应提供 HTTP
Accept-Languageheader。
presentation identifier 必须遵循 Presentation API 第 6.1 节定义的限制,即 它必须由至少 16 个 ASCII 字符组成。
当 receiver 接收到 presentation-start-request 时,它应在 presentation URL 已被获取并加载,或 receiver 未能这样做之后,发回 presentation-start-response 消息。如果失败,它 必须以适当结果(如 invalid-url 或 timeout)响应。如果 成功,则必须以 success 结果回复。
此外,响应必须包括以下内容:
- connection-id
-
两个 agent 都可以用来彼此发送连接消息的 ID。 它由 receiver 选择,以便实现:如果 消息接收方选择 connection-id,它可以使该 ID 在连接之间保持唯一, 从而使消息解复用/路由更容易。
响应应包括以下内容:
- http-response-code
-
获取 presentation URL(重定向之后)返回的数字 HTTP 响应码。
为了发送 presentation 消息,controller 或 receiver 可以发送 presentation-connection-message,其值如下:
- connection-id
-
来自 presentation-start-response 或 presentation-connection-open-response 消息的 ID。
- message
-
presentation 消息数据。
将以下 NOTE 重写为适用于任何 消息传输的传输需求。[Issue #342]
NOTE: OSP agent 应最小化对已发送或 已接收消息的缓冲和处理,超出严格必要(即 CBOR 序列化)的部分应避免。 消息 载荷应视为实时数据,因为它们可能用于在 agent 之间同步媒体流的播放,或用于其他低延迟用例。 [ITU-R-BT.1359-1] 中推荐的同步阈值 意味着总的 agent 到 agent 处理延迟(包括序列化、缓冲和 网络延迟)必须不大于 45 ms,以允许在媒体播放期间有效对口型同步。
为了终止 presentation,controller 可以发送 presentation-termination-request 消息,其 值如下:
- presentation-id
-
要终止的 presentation 的 ID。
- reason
-
如果应用请求终止,则设置为
application-request, 如果用户请求终止,则设置为user-request。(这些是 presentation-termination-request 中reason唯一有效的值。)
当 receiver 接收到 presentation-termination-request 时,它应 向请求的 controller 发回 presentation-termination-response 消息。
它还应通过发送 presentation-termination-event 消息来通知其他 controller 关于该终止。 如果它在没有 controller 请求的情况下终止了 presentation,也可以发送 同样的消息。此 消息包含以下值:
- presentation-id
-
已被终止的 presentation 的 ID。
- source
-
当终止是响应 presentation-termination-request 时设置为
controller,否则为receiver。 - reason
-
presentation 被终止的详细原因。
为了接受来自 controller 的传入连接请求,receiver 必须接收 并处理 presentation-connection-open-request 消息, 该消息包含 以下值:
- presentation-id
-
要连接到的 presentation 的 ID。
- url
-
要连接到的 presentation 的 URL。
receiver 在接收到 presentation-connection-open-request 消息后,应 发回 presentation-connection-open-response 消息, 该消息包含 以下值:
- result
-
指示成功或失败以及失败原因的代码
- connection-id
-
两个 agent 都可以用来彼此发送连接消息的 ID。 它由 receiver 选择,以便实现(如果 消息接收方选择 connection-id,它可以使该 ID 在连接之间保持唯一, 从而使消息解复用/路由更容易)。
- connection-count
-
接收到传入连接请求的 presentation 的新打开连接数。
如果 presentation-connection-open-response 消息 表示成功,则 receiver 还应向 与该 presentation 拥有活动 presentation 连接的所有其他端点发送 presentation-change-event,其 值为:
- presentation-id
-
刚刚接收到新 presentation 连接的 presentation 的 ID。
- connection-count
-
该 presentation 的新打开连接总数。
controller 可以在不终止 presentation 的情况下关闭连接,方法是 向 receiver 发送 presentation-connection-close-event 消息,其 值如下:
- connection-id
-
已关闭连接的 ID。
- reason
-
设置为
close-method-called或connection-object-discarded。
receiver 也可以在不终止 presentation 的情况下关闭连接。如果 它这样做,则应向 controller 发送 presentation-connection-close-event 消息,其值如下:
- connection-id
-
已关闭连接的 ID。
- reason
-
设置为
close-method-called或connection-object-discarded。 - connection-count
-
剩余的打开 presentation 连接数。
如果 receiver 关闭 presentation 连接(出于任何原因),它应向 与该 presentation 拥有打开连接的所有其他 controller 发送 presentation-change-event,其 值为:
- presentation-id
-
刚刚关闭连接的 presentation 的 ID。
- connection-count
-
剩余的打开 presentation 连接数。
Note: 当 agent 关闭 presentation 连接时,它总是成功的, 因此不需要请求和响应消息。终止 presentation 的请求可能成功也可能失败,因此需要响应消息。
5.1. Presentation API
作为 Presentation API
的 controlling user agent 的 Open
Screen Protocol agent
必须支持 control-presentation 能力。
作为 Presentation API
的 receiving user agent 的 OSP
agent
必须支持 receive-presentation 能力。
同一个 OSP agent 可以既是 controlling user agent,也是 receiving user agent。
以下是 Presentation API 如何使用 § 5 Presentation 协议:
当 第 6.4.2 节说“This list of presentation displays ... is populated based on an implementation specific discovery mechanism”时,controller 可以 使用本规范此前定义的 mDNS、QUIC、agent-info-request 和 presentation-url-availability-request 消息 来发现 receiver。
当 第 6.4.2 节说“To further save power, ... implementation specific discovery of presentation displays can be resumed or suspended.”时,agent 可以使用 前一节定义的省电机制。
当 第 6.3.4 节 说 “Using an implementation specific mechanism, tell U to create a receiving browsing context with D, presentationUrl, and I as parameters.”时,U( controller)可以向 D (receiver)发送 presentation-start-request 消息,其中 I 为 presentation identifier,presentationUrl 为所选 presentation URL。
当 第 6.3.5 节说
要
“establish a presentation connection with newConnection,”时,令 U 为
newConnection 的 presentationURL,令 I 为
newConnection. 的 presentation identifier。agent 应发送
presentation-connection-open-request 消息,其中
U 作为 url,I 作为
presentation-id。
当 第 6.5.2 节说“Using an implementation specific mechanism, transmit the contents of messageOrData as the presentation message data and messageType as the presentation message type to the destination browsing context”时, controller 可以发送 presentation-connection-message,其中 messageOrData 作为 presentation 消息数据。请注意,messageType 嵌入在已编码的 CBOR 类型中,不需要消息中的额外值。
当 第 6.5.5 节 说 “Start to signal to the destination browsing context the intention to close the corresponding PresentationConnection”时,agent 可以向另一个 agent 发送 presentation-connection-close-event 消息, 并在需要时带有 destination browsing context 和 presentation-change-event。
当
第
6.5.6 节说“Send a termination request for the presentation to its receiving
user agent using an implementation specific mechanism”时,controller 可以向
receiver 发送 presentation-termination-request 消息,其
reason
为 application-request。
当 第 6.7.1 节 说“it MUST listen to and accept incoming connection requests from a controlling browsing context using an implementation specific mechanism”时,receiver 必须接收并处理 presentation-connection-open-request。
当 第 6.7.1 节说“Establish the connection between the controlling and receiving browsing contexts using an implementation specific mechanism.”时,receiver 必须发送 presentation-connection-open-response 消息,并在需要时发送 presentation-change-event 消息。
6. 时间表示
§ 7 Remote Playback 协议 和 § 8 Streaming 协议 以 time scale 的形式表示时间点和持续时间。time scale 是时间值的公分母,它允许值被 表示为有理数而不会损失精度。time scale 以 赫兹表示,例如 90000 表示 90000 Hz,这是视频常用的 time scale。
7. Remote Playback 协议
本节定义开放屏幕协议用于启动、终止 和控制由 Remote Playback API 定义的媒体远程播放。§ 7.2 Remote Playback API 定义 Remote Playback API 中的 API 如何映射到本节定义的协议消息。
对于本节定义的所有消息,请参阅 附录 A:消息以获取完整的 CDDL 定义。
为了了解哪些 receiver 对于特定 URL 或 URL 集合是 compatible remote playback devices,controller 可以发送 remote-playback-availability-request 消息,其 值如下:
- sources
-
media resources 列表,与 remote-playback-start-request 消息中指定的相同。不得 为空。
- headers
-
receiver 应用于获取 URL 的 header。例如, Remote Playback API 第 6.2.4 节说应提供 Accept-Language header。
- watch-duration
-
如果这些 URL 的可用性发生变化,controller 有兴趣接收其更新的时间段。
- watch-id
-
receiver 在发送 URL 可用性更新时必须使用的标识符,以便 controller 知道 receiver 指的是哪些 URL。
作为响应,receiver 应发送 remote-playback-availability-response 消息,其值如下:
- url-availabilities
-
URL 可用性状态列表。每个状态必须按列表索引对应于请求中的匹配 URL。
如果 URL 可用性发生变化,receiver 稍后(直到当前时间加上请求 watch-duration)应发送 remote-playback-availability-event 消息。 此类事件包含以下值:
- watch-id
-
remote-playback-availability-response 中给出的 watch-id, 用于指代可用性已改变的 remote playback URL。
- url-availabilities
-
URL 可用性状态列表。每个状态必须对应于 watch-id 所引用的请求中的 URL。
请注意,这些消息不会广播给所有 controller。它们会 单独发送给已请求那些 URL 的可用性、且这些 URL 在原始 可用性请求的 watch duration 内发生可用性状态变化的 controller。
将省电作为 传输需求添加,并移除以下内容。[Issue #342]
为了省电,controller 可以断开 QUIC 连接,并 稍后重新连接以发送可用性请求并接收可用性 响应和更新。重新连接时 QUIC 连接 ID 可以相同,也可以不同。
为了启动远程播放,controller 可以向 receiver 发送 remote-playback-start-request 消息,其 值如下:
- remote-playback-id
-
此远程播放的标识符。它在所有远程播放中应具有全局唯一性。
Note: 推荐使用版本 4(伪随机)UUID, 因为它满足 remote-playback-id 的需求。
- sources(可选)
-
controller 为在 receiver 上播放而选择的 media resources。每个 source 必须包含
source URL, 并且在 media resource 可用时应包含extended MIME type。 如果sources缺失或为空, 则remoting字段必须填充,因为 controller 将使用 streaming session 发送编码媒体。 - text-track-urls
-
与 media resources 关联的文本轨道 URL。
- controls
-
用于修改远程播放初始状态的初始控制项,如 § 7.1 Remote Playback 状态和 控制中所定义。controller 可以在知道 receiver 支持它们之前发送 对 receiver 来说可选支持的 controls。 如果 receiver 不支持它们,则它会 忽略它们,而 controller 将从 remote-playback-start-response 消息中得知它不支持它们。
- remoting(可选)
-
用于启动与此远程播放关联的 streaming session 的参数。 如果未包含,则不启动 streaming session。 当
sources缺失或为空时必需。
当 receiver 接收到 remote-playback-start-request 消息时,它应 发回 remote-playback-start-response 消息。它应快速执行, 通常在 media resource 加载完成之前, 而是通过 remote-playback-state-event 消息给出加载进度更新,除非 receiver 决定完全不尝试加载该资源。如果它选择 不加载,则必须以适当的失败结果(如 timeout 或 invalid-url)响应。此外,响应必须包含以下内容:
- state
-
远程播放的初始状态,如 § 7.1 Remote Playback 状态和控制 中所定义。
- remoting(可选)
-
对与此远程播放关联且已启动的 streaming session 的响应。 如果未包含,则未启动 streaming session。
如果启动了 streaming session,则可以像 streaming session 已通过 streaming-session-start-request 和 streaming-session-start-response 启动一样,使用 streaming-session-modify-request 和 video-frame 等 streaming 消息。 streaming session 可以在 remote playback 终止之前终止, 但如果 remote playback 先被 终止,则与其关联的 streaming session 会自动 终止。
如果 controller 希望修改远程播放的状态(例如 暂停、恢复、跳转等),它可以发送 remote-playback-modify-request 消息,其 值如下:
- remote-playback-id
-
要修改的远程播放的 ID。
- controls
-
按 § 7.1 Remote Playback 状态和控制定义的更新控制项。
当 receiver 接收到 remote-playback-modify-request 时,它应回复 remote-playback-modify-response 消息, 其值如下:
- state
-
远程播放的更新状态,如 § 7.1 Remote Playback 状态和控制 中所定义。
当 remote playback 的状态在没有 controller 修改请求的情况下发生变化 (例如由于 receiver 上的用户交互而跳转或暂停)时, receiver 可以向 controller 发送 remote-playback-state-event。
receiver 应在以下情况下发送 remote-playback-state-event 消息:
调用以下任一方法:
自上次发送 remote-playback-state-event 消息以来,以下任一属性发生可观察变化:
与播放关联的 timeline offset 自上次发送 remote-playback-state-event 消息以来发生变化:
stalled
事件需要在关联的
HTMLMediaElement
实例上触发。
自上次 remote-playback-state-event 消息以来超过 250ms, 且以下任一属性自上次 remote-playback-state-event 消息以来发生可观察变化。 任何新的连续变化 属性都属于此规则。
NOTE: media element 被要求每 250ms 或更短时间触发一次 timeupdate
事件。
- remote-playback-id
-
状态已改变的远程播放的 ID。
- state
-
远程播放的更新状态,如 § 7.1 Remote Playback 状态和控制 中所定义。
为了终止远程播放,controller 可以发送 remote-playback-termination-request 消息,其 值如下:
- remote-playback-id
-
要终止的远程播放的 ID。
- reason
-
远程播放被终止的原因。
当 receiver 接收到 remote-playback-termination-request 时,它应 向 controller 发回 remote-playback-termination-response 消息。
如果 receiver 在没有 controller 请求的情况下终止了 remote playback, 它必须向 controller 发送 remote-playback-termination-event 消息,其值如下:
- remote-playback-id
-
已被终止的远程播放的 ID。
- reason
-
远程播放被终止的原因。
如 Remote Playback API 第 6.2.7 节所述,终止远程播放意味着 controller 不再控制 远程播放,并不一定会停止 receiver 上的媒体 渲染。receiver 是否停止渲染媒体取决于 receiver 的实现。
7.1. Remote Playback 状态和控制
为了让 controller 和 receiver 在 remote playback 的 状态方面保持同步,controller 可以发送 controls 来修改状态 (例如通过 remote-playback-modify-request 消息),而 receiver 可以发送关于状态变化的更新(例如通过 remote-playback-state-event 消息)。
controller 发送的 controls 包括以下单独的 control
值,每个值都是可选的。这允许 controller 一次更改一个
control 值或多个 control 值,而不必每次指定所有
control 值。不存在的 control 值表示没有变化。存在的
control 值表示如下定义的变化。这些 controls
有意映射
HTMLMediaElement
的可设置属性和方法。
- source
-
更改 media resource。更多详细信息见
HTMLMediaElement.src。 不得用于 remote-playback-start-request 消息的初始 controls 中(该消息 已包含 media resource)。 - preload
-
设置预加载媒体的积极程度。更多详细信息见
HTMLMediaElement.preload。 应仅用于 remote-playback-start-request 消息的初始 controls 中, 或在 source 被更改时使用。如果未在初始 controls 中设置,则由 receiver 决定。 这对于 receiver 支持来说是可选的,如果不支持, receiver 将表现得像它从未被设置一样。 - loop
-
设置是否循环播放媒体。更多详细信息见
HTMLMediaElement.loop。 应仅用于 remote-playback-start-request 的初始 control 中。如果未在 初始 controls 中设置,则假定为 false。 - paused
-
如果为 true,则暂停;如果为 false,则恢复。更多详细信息见
HTMLMediaElement.pause()和HTMLMediaElement.play()。 如果未在初始 controls 中设置,则由 receiver 决定。 - muted
-
如果为 true,则静音;如果为 false,则取消静音。更多详细信息见
HTMLMediaElement.muted。 如果未在初始 controls 中设置,则由 receiver 决定。 - volume
-
将音量设置在 0.0 到 1.0(含)范围内。更多详细信息见
HTMLMediaElement.volume。 如果未在初始 controls 中设置,则由 receiver 决定。 - seek
-
跳转到精确时间。更多详细信息见
HTMLMediaElement.currentTime。 - fast-seek
-
尽可能快地跳转到近似时间。更多详细信息见
HTMLMediaElement.fastSeek()。 - playback-rate
-
设置媒体播放的速率。更多详细信息见
HTMLMediaElement.playbackRate。 如果未在初始 controls 中设置,则由 receiver 决定。这对于 receiver 支持来说是可选的,如果不 支持,receiver 将表现得像它从未被设置一样。 - poster
-
设置在视频数据不可用时显示的图像 URL。更多详细信息见 poster frame。 如果未在初始 controls 中设置,则不使用 poster, receiver 可以选择在视频数据不可用时渲染什么。这 对于 receiver 支持来说是可选的,如果不支持,receiver 将表现得像它从未被设置一样。
- enabled-audio-track-ids
-
按 ID 启用包含的音频轨道,并禁用所有其他音频轨道。更多详细信息见
HTMLMediaElement.audioTracks。 - selected-video-track-id
-
按 ID 选择给定的视频轨道,并取消选择所有其他视频轨道。更多详细信息见
HTMLMediaElement.videoTracks。 - added-text-tracks
-
添加具有给定 kinds、labels 和 languages 的文本轨道。更多详细信息见
HTMLMediaElement.addTextTrack()。 这对于 receiver 支持来说是可选的,如果不 支持,receiver 将表现得像它从未被设置一样。 - changed-text-tracks
-
按 ID 更改文本轨道。所有其他文本轨道保持 不变。设置 mode、添加 cues,并按 id 移除 cues。更多详细信息见
HTMLMediaElement.textTracks。 请注意,未来规范或此规范的扩展预计会向 text-track-cue 添加新字段(例如文本大小、对齐、位置等)。 添加和移除 cues 对于 receiver 支持来说是可选的,如果不支持, receiver 将表现得像没有添加或移除任何 cues(添加和 移除都通过对“added-cues”的支持来指示)。如HTMLMediaElement.textTracks所指定,如果 cue ID 无效(例如移除未添加的 ID 或重复添加同一 ID), receiver 可以拒绝文本轨道更改。
| 字段 | 初始 controls 的默认值 | Receiver 支持 |
|---|---|---|
| source | remote-playback-start-request 中的
urls
| 必需 |
| preload | 由 receiver 决定 | 非必需 |
| loop | False | 必需 |
| paused | 由 receiver 决定 | 必需 |
| muted | 由 receiver 决定 | 必需 |
| volume | 由 receiver 决定 | 必需 |
| seek | (无) | 必需 |
| fast-seek | (无) | 必需 |
| playback-rate | 由 receiver 决定 | 非必需 |
| poster | 由 receiver 决定 | 非必需 |
| enabled-audio-track-ids | (无) | 必需 |
| selected-video-track-id | (无) | 必需 |
| added-text-tracks | (无) | 非必需 |
| changed-text-tracks | (无) | 非必需 |
7.2. Remote Playback API
实现
Remote Playback
API 的 Open
Screen Protocol agent 必须支持
control-remote-playback 能力。它可以支持 send-streaming
能力,以便它可以通过媒体
远程处理发送 HTMLMediaElement
媒体数据。
作为
Remote Playback
API 的 remote playback device 的 OSP
agent
必须支持
receive-remote-playback 能力。它可以支持 receive-streaming
能力,以便它可以通过媒体远程处理接收 HTMLMediaElement
数据。
同一个 OSP agent 可以同时实现 Remote Playback API 并作为该 API 的 remote playback device。
以下是 Remote Playback API 如何使用 § 7 Remote Playback 协议中定义的消息:
当 第 5.2.1.2 节说“This list contains remote playback devices and is populated based on an implementation specific discovery mechanism”,且 第 5.2.1.4 节说“Retrieve available remote playback devices (using an implementation specific mechanism)”时,用户代理可以使用本规范先前定义的 mDNS、QUIC、 agent-info-request 和 remote-playback-availability-request 消息 来发现 receivers。remote-playback-availability-request URL 必须 包含 availability sources set。
当 第 5.2.4 节说“Request connection of remote to device. The implementation of this step is specific to the user agent.”和“Synchronize the current media element state with the remote playback state”时,controller 可以向 receiver 发送 remote-playback-start-request 消息以启动远程 播放。remote-playback-start-request URL 必须包含 remote playback source。当前 Remote Playback API 只 允许单个 source,但该协议允许多个 source,未来版本的 Remote Playback API 可以允许多个 source。
当 第 5.2.4 节说“The mechanism that is used to connect the user agent with the remote playback device and play the remote playback source is an implementation choice of the user agent. The connection will likely have to provide a two-way messaging abstraction capable of carrying media commands to the remote playback device and receiving media playback state in order to keep the media element state and remote playback state in sync”时,controller 可以向 receiver 发送 remote-playback-modify-request 消息, 以基于本地媒体元素的变化来更改 remote playback state,并接收 remote-playback-modify-response 和 remote-playback-state-event 消息, 以基于 remote playback state 的变化来更改本地媒体元素。
当 第 5.2.7 节说“Request disconnection of remote from the device. The implementation of this step is specific to the user agent,”时,controller 可以 向 receiver 发送 remote-playback-termination-request 消息。
8. Streaming 协议
本节定义开放屏幕协议用于从 media sender 向 media receiver 流式传输 媒体。
如果 Open
Screen Protocol agent 是 media sender,则它必须
通告
send-streaming 能力。如果 OSP agent 是 media receiver,则它必须
通告 receive-streaming 能力。同一个 agent 可以既是 media
sender 又是 media receiver。
8.1. Streaming 协议能力
如果 advertiser 已经认证,则 requester 能够通过发送 streaming-capabilities-request 消息来请求额外信息,并接收返回的 streaming-capabilities-response 消息,该消息包含 以下字段:- receive-audio(必需)
-
接收音频的能力列表。有关字段说明,请见下文。
- receive-video(必需)
-
接收视频的能力列表。有关字段说明,请见下文。
format 类型用作音频和视频能力的基础。 Format 由以下字段组成:
- codec-name(必需)
-
列在 [WEBCODECS-CODEC-REGISTRY] 中的完全限定 codec 字符串,并由该注册表中引用的 codec 特定注册进一步 指定。
对于 codec-name,Open Screen agents 也可以接受如 codec
parameter 中所述的单 codec 参数,该参数见 [RFC6381],
用于未列在
[WEBCODECS-CODEC-REGISTRY] 中的 codec。
音频能力由上述 format 类型组成,并带有以下 额外字段:
- max-audio-channels(可选)
-
一个可选字段,指示 media receiver 能够支持的最大音频 声道数。默认值为“2”,表示 立体声扬声器声道设置。
- min-bit-rate(可选)
-
一个可选字段,指示 media receiver 可处理的最小音频比特率,以千比特每秒为单位。默认无最小值。
视频能力同样由上述 format 类型组成,并带有 以下额外字段:
- max-resolution(可选)
-
一个可选字段,指示 media receiver 能够处理的最大视频分辨率(宽度、高度)。 默认无最大值。
- max-frames-per-second(可选)
-
一个可选字段,指示 media receiver 能够处理的最大每秒帧数。默认无最大值。
- max-pixels-per-second(可选)
-
一个可选字段,指示 media receiver 能够处理的最大每秒像素数,以像素每秒为单位。默认无最大值。
- min-video-bit-rate(可选)
-
一个可选字段,指示设备 能够处理的最小视频比特率,以千比特每秒为单位。默认无最小值。
- aspect-ratio(可选)
-
一个可选字段,指示其理想宽高比,例如 16:10 显示器可以返回 1.6 作为此值,以表示其首选内容 缩放。默认无。
- color-gamut(可选)
-
一个可选字段,指示 media receiver 可解码和 渲染的最宽色彩空间。media sender 可以使用此值来 确定如何编码视频,并且应假定所有较窄的色彩空间 都受支持。有效值对应于 ColorGamut, 其位于 Media Capabilities API 中。默认值为 “srgb”。
NOTE: 对“p3”的支持意味着支持“srgb”,且 对“rec2020”的支持 意味着支持“p3”和“srgb”。
- hdr-formats(可选)
-
一个可选字段,指示 media receiver 可以解码和渲染哪些 HDR 传递函数和元数据格式。每个
video-hdr-format由两个字段组成:transfer-function和hdr-metadata。transfer-function字段必须是有效的 TransferFunction, 且hdr-metadata字段必须是有效的 HdrMetadataType,二者均 定义于 Media Capabilities API。如果提供的
video-hdr-format带有transfer-function但没有hdr-metadata,则 media receiver 可以渲染没有任何关联元数据的transfer-function。 (例如,“hlg”transfer-function就是这种情况。)media receiver 应忽略
hdr-formats.中的重复条目。 如果未列出hdr-formats,则 media receiver 无法解码任何 HDR 格式。 - native-resolutions(可选)
-
一个可选字段,指示 media receiver 支持哪些视频分辨率,并 将其视为“native”,即不需要缩放。 默认值为无。
- supports-scaling(可选)
-
一个可选布尔字段,指示 media receiver 是否可以缩放 未列在 native-resolutions 列表(如果提供)中的视频分辨率内容, 或不同宽高比的内容。默认值为 true。
- supports-rotation(可选)
-
一个可选布尔字段,指示 media receiver 是否可以接收 设置了 rotation 字段的视频帧。默认值为 true。
8.2. 会话
为了启动 streaming session,sender 可以发送 streaming-session-start-request 消息,其 字段如下:
- streaming-session-id
-
标识 streaming session。必须对(sender, receiver)对唯一。稍后可用于修改或终止 streaming session。关于
state-token,这些 ID 应像其他 ID 一样处理,如 § 9 请求、响应和监视中所指定。 - desired-stats-interval
-
指示 receiver 应向 sender 发送 stats 消息的频率。
- stream-offers
-
指示 receiver 可从 sender 请求的 stream。
每个 stream offer 包含以下字段:
- media-stream-id
-
标识正在提供的 media stream。必须在 streaming session 内唯一。receiver 可使用它来请求 media session。关于
state-token,这些 ID 应像其他 ID 一样处理,如 § 9 请求、响应和监视中所指定。 - display-name
-
一个可选名称,旨在向用户显示,使得 receiver 可以允许用户选择要接收哪些 media stream, 或者如果它们由 receiver 自动接收, 则向用户提供一些关于该 media stream 是什么的信息。
- audio
-
所提供的音频 encoding 列表。音频 encoding 是一系列 编码音频帧。Encoding 定义 receiver 了解如何解码该 encoding 所需的字段,例如 codec。 它们可以因 codec 和相关字段而不同,但应是同一音频的不同 encoding。
- video
-
所提供的视频 encoding 列表。视频 encoding 是一系列 编码视频帧。Encoding 定义 receiver 了解如何解码该 encoding 所需的字段,例如 codec 和 默认持续时间。它们可以因 codec 和潜在其他 字段而不同,但应是同一视频的不同 encoding。
- data
-
所提供的数据 encoding 列表。数据 encoding 是一系列 数据帧。Encoding 定义 receiver 了解如何解释该 encoding 所需的字段,例如数据类型和 默认持续时间。它们可以因数据类型和潜在其他 字段而不同,但应是同一数据的不同 encoding。 (对于不同数据的 encoding,请使用不同的 media stream, 而不是在同一个 media stream 中使用不同的 encoding)。
所提供的每个音频 encoding 定义以下字段:
- encoding-id
-
标识正在提供的音频 encoding。必须在 media stream 内唯一。关于
state-token,这些 ID 应像 request ID 一样处理,如 § 9 请求、响应和监视中所指定。 - codec-name
-
encoding 所使用的 codec 名称,遵循 § 8.1 Streaming 协议能力中
codec-name的相同 规则。 - time-scale
-
所有音频帧使用的 time scale。 这允许 sender 通过不在每个 audio-frame 消息中包含 time scale 来缩小 audio-frame 消息。
- default-duration:
-
音频帧的持续时间。这允许 sender 通过 对具有默认持续时间的 audio-frame 消息不包含 duration, 来缩小 audio-frame 消息。
所提供的每个视频 encoding 定义以下字段:
- encoding-id
-
标识正在提供的视频 encoding。必须在 media stream 内唯一。关于
state-token,这些 ID 应像 request ID 一样处理,如 § 9 请求、响应和监视中所指定。 - codec-name
-
encoding 所使用的 codec 名称,遵循 § 8.1 Streaming 协议能力中
codec-name的相同 规则。 - time-scale
-
所有视频帧使用的 time scale。 这允许 sender 通过不在每个 video-frame 消息中包含 time scale 来缩小 video-frame 消息。
- default-duration:
-
视频帧的默认持续时间。这允许 sender 通过 对具有默认持续时间的 video-frame 消息不包含 duration, 来缩小 video-frame 消息。
- default-rotation:
-
视频帧的默认旋转。这允许 sender 通过 对具有默认旋转的 video-frame 消息不包含 rotation, 来缩小 video-frame 消息。
所提供的每个数据 encoding 定义以下字段:
- encoding-id
-
标识正在提供的数据 encoding。必须在 media stream 内唯一。关于
state-token,这些 ID 应像 request ID 一样处理,如 § 9 请求、响应和监视中所指定。 - data-type-name
-
encoding 使用的数据类型名称。
- time-scale
-
所有数据帧使用的 time scale。 这允许 sender 通过不在每个 data-frame 消息中包含 time scale 来缩小 data-frame 消息。
- default-duration:
-
数据帧的持续时间。这允许 sender 通过 对具有默认持续时间的 data-frame 消息不包含 duration, 来缩小 data-frame 消息。
在接收到 streaming-session-start-request 消息后,receiver 应发回 streaming-session-start-response 消息,其 字段如下:
- desired-stats-interval
-
指示 sender 应向 receiver 发送 stats 消息的频率。
- stream-requests
-
指示 receiver 想从 sender 接收哪些 media stream。
每个 stream request 包含以下字段:
- media-stream-id
-
所请求 stream 的 ID。
- audio(可选)
-
请求的音频 encoding,按 encoding ID 指定
- video(可选)
-
请求的视频 encoding,按 encoding ID 指定。它可以 包含目标分辨率和最大帧率。sender 不应超过最大帧率,并应尝试 按目标比特率发送,可能会略微超过。
- data(可选)
-
请求的数据 encoding,按 encoding ID 指定
在 streaming session 期间,receiver 可以通过发送包含修改后 stream-requests 列表的 streaming-session-modify-request 来修改它对 encoding 提出的请求。当 sender 接收到 streaming-session-modify-request 时,它应发回 streaming-session-modify-response,指示来自 streaming-session-modify-request 的新请求 是否已成功应用。
NOTE: 如果 sender 希望发送不同于 receiver 在 streaming-session-start-response 或 streaming-session-modify-request 中所选的 encoding, 则它必须终止当前 session 并启动新 session。
最后,sender 可以通过发送 streaming-session-terminate-request 命令来终止 streaming session。当 receiver 接收到 streaming-session-terminate-request 时,它应 发回 streaming-session-terminate-response。receiver 可以在任何时候终止,并通过发送 streaming-session-terminate-event 消息通知 sender。
8.3. 音频
Media senders 可以通过发送 audio-frame 消息(见 附录 A:消息)以及以下键和值,向 media receivers 发送音频。 audio frame 消息包含某个 时间范围内的一组编码音频样本。共享 codec 和 时间轴的一系列编码音频帧构成音频 encoding。
与大多数 Open Screen Protocol 消息不同,此消息使用 基于数组的分组,而不是基于 struct 的分组。对于 必需字段,这允许更高效地使用线上字节, 这对于流式传输音频很重要,因为其载荷 通常很小,每个字节的开销都相对较大。为了 在基于数组的分组中容纳可选值,数组中的一个 可选字段用于保存基于 struct 分组中的所有可选值。 这有望在效率和灵活性之间提供良好平衡。
重写以下内容以 不依赖 QUIC 细节。[Issue #343]
为了允许音频帧乱序发送,它们应在 单独的 QUIC 流中发送。
- encoding-id
-
标识此 audio frame 所属的 media encoding。这可用于 引用 encoding 的字段(来自 audio-encoding-offer 消息),例如 codec、codec 属性、 time scale 和默认 duration。 通过 encoding id 引用 encoding 的字段 有助于避免在每个 frame 中发送重复信息。
- start-time
-
标识 audio frame 时间范围的起点。 结束时间可以从 start time 和 duration 推断出来。 time scale 等于
encoding-id所引用的 audio-encoding-offer 消息中time-scale字段的值。 - duration
-
如果存在,则为 audio frame 的 duration。如果不存在,则 duration 等于
encoding-id所引用的 audio-encoding-offer 消息中default-duration字段的值。 time scale 等于encoding-id所引用的 audio-encoding-offer 消息中time-scale字段的值。 - sync-time
-
如果存在,则为用于将此 audio frame(以及 因此,此 encoding)的 start time 与不同时间轴上的其他 media encoding 同步的时间。它可以是挂钟时间,但不必 是;它可以是 media sender 选择的任何时钟。
- payload
-
编码音频。codec 等于
encoding-id所引用的 audio-encoding-offer 消息的codec-name字段。
8.4. 视频
Media sender 可以通过发送 video-frame 消息(见 附录 A:消息)以及以下键和值,向 media receiver 发送视频。 video frame 消息包含在特定 时间点或特定时间范围内(如果 duration 已知)的编码视频帧(编码图像)。 共享 codec 和时间轴的一系列 编码视频帧构成视频 encoding。
重写以下内容以 不依赖 QUIC 细节。[Issue #343]
为了允许视频帧乱序发送,它们可以在 单独的 QUIC 流中发送。如果 encoding 是一长串编码视频帧, 这些帧一直依赖前一个帧直到某个独立帧,则 将它们放在单个 QUIC 流中发送可能是合理的,该流从独立帧开始, 到最后一个依赖帧结束。
- encoding-id
-
标识此 video frame 所属的 media encoding。 这可用于引用 encoding 的字段,例如 codec、codec 属性、time scale 和默认 rotation。 通过 encoding id 引用 encoding 的字段有助于 避免在每个 frame 中发送重复信息。
- sequence-number
-
标识该 frame 及其在 encoding 中的顺序。 在一个 encoding 内,更大的 sequence number 表示更晚的 start time。 在一个 encoding 内,sequence number 的间隙表示 frame 缺失。
- depends-on
-
如果存在,则为此 frame 依赖的 frame 的 sequence number。 如果 sequence number 为负,则将其视为相对 sequence number, 并通过将其加到此 frame 的 sequence number 来计算 sequence number。 如果为空,则这是一个独立帧(key frame)。 如果不存在,则默认值为 [-1]。
- start-time
-
标识 video frame 时间范围的起点。 结束时间可以从 start time 和 duration 推断出来。 time scale 等于
encoding-id所引用的 video-encoding-offer 消息中time-scale字段的值。 - duration
-
如果存在,则为 video frame 的 duration。如果不存在,则 表示 duration 未知。time scale 等于
encoding-id所引用的 video-encoding-offer 消息中time-scale字段的值。 - sync-time
-
如果存在,则为用于将此 frame(以及 因此,此 encoding)的 start time 与不同 时间轴上的其他 media encoding 同步的时间。
- rotation
-
如果存在,则指示在解码后但渲染前 应如何旋转该 frame。Rotation 是按 90 度增量顺时针旋转。默认值等于
encoding-id所引用的 video-encoding-offer 消息的default-rotation字段。 - payload
-
编码视频帧(编码图像)。codec 等于
encoding-id所引用的 video-encoding-offer 消息的codec-name字段。
8.5. 数据
Media sender 可以通过发送 data-frame 消息(见 附录 A:消息)以及以下键和值,向 media receiver 发送定时数据。 data frame 消息包含可与音频和视频同步的任意载荷。 共享数据类型和时间轴的一系列 data frame 构成数据 encoding。
重写以下内容以 不依赖 QUIC 细节。[Issue #343]
为了允许 data frame 乱序发送,它们可以在单独的 QUIC 流中发送,但如果对特定数据类型有意义, 多个 data frame 可以在一个 QUIC 流中发送。
- encoding-id
-
标识此 data frame 所属的数据 encoding。这可用于 引用 encoding 的字段,例如数据类型和 time scale。通过 encoding id 引用 encoding 的字段 有助于避免在每个 frame 中发送重复信息。
- sequence-number
-
标识该 frame 及其在 encoding 中的顺序。 在一个 encoding 内,更大的 sequence number 表示更晚的 start time。 在一个 encoding 内,sequence number 的间隙表示 frame 缺失。
- start-time
-
标识 data frame 时间范围的起点。 结束时间可以从 start time 和 duration 推断出来。 time scale 等于
encoding-id所引用的 data-encoding-offer 消息中time-scale字段的值。 - duration
-
如果存在,则为 data frame 的 duration。如果不存在,则 duration 等于
encoding-id所引用的 data-encoding-offer 消息中default-duration字段的值。 time scale 等于encoding-id所引用的 data-encoding-offer 消息中time-scale字段的值。 - sync-time
-
如果存在,则为用于将此 data frame(以及 因此,此 encoding)的 start time 与不同 时间轴上的其他 media encoding 同步的时间。
- payload
-
数据。数据类型等于
encoding-id所引用的 data-encoding-offer 消息的data-type-name字段。
8.6. 反馈
media receiver 可以向 media sender 发送反馈,例如 key frame 请求。
通过发送带有以下键和值的 video-request 消息来请求 video key frame。
重写以下内容以 不依赖 QUIC 细节。[Issue #343]
为了允许视频帧乱序发送,它们可以在单独的 QUIC 流中发送。
- encoding-id
-
media sender 应为其发送新 key frame 的 encoding。
- sequence-number
-
给出 encoding 中的顺序。 在一个 encoding 内,更大的 sequence number 会使先前的 sequence number 失效。 media sender 可以在处理较大的 sequence number 后忽略较小的 sequence number。 这是为了防止乱序请求生成超出必要数量的 key frame。
- highest-decoded-frame-sequence-number: uint
-
如果设置,则 media sender 可以生成依赖于最后解码 frame 的 video frame。如果未设置,则 media sender 必须生成独立(key)frame。
8.7. 统计信息
在 streaming session 期间,sender 应按 receiver 请求的间隔 使用 streaming-session-sender-stats-event 发送 stats。 它应为其正在发送的所有 media stream 发送以下所有 stats。 streaming-session-sender-stats-event 消息 包含以下 字段:
- streaming-session-id
-
这些 stats 所适用的 streaming session 的 ID。
- system-time
-
计算 stats 的时间,使用单调系统 时钟。
- audio
-
特定于音频的 stats。可以一次发送多个 encoding 的 stats, 但如果 stats 没有 变化,则无需包含 encoding。见下文。
- video
-
特定于视频的 stats。可以一次发送多个 encoding 的 stats, 但如果 stats 没有 变化,则无需包含 encoding。见下文。
音频 encoding sender stats 包含以下字段:
- encoding-id
-
stats 所适用的 encoding 的 ID。
- cumulative-sent-frames
-
已发送 frame 的总数。
- cumulative-encode-delay
-
编码已发送 frame 所花费时间的总和。
视频 encoding sender stats 包含以下字段:
- encoding-id
-
stats 所适用的 encoding 的 ID。
- cumulative-sent-duration
-
所有已发送音频帧的所有 duration 之和。
- cumulative-encode-delay
-
编码已发送 frame 所花费时间的总和。
- cumulative-dropped frames
-
由于网络、CPU 或其他约束而未发送的 frame 总数。
在 streaming session 期间,receiver 应按 sender 请求的间隔 使用 streaming-session-receiver-stats-event 发送 stats。 它应为其正在接收的所有 media stream 发送以下所有 stats。
如果 receiver 使用缓冲区在播放前保存 frame,则它
还应使用 remote-buffer-status 字段发送该缓冲区的状态。
它可以具有以下三个值之一:
-
enough-data:缓冲区既没有过多数据,也没有不足数据。 -
insufficient-data:缓冲区将发生欠载,并且在计划播放时没有足够的 frame 数据。 -
too-much-data:按当前发送速率,缓冲区将溢出,未来的 frame 数据会在播放前被丢弃。
接收到 insufficient-data 状态的 sender 应增加其发送
速率,或为未来 frame 切换到更高效的 encoding。接收到
too-much-data 状态的 sender 应降低其发送速率。
如果 receiver 立即播放 frame 而不缓冲,则它应始终
报告 enough-data 缓冲状态。
streaming-session-receiver-stats-event 消息 包含 以下字段:
- streaming-session-id
-
这些 stats 所适用的 streaming session 的 ID。
- system-time
-
计算 stats 的时间,使用单调系统 时钟。
- audio
-
特定于音频的 stats。可以一次发送多个 encoding 的 stats, 但如果 stats 没有 变化,则无需包含 encoding。见下文。
- video
-
特定于视频的 stats。可以一次发送多个 encoding 的 stats, 但如果 stats 没有 变化,则无需包含 encoding。见下文。
音频 encoding receiver stats 包含以下字段。如果不存在, 则表示该值自上次值以来没有变化。
- encoding-id
-
stats 所适用的 encoding 的 ID。
- cumulative-decoded-frames
-
已接收并解码的音频帧总数。
- cumulative-received-duration
-
所有已接收音频帧的所有 duration 之和。
- cumulative-lost-duration
-
所有检测为丢失的音频帧的所有 duration 之和。
- cumulative-buffer-delay
-
frame 在接收和播放输出之间缓冲所花费时间的总和。
- cumulative-decode-delay
-
解码已接收 frame 所花费时间的总和。
- remote-buffer-status : streaming-buffer-status
-
此 encoding 的远程缓冲区状态。
视频 encoding receiver stats 包含以下字段。如果不存在, 则表示该值自上次 值以来没有变化。
- encoding-id
-
stats 所适用的 encoding 的 ID。
- cumulative-decoded-frames
-
已接收并解码的视频帧总数。
- cumulative-lost-frames
-
检测为丢失的视频帧总数。
- cumulative-buffer-delay
-
frame 在接收和渲染之间缓冲所花费时间的总和。
- cumulative-decode-delay
-
解码已接收 frame 所花费时间的总和。
- remote-buffer-status : streaming-buffer-status
-
此 encoding 的远程缓冲区状态。
9. 请求、响应和监视
Open Screen Protocol 中的多个子协议具有充当
请求、响应、监视和事件的消息。大多数请求具有 request-id,且
接收请求的 agent 必须返回恰好一个具有相同 request-id 的 response 消息。
watch request 具有 watch-id,且
接收请求的 agent 可以响应任意数量的具有相同 watch-id 的 event 消息,
直到 watch request 过期。
request-id 和 watch-id 值是无符号整数 ID,
它们由每个 agent 维护的计数器分配,该计数器从 1 开始,并为每个
ID 递增 1。每当 agent 更改其 state-token 时,它必须将计数器重置为 1。
当 agent 看到另一个 agent 已重置其状态时(通过
通告新的 state-token),它应丢弃该 agent 的任何 requests、responses、
watches 和 events。
其他必须唯一、且如果一方
丢失状态会造成混淆的 ID,例如 streaming-session-id、media-session-id 和
encoding-id
应以同样方式处理。
重写以下内容以 不依赖 QUIC 细节。[Issue #343]
Note: Request 和 watch ID 不绑定到 agent 之间的任何 特定 QUIC 连接。 如果 QUIC 连接关闭,agent 不应丢弃 与另一方相关的 requests、responses、watches 或 events。这允许 agent 通过关闭未使用的连接来省电。
Note: Request 和 watch ID 在 agent 之间并非唯一。 agent 可以将 request ID 与发送它的 agent 的唯一标识符(例如其 证书指纹)结合起来,以跨多个 agent 跟踪 requests。
10. 协议扩展
Open Screen Protocol agents 可以交换本规范未定义的扩展消息。 这可用于实验、 定制或其他目的。
为了添加新的扩展消息,扩展作者必须在
公共注册表中注册 capability ID
和一段 message type key 范围。
然后 agent 可以通过在其 agent-info
消息的 capabilities 字段中包含相应 capability ID,
来指示它接受某个扩展。
Capability ID 1-999 保留给 Open Screen Protocol 使用。 Capability ID 1000 及以上可供扩展使用。有关扩展 message type key 的合法范围,请参阅 附录 B:消息类型键范围。
Note: 公共注册表的目的是防止 多个扩展作者的 capability ID 和 message type key 之间 发生冲突。
Agent 不得向未通告对应 extension capability ID 的另一个 agent 发送 extension message。
Note: 关于 agent 如何处理未知 message type key,请参阅 § 4 使用 CBOR 传递消息。
建议 extension message 也使用 CBOR 编码,以简化 实现,并为扩展协议的标准化提供更容易的路径。 但是,这不是必需的;支持非 CBOR 扩展的 agent 必须能够解码包含 CBOR 消息和非 CBOR extension message 混合内容的 QUIC 流。
10.1. 协议扩展字段
agent 向 Open Screen Protocol 定义的任何 map-valued CBOR message type 添加额外的 extension field 是合法的。Extension field 必须是 可选的,且 Open Screen Protocol 消息在设置或 不设置该字段时都必须有意义。
Agent 不得直接向 audio-frame 消息添加扩展字段。
相反,它们可以将其添加到其嵌套的 optional 值中。
Extension field 应使用字符串键,以避免与 Open Screen Protocol 消息中的整数键冲突。除非另一个 agent 在其 agent-info 中通告表示其 理解这些 extension field 的 extension capability ID,否则 agent 不应向该 agent 发送 extension field。
11. 安全和隐私
Open Screen Application Protocol 允许两个 OSP agents 交换用户和应用数据。因此,应仔细审查其安全和隐私 考量。我们首先使用 W3C Security and Privacy Questionnaire 来评估协议 本身。然后,我们检查是否满足 Presentation API 和 Remote Playback API 推荐的安全和隐私指南。最后,我们讨论 agent 可用于满足这些安全和隐私需求的推荐 缓解措施。
11.1. 威胁模型
11.1.1. 同源策略违反
Presentation API 允许 controlling page
和 presentation 之间在每个 origin 同意(通过它们使用该
API)的情况下进行跨源通信。这类似于通过
postMessage()
并使用
targetOrigin 为 * 的跨源通信。但是,Presentation API 不会随每条消息传达 source
origin 信息。因此,Open Screen Protocol 不会在其 agent 之间
传达 origin 信息。
presentation identifier 对不受限制的
跨源访问提供了一些保护;但是,连接到
PresentationConnection
的各方必须在应用层进行严格认证。
11.2. Open Screen Application Protocol 安全和隐私考量
11.2.1. 个人可识别信息和高价值 数据
协议交换的以下数据可能是个人可识别 和/或高价值数据:
-
Presentation URL 和可用性结果
-
Presentation identifier
-
Presentation connection ID
-
Presentation connection message
-
Remote playback URL
-
Remote playback command 和 status message
Presentation identifier 被视为高价值数据, 因为它们可以 与 Presentation URL 结合使用,以连接到正在运行的 presentation。
通过 agent-info 提供的数据无法合理地保密, 应视为公开:
-
显示名称
-
设备型号名称
-
agent 的能力
-
首选语言区域
这些数据虽不被视为个人可识别信息,但仍然重要,需要 保护,以防止攻击者更改它或替换为其他值。
11.2.2. 跨源状态考量
通过 Presentation API 重新连接到由先前 session 启动的 presentation,可以跨浏览 session 访问 origin state。 此场景在 Presentation API § 7.2 Cross-origin access 中处理。
Receiver 可用性可根据用户的网络 上下文跨源获得。此数据向 Web 暴露的问题也在 Presentation API § 7.1 Personally identifiable information 和 Remote Playback API § 6.1 Personally identifiable information 中讨论。
11.2.3. Origin 对其他设备的访问
按设计,Open Screen Protocol 允许 Web 访问 receiver。 通过实现该协议,这些设备是在有意让自己 可供 Web 使用,并应据此进行设计。
下面,我们讨论防止这些设备被恶意使用的缓解步骤。
11.2.4. 私密浏览模式
Open Screen Protocol 本身不区分 user agent 的普通 浏览模式和私密浏览 模式。
更新以反映通用 传输需求。[Issue #342]
但是,建议 user agent 对来自同一 user agent 实例的普通浏览和私密浏览使用单独的认证上下文 和 QUIC 连接。这使 OSP agent 更难将同一用户 在普通浏览和私密浏览中发生的活动匹配起来。
11.2.5. 持久状态
agent 很可能会从 agent-info 消息或其他应用消息中持久化它以前 连接过的 agent 的身份。
但是,这些数据通常不会暴露给 Web,仅在显示选择或显示认证 过程中通过 user agent 的原生 UI 暴露。用户清除浏览数据时,user agent 是清除还是 保留这些数据,可以是实现选择。
11.2.6. 其他考量
Open Screen Protocol 不向 Web 授予对以下内容的 额外访问:
-
新的脚本加载机制
-
对用户位置的访问
-
对设备传感器的访问
-
对用户本地计算环境的访问
-
对 user agent 原生 UI 的控制
-
user agent 的安全特征
11.3. Presentation API 考量
Presentation API § 7 Security and privacy considerations 对 Open Screen Protocol 提出以下 需求:
-
根据跨源访问指南,Presentation URL 和 presentation identifier 应在 被允许连接到 presentation 的各方之间保持私密。
-
根据用户界面指南,当表示多个 user agent profile 的连接 已建立到某个 presentation 时,应通知 controller 和 receiver。
-
根据 presentation connection 之间消息传递的指南, controller 和 receiver 之间的消息传递应经过认证并 保密。
更新以反映 通用传输需求。[Issue #342]
Open Screen Protocol 通过以下方式处理这些考量:
-
在交换 presentation URL、ID 或消息之前, 要求相互认证和 TLS 安全的 QUIC 连接。
-
为单个
PresentationConnections添加显式消息和 connection ID, 以便 agent 可以跟踪 活动连接数。
11.4. Remote Playback API 考量
Remote Playback API § 6 Security and privacy considerations 也指出 controller 和 receiver 之间的消息传递也应经过认证并 保密。
更新以反映 通用传输需求。[Issue #342]
此考量通过在交换任何 remote playback 相关消息之前 要求相互认证和 TLS 安全的 QUIC 连接来处理。
11.5. 缓解策略
11.5.1. 恶意输入
OSP agent 应对试图通过利用解析漏洞来破坏 目标设备的恶意输入具有鲁棒性。
相对于 JSON 和 XML 等替代方案,CBOR 旨在更不易受到此类攻击。 不过,agent 仍应使用 模糊测试等方法进行充分测试。
在可能的情况下,OSP agent(包括内容渲染组件)应 使用防御纵深技术,例如 沙箱化, 以防止漏洞获取用户数据或导致 持久化利用。
11.6. 用户界面考量
本规范不对 OSP agent 的安全
相关用户界面提出任何具体需求。但是,在 agent
认证另一个 agent 之前,用户界面应清楚表明来自该 agent 的任何
agent-info 或其他数据尚未通过
认证验证。
11.6.1. 实例和显示名称
更新以反映通用 发现需求。[Issue #342]
由于 DNS-SD Instance Name 是用户在认证前 看到的主要信息,因此必须谨慎呈现这些名称。
Agent 必须将 Instance Name 视为未经验证的信息,并应检查 Instance Name 是否是成功 QUIC 连接后通过 agent-info 消息接收的 display name 的前缀。 一旦 agent 完成 此检查,它即可将该名称显示为 verified display name。
Agent 应仅向用户显示完整 display name,而不是来自 DNS-SD 的截断 display name。截断的 display name 应按上述方式验证, 然后再完整显示为 verified display name。
- 截断且未经验证的 DNS-SD Instance Name,不应向用户显示。
- 完整但未经验证的 DNS-SD Instance Name,可在认证前显示为 未验证。
- Verified display name。
附录 A:消息
以下消息使用 Concise Data Definition Language 语法定义。使用整数键时,会在该行追加注释 以指示字段名称。本规范中的对象定义具有 这种不寻常的语法,以在保持每个键的人类可读名称的同时, 减少线上传输字节数。使用整数键而不是对象 数组,以便轻松索引可选字段。
每个 root message(即可以放入 QUIC 流中而无需被其他消息包围的消息) 都有一个注释来指示 message type key。
较小的数字应保留给发送更频繁 或非常小或两者兼具的消息,较大的数字应保留给 发送不频繁或较大或两者兼具的消息,因为较小的 type key 在线上编码时 更小。
; type key 10 agent-info-request = { request } ; type key 11 agent-info-response = { response 1: agent-info ; agent-info } ; type key 120 agent-info-event = { 0: agent-info ; agent-info } agent-capability = &( receive-audio: 1 receive-video: 2 receive-presentation: 3 control-presentation: 4 receive-remote-playback: 5 control-remote-playback: 6 receive-streaming: 7 send-streaming: 8 ) agent-info = { 0: text ; display-name 1: text ; model-name 2: [* agent-capability] ; capabilities 3: text ; state-token 4: [* text] ; locales } ; type key 12 agent-status-request = { request ? 1: status ; status } ; type key 13 agent-status-response = { response ? 1: status ; status } status = { 0: text ; status } request = ( 0: request-id ; request-id ) response = ( 0: request-id ; request-id ) request-id = uint microseconds = uint epoch-time = int media-timeline = float64 media-timeline-range = [ start: media-timeline end: media-timeline ] watch-id = uint ; type key 14 presentation-url-availability-request = { request 1: [1* text] ; urls 2: microseconds ; watch-duration 3: watch-id ; watch-id } ; type key 15 presentation-url-availability-response = { response 1: [1* url-availability] ; url-availabilities } ; type key 103 presentation-url-availability-event = { 0: watch-id ; watch-id 1: [1* url-availability] ; url-availabilities } ; idea: use HTTP response codes? url-availability = &( available: 0 unavailable: 1 invalid: 10 ) ; type key 104 presentation-start-request = { request 1: text ; presentation-id 2: text ; url 3: [* http-header] ; headers } http-header = [ key: text value: text ] ; type key 105 presentation-start-response = { response 1: &result ; result 2: uint ; connection-id ? 3: uint ; http-response-code } presentation-termination-source = &( controller: 1 receiver: 2 unknown: 255 ) presentation-termination-reason = &( application-request: 1 user-request: 2 receiver-replaced-presentation: 20 receiver-idle-too-long: 30 receiver-attempted-to-navigate: 31 receiver-powering-down: 100 receiver-error: 101 unknown: 255 ) ; type key 106 presentation-termination-request = { request 1: text ; presentation-id 2: presentation-termination-reason ; reason } ; type key 107 presentation-termination-response = { response 1: &result ; result } ; type key 108 presentation-termination-event = { 0: text ; presentation-id 1: presentation-termination-source ; source 2: presentation-termination-reason ; reason } ; type key 109 presentation-connection-open-request = { request 1: text ; presentation-id 2: text ; url } ; type key 110 presentation-connection-open-response = { response 1: &result ; result 2: uint ; connection-id 3: uint ; connection-count } ; type key 113 presentation-connection-close-event = { 0: uint ; connection-id 1: &( close-method-called: 1 connection-object-discarded: 10 unrecoverable-error-while-sending-or-receiving-message: 100 ) ; reason ? 2: text ; error-message 3: uint ; connection-count } ; type key 121 presentation-change-event = { 0: text ; presentation-id 1: uint ; connection-count } ; type key 16 presentation-connection-message = { 0: uint ; connection-id 1: bytes / text ; message } result = ( success: 1 invalid-url: 10 invalid-presentation-id: 11 timeout: 100 transient-error: 101 permanent-error: 102 terminating: 103 unknown-error: 199 ) ; type key 17 remote-playback-availability-request = { request 1: [* remote-playback-source] ; sources 2: microseconds ; watch-duration 3: watch-id ; watch-id } ; type key 18 remote-playback-availability-response = { response 1: [* url-availability] ; url-availabilities } ; type key 114 remote-playback-availability-event = { 0: watch-id ; watch-id 1: [* url-availability] ; url-availabilities } ; type key 115 remote-playback-start-request = { request 1: remote-playback-id ; remote-playback-id ? 2: [* remote-playback-source] ; sources ? 3: [* text] ; text-track-urls ? 4: [* http-header] ; headers ? 5: remote-playback-controls ; controls ? 6: {streaming-session-start-request-params} ; remoting } remote-playback-source = { 0: text; url 1: text; extended-mime-type } ; type key 116 remote-playback-start-response = { response ? 1: remote-playback-state ; state ? 2: {streaming-session-start-response-params} ; remoting } ; type key 117 remote-playback-termination-request = { request 1: remote-playback-id ; remote-playback-id 2: &( user-terminated-via-controller: 11 unknown: 255 ) ; reason } ; type key 118 remote-playback-termination-response = { response 1: &result ; result } ; type key 119 remote-playback-termination-event = { 0: remote-playback-id ; remote-playback-id 1: &( receiver-called-terminate: 1 user-terminated-via-receiver: 2 receiver-idle-too-long: 30 receiver-powering-down: 100 receiver-crashed: 101 unknown: 255 ) ; reason } ; type key 19 remote-playback-modify-request = { request 1: remote-playback-id ; remote-playback-id 2: remote-playback-controls ; controls } ; type key 20 remote-playback-modify-response = { response 1: &result ; result ? 2: remote-playback-state ; state } ; type key 21 remote-playback-state-event = { 0: remote-playback-id ; remote-playback-id 1: remote-playback-state ; state } remote-playback-id = uint remote-playback-controls = { ? 0: remote-playback-source ; source ? 1: &( none: 0 metadata: 1 auto: 2 ) ; preload ? 2: bool ; loop ? 3: bool ; paused ? 4: bool ; muted ? 5: float64 ; volume ? 6: media-timeline ; seek ? 7: media-timeline ; fast-seek ? 8: float64 ; playback-rate ? 9: text ; poster ? 10: [* text] ; enabled-audio-track-ids ? 11: text ; selected-video-track-id ? 12: [* added-text-track] ; added-text-tracks ? 13: [* changed-text-track] ; changed-text-tracks } remote-playback-state = { ? 0: { 0: bool ; rate 1: bool ; preload 2: bool ; poster 3: bool ; added-text-track 4: bool ; added-cues } ; supports ? 1: remote-playback-source ; source ? 2: &( empty: 0 idle: 1 loading: 2 no-source: 3 ) ; loading ? 3: &( nothing: 0 metadata: 1 current: 2 future: 3 enough: 4 ) ; loaded ? 4: media-error ; error ? 5: epoch-time / null ; epoch ? 6: media-timeline / null ; duration ? 7: [* media-timeline-range] ; buffered-time-ranges ? 8: [* media-timeline-range] ; seekable-time-ranges ? 9: [* media-timeline-range] ; played-time-ranges ? 10: media-timeline ; position ? 11: float64 ; playbackRate ? 12: bool ; paused ? 13: bool ; seeking ? 14: bool ; stalled ? 15: bool ; ended ? 16: float64 ; volume ? 17: bool ; muted ? 18: video-resolution / null ; resolution ? 19: [* audio-track-state] ; audio-tracks ? 20: [* video-track-state] ; video-tracks ? 21: [* text-track-state] ; text-tracks } added-text-track = { 0: &( subtitles: 1 captions: 2 descriptions: 3 chapters: 4 metadata: 5 ) ; kind ? 1: text ; label ? 2: text ; language } changed-text-track = { 0: text ; id 1: text-track-mode ; mode ? 2: [* text-track-cue] ; added-cues ? 3: [* text] ; removed-cue-ids } text-track-mode = &( disabled: 1 showing: 2 hidden: 3 ) text-track-cue = { 0: text ; id 1: media-timeline-range ; range 2: text ; text } media-sync-time = [ value: uint scale: uint ] media-error = [ code: &( user-aborted: 1 network-error: 2 decode-error: 3 source-not-supported: 4 unknown-error: 5 ) message: text ] track-state = ( 0: text ; id 1: text ; label 2: text ; language ) audio-track-state = { track-state 3: bool ; enabled } video-track-state = { track-state 3: bool ; selected } text-track-state = { track-state 3: text-track-mode ; mode } ; type key 22 audio-frame = [ encoding-id: uint start-time: uint payload: bytes ? optional: { ? 0: uint ; duration ? 1: media-sync-time ; sync-time } ] ; type key 23 video-frame = { 0: uint ; encoding-id 1: uint ; sequence-number ? 2: [* int] ; depends-on 3: uint ; start-time ? 4: uint ; duration 5: bytes ; payload ? 6: uint ; video-rotation ? 7: media-sync-time ; sync-time } ; type key 24 data-frame = { 0: uint ; encoding-id ? 1: uint ; sequence-number ? 2: uint ; start-time ? 3: uint ; duration 4: bytes ; payload ? 5: media-sync-time ; sync-time } ratio = [ antecedent: uint consequent: uint ] ; type key 122 streaming-capabilities-request = { request } ; type key 123 streaming-capabilities-response = { response 1: streaming-capabilities ; streaming-capabilities } streaming-capabilities = { 0: [* receive-audio-capability] ; receive-audio 1: [* receive-video-capability] ; receive-video 2: [* receive-data-capability] ; receive-data } format = { 0: text ; codec-name } receive-audio-capability = { 0: format ; codec ? 1: uint ; max-audio-channels ? 2: uint ; min-bit-rate } video-resolution = { 0: uint ; height 1: uint ; width } video-hdr-format = { 0: text; transfer-function ? 1: text; hdr-metadata } receive-video-capability = { 0: format ; codec ? 1: video-resolution ; max-resolution ? 2: ratio ; max-frames-per-second ? 3: uint ; max-pixels-per-second ? 4: uint ; min-bit-rate ? 5: ratio ; aspect-ratio ? 6: text ; color-gamut ? 7: [* video-resolution] ; native-resolutions ? 8: bool ; supports-scaling ? 9: bool ; supports-rotation ? 10: [* video-hdr-format] ; hdr-formats } receive-data-capability = { 0: format ; data-type } ; type key 124 streaming-session-start-request = { request streaming-session-start-request-params } ; type key 125 streaming-session-start-response = { response streaming-session-start-response-params } ; A separate group so it can be used in remote-playback-start-request streaming-session-start-request-params = ( 1: uint ; streaming-session-id 2: [* media-stream-offer] ; stream-offers 3: microseconds ; desired-stats-interval ) ; type key 126 streaming-session-modify-request = { request streaming-session-modify-request-params } ; A separate group so it can be used in remote-playback-start-response streaming-session-start-response-params = ( 1: &result ; result 2: [* media-stream-request] ; stream-requests 3: microseconds ; desired-stats-interval ) streaming-session-modify-request-params = ( 1: uint ; streaming-session-id 2: [* media-stream-request] ; stream-requests ) ; type key 127 streaming-session-modify-response = { response 1: &result ; result } ; type key 128 streaming-session-terminate-request = { request 1: uint ; streaming-session-id } ; type key 129 streaming-session-terminate-response = { response } ; type key 130 streaming-session-terminate-event = { 0: uint ; streaming-session-id } media-stream-offer = { 0: uint ; media-stream-id ? 1: text ; display-name ? 2: [1* audio-encoding-offer] ; audio ? 3: [1* video-encoding-offer] ; video ? 4: [1* data-encoding-offer] ; data } media-stream-request = { 0: uint ; media-stream-id ? 1: audio-encoding-request ; audio ? 2: video-encoding-request ; video ? 3: data-encoding-request ; data } audio-encoding-offer = { 0: uint ; encoding-id 1: text ; codec-name 2: uint ; time-scale ? 3: uint ; default-duration } video-encoding-offer = { 0: uint ; encoding-id 1: text ; codec-name 2: uint ; time-scale ? 3: uint ; default-duration ? 4: video-rotation ; default-rotation } data-encoding-offer = { 0: uint ; encoding-id 1: text ; data-type-name 2: uint ; time-scale ? 3: uint ; default-duration } audio-encoding-request = { 0: uint ; encoding-id } video-encoding-request = { 0: uint ; encoding-id ? 1: video-resolution ; target-resolution ? 2: ratio ; max-frames-per-second } data-encoding-request = { 0: uint ; encoding-id } video-rotation = &( ; Degrees clockwise video-rotation-0: 0 video-rotation-90: 1 video-rotation-180: 2 video-rotation-270: 3 ) sender-stats-audio = { 0: uint ; encoding-id ? 1: uint ; cumulative-sent-frames ? 2: microseconds ; cumulative-encode-delay } sender-stats-video = { 0: uint ; encoding-id ? 1: microseconds ; cumulative-sent-duration ? 2: microseconds ; cumulative-encode-delay ? 3: uint ; cumulative-dropped-frames } ; type key 131 streaming-session-sender-stats-event = { 0: uint; streaming-session-id 1: microseconds ; system-time ? 2: [1* sender-stats-audio] ; audio ? 3: [1* sender-stats-video] ; video } streaming-buffer-status = &( enough-data: 0 insufficient-data: 1 too-much-data: 2 ) receiver-stats-audio = { 0: uint ; encoding-id ? 1: microseconds ; cumulative-received-duration ? 2: microseconds ; cumulative-lost-duration ? 3: microseconds ; cumulative-buffer-delay ? 4: microseconds ; cumulative-decode-delay ? 5: streaming-buffer-status ; remote-buffer-status } receiver-stats-video = { 0: uint ; encoding-id ? 1: uint ; cumulative-decoded-frames ? 2: uint ; cumulative-lost-frames ? 3: microseconds ; cumulative-buffer-delay ? 4: microseconds ; cumulative-decode-delay ? 5: streaming-buffer-status ; remote-buffer-status } ; type key 132 streaming-session-receiver-stats-event = { 0: uint; streaming-session-id 1: microseconds ; system-time ? 2: [1* receiver-stats-audio] ; audio ? 3: [1* receiver-stats-video] ; video }
附录 B:消息类型键范围
以下附录描述了消息类型键的范围如何划分。 合法值为 1 到 264。
每个类型键在线路上被编码为 1、2、4 或 8 字节的 可变长度整数。对于每种线路字节大小,1/4 到 1/2 的键可用于 扩展。
| 字节 | 范围 | 用途 |
|---|---|---|
| 1 | 1 - 48 | Open Screen Protocol |
| 1 | 49 - 63 | 可用于扩展 |
| 2 | 64 - 8,192 | Open Screen Protocol |
| 2 | 8,193 - 16,383 | 可用于扩展 |
| 4 | 16,384 - 229 | 保留供未来使用 |
| 4 | 229+1 - 230-1 | 可用于扩展 |
| 8 | >= 230 | 保留供未来使用 |
附录 C:媒体时间转换
为了在给定音频或视频 帧的媒体同步时间戳与媒体时间轴值之间进行转换,可以使用以下公式:
media-timeline-value = media-zero-time + (value / scale)
其中:
-
media-zero-time是 HTML 中定义的 媒体时间轴的原点, 转换为 IEEE-754 双精度浮点数 [IEEE-754]。 -
value和scale是在相应 audio-frame 或 video-frame 的sync-time字段中传递的值。 -
value / scale应使用双精度浮点精度计算。 -
media-timeline-value是 IEEE-754 双精度浮点数 [IEEE-754]。
在 media-timeline-value 发生溢出的情况下,应使用最大可表示
值。