Live video on the AT Protocol
1import { v7 as uuidv7 } from "uuid";
2import { makeWindow } from "../window";
3import { E2ETest, TestEnv } from "./test-env";
4import { delay, PlayerReport } from "./util";
5
6const PLAYING_SUCCESS = 0.5;
7
8export const playbackTest: E2ETest = {
9 setup: async (testEnv: TestEnv) => {
10 return {
11 ...testEnv,
12 env: {
13 ...testEnv.env,
14 SP_TEST_STREAM: "true",
15 },
16 };
17 },
18 test: async (testEnv: TestEnv): Promise<string | null> => {
19 const mainWindow = await makeWindow();
20
21 const testId = uuidv7();
22 const definitions = [
23 // {
24 // name: "hls",
25 // forceProtocol: "hls",
26 // },
27 {
28 name: "progressive-mp4",
29 forceProtocol: "progressive-mp4",
30 },
31 {
32 name: "progressive-webm",
33 forceProtocol: "progressive-webm",
34 },
35 {
36 name: "webrtc",
37 forceProtocol: "webrtc",
38 },
39 ];
40 const tests = definitions.map((x) => ({
41 name: x.name,
42 playerId: `${testId}-${x.name}`,
43 src: "self-test",
44 showControls: true,
45 telemetry: true,
46 forceProtocol: x.forceProtocol,
47 }));
48 const enc = encodeURIComponent(JSON.stringify(tests));
49
50 const load = `${testEnv.addr}/multi/${enc}`;
51
52 console.log(`opening ${load}`);
53 mainWindow.loadURL(load);
54
55 let foundThumbnail = false;
56 const interval = setInterval(async () => {
57 const thumb = `${testEnv.addr}/api/playback/self-test/stream.jpg`;
58 console.log(`fetching thumbnail at ${thumb}`);
59 const res = await fetch(thumb);
60 if (res.status === 404) {
61 console.log("no thumbnail found");
62 return;
63 }
64 if (res.status !== 200) {
65 console.log(
66 `unexpected http status ${res.status}, failing thumbnail test`,
67 );
68 clearInterval(interval);
69 return;
70 }
71 const blob = await res.arrayBuffer();
72 if (blob.byteLength < 1) {
73 console.log("thumbnail was empty :(");
74 return;
75 }
76 console.log("found thumbnail!");
77 foundThumbnail = true;
78 clearInterval(interval);
79 }, 1000);
80
81 await delay(testEnv.testDuration);
82 clearInterval(interval);
83 const reports = await Promise.all(
84 tests.map(async (t) => {
85 const res = await fetch(
86 `${testEnv.internalAddr}/player-report/${t.playerId}`,
87 );
88 const data = (await res.json()) as PlayerReport;
89 return { ...t, data: data.whatHappened, retries: data.retries };
90 }),
91 );
92 const failures = [];
93 if (!foundThumbnail) {
94 console.log("never found a thumbnail, failing test");
95 failures.push("never found a thumbnail");
96 }
97 const percentages = reports.map((report) => {
98 if (typeof report.retries === "number" && report.retries > 1) {
99 console.log(`${report.name} had ${report.retries} retries`);
100 // we only care about webrtc failures right now
101 if (report.name === "webrtc") {
102 failures.push("webrtc had retries");
103 }
104 }
105 let total = 0;
106 for (const [state, ms] of Object.entries(report.data)) {
107 total += ms;
108 }
109 const pcts: { [k: string]: number } = { playing: 0 };
110 for (const [state, ms] of Object.entries(report.data)) {
111 pcts[state] = ms / total;
112 }
113 if (pcts.playing < PLAYING_SUCCESS) {
114 failures.push("playing was less than 50%");
115 }
116 return { ...report, pcts };
117 });
118 console.log(JSON.stringify(percentages, null, 2));
119 await mainWindow.close();
120 if (failures.length > 0) {
121 return failures.join(", ");
122 }
123 return null;
124 },
125};