oficina / meet Public

Clone
git clone https://amadan.net/oficina/meet

Plain git — no account needed to clone.

Download

Download this file

1// native-motion.mjs — the app shares a 1080p page scrolling at 30 fps
2// and the browser measures what actually arrives: frames a second,
3// frame size and freezes on the received screen, beside the app's own
4// account of its sender (encoder, limitation, bitrate, path).
5//
6// Written for a report on 2026-09-24 that a screen shared from the Mac
7// app "isn't keeping up with what I'm doing on the screen". It measures
8// each "Optimise for" mode twice — on an open local link, and with the
9// app's uplink estimate capped the way a thin home connection caps it —
10// and holds motion (and the balanced default) to a frame rate a scroll
11// can be followed at.
12//
13// Run by TestNativeMacScreenMotion (make native-browser). Environment:
14// MOTION_MODES comma-separated modes, default "balanced,detail,motion"
15// MOTION_UPLINKS comma-separated caps in bit/s, 0 for none, default "0,1500000"
16// MOTION_ASSERT "0" to measure without asserting
17import { chromium } from 'playwright';
18import { spawn } from 'node:child_process';
19import { createInterface } from 'node:readline';
20
21const [origin, room, binary] = process.argv.slice(2);
22const modes = (process.env.MOTION_MODES || 'balanced,detail,motion').split(',').filter(Boolean);
23const uplinks = (process.env.MOTION_UPLINKS || '0,1500000').split(',').map(Number);
24const asserting = process.env.MOTION_ASSERT !== '0';
25// A scroll followed at fewer frames than this reads as a slideshow.
26const MOTION_FLOOR_FPS = Number(process.env.MOTION_FLOOR_FPS || 15);
27
28const browser = await chromium.launch({ headless: true, args: [
29 '--use-fake-device-for-media-stream', '--use-fake-ui-for-media-stream',
30 '--autoplay-policy=no-user-gesture-required', '--disable-features=WebRtcHideLocalIpsWithMdns',
31 '--mute-audio',
32] });
33const context = await browser.newContext({ permissions: ['camera', 'microphone'] });
34await context.addCookies([{ name: process.env.CALLS_COOKIE_NAME, value: process.env.CALLS_COOKIE_A, url: origin }]);
35const page = await context.newPage();
36await page.addInitScript(() => {
37 const Native = window.RTCPeerConnection;
38 window.__nativeInteropPCs = new Set();
39 window.RTCPeerConnection = class extends Native {
40 constructor(...args) { super(...args); window.__nativeInteropPCs.add(this); }
41 };
42});
43let native;
44let failed = false;
45try {
46 await page.goto(`${origin}/m/${room}`);
47 await page.waitForFunction(() => !!window.meet);
48 native = spawn(binary, [], { stdio: ['pipe', 'pipe', 'pipe'] });
49 native.stdin.write(process.env.CALLS_NATIVE_INPUT + '\n');
50 let sample = null;
51 let exited = false;
52 native.on('exit', () => { exited = true; });
53 createInterface({ input: native.stdout }).on('line', (line) => {
54 try {
55 const value = JSON.parse(line);
56 if (value.stats) sample = value;
57 } catch {}
58 });
59 native.stderr.resume();
60 const wait = async (predicate, label, seconds = 35) => {
61 const until = Date.now() + seconds * 1000;
62 while (Date.now() < until) {
63 if (exited) throw new Error('native executable exited');
64 if (await predicate()) return;
65 await new Promise((r) => setTimeout(r, 250));
66 }
67 throw new Error(label + ': ' + JSON.stringify(sample));
68 };
69 const sleep = (s) => new Promise((r) => setTimeout(r, s * 1000));
70 await wait(() => sample?.stats.framesDecoded > 15 && sample?.stats.videoBytesSent > 0, 'native and browser connect');
71
72 // The received screen's counters, found by the share tile's own track.
73 const received = () => page.evaluate(async () => {
74 const v = document.querySelector('#tiles figure.calls-tile-share video');
75 const track = v && v.srcObject ? v.srcObject.getVideoTracks()[0] : null;
76 if (!track) return null;
77 for (const pc of window.__nativeInteropPCs) {
78 if (pc.connectionState === 'closed') continue;
79 const stats = await pc.getStats();
80 for (const r of stats.values()) {
81 if (r.type === 'inbound-rtp' && r.kind === 'video' && r.trackIdentifier === track.id) {
82 return {
83 at: r.timestamp, decoded: r.framesDecoded || 0, width: r.frameWidth || 0, height: r.frameHeight || 0,
84 freezes: r.freezeCount || 0, frozen: r.totalFreezesDuration || 0,
85 jitter: r.jitterBufferDelay || 0, emitted: r.jitterBufferEmittedCount || 0,
86 codec: (stats.get(r.codecId) || {}).mimeType || '', decoder: r.decoderImplementation || '',
87 };
88 }
89 }
90 }
91 return null;
92 });
93
94 const results = [];
95 for (const mode of modes) {
96 native.stdin.write(`screen-mode ${mode}\n`);
97 native.stdin.write('screen-motion\n');
98 await wait(() => sample?.sharingScreen === true, 'native shares the moving screen');
99 await wait(async () => (await received())?.decoded > 30, 'the browser decodes the moving screen');
100 for (const uplink of uplinks) {
101 native.stdin.write(`uplink ${uplink}\n`);
102 // Long enough for the estimate to ramp or fall and the encoder to
103 // settle on it; then ten seconds measured.
104 await sleep(uplink ? 12 : 15);
105 const a = await received();
106 const s0 = sample.stats;
107 const p0 = sample.syntheticScreenFrames || 0;
108 await sleep(10);
109 const b = await received();
110 const s1 = sample.stats;
111 const p1 = sample.syntheticScreenFrames || 0;
112 const seconds = (b.at - a.at) / 1000;
113 const result = {
114 mode, uplink,
115 receivedFps: +((b.decoded - a.decoded) / seconds).toFixed(1),
116 receivedSize: `${b.width}x${b.height}`,
117 freezes: b.freezes - a.freezes,
118 frozenSeconds: +(b.frozen - a.frozen).toFixed(2),
119 jitterBufferMs: b.emitted > a.emitted ? Math.round(((b.jitter - a.jitter) / (b.emitted - a.emitted)) * 1000) : 0,
120 codec: b.codec, decoder: b.decoder,
121 sent: `${s1.screenSendWidth}x${s1.screenSendHeight}`,
122 sentFps: (s1.screenSendFpsTenths || 0) / 10,
123 producedFps: +((p1 - p0) / seconds).toFixed(1),
124 capturedFps: (s1.screenCapturedFpsTenths || 0) / 10,
125 encodedFps: +(((s1.screenFramesEncoded || 0) - (s0.screenFramesEncoded || 0)) / seconds).toFixed(1),
126 limitation: ['none', 'cpu', 'bandwidth', 'other'][s1.screenLimitation || 0],
127 sentKbps: Math.round((s1.screenSentBitrate || 0) / 1000),
128 targetKbps: Math.round((s1.screenTargetBitrate || 0) / 1000),
129 cameraKbps: Math.round(((s1.videoBytesSent || 0) - (s0.videoBytesSent || 0)) * 8 / seconds / 1000),
130 encodeMs: (s1.screenEncodeMsTenths || 0) / 10,
131 hardware: s1.screenHardwareEncoder === 1,
132 path: ['', 'host', 'srflx', 'prflx', 'relay'][s1.screenPath || 0],
133 };
134 results.push(result);
135 console.log('MEASURE ' + JSON.stringify(result));
136 }
137 native.stdin.write('uplink 0\n');
138 native.stdin.write('screen-off\n');
139 await wait(() => sample?.sharingScreen === false, 'native stops sharing');
140 await wait(async () => (await received()) === null, 'the browser drops the screen');
141 }
142
143 if (asserting) {
144 for (const r of results) {
145 if (r.mode === 'motion' || r.mode === 'balanced') {
146 if (r.receivedFps < MOTION_FLOOR_FPS) {
147 failed = true;
148 console.log(`FAIL ${r.mode} at uplink ${r.uplink || 'open'}: ${r.receivedFps} fps received, below ${MOTION_FLOOR_FPS}`);
149 } else {
150 console.log(`PASS ${r.mode} at uplink ${r.uplink || 'open'}: ${r.receivedFps} fps received at ${r.receivedSize}`);
151 }
152 }
153 if (r.mode === 'detail' && r.receivedSize !== '1920x1080') {
154 failed = true;
155 console.log(`FAIL detail at uplink ${r.uplink || 'open'} gave up resolution: ${r.receivedSize}`);
156 }
157 }
158 }
159 native.stdin.write('leave\n');
160 await sleep(1);
161} finally {
162 native?.kill('SIGTERM');
163 await browser.close();
164}
165if (failed) process.exit(1);
166