Live video on the AT Protocol
1import { BrowserWindow, session, WebFrameMain, webFrameMain } from "electron";
2import { v7 as uuidv7 } from "uuid";
3import { MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY } from "../env";
4import { E2ETest, TestEnv } from "./test-env";
5import { delay, PlayerReport } from "./util";
6
7const SYNC_TOO_FAR = 20000;
8
9export const syncTest: E2ETest = {
10 test: async (testEnv: TestEnv): Promise<string | null> => {
11 // disabled until i can make the audio consistent
12 return null;
13 const playerId = `${uuidv7()}-sync-test`;
14 const window = new BrowserWindow({
15 height: 720,
16 width: 1280,
17 webPreferences: {
18 preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
19 },
20 title: "streamplace-sync-test",
21 // titleBarStyle: "hidden",
22 // titleBarOverlay: true,
23 });
24 let frame: WebFrameMain | undefined;
25 await new Promise<void>((resolve) => {
26 window.webContents.on(
27 "did-frame-navigate",
28 (
29 event,
30 url,
31 httpResponseCode,
32 httpStatusText,
33 isMainFrame,
34 frameProcessId,
35 frameRoutingId,
36 ) => {
37 frame = webFrameMain.fromId(frameProcessId, frameRoutingId);
38 resolve();
39 },
40 );
41 window.loadURL(`${testEnv.addr}/sync-test`);
42 });
43 session.defaultSession.setDisplayMediaRequestHandler(
44 async (request, callback) => {
45 callback({ video: frame, audio: frame });
46 },
47 );
48 const streamWindow = new BrowserWindow({
49 height: 720,
50 width: 1280,
51 x: 0,
52 y: 0,
53 webPreferences: {
54 preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
55 },
56 title: "streamplace-sync-stream",
57 // titleBarStyle: "hidden",
58 // titleBarOverlay: true,
59 });
60 const streamParams = new URLSearchParams({
61 ingestMediaSource: "display",
62 ingestStreamKey: testEnv.multibaseKey,
63 ingestAutoStart: "true",
64 });
65 streamWindow.loadURL(
66 `${testEnv.addr}/live/webcam?${streamParams.toString()}`,
67 );
68 const playbackParams = new URLSearchParams({
69 avSyncTest: "true",
70 playerId: playerId,
71 });
72 const playbackWindow = new BrowserWindow({
73 height: 720,
74 width: 1280,
75 x: 0,
76 y: 0,
77 webPreferences: {
78 preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
79 },
80 title: "streamplace-sync-playback",
81 // titleBarStyle: "hidden",
82 // titleBarOverlay: true,
83 });
84 playbackWindow.loadURL(
85 `${testEnv.addr}/${testEnv.publicAddress}?${playbackParams.toString()}`,
86 );
87 await delay(testEnv.testDuration);
88 await Promise.all([
89 streamWindow.close(),
90 playbackWindow.close(),
91 window.close(),
92 ]);
93 const res = await fetch(
94 `${testEnv.internalAddr}/player-report/${playerId}`,
95 );
96 const data = (await res.json()) as PlayerReport;
97 if (!data.avSync) {
98 return "av sync not present in output";
99 }
100 console.log(JSON.stringify(data.avSync));
101 let problems = [];
102 for (const f of ["min", "max", "avg"] as const) {
103 if (Math.abs(data.avSync[f]) > SYNC_TOO_FAR) {
104 problems.push(`av sync ${f} is ${data.avSync[f]}`);
105 }
106 }
107 if (problems.length > 0) {
108 return problems.join(", ");
109 }
110 return null;
111 },
112};