open-standards/wai/examples/package-and-deliver/player.js
Transaction Science 632f8cf394 wai: the developer's path, hello, create, dev and verify, a floor build with no system library, and nine examples
wai-rs gains the feature floor (the mandatory floor of SPEC 5: PNG, FLAC and
zstd, decoded with no system codec library) and quickstart (the floor, and the
receipt checks hello and verify run). The wai command line gains four verbs,
one module each, named in one table (dx::VERBS):

- hello: name, dispatch, rebuild and receipt on a picture made here, the
  receipt laid out as http-delivery 8 lays receipts beside an object;
- create: a new project from one of the examples, whose files the binary
  holds; tests/cli_dx.rs holds them to wai/examples/ byte for byte;
- dev: the relay, the room service and the room page on one loopback origin,
  as a production front serves them; --check checks each piece through it;
- verify: the receipt indexes under a directory, checked offline.

The project file a template writes, wai.json, is read in one place
(dx/project.rs). wai/examples/ holds video-room, watch-together, messaging,
encoder-ingest, package-and-deliver, verify-receipts, apple-app, android-app
and remote-desktop, each with a README and a test that runs it through
wai create and wai dev. wai/quickstart/ builds the room client and the
archive that holds wai, the relay, the room service, the remote host and the
packager.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 22:58:39 -04:00

30 lines
1.9 KiB
JavaScript

// Play a packed title's floor in a <video> element with Media Source Extensions: read the HLS
// media playlist wai-pack wrote, then append its initialization segment and each CMAF segment
// to one SourceBuffer. No player library: a VOD playlist of one rendition needs no more.
export async function play(video, playlistUrl, onState = () => {}) {
const base = new URL(playlistUrl, location.href);
const text = await (await fetch(base, { cache: "no-store" })).text();
const lines = text.split("\n").map((l) => l.trim());
const map = lines.find((l) => l.startsWith("#EXT-X-MAP:"));
const init = map && map.match(/URI="([^"]+)"/)[1];
const segments = lines.filter((l) => l && !l.startsWith("#"));
if (!init || !segments.length) throw new Error("the playlist names no initialization segment or no segment");
// The codec the multivariant playlist states for this rendition.
const master = await (await fetch(new URL("master.m3u8", base), { cache: "no-store" })).text();
const codecs = (master.match(/CODECS="([^"]+)"/) || [])[1] || "av01.0.00M.08";
const type = `video/mp4; codecs="${codecs}"`;
if (!window.MediaSource || !MediaSource.isTypeSupported(type)) throw new Error(`this browser does not play ${type}`);
const ms = new MediaSource();
video.src = URL.createObjectURL(ms);
await new Promise((r) => ms.addEventListener("sourceopen", r, { once: true }));
const sb = ms.addSourceBuffer(type);
const append = (b) => new Promise((ok, no) => { sb.addEventListener("updateend", ok, { once: true }); sb.addEventListener("error", no, { once: true }); sb.appendBuffer(b); });
const get = async (u) => new Uint8Array(await (await fetch(new URL(u, base))).arrayBuffer());
await append(await get(init));
for (const [i, s] of segments.entries()) {
await append(await get(s));
onState(`segment ${i + 1} of ${segments.length} appended`);
}
ms.endOfStream();
return { codecs, segments: segments.length };
}