Skip to main content

WAI Extension: Player Integration

Mirrored from the canonical text at commit c5e7133e ().

Status: Draft. Registers no capability and no record. It says what a player integration must do: code that sits between a player that fetches CMAF media over HLS or DASH and that player’s media pipeline, and holds what the player fetched to the receipts WAI serves beside it. It adds no wire form. What is verified is what packaging, http-delivery and live-http-egress already define; what is reported is what delivery-telemetry already defines. This text states the order (verify, then present), the answer to a mismatch (refuse), what is and is not covered, and what may be reported. The reference integration is integrations/web-players/: one core, and a plugin for each of three open-source web players, with the checks themselves in wai-web (feature player), which is wai-rs compiled to wasm32. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.

1. Scope

A WAI package’s floor is plain CMAF with HLS and DASH presentations (packaging §7, §8), and a room’s CMAF tracks are served live as low-latency HLS and DASH (live-http-egress §5, §6). Any player plays them, and a player that knows nothing of WAI plays them unverified. A player integration is how such a player verifies: it is given each thing the player fetched, checks it, and only then lets the player have it.

This text covers an integration in a web page, where the player loads media itself and gives it to Media Source Extensions. It does not cover a sink that decodes WAI objects (browser-sink), and it does not cover playback a page cannot see into: a media element given an HLS address plays through the user agent’s own code, no script is shown the bytes, and nothing here applies to it.

2. Terms

3. Verify before presenting

3.1 An integration MUST NOT hand a player a media segment, a part or a header of its source before that thing is verified, and MUST NOT hand it one that was refused. Verifying after the fact, beside playback, is not conformance.

3.2 An on-demand package. An integration holds a floor’s files to the package receipt as packaging §7 (the CMAF form) and §9 bind them: the receipt verifies against its indexes and its signature (§9); it is signed by the key the page trusts; policy.json hashes to the receipt’s policy digest (packaging §3); floor.listing hashes to the digest the policy pins; and a header is the file the listing names for its floor rung, and a media segment is named by its SHA-256 and is one of the listing’s for that rung. A package whose policy pins no listing binds its segments’ names only through a verifier that rebuilds them from the floor objects, which an integration does not do: it refuses every segment of such a package (floor_not_listed).

3.3 A package’s playlist and MPD. The classical media playlist of a floor rung and the classical MPD list that rung’s header and its segments. An integration holds what they list to the listing: the header named is the rung’s, and the segments named are the listing’s for that rung, all of them, in the listing’s order. A playlist or an MPD that lists anything else, in another order, or by any other addressing than whole files, or an MPD that takes any part of itself from another address, is refused. A multivariant playlist names only playlists of the presentation, each of a floor the package holds. The durations a playlist states, a multivariant playlist’s variants and their bit rates are signed by nothing unless the package states its playlists (§3.9, §5).

3.4 A live part. A part an egress serves is held to its receipt index (live-http-egress §8.1) as http-delivery §8.2 checks a delivery item, over the bytes the player was sent, the group receipt fetched by its name (http-delivery §8.1), under these further conditions:

  1. the group receipt is signed by the owner’s key the page trusts (signer_not_expected);
  2. the object receipt’s group and object are those the part was asked for under (receipt_other_object): a part’s name states its Location (live-http-egress §4.1), and a receipt of another object, however valid, is not this part’s;
  3. every part of one track verifies under one receipt alias, the one the page gave for the track or, where it gave none, the first the track’s parts verified under (receipt_other_track).

An integration joins (jwp-receipts §3): it anchors each chain at the first group it verifies, and recovers a missing parent as http-delivery §8.1 states. A player that starts behind the live edge also asks for a group below every group the chain holds. What links that group to the chain is above it, so an integration fetches downward from the lowest group it holds, each group receipt by the link the one above it names, at most 64 of them, and offers each to the chain, which admits it as history (jwp-receipts §3) or not at all. A group that neither walk links to the chain is refused (parent_not_held).

3.5 A part held on its run. While a part’s group is open it has no receipt index, and an egress that took running receipts serves the run beside it (live-http-egress §8.4). An integration checks it as that section states, against the room, the owner’s key, the track’s anchor digest and its receipt alias, which the page gives it from the room’s catalog. A run that holds fewer records than the part’s position needs does not cover the part (served_uncovered). An integration that was not given what a run is read against refuses the part (running_not_configured).

3.6 A segment. A closed segment is cut at the lengths its parts list states (live-http-egress §8.3) and each piece is checked as §3.4 or §3.5 checks a part. A segment that is still growing (live-http-egress §6.3) is checked as it arrives: an integration hands the player a part’s bytes once those bytes, whole, verified, and holds back everything after the last verified part. Bytes left when the response ends that are no verified part are refused. An integration bounds what it holds unverified.

3.7 Protected media. An integration verifies the bytes as they were served, before any decryption, and does not ask for a key. A protected live part is an object the publisher signed in its protected form, so §3.4 to §3.6 apply to it unchanged. A protected presentation of a package (packaging §7, the protected floor) is held through its index, protection.json: the index names the package’s receipt and policy digest, lists for each floor rung the package’s clear header and segments in the listing’s order, and pairs each with a protected file; a protected segment is named by its SHA-256 and is one the index pairs. The index is signed only where the package’s policy has a statements table (§3.9); otherwise it is not (§5).

