@hypersocial/decks-cli 0.9.4 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypersocial/decks-cli",
3
- "version": "0.9.4",
3
+ "version": "0.9.5",
4
4
  "description": "Command-line client for Hypersocial Decks live analytics and share management.",
5
5
  "type": "module",
6
6
  "main": "./src/commands.mjs",
@@ -19,7 +19,7 @@ import { tmpdir } from 'node:os';
19
19
  import { dirname, extname, join } from 'node:path';
20
20
  import { execFile } from 'node:child_process';
21
21
  import { promisify } from 'node:util';
22
- import { allowCaptureDestination, installChromium, isMissingBrowser, routeCaptureRequest } from './preview-capture.mjs';
22
+ import { allowCaptureDestination, installChromium, isMissingBrowser, noteSoftwareWebgl, recorderArgs, routeCaptureRequest } from './preview-capture.mjs';
23
23
  import { installFrameClock } from './frame-clock.mjs';
24
24
 
25
25
  const run = promisify(execFile);
@@ -272,9 +272,11 @@ export async function recordVideo(config, { log = () => {}, chromium: injected,
272
272
  browser = await launchRecorder(chromium, {
273
273
  // config.sandbox: false only for CI runners whose AppArmor blocks Chromium's sandbox (the smoke suite).
274
274
  headless: true, timeout: 30000, chromiumSandbox: config.sandbox ?? process.platform === 'linux',
275
- args: ['--autoplay-policy=no-user-gesture-required', '--mute-audio', '--enable-unsafe-swiftshader'],
275
+ // On a Mac the head is drawn on the graphics card (recorderArgs); elsewhere in software.
276
+ args: ['--autoplay-policy=no-user-gesture-required', '--mute-audio', ...recorderArgs()],
276
277
  env: { ...(process.platform === 'win32' ? { SystemRoot: process.env.SystemRoot } : {}), PATH: process.env.PATH, TMPDIR: temp, HOME: temp },
277
278
  }, { log, ...(install ? { install } : {}) });
279
+ await noteSoftwareWebgl(browser, log);
278
280
  const pageUrl = exportPageUrl(config.url, config);
279
281
  const context = await browser.newContext({ viewport: geometry.viewport, deviceScaleFactor: geometry.scale, serviceWorkers: 'block', acceptDownloads: false });
280
282
  const blockedUrls = [];
@@ -22,6 +22,42 @@ export async function installChromium({ exec = run } = {}) {
22
22
  }
23
23
  // Playwright's launch error when the browser build is not on disk.
24
24
  export const isMissingBrowser = (e) => /Executable doesn't exist/.test(String(e?.message ?? e));
25
+ // Chromium's WebGL flags for capture and video export. On a Mac, ANGLE's Metal
26
+ // backend draws WebGL (the live head) on the graphics card: the investor deck's
27
+ // 1080p export rendered in 7 min instead of 48 (docs/spikes/mac-video-export-gpu.md).
28
+ // Elsewhere (Linux, the render worker's GPU-less machines) WebGL stays in
29
+ // SwiftShader, on the CPU. --enable-unsafe-swiftshader is kept on every
30
+ // platform: it is what Chromium falls back to when it cannot use the GPU.
31
+ export function recorderArgs(platform = process.platform) {
32
+ return platform === 'darwin' ? ['--use-angle=metal', '--enable-unsafe-swiftshader'] : ['--enable-unsafe-swiftshader'];
33
+ }
34
+ // The WebGL renderer (UNMASKED_RENDERER_WEBGL) a launched browser draws with,
35
+ // or null when it cannot tell (no WebGL, or no answer within `ms`).
36
+ export async function webglRenderer(browser, ms = 10000) {
37
+ let page, timer;
38
+ try {
39
+ const read = (async () => {
40
+ page = await browser.newPage();
41
+ return page.evaluate(() => {
42
+ const gl = document.createElement('canvas').getContext('webgl');
43
+ const info = gl?.getExtension('WEBGL_debug_renderer_info');
44
+ return gl ? String(gl.getParameter(info ? info.UNMASKED_RENDERER_WEBGL : gl.RENDERER)) : null;
45
+ });
46
+ })();
47
+ read.catch(() => {});
48
+ return await Promise.race([read, new Promise((ok) => { timer = setTimeout(() => ok(null), ms); })]);
49
+ } catch { return null; }
50
+ finally { clearTimeout(timer); await page?.close().catch(() => {}); }
51
+ }
52
+ export const isSoftwareRenderer = (renderer) => /swiftshader/i.test(renderer ?? '');
53
+ // On a Mac, one line when Chromium could not use the graphics card and fell
54
+ // back to drawing WebGL in software. Returns the renderer it read.
55
+ export async function noteSoftwareWebgl(browser, log, platform = process.platform) {
56
+ if (platform !== 'darwin') return null;
57
+ const renderer = await webglRenderer(browser);
58
+ if (isSoftwareRenderer(renderer)) log('Chromium could not use this Mac\'s graphics card; the head is drawn in software, so the export runs several times slower');
59
+ return renderer;
60
+ }
25
61
  export async function setupCapture(pos, opts) {
26
62
  if (pos.length || Object.keys(opts).some(k => !['json', 'dev', 'convex', 'prod'].includes(k))) throw new Error('usage: preview setup');
27
63
  await installChromium();
@@ -207,7 +243,7 @@ export async function capturePreview(config) {
207
243
  try {
208
244
  temp = await mkdtemp(join(tmpdir(), 'hyperdecks-preview-'));
209
245
  try {
210
- browser = await chromium.launch({ headless: true, timeout:30000, chromiumSandbox: process.platform === 'linux', args: ['--mute-audio', '--enable-unsafe-swiftshader'], env: { ...(process.platform === 'win32' ? { SystemRoot: process.env.SystemRoot } : {}), PATH: process.env.PATH, TMPDIR: temp, HOME: temp } });
246
+ browser = await chromium.launch({ headless: true, timeout:30000, chromiumSandbox: process.platform === 'linux', args: ['--mute-audio', ...recorderArgs()], env: { ...(process.platform === 'win32' ? { SystemRoot: process.env.SystemRoot } : {}), PATH: process.env.PATH, TMPDIR: temp, HOME: temp } });
211
247
  } catch { throw new Error(`Could not launch isolated Chromium. ${SETUP}`); }
212
248
  deadline = setTimeout(() => { timedOut = true; browser.close().catch(() => {}); }, 60000);
213
249
  server = await serveSource(config);
package/src/runtime.mjs CHANGED
@@ -8,7 +8,7 @@ import { fileURLToPath } from "node:url";
8
8
  import { ConvexHttpClient } from "convex/browser";
9
9
  import { makeFunctionReference } from "convex/server";
10
10
 
11
- export const VERSION = "0.9.4";
11
+ export const VERSION = "0.9.5";
12
12
  const UPLOAD_TIMEOUT_MS = 125_000;
13
13
 
14
14
  export const PROD = {