diff --git a/.github/workflows/auto-publish.yml b/.github/workflows/auto-publish.yml index 35aea56..a09ef0a 100644 --- a/.github/workflows/auto-publish.yml +++ b/.github/workflows/auto-publish.yml @@ -51,6 +51,14 @@ jobs: W3C_BUILD_OVERRIDE: | status: WD + - name: Build and validate application.html, push to gh-pages branch if needed + uses: w3c/spec-prod@v2 + with: + SOURCE: application.bs + TOOLCHAIN: bikeshed + GH_PAGES_BRANCH: gh-pages + W3C_ECHIDNA_TOKEN: ${{ secrets.ECHIDNA_TOKEN_APPLICATION }} + - name: Build and validate network.html, push to gh-pages branch if needed uses: w3c/spec-prod@v2 with: @@ -58,4 +66,3 @@ jobs: TOOLCHAIN: bikeshed GH_PAGES_BRANCH: gh-pages W3C_ECHIDNA_TOKEN: ${{ secrets.ECHIDNA_TOKEN_NETWORK }} - W3C_WG_DECISION_URL: https://lists.w3.org/Archives/Public/public-secondscreen/2022Apr/0007.html \ No newline at end of file diff --git a/.gitignore b/.gitignore index db89f82..fadaf98 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ *.pyc index.html messages_appendix.html +application.html +application_messages.html network.html network_messages.html + diff --git a/Makefile b/Makefile index ccbb10d..95ab967 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,27 @@ BIKESHED ?= bikeshed BIKESHED_ARGS ?= --print=plain -.PHONY: lint watch watch-net +.PHONY: lint watch watch-app watch-net all -all: index.html network.html +# TODO: Generalize targets to reduce duplication. + +all: index.html application.html network.html index.html: index.bs messages_appendix.html $(BIKESHED) $(BIKESHED_ARGS) spec $< -messages_appendix.html: messages_appendix.cddl scripts/pygmentize_dir.py scripts/cddl_lexer.py scripts/openscreen_cddl_dfns.py - ./scripts/pygmentize_dir.py +application.html: application.bs application_messages.html + $(BIKESHED) $(BIKESHED_ARGS) spec $< network.html: network.bs network_messages.html $(BIKESHED) $(BIKESHED_ARGS) spec $< +messages_appendix.html: messages_appendix.cddl scripts/pygmentize_dir.py scripts/cddl_lexer.py scripts/openscreen_cddl_dfns.py + ./scripts/pygmentize_dir.py + +application_messages.html: application_messages.cddl scripts/pygmentize_dir.py scripts/cddl_lexer.py scripts/openscreen_cddl_dfns.py + ./scripts/pygmentize_dir.py + network_messages.html: network_messages.cddl scripts/pygmentize_dir.py scripts/cddl_lexer.py scripts/openscreen_cddl_dfns.py ./scripts/pygmentize_dir.py @@ -24,6 +32,10 @@ watch: index.bs @echo 'Browse to file://${PWD}/index.html' $(BIKESHED) $(BIKESHED_ARGS) watch $< +watch-app: application.bs + @echo 'Browse to file://${PWD}/application.html' + $(BIKESHED) $(BIKESHED_ARGS) watch $< + watch-net: network.bs @echo 'Browse to file://${PWD}/network.html' $(BIKESHED) $(BIKESHED_ARGS) watch $< diff --git a/application.bs b/application.bs new file mode 100644 index 0000000..6fa2634 --- /dev/null +++ b/application.bs @@ -0,0 +1,2350 @@ +
+Title: Open Screen Application Protocol
+Shortname: openscreenprotocol-application
+Level: None
+Status: w3c/ED
+ED: https://w3c.github.io/openscreenprotocol/application.html
+Canonical URL: ED
+Editor: Mark Foltz, Google, https://github.com/mfoltzgoogle, w3cid 68454
+Repository: w3c/openscreenprotocol
+Abstract: The Open Screen Application Protocol allows user agents to implement the
+          [[PRESENTATION-API|Presentation API]] and the
+          [[REMOTE-PLAYBACK|Remote Playback API]] in an interoperable fashion.
+
+Group: secondscreenwg
+Markup Shorthands: markdown yes, dfn yes, idl yes, markup yes
+
+ +
+url: https://html.spec.whatwg.org/multipage/media.html#concept-mediaerror-code; type: dfn; spec: html
+    text: media error code
+urlPrefix: https://html.spec.whatwg.org/multipage/media.html#; type: dfn; spec: html
+    text: official playback position
+    text: poster frame
+    text: timeline offset
+    text: media resource
+    text: media timeline
+urlPrefix: https://www.w3.org/TR/presentation-api/#dfn-; type: dfn; spec: PRESENTATION-API
+    text: available presentation display
+    text: controlling user agent
+    text: presentation; url: receiving-browsing-context
+    text: presentation identifier
+    text: presentation request url
+    text: receiving user agent
+urlPrefix: https://w3c.github.io/remote-playback/#dfn-; type: dfn; spec: REMOTE-PLAYBACK
+    text: availability sources set
+    text: compatible remote playback device
+    text: initiate remote playback
+    text: media element state
+    text: remote playback devices
+    text: remote playback source
+url: https://datatracker.ietf.org/doc/html/rfc9000#name-variable-length-integer-enc; type: dfn; spec: RFC9000; text: Variable-Length Integer Encoding
+url: https://datatracker.ietf.org/doc/html/rfc9000#name-variable-length-integer-enc; type: dfn; spec: RFC9000; text: variable-length integer
+url: https://tools.ietf.org/html/rfc5646#section-2; type: dfn; spec: RFC5646; text: language tag
+url: https://tools.ietf.org/html/rfc4122#section-4.4; type: dfn; spec: RFC4122; text: UUID
+url: https://tools.ietf.org/html/rfc6381#section-3; type: dfn; spec: RFC6381; text: codecs parameter
+url: https://tools.ietf.org/html/rfc8610#section-3; type: dfn; spec: RFC8610; text: concise data definition language
+
+ +Introduction {#introduction} +============================ + +The Open Screen Application Protocol connects browsers to devices capable of +rendering Web content for a shared audience. Typically, these are devices like +Internet-connected TVs, HDMI dongles, and smart speakers. + +The Open Screen Application Protocol is intended to be supported by a variety of +connection technologies between browsers and devices. There are specific +requirements to allow browsers and devices to discover and authenticate to one +another, but these requirements may be met by multiple possible implementations. +To maximize interoperability, browsers and devices should support the Open +Screen Network Protocol, which provides a way for browsers and devices to +discover, connect, and authenticate each other on a local area network. + +The Open Screen Application Protocol allows a browser to present a URL, initiate +remote playback of an HTML [=media element=], and stream media +data to another device. + +The Open Screen Application Protocol is intended to be extensible, so that +additional capabilities can be added over time. This may include additions to +existing Web APIs or new Web APIs. + +The accompanying [explainer](https://w3c.github.io/openscreenprotocol/explainer.html) +provides more background on the protocol. + +Terminology {#terminology} +-------------------------- + +An Open Screen Protocol +agent (or OSP agent) is any implementation of this protocol (browser, +display, speaker, or other software). + +Some OSP agents support the [[PRESENTATION-API|Presentation API]]. The +API allows a [=controlling user agent=] to initiate presentation of Web content +on another device. We call this agent a controller for short. A +[=receiving user agent=] is responsible for rendering the Web content, which we +will call a receiver for short. The the Web content itself is called +a [=presentation=]. + +Some OSP agents also support the [[REMOTE-PLAYBACK|Remote Playback API]]. That +API allows an agent to render a [=media element=] on a +[=remote playback device=]. In this document, we refer to it as a [=receiver=] +because it is shorter and keeps terminology consistent between presentations and +remote playbacks. Similarly, we use the term [=controller=] to refer to the +agent that starts, terminates, and controls the remote playback. + +For media streaming, we refer to an OSP agent that sends a media stream +as a media sender and an agent that receives a media stream as a +media receiver. Note that an agent can be both a media sender and a +media receiver. + +For additional terms and idioms specific to the +[[PRESENTATION-API|Presentation API]] or [[REMOTE-PLAYBACK|Remote Playback API]], +please consult the respective specifications. + +Requirements {#requirements} +============================ + +Agent Discovery Requirements {#requirements-discovery} +------------------------------------------------------ + +Issue(342): Define Discovery Requirements + +Transport Layer Requirements {#requirements-transport} +------------------------------------------------------ + +Issue(342): Define Transport Layer Requirements + +Presentation API Requirements {#requirements-presentation-api} +-------------------------------------------------------------- + +1. A [=controller=] must be able to determine if a [=receiver=] is reasonably + capable of rendering a specific [=presentation request URL=]. + +2. A controller must be able to start a new [=presentation=] on a + receiver given a [=presentation request URL=] and [=presentation + identifier=]. + +3. A controller must be able to create a new {{PresentationConnection}} to an + existing presentation on the receiver, given its [=presentation request + URL=] and [=presentation identifier=]. + +4. It must be possible to close a {{PresentationConnection}} between a + controller and a presentation, and signal both parties with the reason why + the connection was closed. + +5. Multiple controllers must be able to connect to a single presentation + simultaneously. + +6. Messages sent by the controller must be delivered to the presentation (or + vice versa) in a reliable and in-order fashion. + +7. If a message cannot be delivered, then the controller must be + able to signal the receiver (or vice versa) that the connection should be + closed with reason `error`. + +8. The controller and presentation must be able to send and receive `DOMString` + messages (represented as `string` type in ECMAScript). + +9. The controller and presentation must be able to send and receive binary + messages (represented as `Blob` objects in HTML5, or `ArrayBuffer` or + `ArrayBufferView` types in ECMAScript). + +10. The controller must be able to signal to the receiver to + terminate a presentation, given its [=presentation request URL=] and + [=presentation identifier=]. + +11. The receiver must be able to signal all connected controllers + when a presentation is terminated. + + +Remote Playback API Requirements {#requirements-remote-playback} +---------------------------------------------------------------- + +1. A [=controller=] must be able to find out whether there is at least one + compatible [=receiver=] for a given {{HTMLMediaElement}}, both + instantaneously and continuously. + +2. A controller must be able to to [=initiate remote playback=] of + an {{HTMLMediaElement}} to a compatible receiver. + +3. The controller must be able send media sources as URLs and text + tracks from an {{HTMLMediaElement}} to a compatible receiver. + +4. The controller must be able send media data from an {{HTMLMediaElement}} to + a compatible receiver. + +5. During remote playback, the controller and the remote playback + device must be able to synchronize the [=media element state=] of the + {{HTMLMediaElement}}. + +6. During remote playback, either the controller or the receiver must be able + to disconnect from the other party. + +7. The controller should be able to pass locale and text direction information + to the receiver to assist in rendering text during remote playback. + + +Non-Functional Requirements {#requirements-non-functional} +---------------------------------------------------------- + +1. It should be possible to implement an OSP agent using modest + hardware requirements, similar to what is found in a low end smartphone, + smart TV or streaming device. See the [Device + Specifications](https://w3c.github.io/openscreenprotocol/device_specs.html) + document for agent hardware specifications. + +2. Agents should present sensible information to the user when a protocol + operation fails. For example, if a controller is unable to start a + presentation, it should be possible to report in the controller interface if + it was a network error, authentication error, or the presentation content + failed to load. + +3. Message latency between agents should be minimized to permit interactive + use. For example, it should be comfortable to type in a form in one agent + and have the text appear in the presentation in real time. Real-time + latency for gaming or mouse use is ideal, but not a requirement. + +4. The controller initiating a presentation or remote playback should + communicate its preferred locale to the receiver, so it can render the + content in that locale. + +5. It should be possible to extend the application protocol with optional + features not defined explicitly by the specification, to facilitate + experimentation and enhancement of the base APIs. + + +Metadata Discovery {#metadata} +============================== + +To learn further metadata, an agent may send an [=agent-info-request=] message +and receive back an [=agent-info-response=] message. Any agent may send this +request at any time to learn about the state and capabilities of another device, +which are described by the [=agent-info=] message in the +[=agent-info-response=]. + +If an agent changes any information in its [=agent-info=] message, it should +send an [=agent-info-event=] message to all other connected agents with the new +[=agent-info=] (without waiting for an [=agent-info-request=]). + +The [=agent-info=] message contains the following fields: + +: display-name (required) +:: The display name of the agent, intended to be displayed to a user by the + requester. The requester should indicate through the UI if the responder + is not authenticated or if the display name changes. + +: model-name (optional) +:: If the agent is a hardware device, the model name of + the device. This is used mainly for debugging purposes, but may be + displayed to the user of the requesting agent. + +: capabilities (required) +:: The control protocols, roles, and media types the agent supports. + Presence indicates a capability and absence indicates lack of a + capability. Capabilities should should affect how an agent is + presented to a user, such as drawing a different icon depending on + the whether it receives audio, video or both. + +: state-token (required) +:: A random alphanumeric value consisting of 8 characters in the range + [0-9A-Za-z]. This value is set before the agent makes its first connection + and must be set to a new value when the agent is reset or otherwise lost all + of its state related to this protocol. + +: locales (required) +:: The agent's preferred locales for display of localized content, in the order + of user preference. Each entry is an RFC5646 [=language tag=]. + +The various capabilities have the following meanings: + +: receive-audio +:: The agent can render audio via the other protocols it supports. Those other + protocols may report more specific capabilities, such as support for + certain audio codecs in the streaming protocol. + +: receive-video +:: The agent can receive video via the other protocols it supports. Those other + protocols may report more specific capabilities, such as support for + certain video codecs in the streaming protocol. + +: receive-presentation +:: The agent can receive presentations using the presentation protocol. + +: control-presentation +:: The agent can control presentations using the presentation protocol. + +: receive-remote-playback +:: The agent can receive remote playback using the remote playback + protocol. + +: control-remote-playback +:: The agent can control remote playback using the remote playback + protocol. + +: receive-streaming +:: The agent can receiving streaming using the streaming protocol. + +: send-streaming +:: The agent can send streaming using the streaming protocol. + +NOTE: See the [Capabilities Registry](https://github.com/w3c/openscreenprotocol/blob/main/capabilities.md) +for a list of all known capabilities (both defined by this specification, and +through [[#protocol-extensions]]). + +Issue(343): Rewrite to not depend on QUIC, or move agent-status messges to the network spec + +If a listening agent wishes to receive messages from an advertising agent or an +advertising agent wishes to send messages to a listening agent, it may wish to +keep the QUIC connection alive. Once neither side needs to keep the connection +alive for the purposes of sending or receiving messages, the connection should +be closed with an error code of 5139. In order to keep a QUIC connection alive, an +agent may send an [=agent-status-request=] message, and any agent that receives an +[=agent-status-request=] message should send an [=agent-status-response=] message. Such +messages should be sent more frequently than the QUIC idle_timeout transport +parameter (see Transport Parameter Encoding in QUIC) and QUIC PING +frames should not be used. An idle_timeout transport parameter of 25 seconds is +recommended. The agent should behave as though a timer less than the +idle_timeout were reset every time a message is sent on a QUIC stream. If the +timer expires, a [=agent-status-request=] message should be sent. + +If a listening agent wishes to send messages to an advertising agent, the +listening agent can connect to the advertising agent "on demand"; it does not +need to keep the connection alive. + +If an OSP agent suspends its network connectivity (e.g. for power saving +reasons), it should attempt to resume QUIC connections to the OSP agents to +which it was previously connected once network connectivity is restored. Once +reconnected, it should send `agent-status-request` messages to those agents. + +The [=agent-info=] and [=agent-status-response=] messages may be extended to +include additional information not defined in this spec, as described in +[[#protocol-extension-fields]]. + +Message delivery using CBOR {#messages} +======================================= + +Issue(343): Rewrite to not depend on QUIC + +Messages are serialized using [[!RFC8949|CBOR]]. To send a group of messages in +order, that group of messages must be sent in one QUIC stream. Independent +groups of messages (with no ordering dependency across groups) should be sent in +different QUIC streams. In order to put multiple CBOR-serialized messages into +the the same QUIC stream, the following is used. + +For each message, the [=OSP agent=] must write into a unidirectional QUIC stream +the following: + +1. A type key representing the type of the message, encoded as a [=variable-length + integer=] (see [[#appendix-a]] for type keys) + +2. The message encoded as CBOR. + +If an agent receives a message for which it does not recognize a type key, it +must close the QUIC connection with an application error code of 404 and should +include the unknown type key in the reason phrase of the CONNECTION_CLOSE +frame. + +Variable-length integers are encoded in the [=Variable-Length Integer Encoding=] +used by [[!RFC9000|QUIC]]. + +Many messages are requests and responses, so a common format is defined for +those. A request and a response includes a request ID which is an unsigned +integer chosen by the requester. Responses must include the request ID of the +request they are associated with. + +Type Key Backwards Compatibility {#message-compatibility} +-------------------------------- + +As messages are modified or extended over time, certain rules must be followed +to maintain backwards compatibiilty with agents that understand older versions +of messages. + +1. If a required field is added to or removed from a message (either to/from the + message directly or indirectly through the field of a field), a new type key + must be assigned to the message. Is is effectively a new message and must not + be sent unless the receiving agent is known to understand the new type key. + +1. If an optional field is added to a message (either to the message directly + or indirectly through the field of a field), the type key may remain unchanged + if the behavior of older receiving agents that do not understand the added field + is compatible with newer sending agents that include the field. + Otherwise, a new type key must be assigned. + +1. If an optional field is removed from a message (either from the message + directly or indirectly through the field of a field), the type key may remain + unchanged if the behavior of newer receiving agents that do not understand the + removed field is compatible with older sending agents that include the field. + Otherwise, a new type key must be assigned. + +1. Required fields may not be added or removed from array-based messages, such + as audio-frame. + +Presentation Protocol {#presentation-protocol} +===================== + +This section defines the use of the Open Screen Protocol for starting, +terminating, and controlling presentations as defined by +[[PRESENTATION-API|Presentation API]]. [[#presentation-api]] +defines how APIs in [[PRESENTATION-API|Presentation API]] map to the +protocol messages defined in this section. + +To learn which receivers are [=available presentation displays=] for a +particular [=presentation request URL=] or set of URLs, the controller may send +a [=presentation-url-availability-request=] message with the following values: + +: urls +:: A list of presentation URLs. Must not be empty. + +: watch-duration +:: The period of time that the controller is interested in receiving updates + about their URLs, should the availability change. + +: watch-id +:: An identifier the receiver must use when sending updates about URL + availability so that the controller knows which URLs the receiver is referring + to. + +In response, the receiver should send one [=presentation-url-availability-response=] +message with the following values: + +: url-availabilities +:: A list of URL availability states. Each state must correspond to the matching URL + from the request by list index. + + +While the watch is valid (the watch-duration has not expired), the receivers +should send [=presentation-url-availability-event=] messages when URL +availabilities change. Such events contain the following values: + +: watch-id +:: The watch-id given in the [=presentation-url-availability-response=], + used to refer to the presentation URLs whose availability has changed. + +: url-availabilities +:: A list of URL availability states. Each state must correspond to the URLs from the + request referred to by the watch-id. + +Note that these messages are not broadcasted to all controllers. They are sent +individually to controllers that have requested availability for the URLs that +have changed in availability state within the watch duration of the original +availability request. + +Issue(342): Add power saving as a transport requirement and remove the following. + +To save power, the controller may disconnect the QUIC connection and +later reconnect to send availability requests and receive availability +responses and updates. The QUIC connection ID may or may not be the same +when reconnecting. + +To start a presentation, the controller may send a +[=presentation-start-request=] message to the receiver with the following +values: + +: presentation-id +:: The [=presentation identifier=] + +: url +:: The selected presentation URL + +: headers +:: headers that the receiver should use to fetch the presentation URL. For example, + [[PRESENTATION-API#creating-a-receiving-browsing-context|section 6.6.1]] of + the Presentation API says that the HTTP `Accept-Language` header should be + provided. + +The [=presentation identifier=] must follow the restrictions defined by +[[PRESENTATION-API#common-idioms|section 6.1]] of the Presentation API, in that +it must consist of at least 16 ASCII characters. + +When the receiver receives the [=presentation-start-request=], it should send back a +[=presentation-start-response=] message after either the presentation URL has been +fetched and loaded, or the receiver has failed to do so. If it has failed, it +must respond with the appropriate result (such as invalid-url or timeout). If +it has succeeded, it must reply with a success result. + +Additionally, the response must include the following: + +: connection-id +:: An ID that both agents can use to send connection messages + to each other. It is chosen by the receiver for ease of implementation: if + the message receiver chooses the connection-id, it may keep the ID unique + across connections, thus making message demuxing/routing easier. + +The response should include the following: + +: http-response-code +:: The numeric HTTP response code that was returned from fetching the + presentation URL (after redirects). + +To send a presentation message, the controller or receiver may send a +[=presentation-connection-message=] with the following values: + +: connection-id +:: The ID from the [=presentation-start-response=] or + [=presentation-connection-open-response=] messages. + +: message +:: The presentation message data. + +Issue(342): Rewrite the following NOTE as a transport requirement for any +message transport. + +NOTE: An OSP agent should minimize buffering and processing of messages sent or +received beyond what is strictly necessary (i.e., CBOR serialization). Message +payloads should be treated as real-time data, as they may be used to synchronize +playback of media streams between agents or other low latency use cases. The +synchronization thresholds recommended in [[ITU-R-BT.1359-1]] imply that the +total agent-to-agent processing latency (including serialization, buffering, and +network latency) must be no greater than 45 ms to permit effective lip sync +during media playback. + +To terminate a presentation, the controller may send a +[=presentation-termination-request=] message with the following values: + +: presentation-id +:: The ID of the presentation to terminate. + +: reason +:: Set to `application-request` if the application requested termination, + or `user-request` if the user requested termination. (These are the only + valid values for `reason` in a [=presentation-termination-request=].) + +When a [=receiver=] receives a [=presentation-termination-request=], it should +send back a [=presentation-termination-response=] message to the requesting +controller. + +It should also notify other controllers about the termination by sending +a [=presentation-termination-event=] message. And it can send the same message if +it terminates a presentation without a request from a controller to do so. This +message contains the following values: + +: presentation-id +:: The ID of the presentation that was terminated. + +: source +:: Set to `controller` when the termination was in response to a + [=presentation-termination-request=], or `receiver` otherwise. + +: reason +:: The detailed reason why the presentation was terminated. + +To accept incoming connection requests from controller, a receiver must receive +and process the [=presentation-connection-open-request=] message which contains the +following values: + +: presentation-id +:: The ID of the presentation to connect to. + +: url +:: The URL of the presentation to connect to. + +The receiver should, upon receipt of a +[=presentation-connection-open-request=] message, send back a +[=presentation-connection-open-response=] message which contains the +following values: + +: result +:: a code indicating success or failure, and the reason for the failure + +: connection-id +:: An ID that both agents can use to send connection messages + to each other. It is chosen by the receiver for ease of implementation (if + the message receiver chooses the connection-id, it may keep the ID unique + across connections, thus making message demuxing/routing easier). + +: connection-count +:: The new number of open connections to the presentation that received + the incoming connection request. + +If the [=presentation-connection-open-response=] message indicates success, the +receiver should also send a [=presentation-change-event=] to all other endpoints +that have an active presentation connection to that presentation with the +values: + +: presentation-id +:: The ID of the presentation that just received a new presentation connection. + +: connection-count +:: The new total number of open connections to that presentation. + +A controller may close a connection without terminating the presentation by +sending a [=presentation-connection-close-event=] message to the receiver with the +following values: + +: connection-id +:: The ID of the connection that was closed. + +: reason +:: Set to `close-method-called` or `connection-object-discarded`. + +The receiver may also close a connection without terminating a presentation. If +it does so, it should send a [=presentation-connection-close-event=] message to the +controller with the following values: + +: connection-id +:: The ID of the connection that was closed. + +: reason +:: Set to `close-method-called` or `connection-object-discarded`. + +: connection-count +:: The number of open presentation connections that remain. + +If a receiver closes a presentation connection (for any reason), it should send +a [=presentation-change-event=] to all other controllers with an open connection +to that presentation with the values: + +: presentation-id +:: The ID of the presentation that just closed a connection. + +: connection-count +:: The number of open presentation connections that remain. + +Note: When an agent closes a presentation connection, it is always successful, +so request and response messages are not needed. A request to terminate a +presentation may succeed or fail, so a response message is required. + + +Presentation API {#presentation-api} +--------------------------------------------- + +An [=Open Screen Protocol agent=] that is a [=controlling user agent=] for the +[[PRESENTATION-API|Presentation API]] must support the `control-presentation` capability. +An [=OSP agent=] that is a [=receiving user agent=] for the +[[PRESENTATION-API|Presentation API]] must support the `receive-presentation` capability. +The same OSP agent may be a [=controlling user agent=] and a [=receiving user agent=]. + +This is how the [[PRESENTATION-API|Presentation API]] uses the +[[#presentation-protocol]]: + +When [[PRESENTATION-API#the-list-of-available-presentation-displays|section +6.4.2]] says "This list of presentation displays ... is populated based on an +implementation specific discovery mechanism", the [=controller=] may +use the mDNS, QUIC, [=agent-info-request=], and +[=presentation-url-availability-request=] messages defined previously in this spec +to discover receivers. + +When [[PRESENTATION-API#the-list-of-available-presentation-displays|section +6.4.2]] says "To further save power, ... implementation specific discovery of +presentation displays can be resumed or suspended.", the agent may use the +power saving mechanism defined in the previous section. + +When [[PRESENTATION-API#starting-a-presentation-connection|section 6.3.4]] says +"Using an implementation specific mechanism, tell U to create a receiving +browsing context with D, presentationUrl, and I as parameters.", U (the +controller) may send a [=presentation-start-request=] message to D +(the receiver), with I for the [=presentation identifier=] and presentationUrl +for the selected presentation URL. + +When [[PRESENTATION-API#reconnecting-to-a-presentation|section 6.3.5]] says to +"establish a presentation connection with newConnection," let U be the +presentationURL of `newConnection` and I the presentation identifier of +`newConnection.` The agent should send a +[=presentation-connection-open-request=] message with U for the `url` and I for +the `presentation-id`. + +When [[PRESENTATION-API#sending-a-message-through-presentationconnection|section +6.5.2]] says "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", the +controller may send a [=presentation-connection-message=] with +messageOrData for the presentation message data. Note that the messageType is +embedded in the encoded CBOR type and does not need an additional value in the +message. + +When [[PRESENTATION-API#closing-a-presentationconnection|section 6.5.5]] says +"Start to signal to the destination browsing context the intention to close the +corresponding PresentationConnection", the agent may send a +[=presentation-connection-close-event=] message to the other agent with the +destination browsing context and a [=presentation-change-event=] when required. + +When +[[PRESENTATION-API#terminating-a-presentation-in-a-controlling-browsing-context|section +6.5.6]] says "Send a termination request for the presentation to its receiving +user agent using an implementation specific mechanism", the controller may send +a [=presentation-termination-request=] message to the receiver with a `reason` +of `application-request`. + +When [[PRESENTATION-API#monitoring-incoming-presentation-connections|section +6.7.1]] +says "it MUST listen to and accept incoming connection requests from a +controlling browsing context using an implementation specific +mechanism", the receiver must receive and process the +[=presentation-connection-open-request=]. + +When [[PRESENTATION-API#monitoring-incoming-presentation-connections|section +6.7.1]] says "Establish the connection between the controlling and receiving +browsing contexts using an implementation specific mechanism.", the receiver +must send a [=presentation-connection-open-response=] message and +[=presentation-change-event=] messages when required. + + +Representation Of Time {#time-representation} +====================== + +The [[#remote-playback-protocol]] and the [[#streaming-protocol]] represent +points of time and durations in terms of a [=time scale=]. A time +scale is a common denominator for time values that allows values to be +expressed as rational numbers without loss of precision. The [=time scale=] is +represented in hertz, such as 90000 for 90000 Hz, a common time scale for +video. + +Remote Playback Protocol {#remote-playback-protocol} +======================== + +This section defines the use of the Open Screen Protocol for starting, terminating, +and controlling remote playback of media as defined by the +[[REMOTE-PLAYBACK|Remote Playback API]]. [[#remote-playback-api]] defines how +APIs in [[REMOTE-PLAYBACK|Remote Playback API]] map to the protocol messages +defined in this section. + +For all messages defined in this section, see [[#appendix-a]] for the full CDDL +definitions. + +To learn which receivers are [=compatible remote playback devices=] for a +particular URL or set of URLs, the controller may send a +[=remote-playback-availability-request=] message with the following values: + +: sources +:: A list of [=media resources=], the same as specified in the + [=remote-playback-start-request=] message. Must not be empty. + +: headers +:: headers that the receiver should use to fetch the + urls. For example, + [[REMOTE-PLAYBACK#establishing-a-connection-with-a-remote-playback-device|section 6.2.4 of + the Remote Playback API]] says that the Accept-Language header should be + provided. + +: watch-duration +:: The period of time that the controller is interested in receiving updates + about their URLs, should the availability change. + +: watch-id +:: An identifier the receiver must use when sending updates about URL + availability so that the controller knows which URLs the receiver is referring + to. + +In response, the receiver should send a [=remote-playback-availability-response=] +message with the following values: + +: url-availabilities +:: A list of URL availability states. Each state must correspond to the matching URL + from the request by list index. + + +The receivers should later (up to the current time plus request +watch-duration) send [=remote-playback-availability-event=] messages if +URL availabilities change. Such events contain the following values: + +: watch-id +:: The watch-id given in the [=remote-playback-availability-response=] + used to refer to the remote playback URLs whose availability has changed. + +: url-availabilities +:: A list of URL availability states. Each state must correspond to the URLs from the + request referred to by the watch-id. + +Note that these messages are not broadcasted to all controllers. They are sent +individually to controllers that have requested availability for the URLs that +have changed in availability state within the watch duration of the original +availability request. + +Issue(342): Add power saving as a transport requirement and remove the following. + +To save power, the controller may disconnect the QUIC connection and +later reconnect to send availability requests and receive availability +responses and updates. The QUIC connection ID may or may not be the same +when reconnecting. + +To start remote playback, the controller may send a +[=remote-playback-start-request=] message to the receiver with the following +values: + +: remote-playback-id +:: An identifier for this remote playback. It should be universally unique + among all remote playbacks. + +Note: A version 4 (pseudorandom) [=UUID=] is recommended as it meets the +requirements for a remote-playback-id. + +: sources (optional) +:: The [=media resources=] that the controller has selected for playback + on the receiver. Each source must include a <{source/src|source URL}> + and should include an <{source/type|extended MIME type}> when available + for the [=media resource=]. If `sources` is missing or empty, the + `remoting` field must be populated, as the controller will use a + streaming session to send encoded media. + +: text-track-urls +:: URLs of text tracks associated with the [=media resources=]. + +: controls +:: Initial controls for modifying the initial state of the remote playback, as + defined in [[#remote-playback-state-and-controls]]. The controller may send + controls that are optional for the receiver to support before it knows the + receiver supports them. If the receiver does not support them, it will + ignore them and the controller will learn that it does not support them from + the [=remote-playback-start-response=] message. + +: remoting (optional) +:: Parameters for starting a streaming session associated with this + remote playback. If not included, no streaming session is started. + Required when `sources` is missing or empty. + +When the receiver receives a [=remote-playback-start-request=] message, it should +send back a [=remote-playback-start-response=] message. It should do so quickly, +usually before the [=media resource=] has been loaded and instead give updates +of the progress of loading with [=remote-playback-state-event=] messages, unless +the receiver decides to not attempt to load the resource at all. If it chooses +not to, it must respond with the appropriate failure result (such as timeout or +invalid-url). Additionally, the response must include the following: + +: state +:: The initial state of the remote playback, as defined in + [[#remote-playback-state-and-controls]]. + +: remoting (optional) +:: A response to the started streaming session associated with this remote playback. + If not included, no streaming session is started. + +If a streaming session is started, streaming messages such a +[=streaming-session-modify-request=] and [=video-frame=] can be used for the +streaming session as if the streaming session had been started with +[=streaming-session-start-request=] and +[=streaming-session-start-response=]. The streaming session may be terminated +before the remote playback is terminated, but if the remote playback is +terminated first, the streaming session associated with it is automatically +terminated. + +If the controller wishes to modify the state of the remote playback (for +example, to pause, resume, skip, etc), it may send a +[=remote-playback-modify-request=] message with the following values: + +: remote-playback-id +:: The ID of the remote playback to be modified. + +: controls +:: Updated controls as defined in [[#remote-playback-state-and-controls]]. + +When a receiver receives a [=remote-playback-modify-request=] it should send a +[=remote-playback-modify-response=] message in reply with the following values: + +: state +:: The updated state of the remote playback as defined in + [[#remote-playback-state-and-controls]]. + +When the state of remote playback changes without request for modification from +the controller (such as when the skips or pauses due to user user interaction on +the receiver), the receiver may send a [=remote-playback-state-event=] to the +controller. + +The receiver should send a [=remote-playback-state-event=] message whenever: + +Any of the following methods are called: + +* {{HTMLMediaElement/fastSeek()|HTMLMediaElement.fastSeek()}} +* {{HTMLMediaElement/pause()|HTMLMediaElement.pause()}} +* {{HTMLMediaElement/play()|HTMLMediaElement.play()}} + +Any of the following attributes observably change since the last sent [=remote-playback-state-event=] message: + +* {{HTMLMediaElement/currentSrc|HTMLMediaElement.currentSrc}} +* {{HTMLMediaElement/networkState|HTMLMediaElement.networkState}} +* {{HTMLMediaElement/readyState|HTMLMediaElement.readyState}} +* {{HTMLMediaElement/error!!attribute|HTMLMediaElement.error}} +* {{HTMLMediaElement/duration|HTMLMediaElement.duration}} +* {{HTMLMediaElement/buffered|HTMLMediaElement.buffered}} +* {{HTMLMediaElement/seekable|HTMLMediaElement.seekable}} +* {{HTMLMediaElement/playbackRate|HTMLMediaElement.playbackRate}} +* {{HTMLMediaElement/paused|HTMLMediaElement.paused}} +* {{HTMLMediaElement/seeking!!attribute|HTMLMediaElement.seeking}} +* {{HTMLMediaElement/ended!!attribute|HTMLMediaElement.ended}} +* {{HTMLMediaElement/volume|HTMLMediaElement.volume}} +* {{HTMLMediaElement/muted|HTMLMediaElement.muted}} +* {{HTMLMediaElement/audioTracks|HTMLMediaElement.audioTracks}} +* {{HTMLMediaElement/videoTracks|HTMLMediaElement.videoTracks}} +* {{HTMLMediaElement/textTracks|HTMLMediaElement.textTracks}} +* {{HTMLVideoElement/videoWidth|HTMLVideoElement.videoWidth}} +* {{HTMLVideoElement/videoHeight|HTMLVideoElement.videoHeight}} + +The [=timeline offset=] associated with the playback changes since the last sent [=remote-playback-state-event=] message: + +The {{stalled}} event needs to fire at the associated {{HTMLMediaElement}} instance. + +More than 250ms pass since the last [=remote-playback-state-event=] message + and any of the following attributes observably change since the last + [=remote-playback-state-event=] message. Any new continuously changing + attributes fall under this rule. + +* {{HTMLMediaElement/played|HTMLMediaElement.played}} +* {{HTMLMediaElement/currentTime|HTMLMediaElement.currentTime}} + +NOTE: A media element is required to fire a {{HTMLMediaElement/timeupdate}} +event every 250ms or sooner. + +: remote-playback-id +:: The ID of the remote playback whose state has changed. + +: state +:: The updated state of the remote playback, as defined in + [[#remote-playback-state-and-controls]]. + + +To terminate the remote playback, the controller may send a +[=remote-playback-termination-request=] message with the following values: + +: remote-playback-id +:: The ID of the remote playback to terminate. + +: reason +:: The reason the remote playback is being terminated. + +When a receiver receives a [=remote-playback-termination-request=], it should +send back a [=remote-playback-termination-response=] message to the controller. + +If a receiver terminates a remote playback without a request from the controller +to do so, it must send a [=remote-playback-termination-event=] message to the +controller with the following values: + +: remote-playback-id +:: The ID of the remote playback that was terminated. + +: reason +:: The reason the remote playback was terminated. + +As mentioned in +[[REMOTE-PLAYBACK#disconnecting-from-a-remote-playback-device|Remote Playback +API section 6.2.7]], terminating the remote playback means the controller is no +longer controlling the remote playback and does not necessarily stop media from +rendering on the receiver. Whether or not the receiver stops rendering media depends +upon the implementation of the receiver. + +Remote Playback State and Controls {#remote-playback-state-and-controls} +------------------------------------------------------------------------ + +In order for the controller and receiver to stay in sync with regards to the +state of the remote playback, the controller may send controls to modify the state +(for example, via the [=remote-playback-modify-request=] message) and the receiver +may send updates about state changes (for example, via the +[=remote-playback-state-event=] message). + +The controls sent by the controller include the following individual control +values, each of which is optional. This allows the controller to change one +control value or many control values at once without having to specify all +control values every time. A non-present control value indicates no change. A +present control value indicates the change defined below. These controls +intentionally mirror settable attributes and methods of the +{{HTMLMediaElement}}. + +: source +:: Change the [=media resource=]. See + {{HTMLMediaElement/src|HTMLMediaElement.src}} + for more details. Must not be used in the initial controls of the + [=remote-playback-start-request=] message (which already contains a + [=media resource=]). + +: preload +:: Set how aggressively to preload media. See + {{HTMLMediaElement/preload|HTMLMediaElement.preload}} + for more details. Should only be used in the initial controls of the + [=remote-playback-start-request=] message or when the source is changed. If not + set in the initial controls, it is left to the receiver to decide. This is + optional for the receiver to support and if not supported, the receiver will + behave as though it were never set. + +: loop +:: Set whether or not to loop media. See + {{HTMLMediaElement/loop|HTMLMediaElement.loop}} + for more details. Should only be used in the initial control of the + [=remote-playback-start-request=]. If not set in the initial controls, it is + assumed to be false. + +: paused +:: If true, pause; if false, resume. See + {{HTMLMediaElement/pause()|HTMLMediaElement.pause()}} + and + {{HTMLMediaElement/play()|HTMLMediaElement.play()}} + for more details. If not set in the initial controls, it is left to the + receiver to decide. + +: muted +:: If true, mute; if false, unmute. See + {{HTMLMediaElement/muted|HTMLMediaElement.muted}} + for more details. If not set in the initial controls, it is left to the + receiver to decide. + +: volume +:: Set the audio volume in the range from 0.0 to 1.0 inclusive. See + {{HTMLMediaElement/volume|HTMLMediaElement.volume}} + for more details. If not set in the initial controls, it is left to the + receiver to decide. + +: seek +:: Seek to a precise time. See + {{HTMLMediaElement/currentTime|HTMLMediaElement.currentTime}} + for more details. + +: fast-seek +:: Seek to an approximate time as fast as possible. See + {{HTMLMediaElement/fastSeek()|HTMLMediaElement.fastSeek()}} + for more details. + +: playback-rate +:: Set the rate a which the media plays. See + {{HTMLMediaElement/playbackRate|HTMLMediaElement.playbackRate}} + for more details. If not set in the initial controls, it is left to the + receiver to decide. This is optional for the receiver to support and if not + supported, the receiver will behave as though it were never set. + +: poster +:: Set the URL of an image to show when video data is not available. See + [=poster frame=] + for more details. If not set in the initial controls, no poster is used and + the receiver can choose what to render when video data is unavailable. This + is optional for the receiver to support and if not supported, the receiver + will behave as though it were never set. + +: enabled-audio-track-ids +:: Enable included audio tracks by ID and disable all other audio tracks. See + {{HTMLMediaElement/audioTracks|HTMLMediaElement.audioTracks}} + for more details. + +: selected-video-track-id +:: Select the given video track by ID and unselect all other video tracks. See + {{HTMLMediaElement/videoTracks|HTMLMediaElement.videoTracks}} + for more details. + +: added-text-tracks +:: Add text tracks with the given kinds, labels, and languages. See + {{HTMLMediaElement/addTextTrack()|HTMLMediaElement.addTextTrack()}} + for more details. This is optional for the receiver to support and if not + supported, the receiver will behave as though it were never set. + +: changed-text-tracks +:: Change text tracks by ID. All other text tracks are left + unchanged. Set the mode, add cues, and remove cues by id. See + {{HTMLMediaElement/textTracks|HTMLMediaElement.textTracks}} + for more details. Note that future specifications or extensions to this + specifications are expected to add new fields to the [=text-track-cue=] + (such as text size, alignment, position, etc). Adding and removing + cues is optional for the receiver to support and if not supported, the + receiver will behave as though no cues were added or removed (both adding + and removing are indicated via the support for "added-cues"). As specified in + {{HTMLMediaElement/textTracks|HTMLMediaElement.textTracks}}, + if a cue ID is invalid (removing an un-added ID or adding an ID twice, for example), + the receiver may reject the text track change. + +
+ + + + + + + + + + + + + + + + +
Field Default value for the initial controls Receiver support
source `urls` in [=remote-playback-start-request=]Required
preload Decided by the receiver Not required
loop False Required
paused Decided by the receiver Required
muted Decided by the receiver Required
volume Decided by the receiver Required
seek (None) Required
fast-seek (None) Required
playback-rate Decided by the receiver Not required
poster Decided by the receiver Not required
enabled-audio-track-ids(None) Required
selected-video-track-id(None) Required
added-text-tracks (None) Not required
changed-text-tracks (None) Not required
+
+ +The states sent by the receiver include the following individual state values, +each of which is optional. This allows the receiver to update the controller +about more than one state value at once without having to specify all +state values every time. A non-present state value indicates the state has not +changed. + +: supports +:: The controls the receiver supports. These may differ according to the [=media + resource=] and should not change unless the [=media resource=] also changes. + The default is empty (support for nothing) + for the initial state in the [=remote-playback-start-response=] message. + +: source +:: The current [=media resource=]. See + {{HTMLMediaElement/currentSrc|HTMLMediaElement.currentSrc}}. + Must be present in the initial state in the [=remote-playback-start-response=] message + so the controller knows what [=media resource=] was selected for playback. + +: loading +:: The state of network activity for loading the [=media resource=]. See + {{HTMLMediaElement/networkState|HTMLMediaElement.networkState}}. + The default is empty ({{NETWORK_EMPTY}}) + for the initial state in the [=remote-playback-start-response=] message. + +: loaded +:: The state of the loaded media (whether enough is loaded to play). See + {{HTMLMediaElement/readyState|HTMLMediaElement.readyState}}. + The default is nothing ({{HAVE_NOTHING}}) + for the initial state in the [=remote-playback-start-response=] message. + +: error +:: A major error occurred which prevents the remote playback from continuing. See + {{HTMLMediaElement/error!!attribute|HTMLMediaElement.error}} and + [=media error codes=]. + The default is no error + for the initial state in the [=remote-playback-start-response=] message. + +: epoch +:: The "zero time" of the [=media timeline=], in milliseconds relative to the + epoch. See [=timeline offset=] and + {{HTMLMediaElement/getStartDate()|HTMLMediaElement.getStartDate()}}. + The default is an unknown epoch for the initial state in the + [=remote-playback-start-response=] message, which is represented by null. + +: duration +:: The duration of the [=media timeline=], in seconds. See + {{HTMLMediaElement/duration|HTMLMediaElement.duration}}. + The default is an unknown duration + for the initial state in the [=remote-playback-start-response=] message, + which is represented by null. + +: buffered-time-ranges +:: The time ranges for which media has been buffered. See + {{HTMLMediaElement/buffered|HTMLMediaElement.buffered}}. + The default is an empty array + for the initial state in the [=remote-playback-start-response=] message. + +: played-time-ranges +:: The time ranges reached by the playback position during normal playback. See + {{HTMLMediaElement/played|HTMLMediaElement.played}}. + The default is an empty array + for the initial state in the [=remote-playback-start-response=] message. + +: seekable-time-ranges +:: The time ranges for which media is seekable by the controller or the receiver. See + {{HTMLMediaElement/seekable|HTMLMediaElement.seekable}}. + The default is an empty array + for the initial state in the [=remote-playback-start-response=] message. + +: position +:: The playback position. See + [=official playback position=] + and + {{HTMLMediaElement/currentTime|HTMLMediaElement.currentTime}}. + The default is 0 + for the initial state in the [=remote-playback-start-response=] message. + +: playbackRate +:: The current rate of playback on a scale where 1.0 is "normal speed". See + {{HTMLMediaElement/playbackRate|HTMLMediaElement.playbackRate}}. + The default is 1.0 + for the initial state in the [=remote-playback-start-response=] message. + +: paused +:: Whether media is paused or not. See + {{HTMLMediaElement/paused|HTMLMediaElement.paused}}. + The default is false + for the initial state in the [=remote-playback-start-response=] message. + +: seeking +:: Whether the receiver is seeking or not. See + {{HTMLMediaElement/seeking!!attribute|HTMLMediaElement.seeking}}. + The default is false + for the initial state in the [=remote-playback-start-response=] message. + +: stalled +:: If true, media is not playing because not enough media is loaded, and false otherwise. See + the {{stalled}} event. + The default is false + for the initial state in the [=remote-playback-start-response=] message. + +: ended +:: Whether media has reached the end or not. See + {{HTMLMediaElement/ended!!attribute|HTMLMediaElement.ended}}. + The default is false + for the initial state in the [=remote-playback-start-response=] message. + +: volume +:: The current volume of playback on a scale of 0.0 to 1.0. See + {{HTMLMediaElement/volume|HTMLMediaElement.volume}}. + The default is 1.0 + for the initial state in the [=remote-playback-start-response=] message. + +: muted +:: True if audio is muted (overriding the volume value) and false otherwise. + See + {{HTMLMediaElement/muted|HTMLMediaElement.muted}}. + The default is false + for the initial state in the [=remote-playback-start-response=] message. + +: resolution +:: The "intrinsic width" and "intrinsic width" of the video. See + {{HTMLVideoElement/videoWidth|HTMLVideoElement.videoWidth}} + and + {{HTMLVideoElement/videoHeight|HTMLVideoElement.videoHeight}}. + The default is an unknown resolution + for the initial state in the [=remote-playback-start-response=] message, + which is represented by null. + +: audio-tracks +:: The available audio tracks, which can individually enabled or disabled. See + {{HTMLMediaElement/audioTracks|HTMLMediaElement.audioTracks}}. + The default is an empty array + for the initial state in the [=remote-playback-start-response=] message. + +: video-tracks +:: The available video tracks. Only one may be selected. See + {{HTMLMediaElement/videoTracks|HTMLMediaElement.videoTracks}}. + The default is an empty array + for the initial state in the [=remote-playback-start-response=] message. + +: text-tracks +:: The available text tracks, which can be individually shown, hidden, or disabled. See + {{HTMLMediaElement/textTracks|HTMLMediaElement.textTracks}}. + The controller can also add cues to and remove cues from text tracks. + The default is an empty array + for the initial state in the [=remote-playback-start-response=] message. + +Media positions, durations, and time ranges are defined in terms of the [=media +timeline=] specified in HTML, which are fractional seconds between zero and the +media duration. + +NOTE: An Open Screen agent can convert between values on the media timeline and +the media sync time sent with individual media frames using the steps in +[[#appendix-c]]. + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Field Default value for the initial state
supports Empty
source `url` in `state` in [=remote-playback-start-response=] (required field)
loading `empty`
loaded `nothing`
error No error
epoch `null`
duration `null`
buffered-time-rangesEmpty array
played-time-ranges Empty array
seekable-time-rangesEmpty array
position 0.0
playbackRate 1.0
paused False
seeking False
stalled False
ended False
volume 1.0
muted False
resolution `null`
audio-tracks Empty array
video-tracks Empty array
text-tracks Empty array
+
+ + +Remote Playback API {#remote-playback-api} +------------------------------------------ + +An [=Open Screen Protocol agent=] that implements the +[[REMOTE-PLAYBACK|Remote Playback API]] must support the +`control-remote-playback` capability. It may support the `send-streaming` +capability so it can send {{HTMLMediaElement}} media data through media +remoting. + +An an [=OSP agent=] that is a [=remote playback device=] for the +[[REMOTE-PLAYBACK|Remote Playback API]] must support the +`receive-remote-playback` capability. It may support the `receive-streaming` +capability so it can receive {{HTMLMediaElement}} data through media remoting. + +The same OSP agent may implement both the [[REMOTE-PLAYBACK|Remote Playback API]] +and be a [=remote playback device=] for that API. + +This is how the [[REMOTE-PLAYBACK|Remote Playback API]] uses the +messages defined in [[#remote-playback-protocol]]: + +When [[REMOTE-PLAYBACK#the-list-of-available-remote-playback-devices|section +5.2.1.2]] says "This list contains [=remote playback devices=] and is populated +based on an implementation specific discovery mechanism" and +[[REMOTE-PLAYBACK#the-list-of-available-remote-playback-devices|section +5.2.1.4]] says "Retrieve available remote playback devices (using an +implementation specific mechanism)", the user agent may use the mDNS, QUIC, +[=agent-info-request=], and [=remote-playback-availability-request=] messages +defined previously in this spec to discover [=receivers=]. The +[=remote-playback-availability-request=] URLs must contain the [=availability +sources set=]. + +When +[[REMOTE-PLAYBACK#establishing-a-connection-with-a-remote-playback-device|section +5.2.4]] says "Request connection of remote to device. The implementation of this +step is specific to the user agent." and "Synchronize the current media element +state with the remote playback state", the controller may send the +[=remote-playback-start-request=] message to the receiver to start remote +playback. The [=remote-playback-start-request=] URLs must contain the [=remote +playback source=]. The current [[REMOTE-PLAYBACK|Remote Playback API]] only +allows a single source, but the protocol allows for several and future versions +of [[REMOTE-PLAYBACK|Remote Playback API]] may allow for several. + +When +[[REMOTE-PLAYBACK#establishing-a-connection-with-a-remote-playback-device|section +5.2.4]] says "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", the controller may send +[=remote-playback-modify-request=] messages to the receiver to change the remote playback state +based on changes to the local media element and receive +[=remote-playback-modify-response=] and [=remote-playback-state-event=] messages to +change the local media element based on changes to the remote playback state. + +When +[[REMOTE-PLAYBACK#disconnecting-from-a-remote-playback-device|section +5.2.7]] says "Request disconnection of remote from the device. The +implementation of this step is specific to the user agent," the controller may +send the [=remote-playback-termination-request=] message to the receiver. + + +Streaming Protocol {#streaming-protocol} +======================================== + +This section defines the use of the Open Screen Protocol for streaming +media from a [=media sender=] to a [=media receiver=]. + +If an [=Open Screen Protocol agent=] is a media sender, it must advertise the +`send-streaming` capability. If an OSP agent is a media receiver, it must +advertise the `receive-streaming` capability. The same agent may be a media +sender and a media receiver. + +Streaming Protocol Capabilities {#streaming-protocol-capabilities} +-------------------------------------------- +If the advertiser is already authenticated, the requester has the ability to +request additional information by sending an [=streaming-capabilities-request=] +message, and receive back a [=streaming-capabilities-response=] message with the +following fields: + +: receive-audio (required) +:: A list of capabilities for receiving audio. For an explanation of fields, see below. + +: receive-video (required) +:: A list of capabilities for receiving video. For an explanation of fields, see below. + + +The format type is used as the basis for audio and video capabilities. +Formats are composed of the following fields: + +: codec-name (required) +:: A fully qualified codec string listed in the [[WEBCODECS-CODEC-REGISTRY]] and further + specified by the codec-specific registrations referenced in that registry. + +For `codec-name`, Open Screen agents may also accept a single-codec [=codec +parameter=] as described in [[!RFC6381]] for codecs not listed in the +[[WEBCODECS-CODEC-REGISTRY]]. + +Audio capabilities are composed of the above format type, with the following +additional fields: + +: max-audio-channels (optional) +:: An optional field indicating the maximum amount of audio + channels the media receiver is capable of supporting. Default value is "2," meaning + a stereo speaker channel setup. + +: min-bit-rate (optional) +:: An optional field indicating the minimum audio bit rate that + the media receiver can handle, in kilobits per second. Default is no minimum. + +Video capabilities are similarly composed of the above format type, with the +following additional fields: + +: max-resolution (optional) +:: An optional field indicating the maximum video-resolution (width, height) + that the media receiver is capable of processing. Default is no maximum. + +: max-frames-per-second (optional) +:: An optional field indicating the maximum frames-per-second the media receiver is + capable of processing. Default is no maximum. + +: max-pixels-per-second (optional) +:: An optional field indicating the maximum pixels-per-second the media receiver is + capable of processing, in pixels per second. Default is no maximum. + +: min-video-bit-rate (optional) +:: An optional field indicating the minimum video bit rate the device is + capable of processing, in kilobits per second. Default is no minimum. + +: aspect-ratio (optional) +:: An optional field indicating what its ideal aspect ratio is, e.g. a 16:10 + display could return this value as 1.6 to indicate its preferred content + scaling. Default is none. + +: color-gamut (optional) +:: An optional field indicating the widest color space that can be decoded and + rendered by the media receiver. The media sender may use this value to + determine how to encode video, and should assume all narrower color spaces + are supported. Valid values correspond to [[MEDIA-CAPABILITIES#colorgamut|ColorGamut]] + in the [[!MEDIA-CAPABILITIES|Media Capabilities]] API. The default value is + "srgb". + +NOTE: Support for "p3" implies support for "srgb", and support for "rec2020" +implies support for "p3" and "srgb". + +: hdr-formats (optional) +:: An optional field indicating what HDR transfer functions and metadata formats + can be decoded and rendered by the media receiver. Each `video-hdr-format` + consists of two fields, `transfer-function` and `hdr-metadata`. + + The `transfer-function` field must be a valid + [[MEDIA-CAPABILITIES#transferfunction|TransferFunction]] + and the `hdr-metadata` field must be a valid + [[MEDIA-CAPABILITIES#hdrmetadatatype|HdrMetadataType]], both defined in the + [[!MEDIA-CAPABILITIES|Media Capabilities]] API. + + If a `video-hdr-format` is provided with a `transfer-function` but no + `hdr-metadata`, then the media receiver can render the `transfer-function` + without any associated metadata. (This is the case, for example, with the + "hlg" `transfer-function`.) + + The media receiver should ignore duplicate entries in `hdr-formats.` + If no `hdr-formats` are listed, then the media reciever cannot decode any + HDR formats. + + +: native-resolutions (optional) +:: An optional field indicating what video-resolutions the media receiver supports and + considers to be "native," meaning that scaling is not required. + The default value is none. + +: supports-scaling (optional) +:: An optional boolean field indicating whether the media receiver can scale + content provided in a video-resolution not listed in the native-resolutions + list (if provided) or of a different aspect ratio. The default value is + true. + +: supports-rotation (optional) +:: An optional boolean field indicating whether the media receiver can receive + video frames with the rotation field set. The default value is true. + + + +Sessions {#streaming-sessions} +------------------------------------ + +To start a streaming session, a sender may send a +[=streaming-session-start-request=] message with the following fields: + +: streaming-session-id +:: Identifies the streaming session. Must be unique for the (sender, + receiver) pair. Can be used later to modify or terminate a + streaming session. These IDs should be treated like other IDs + with regards to the `state-token` as specified in + [[#requests-responses-watches]]. + +: desired-stats-interval +:: Indicates the frequency the receiver should send stats messages to + the sender. + +: stream-offers +:: Indicates the streams that the receiver can request from the sender. + +Each stream offer contains the following fields: + +: media-stream-id +:: Identifies the media stream being offered. Must be unique within + the streaming session. Can be used by the receiver to request the + media session. These IDs should be treated like other IDs + with regards to the `state-token` as specified in + [[#requests-responses-watches]]. + +: display-name +:: An optional name intended to be shown to a user, such that the + receiver may allow the user to choose which media streams to + receive, or if they are received automatically by the receiver, + give the user some information about what the media stream is. + +: audio +:: A list of audio encodings offered. An audio encoding is a series + of encoded audio frames. Encodings define fields needed by + the receiver to know how to decode the encoding, such as codec. + They can differ by codec and related fields, but should be different + encodings of the same audio. + +: video +:: A list of video encodings offered. A video encoding is a series of + encoded video frames. Encodings define fields needed by the + receiver to know how to decode the encoding, such as codec and + default duration. They can differ by codec and potentially other + fields, but should be different encodings of the same video. + +: data +:: A list of data encodings offered. A data encoding is a series of + data frames. Encodings define fields needed by the + receiver to know how to interpret the encoding, such as data type and + default duration. They can differ by data type and potentially other + fields, but should be different encodings of the same data. + (For encodings of different data, use distinct media streams, + not distinct encodints with the same media stream). + + +Each audio encoding offered defines the following fields: + +: encoding-id +:: Identifies the audio encoding being offered. Must be unique within + the media stream. These IDs should be treated like request IDs + with regards to the `state-token` as specified in + [[#requests-responses-watches]]. + +: codec-name +:: The name of the codec used by the encoding, following the same + rules as `codec-name` in [[#streaming-protocol-capabilities]]. + +: time-scale +:: The [=time scale=] used by all audio frames. This allows senders to + make audio-frame messages smaller by not including the time scale + in each one. + +: default-duration: +:: The duration of an audio frame. This allows senders to make + audio-frame messages smaller by not including the duration for + audio-frame messages that have the default duration. + +Each video encoding offered defines the following fields: + +: encoding-id +:: Identifies the video encoding being offered. Must be unique within + the media stream. These IDs should be treated like request IDs + with regards to the `state-token` as specified in + [[#requests-responses-watches]]. + +: codec-name +:: The name of the codec used by the encoding, following the same + rules as `codec-name` in [[#streaming-protocol-capabilities]]. + +: time-scale +:: The [=time scale=] used by all video frames. This allows senders to + make video-frame messages smaller by not including the time scale + in each one. + +: default-duration: +:: The default duration of a video frame. This allows senders to make + video-frame messages smaller by not including the duration for + video-frame messages that have the default duration. + +: default-rotation: +:: The default rotation of a video frame. This allows senders to make + video-frame messages smaller by not including the rotation for + video-frame messages that have the default rotation. + +Each data encoding offered defines the following fields: + +: encoding-id +:: Identifies the data encoding being offered. Must be unique within + the media stream. These IDs should be treated like request IDs + with regards to the `state-token` as specified in + [[#requests-responses-watches]]. + +: data-type-name +:: The name of the data type used by the encoding. + +: time-scale +:: The [=time scale=] used by all data frames. This allows senders to + make data-frame messages smaller by not including the time scale + in each one. + +: default-duration: +:: The duration of an data frame . This allows senders to make + data-frame messages smaller by not including the duration for + data-frame messages that have the default duration. + +After receiving a [=streaming-session-start-request=] message, a receiver +should send back a [=streaming-session-start-response=] message with the +following fields: + +: desired-stats-interval +:: Indicates the frequency the sender should send stats messages to + the receiver. + +: stream-requests +:: Indicates which media streams the receiver would like to receive + from the sender. + +Each stream request contains the following fields: + +: media-stream-id +:: The ID of the stream reqeusted. + +: audio (optional) +:: The requested audio encoding, by encoding ID + +: video (optional) +:: The requested video encoding, by encoding ID. It may + include a target resolution and maximum frame rate. The sender + should not exceed the maximum frame rate and should attempt to + send at the target bitrate, possibly exceeding it by a small amount. + +: data (optional) +:: The requested data encoding, by encoding ID + + +During a streaming session, the receiver can modify the requests it +made for encodings by sending a [=streaming-session-modify-request=] +containing a modified list of stream-requests. When the sender receives +a [=streaming-session-modify-request=], it should send back a +[=streaming-session-modify-response=] indicate whether or not the +application of the new request from the +[=streaming-session-modify-request=] was successful. + +NOTE: If the sender wishes to send an encoding other than the one selected by +the receiver in a [=streaming-session-start-response=] or +[=streaming-session-modify-request=], it must terminate the current session +and start a new session. + +Finally, the sender may terminate the streaming session by sending +a [=streaming-session-terminate-request=] command. When the receiver +receives the [=streaming-session-terminate-request=], it should send back +a [=streaming-session-terminate-response=]. The receiver can terminate at +any point and notify the sender by sending a +[=streaming-session-terminate-event=] message. + +Audio {#streaming-audio} +------------------------------ + +[=Media senders=] may send audio to [=media receivers=] by sending +[=audio-frame=] messages (see [[#appendix-a]]) with the following keys and +values. An audio frame message contains a set of encoded audio samples for a +range of time. A series of encoded audio frames that share a codec and a +timeline form an audio encoding. + +Unlike most Open Screen Protocol messages, this one uses an +array-based grouping rather than a struct-based grouping. For +required fields, this allows for a more efficient use of bytes on the +wire, which is important for streaming audio because the payload is +typically so small and every byte of overhead is relatively large. In +order to accomodate optional values in the array-based grouping, one +optional field in the array is used to hold all optional values in a +struct-based grouping. This will hopefully provide a good balance of +efficiency and flexibility. + +Issue(343): Rewrite the following to not depend on QUIC specifics. + +To allow for audio frames to be sent out of order, they should be sent in +separate QUIC streams. + +: encoding-id +:: Identifies the media encoding to which this audio frame belongs. This can be + used to reference fields of the encoding (from the + [=audio-encoding-offer=] message) such as the codec, codec properties, + [=time scale=], and default duration. + Referencing fields of the encoding through the encoding id + helps to avoid sending duplicate information in every frame. + +: start-time +:: Identifies the beginning of the time range of the audio frame. The + end time can be inferred from the start time and duration. The + [=time scale=] is equal to the value in the `time-scale` field of the + [=audio-encoding-offer=] message referenced by the `encoding-id`. + +: duration +:: If present, the duration of the audio frame. If not present, the + duration is equal to the `default-duration` field of the + [=audio-encoding-offer=] message referenced by the `encoding-id`. + The [=time scale=] is equal to the value in the `time-scale` field of + the [=audio-encoding-offer=] message referenced by the `encoding-id`. + +: sync-time +:: If present, a time used to synchronize the start time of this audio frame (and + thus, this encoding) with that of other media encodings on + different timelines. It may be wall clock time, but it need not + be; it can be any clock chosen by the media sender. + +: payload +:: The encoded audio. The codec is equal to the `codec-name` field of the + [=audio-encoding-offer=] message referenced by the `encoding-id`. + +Video {#streaming-video} +-------------------------------------------- + +Media senders may send video to media receivers by sending [=video-frame=] +messages (see [[#appendix-a]]) with the following keys and values. A video +frame message contains an encoded video frame (an encoded image) at a specific +point in time or over a specfic time range (if the duration is known). A series +of encoded video frames that share a codec and a timeline form a video encoding. + +Issue(343): Rewrite the following to not depend on QUIC specifics. + +To allow for video frames to be sent out of order, they may be sent in +separate QUIC streams. If the encoding is a long chain of encoded video frames +dependent on the previous one back until an independent frame, it may make sense +to send them in a single QUIC stream starting at the indepdendent frame and +ending at the last dependent frame. + +: encoding-id +:: Identifies the media encoding to which this video frame belongs. + This can be used to reference fields of the encoding such as the + codec, codec properties, [=time scale=], and default rotation. + Referencing fields of the encoding through the encoding id helps + to avoid sending duplicate information in every frame. + +: sequence-number +:: Identifies the frame and its order in the encoding. + Within an encoding, larger sequence numbers mean later start times. + Within an encoding, gaps in sequence numbers mean frames are missing. + +: depends-on +:: If present, the sequence numbers of the frames this frame depends on. + If a sequence numbers is negative, it is treated as a relative sequence numbers + and the sequence numbers is calculated by adding it to the sequence number of this frame. + If empty, this is an independent frame (a key frame). + If not present, the default value is [-1]. + +: start-time +:: Identifies the beginning of the time range of the video frame. The + end time can be inferred from the start time and duration. The + [=time scale=] is equal to the value in the `time-scale` field of the + [=video-encoding-offer=] message referenced by the `encoding-id`. + +: duration +:: If present, the duration of the video frame. If not present, that + means duration is unknown. The [=time scale=] is equal to the value + in the `time-scale` field of the [=video-encoding-offer=] message + referenced by the `encoding-id`. + +: sync-time +:: If present, a time used to synchronize the start time of this frame (and + thus, this encoding) with that of other media encodings on different + timelines. + +: rotation +:: If present, indicates how the frame should be rotated after + decoding but before rendering. Rotation is clockwise in + increments of 90 degrees. The default is equal to the + `default-rotation` field of the [=video-encoding-offer=] message + referenced by the `encoding-id`. + +: payload +:: The encoded video frame (encoded image). The codec is equal to the + `codec-name` field of the [=video-encoding-offer=] message referenced + by the `encoding-id`. + +Data {#streaming-data} +------------------------------------ + +Media senders may send timed data to media receivers by sending [=data-frame=] messages (see +[[#appendix-a]]) with the following keys and values. A data frame message +contains an arbitrary payload that can be synchronized with audio and video. +A series of data frames that share a data type and timeline form a data encoding. + +Issue(343): Rewrite the following to not depend on QUIC specifics. + +To allow for data frames to be sent out of order, they may be sent in separate +QUIC streams, but more than one data frame may be sent in one QUIC stream if +that makes sense for a specific type of data. + +: encoding-id +:: Identifies the data encoding to which this data frame belongs. This can be + used to reference fields of the encoding such as the type of data and + [=time scale=]. Referencing fields of the encoding through the encoding id + helps to avoid sending duplicate information in every frame. + +: sequence-number +:: Identifies the frame and its order in the encoding. + Within an encoding, larger sequence numbers mean later start times. + Within an encoding, gaps in sequence numbers mean frames are missing. + +: start-time +:: Identifies the beginning of the time range of the data frame. The + end time can be inferred from the start time and duration. The + [=time scale=] is equal to the value in the `time-scale` field of the + [=data-encoding-offer=] message referenced by the `encoding-id`. + +: duration +:: If present, the duration of the data frame. If not present, the + duration is equal to the `default-duration` field of the + [=data-encoding-offer=] message referenced by the `encoding-id`. + The [=time scale=] is equal to the value in the `time-scale` field of + the [=data-encoding-offer=] message referenced by the `encoding-id`. + +: sync-time +:: If present, a time used to synchronize the start time of this data frame (and + thus, this encoding) with that of other media encodings on different + timelines. + +: payload +:: The data. The data type is equal to the `data-type-name` field of the + the [=data-encoding-offer=] message referenced by the `encoding-id`. + +Feedback {#streaming-feedback} +------------------------------------ + +The media receiver can send feedback to the media sender, such as key frame +requests. + +A video key frame is requested by sending a video-request message with +the following keys and values. + +Issue(343): Rewrite the following to not depend on QUIC specifics. + +To allow for video frames to be sent out of order, they may be sent in separate +QUIC streams. + +: encoding-id +:: The encoding for which the media sender should send a new key frame. + +: sequence-number +:: Gives the order in the encoding. + Within an encoding, larger sequence numbers invalidate previous ones. + A media sender may ignore smaller sequence numbers after a larger one has been processed. + This it to prevent out-of-order requests from generating more key frames than necessary. + +: highest-decoded-frame-sequence-number: uint +:: If set, the media sender may generate a video frame dependent on the last decoded + frame. If not set, the media sender must generate an indepdendent (key) frame. + +Stats {#streaming-stats} +------------------------------ + +During a streaming session, the sender should send stats with the +[=streaming-session-sender-stats-event=] at the interval the receiver requested. It +should send all of the following stats for all of the media streams it is +sending. The [=streaming-session-sender-stats-event=] message contains the following +fields: + +: streaming-session-id +:: The ID of the streaming session these stats apply to. + +: system-time +:: The time when the stats were calculated, using a monotonic system + clock. + +: audio +:: Stats specific to audio. Stats for multiple encodings can be sent + at once, but encodings need not be included if the stats haven't + changed. See below. + +: video +:: Stats specific to video. Stats for multiple encodings can be sent + at once, but encodings need not be included if the stats haven't + changed. See below. + +Audio encoding sender stats include the following fields: + +: encoding-id +:: The ID of the encoding for which the stats apply. + +: cumulative-sent-frames +:: The total number of frames sent. + +: cumulative-encode-delay +:: The sum of the time spent encoding frames sent. + +Video encoding sender stats include the following fields: + +: encoding-id +:: The ID of the encoding for which the stats apply. + +: cumulative-sent-duration +:: The sum of all of the durations of all of the audio frames sent. + +: cumulative-encode-delay +:: The sum of the time spent encoding frames sent. + +: cumulative-dropped frames +:: The total number of frames that were not sent due to network, CPU, + or other contraints. + + +During a streaming session, the receiver should send stats with the +[=streaming-session-receiver-stats-event=] at the interval the sender requested. +It should send all of the following stats for all of the media streams it is +receiving. + +If the receiver is using a buffer to hold frames before playing them out, it +should also send the status of that buffer using the `remote-buffer-status` field. +It can have one of three values: + +- `enough-data`: The buffer has neither too much data nor insufficient data. +- `insufficient-data`: The buffer will underrun and not have sufficient frame + data at the time it is scheduled to be played out. +- `too-much-data`: At the current send rate, the buffer will overrun and future + frame data will be discarded before it can be played out. + +A sender that receives a status of `insufficient-data` should increase its send +rate, or switch to a more efficient encoding for future frames. A sender that +receives a status of `too-much-data` should decrease its send rate. + +If the receiver is playing frames immediately without buffering, it should always +report a buffering status of `enough-data`. + +The [=streaming-session-receiver-stats-event=] message contains the +following fields: + +: streaming-session-id +:: The ID of the streaming session these stats apply to. + +: system-time +:: The time when the stats were calculated, using a monotonic system + clock. + +: audio +:: Stats specific to audio. Stats for multiple encodings can be sent + at once, but encodings need not be included if the stats haven't + changed. See below. + +: video +:: Stats specific to video. Stats for multiple encodings can be sent + at once, but encodings need not be included if the stats haven't + changed. See below. + +Audio encoding receiver stats include the following fields. If not +present, that indicates the value has not changed since the last value. + +: encoding-id +:: The ID of the encoding for which the stats apply. + +: cumulative-decoded-frames +:: The total number of audio frames received and decoded. + +: cumulative-received-duration +:: The sum of all of the durations of all of the audio frames received. + +: cumulative-lost-duration +:: The sum of all of the durations of all of the audio frames detected as lost. + +: cumulative-buffer-delay +:: The sum of the time frames spent buffered between receipt and playout. + +: cumulative-decode-delay +:: The sum of the time spent decoding frames received. + +: remote-buffer-status : streaming-buffer-status +:: The status of the remote buffer for this encoding. + +Video encoding receiver stats include the following fields. If not +present, that indicates the value has not changed since the last +value. + +: encoding-id +:: The ID of the encoding for which the stats apply. + +: cumulative-decoded-frames +:: The total number of video frames received and decoded. + +: cumulative-lost-frames +:: The total number of video frames detected as lost. + +: cumulative-buffer-delay +:: The sum of the time frames spent buffered between receipt and render. + +: cumulative-decode-delay +:: The sum of the time spent decoding frames received. + +: remote-buffer-status : streaming-buffer-status +:: The status of the remote buffer for this encoding. + + +Requests, Responses, and Watches {#requests-responses-watches} +=============================================================== + +Multiple sub-protocols in the Open Screen Protocol have messages that act as +requests, responses, watches, and events. Most requests have a `request-id`, and +the agent that receives the request must send exactly one reponse message in +return with the same `request-id`. A watch request has a `watch-id`, and the +agent that receives the request may send any number of event messages in +response with the same `watch-id`, until the watch request expires. + +`request-id` and `watch-id` values are unsigned integer IDs that are assigned +from a counter kept by each agent that starts at 1 and increments by 1 for each +ID. Whenever an agent changes its `state-token`, it must reset its counter to 1. + +When an agent sees that another agent has reset its state (by virtue of +advertising a new `state-token`), it should discard any requests, responses, +watches and events for that agent. + +Other IDs that must be unique and would cause confusion if one side +loses state, such as `streaming-session-id`, `media-session-id`, and `encoding-id` +should be treated the same. + +Issue(343): Rewrite the following to not depend on QUIC specifics. + +Note: Request and watch IDs are not tied to any particular QUIC connection +between agents. If a QUIC connection is closed, an agent should not discard +requests, responses, watches, or events related to the other party. This allows +agents to save power by closing unused connections. + +Note: Request and watch IDs are not unique across agents. An agent can combine +a request ID with a unique identifier for the agent that sent it (like its +certificate fingerprint) to track requests across multiple agents. + +Protocol Extensions {#protocol-extensions} +=================== + +[=Open Screen Protocol agents=] may exchange extension messages that are not +defined by this specification. This could be used for experimentation, +customization or other purposes. + +To add new extension messages, extension authors must register a capability ID +with a range of message type keys in a +[public registry](https://w3c.github.io/openscreenprotocol/capabilities.html). +Agents may then indicate that they accept an extension by including the +corresponding capability ID in the `capabilities` field of its [=agent-info=] +message. + +Capability IDs 1-999 are reserved for use by the Open Screen Protocol. +Capability IDs 1000 and above are available for extensions. See [[#appendix-b]] +for legal ranges for extension message type keys. + +Note: The purpose of the public registry is to prevent conflicts between +multiple extension authors' capability IDs and message type keys. + +Agents must not send extension messages to another agent that has not advertised +the corresponding extension capability ID. + +Note: See [[#messages]] for how agents handle unknown message type keys. + +It is recommended that extension messages are also encoded in CBOR, to simplify +implementations and provide an easier path to standardization of extension +protocols. However, this is not required; agents that support non-CBOR +extensions must be able to decode QUIC streams that contain a mix of CBOR +messages and non-CBOR extension messages. + +Protocol Extension Fields {#protocol-extension-fields} +------------------------- + +It is legal for an agent to add additional, extension fields to any map-valued +CBOR message type defined by the Open Screen Protocol. Extension fields must be +optional, and the Open Screen Protocol message must make sense both with and +without the field set. + +Agents must not add extended fields to the [=audio-frame=] message directly. +Instead, they may add them to its nested `optional` value. + +Extension fields should use string keys to avoid conflicts with integer keys in +Open Screen Protocol messages. An agent should not send extension fields to +another agent unless that agent advertises an extension capability ID in its +[=agent-info=] that indicates that it understands the extension fields. + +Security and Privacy {#security-privacy} +==================== + +The Open Screen Application Protocol allows two [=OSP agents=] to +exchange user and application data. As such, its security and privacy +considerations should be closely examined. We first evaluate the protocol +itself using the W3C [[SECURITY-PRIVACY-QUESTIONNAIRE|Security and Privacy +Questionnaire]]. We then examine whether the security and privacy guidelines +recommended by the [[PRESENTATION-API|Presentation API]] and the +[[REMOTE-PLAYBACK|Remote Playback API]] are met. Finally we discuss recommended +mitigations that agents can use to meet these security and privacy requirements. + +Threat Models {#threat-models} +-------------------------------- + +### Same-Origin Policy Violations ### {#same-origin-policy-violations} + +The Presentation API allows cross-origin communication between controlling pages +and presentations with the consent of each origin (through their use of the +API). This is similar to cross-origin communication via +{{Window/postMessage(message, targetOrigin, transfer)|postMessage()}} with a +`targetOrigin` of `*`. However, the Presentation API does not convey source +origin information with each message. Therefore, the Open Screen Protocol does +not convey origin information between its agents. + +The [=presentation identifier=] carries some protection against unrestricted +cross-origin access; but, rigorous authentication of the parties connected by a +{{PresentationConnection}} must be done at the application level. + +Open Screen Application Protocol Security and Privacy Considerations {#security-privacy-questions} +----------------------------------- + +### Personally Identifiable Information & High-Value Data ### {#personally-identifiable-information} + +The following data exchanged by the protocol can be personally identifiable +and/or high value data: + +1. Presentation URLs and availability results +1. Presentation identifiers +1. Presentation connection IDs +1. Presentation connection messages +1. Remote playback URLs +1. Remote playback commands and status messages + +[=Presentation identifiers=] are considered high value data because they can be +used in conjunction with a Presentation URL to connect to a running +presentation. + +Data provided through [=agent-info=] cannot be reasonably made confidential and +should be considered public: + +- The display name +- The device model name +- The agent's capabilities +- Preferred locales + +This data, while not considered personally identifiable, is important to +protect to prevent an attacker from changing it or substituting other values. + +### Cross Origin State Considerations ### {#cross-origin-state} + +Access to origin state across browsing sessions is possible through the +Presentation API by reconnecting to a presentation that was started by a +previous session. This scenario is addressed in +[[PRESENTATION-API#cross-origin-access]]. + +Receiver availability is available cross-origin depending on the user's network +context. Exposure of this data to the Web is also discussed in +[[PRESENTATION-API#personally-identifiable-information]] and +[[REMOTE-PLAYBACK#personally-identifiable-information]]. + +### Origin Access to Other Devices ### {#origin-access-devices} + +By design, the Open Screen Protocol allows access to receivers from the Web. By +implementing the protocol, these devices are knowingly making themselves +available to the Web and should be designed accordingly. + +Below, we discuss mitigation steps to prevent malicious use of these devices. + +### Private Browsing Mode ### {#private-browsing-mode} + +The Open Screen Protocol itself does not distinguish between the user agent's normal +browsing and [private browsing](https://www.w3.org/2001/tag/doc/private-browsing-modes/) +modes. + +Issue(342): Update to reflect generic transport requirements. + +However, it's recommended that user agents use separate authentication contexts +and QUIC connections for normal and private browsing from the same user agent +instance. This makes it more difficult for OSP agents to match activities +occurring in normal and private browsing by the same user. + +### Persistent State ### {#persistent-state} + +An agent is likely to persist the identity of agents that it has previously +connected to from the [=agent-info=] message or other application messages. + +However, this data is not normally exposed to the Web, only through the native +UI of the user agent during the display selection or display authentication +process. It can be an implementation choice whether the user agent clears or +retains this data when the user clears browsing data. + +### Other Considerations ### {#other-considerations} + +The Open Screen Protocol does not grant to the Web additional access to the +following: + +* New script loading mechanisms +* Access to the user's location +* Access to device sensors +* Access to the user's local computing environment +* Control over the user agent's native UI +* Security characteristics of the user agent + +Presentation API Considerations {#presentation-api-considerations} +------------------------------- + +[[PRESENTATION-API#security-and-privacy-considerations]] place these +requirements on the Open Screen Protocol: + +1. Presentation URLs and [=presentation identifiers=] should remain private + among the parties that are allowed to connect to a presentation, per the + cross-origin access guidelines. +1. Controllers and receivers should be notified when connections representing + multiple user agent profiles have been made to a presentation, per the user + interface guidelines. +1. Messaging between controllers and receivers should be authenticated and + confidential, per the guidelines for messaging between presentation + connections. + +Issue(342): Update to reflect generic transport requirements. + +The Open Screen Protocol addresses these considerations by: + +1. Requiring mutual authentication and a TLS-secured QUIC connection before + presentation URLs, IDs, or messages are exchanged. +1. Adding explicit messages and connection IDs for individual + {{PresentationConnection|PresentationConnections}} so that agents can track + the number of active connections. + +Remote Playback API Considerations {#remote-playback-considerations} +---------------------------------- + +The [[REMOTE-PLAYBACK#security-and-privacy-considerations]] also state that +messaging between controllers and receivers should also be authenticated and +confidential. + +Issue(342): Update to reflect generic transport requirements. + +This consideration is handled by requiring mutual authentication and a +TLS-secured QUIC connection before any remote playback related messages are +exchanged. + +Mitigation Strategies {#security-mitigations} +-------------------------------------------- + +### Malicious input ### {#malicious-input-mitigations} + +OSP agents should be robust against malicious input that attempts to compromise +the target device by exploiting parsing vulnerabilities. + +CBOR is intended to be less vulnerable to such attacks relative to alternatives +like JSON and XML. Still, agents should be thoroughly tested using approaches +like [fuzz testing](https://en.wikipedia.org/wiki/Fuzzing). + +Where possible, OSP agents (including the content rendering components) should +use defense-in-depth techniques like sandboxing +to prevent vulnerabilities from gaining access to user data or leading to +persistent exploits. + +User Interface Considerations {#security-ui} +----------------------------- + +This specification does not make any specific requirements of the security +relevant user interfaces of OSP agents. However, before an agent has +authenticated another agent, the user interface should make it clear that any +`agent-info` or other data from that agent has not been verified by +authentication. + +### Instance and Display Names ### {#instance-names} + +Issue(342): Update to reflect generic discovery requirements. + +Because DNS-SD Instance Names are the primary information that the user +sees prior to authentication, careful presentation of these names is necessary. + +Agents must treat Instance Names as unverified information, and should check +that the Instance Name is a prefix of the display name received through the +[=agent-info=] message after a successful QUIC connection. Once an agent has done +this check, it can show the name as a verified display name. + +Agents should show only complete display names to the user, instead of truncated +display names from DNS-SD. A truncated display name should be verified as above +before being shown in full as a [=verified display name=]. + +
+This means there are three categories of display names that agents should be +capable of handling: +
    +
  1. Truncated and unverified DNS-SD Instance Names, which should not be shown + to the user.
  2. +
  3. Complete but unverified DNS-SD Instance Names, which can be shown as + unverified prior to authentication.
  4. +
  5. Verified display names.
  6. +
+
+ +Appendix A: Messages {#appendix-a} +==================== + +The following messages are defined using the [=Concise Data Definition +Language=] syntax. When integer keys are used, a comment is appended to the line +to indicate the name of the field. Object definitions in this specification have +this unusual syntax to reduce the number of bytes-on-the-wire, while maintaining +a human-readable name for each key. Integer keys are used instead of object +arrays to allow for easy indexing of optional fields. + +Each root message (one that can be put into a QUIC stream without being enclosed +by another message) has a comment indicating the message type key. + +Smaller numbers should be reserved for message that will be sent more frequently +or are very small or both and larger numbers should be reserved for messages +that are infrequently sent or large or both because smaller type keys encode on +the wire smaller. + +
+path: application_messages.html
+
+ +
+path: code-style.html
+
+ +Appendix B: Message Type Key Ranges {#appendix-b} +=================================== + +The following appendix describes how the range of message type keys is divided. +Legal values are 1 to 264. + +Each type key is encoded as a [=variable-length integer=] on the wire of 1, 2, 4 or +8 bytes. For each wire byte size, 1/4 to 1/2 of the keys are available for +extensions. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BytesRangePurpose
11 - 48Open Screen Protocol
149 - 63Available for extensions
264 - 8,192Open Screen Protocol
28,193 - 16,383Available for extensions
416,384 - 229Reserved for future use
4229+1 - 230-1Available for extensions
8>= 230Reserved for future use
+ +Appendix C: Media Time Conversions {#appendix-c} +================================== + +To convert between a media synchronization timestamp for a given audio or video +frame and a media timeline value, the following formula can be used: + +``` +media-timeline-value = media-zero-time + (value / scale) +``` + +Where: +- `media-zero-time` is the origin of the [=media timeline=] as defined in HTML, + converted to an IEEE-754 double precision floating point number [[IEEE-754]]. +- `value` and `scale` are the values passed in the `sync-time` field of the + corresponding [=audio-frame=] or [=video-frame=]. +- `value / scale` should be computed with double floating point precision. +- `media-timeline-value` is an IEEE-754 double precision floating point number [[IEEE-754]]. + +In the event of an overflow in `media-timeline-value`, the maximum representable +value should be used. diff --git a/application_messages.cddl b/application_messages.cddl new file mode 100644 index 0000000..4f9eb48 --- /dev/null +++ b/application_messages.cddl @@ -0,0 +1,698 @@ +; 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 +} +