import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { test } from "node:test"; import ts from "typescript"; import { parseHTML } from "linkedom"; const source = readFileSync(new URL("../src/player/movie.ts", import.meta.url), "utf8"); const { outputText } = ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext } }); const { applyMovieMask, movieDisplayable } = await import(`data:text/javascript;base64,${Buffer.from(outputText).toString("base64")}`); test("video opacity uses the mask's red channel and retains existing transparency", () => { const pixels = new Uint8ClampedArray([200, 10, 20, 255, 30, 40, 50, 128]); applyMovieMask(pixels, new Uint8ClampedArray([0, 255, 255, 255, 128, 0, 0, 255])); assert.deepEqual([...pixels], [200, 10, 20, 0, 30, 40, 50, 64]); }); test("masked movie composites frames, synchronizes the mask and stops when removed", () => { const { document } = parseHTML(""); globalThis.document = document; const create = document.createElement.bind(document); const videos = [], frames = [], painted = []; let canvases = 0; globalThis.requestAnimationFrame = (callback) => frames.push(callback); document.createElement = (name) => { const element = create(name); if (name === "video") { Object.assign(element, { readyState: 2, videoWidth: 2, videoHeight: 1, currentTime: 0, ended: false, play: () => Promise.resolve(), pause: () => { element.paused = true; } }); videos.push(element); } if (name === "canvas") { const mask = canvases++ > 0; element.getContext = () => ({ clearRect() {}, drawImage() {}, getImageData: () => ({ data: new Uint8ClampedArray(mask ? [0, 0, 0, 255, 128, 128, 128, 255] : [200, 10, 20, 255, 30, 40, 50, 255]) }), putImageData: (image) => painted.push([...image.data]), }); } return element; }; const canvas = movieDisplayable("/movie.webm", "/mask.webm", false); document.body.append(canvas); videos[1].currentTime = 2; frames.shift()(); assert.deepEqual(painted[0], [200, 10, 20, 0, 30, 40, 50, 128]); assert.equal(videos[1].currentTime, 0); canvas.remove(); frames.shift()(); assert.ok(videos.every((video) => video.paused)); assert.equal(frames.length, 0); });