3.8 A live playlist or MPD. Every media playlist, multivariant playlist and MPD an egress serves carries a statement in its response’s WAI-Statement header (live-http-egress §8.5), signed by the egress’s statement key under the owner’s delegation of it. An integration checks it as live-http-egress §8.5 has a client check one, against the room’s id and the owner’s key the page gave, before the player is given the playlist, and keeps what that section has a client keep: the last statement it accepted for each playlist, each track’s alias, and the entries of the segments it accepted. So a playlist changed on its way, another track’s playlist, one older than one the player was given, a delta update that does not follow from what the player holds, and a segment restated with another duration are refused before the player acts on them. A track’s alias is the one the page gave, or the first a statement or a part verified under gave it, and a statement and a part that disagree on it are refused (statement_other_track, receipt_other_track). An integration that was not given the room refuses every live playlist (statement_not_configured).

3.9 A stated package. A package whose policy has a statements table carries a statement beside each playlist, multivariant playlist and MPD, and its protected presentation beside each of its own and beside protection.json (packaging §8, statements in a package): <file>.sig, signed by the package receipt’s signer and naming the package receipt. The policy is signed by the receipt, so an integration that holds the receipt (§3.2) knows the statements are required. It checks each such file against the statement fetched beside it, as that item has a verifier check one, before §3.3 and §3.7 hold what the file lists and before the player is given it: a statement not served (statement_not_served), one that does not read, of another file or other bytes, not signed by the package’s signer, of another package or stating other tracks are refused with http-delivery §8.10’s and packaging §13’s reasons. A multivariant playlist so stated is verified, not uncovered, and its variants are still held to the package’s floors (§3.3).

4. Refuse on mismatch

4.1 A thing that does not verify is refused: it is not handed to the player, and the integration answers the player’s request as a failed load, through the player’s own failure path, so that the player’s retry and error handling run and the page’s error listener is told. An integration MUST NOT present refused media, in whole or in part, and MUST NOT replace it with other media.

4.2 A thing with no receipts is refused as one whose receipts do not verify: a part with neither a receipt index nor a run (receipt_not_served), a package without its receipt (package_not_served), a live playlist or MPD with no statement, or a stated package’s (§3.9), (statement_not_served). An integration MUST NOT play unverified media by default, and this text defines no setting that makes it do so.

4.3 An integration is asked about every playlist and every piece of media its player loads, and refuses one that is not of its source (outside_source): a package’s playlist binds only the names it lists (§3.3), and a live one’s statement binds its bytes, not where what they name is served (§3.8), so media from elsewhere would otherwise be presented unverified. It is not asked about what is neither: a key, a licence, a clock.

4.4 What the viewer sees when media is refused is the player’s: its retries, its error, a stall. An integration documents it for each player it supports, and MUST make the reason available to the page.

5. What is not covered

A verdict of uncovered says that nothing signs a thing in this revision, and that the integration handed it on. An integration MUST report as uncovered, never as verified:

And these are outside any verdict:

6. What an integration reports

6.1 An integration reports nothing but delivery-telemetry’s observations (§2 there), with exactly their members: start, a response for each header, segment and part of its source, the first presentation, stalls, and end or abandon. It adds no member, and no identifier of a person, a device, a browser or an account: sid is fresh for each session and derived from nothing (delivery-telemetry §8).

6.2 A response observation’s receipt is the link hash of the receipt the bytes verified against (the group receipt, or the package’s receipt hash), and is null for bytes that were refused and for a part held on its run. Its content_hash is the content hash (jwp-receipts §2.1) of the bytes received. Both are pointers, and neither is evidence (delivery-telemetry §1). Its url is null unless the page states that its addresses carry no token.

6.3 An observation has no member for a verdict, and an integration MUST NOT add one. What was verified, refused and left uncovered is the integration’s tally, which it keeps for the page.

6.4 An integration sends nothing anywhere. Where a session’s log goes is the page’s (delivery-telemetry §8). It signs nothing: a reconstruct-session claim is a sink’s, under a key its decoder host holds (jwp-receipts §2.4.12), and an integration holds no key.

7. Reasons

An integration answers with the reasons of the texts it checks under wherever one applies: packaging §13’s for a package receipt, http-delivery §13’s and jwp-receipts’s for a part’s receipts, live-http-egress §3.5’s for a run, its §8.5’s for a live playlist’s statement, and http-delivery §8.10’s and packaging §13’s for a stated package’s (§3.9). It adds these:

reasonwhat was refused
package_not_serveda file the package’s chain needs (its receipt, an index, its policy) is not served
floor_not_listedthe package’s policy pins no listing of its floor’s CMAF files
signer_not_expectedthe receipt is signed by another key than the one the page trusts
segment_name_mismatcha segment’s bytes do not hash to its name
init_mismatcha header is not the one the listing, or the page’s pin, names
not_in_packagemedia the listing does not name
playlist_not_listeda playlist or an MPD lists other files than the listing’s, or in another order; a multivariant playlist names a playlist outside the presentation or of a floor the package does not hold
playlist_malformeda playlist or an MPD that addresses media otherwise than by whole files, or is not read
protection_index_mismatcha protected presentation’s index is not about this package’s clear floor
receipt_other_objecta part’s receipt or run is of another object than the part was asked for under
receipt_other_tracka part’s receipt is under another alias than its track’s
served_uncovereda part’s run stops short of it (live-http-egress §11 names the same fault of an egress)
running_not_configureda run cannot be read: the page gave no room, anchor or alias
unverified_overflowa growing segment sent more bytes than an integration holds before a part verified
sidecar_too_largea receipt index, a run or a parts list past what an integration reads
outside_sourcea playlist or media that is not of the source the page gave
config_invalidthe page’s source cannot be read: a key that is not one, a name the source does not have
statement_not_configureda live playlist’s statement cannot be read: the page gave no room

8. Conformance

A conforming integration meets §3, §4, §5 and §6. The reference integration (integrations/web-players/) is tested in two ways:

What verifying costs is measured where it is run and stated with its host and date; it is not a property of this text.

9. Security and privacy considerations