/ proof-of-concept / plate-dynamics-lab
Sign in
proof-of-concept / plate-dynamics-lab
plate-dynamics-lab / packages / components / test / movie.test.ts
91 lines · 4.1 KBBlameHistoryRaw
1import { describe, expect, it } from "vitest";
2import { frameAtTime, movieSpan, pickMovieFormat } from "../src/video/recordMovie";
3import { readWebmDuration, withWebmDuration } from "../src/video/webmDuration";
4import type { AudioPrep } from "@plate-dyn/core";
6const prep = (durationS: number): AudioPrep => ({
7 samples: new Float32Array(2),
8 fs: 44100,
9 peakPa: 1,
10 gain: 1,
11 durationS,
12});
14describe("the movie recorder's bookkeeping", () => {
15 it("prefers WebM with sound, and falls back to what the browser writes", () => {
16 // WebM with Opus first: it is the one that records with the sound
17 expect(pickMovieFormat(() => true)?.mimeType).toBe("video/webm;codecs=vp9,opus");
18 expect(pickMovieFormat((m) => m === "video/webm")?.ext).toBe("webm");
19 expect(pickMovieFormat((m) => m.startsWith("video/mp4"))?.ext).toBe("mp4");
20 expect(pickMovieFormat(() => false)).toBe(null);
21 });
23 it("shows the frame whose time has arrived, skipping the ones it fell behind", () => {
24 const t = Float64Array.from([0, 0.1, 0.2, 0.3, 0.4]);
25 expect(frameAtTime(t, 0)).toBe(0);
26 expect(frameAtTime(t, 0.05)).toBe(0);
27 expect(frameAtTime(t, 0.1)).toBe(1);
28 expect(frameAtTime(t, 0.25)).toBe(2);
29 expect(frameAtTime(t, 99)).toBe(4);
30 // a recording that fell behind jumps forward rather than replaying
31 expect(frameAtTime(t, 0.35, 1)).toBe(3);
32 // and never goes back, since the search starts where it left off
33 expect(frameAtTime(t, 0.0, 3)).toBe(3);
34 });
36 it("runs as long as the frames or the sound, whichever lasts longer", () => {
37 const t = Float64Array.from([0, 0.5, 1.0]);
38 expect(movieSpan(t)).toBe(1.0);
39 expect(movieSpan(t, prep(2.5))).toBe(2.5);
40 expect(movieSpan(t, prep(0.4))).toBe(1.0);
41 expect(movieSpan(new Float64Array(0), prep(3))).toBe(3);
42 });
43});
45/** A minimal WebM: the header, a Segment of unknown size, an Info with a TimecodeScale, a Cluster. */
46function fakeWebm(): Uint8Array {
47 const header = [0x1a, 0x45, 0xdf, 0xa3, 0x84, 1, 2, 3, 4];
48 const segment = [0x18, 0x53, 0x80, 0x67, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
49 const info = [0x15, 0x49, 0xa9, 0x66, 0x87, 0x2a, 0xd7, 0xb1, 0x83, 0x0f, 0x42, 0x40];
50 const cluster = [0x1f, 0x43, 0xb6, 0x75, 0x84, 9, 8, 7, 6];
51 return Uint8Array.from([...header, ...segment, ...info, ...cluster]);
54describe("the length written into a recorded WebM", () => {
55 it("writes a duration the file did not have, and keeps everything after it", () => {
56 const before = fakeWebm();
57 expect(readWebmDuration(before)).toBe(null);
58 const after = withWebmDuration(before, 2.5);
59 expect(after.length).toBe(before.length + 11);
60 expect(readWebmDuration(after)).toBeCloseTo(2.5, 9);
61 // the Cluster that followed the Info section is byte for byte where it was
62 expect(Array.from(after.subarray(after.length - 9))).toEqual([
63 0x1f, 0x43, 0xb6, 0x75, 0x84, 9, 8, 7, 6,
64 ]);
65 // and the Info section now declares the longer payload: its size byte sits
66 // after the 9-byte header, the 12-byte Segment header and the 4-byte id
67 expect(after[9 + 12 + 4]).toBe(0x87 + 11);
68 });
70 it("overwrites a duration that is already there", () => {
71 const once = withWebmDuration(fakeWebm(), 2.5);
72 const twice = withWebmDuration(once, 7.25);
73 expect(twice.length).toBe(once.length);
74 expect(readWebmDuration(twice)).toBeCloseTo(7.25, 9);
75 });
77 it("writes the length in the units the timecode scale sets", () => {
78 // the fixture's scale is 0x0F4240 ns, a millisecond, so a second is a thousand ticks
79 const patched = withWebmDuration(fakeWebm(), 1);
80 const view = new DataView(patched.buffer, patched.byteOffset, patched.byteLength);
81 expect(view.getFloat64(patched.length - 9 - 8, false)).toBeCloseTo(1000, 9);
82 expect(readWebmDuration(patched)).toBeCloseTo(1, 9);
83 });
85 it("leaves anything that is not WebM alone", () => {
86 const notWebm = Uint8Array.from([0, 0, 0, 0x20, 0x66, 0x74, 0x79, 0x70, 1, 2, 3, 4]);
87 expect(withWebmDuration(notWebm, 3)).toBe(notWebm);
88 expect(readWebmDuration(notWebm)).toBe(null);
89 expect(withWebmDuration(fakeWebm(), 0)).toEqual(fakeWebm());
90 });
91});
↑↓ move↵openescclose