pa_encoder 0.1.0 → 0.2.0
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/README.md +191 -0
- package/dist/browser/index.js +17 -0
- package/dist/cli.js +144 -0
- package/dist/index.js +402 -160
- package/dist/ui/encoder.html +469 -0
- package/dist/ui/encoder.js +368 -0
- package/dist/ui/preview.html +20 -0
- package/dist/ui/preview.js +322 -0
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -206,63 +206,168 @@ async function startLiveCapture({
|
|
|
206
206
|
function installVirtualTime({
|
|
207
207
|
fps = 60,
|
|
208
208
|
hookDateNow = true,
|
|
209
|
-
hookPerformanceNow = true
|
|
209
|
+
hookPerformanceNow = true,
|
|
210
|
+
hookTimers = true,
|
|
211
|
+
maxTimerCallbacksPerStep = 1e4
|
|
210
212
|
} = {}) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
+
var _a, _b, _c, _d, _e, _f;
|
|
214
|
+
if (!Number.isFinite(fps) || fps <= 0) {
|
|
215
|
+
throw new TypeError("installVirtualTime: fps must be a positive number");
|
|
216
|
+
}
|
|
213
217
|
const dtMs = 1e3 / fps;
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
218
|
+
const orig = {
|
|
219
|
+
requestAnimationFrame: (_a = window.requestAnimationFrame) == null ? void 0 : _a.bind(window),
|
|
220
|
+
cancelAnimationFrame: (_b = window.cancelAnimationFrame) == null ? void 0 : _b.bind(window),
|
|
221
|
+
setTimeout: (_c = window.setTimeout) == null ? void 0 : _c.bind(window),
|
|
222
|
+
clearTimeout: (_d = window.clearTimeout) == null ? void 0 : _d.bind(window),
|
|
223
|
+
setInterval: (_e = window.setInterval) == null ? void 0 : _e.bind(window),
|
|
224
|
+
clearInterval: (_f = window.clearInterval) == null ? void 0 : _f.bind(window),
|
|
225
|
+
dateNow: Date.now.bind(Date),
|
|
226
|
+
perfNow: performance.now.bind(performance)
|
|
227
|
+
};
|
|
228
|
+
if (typeof orig.requestAnimationFrame !== "function") {
|
|
229
|
+
throw new Error("installVirtualTime: requestAnimationFrame not available");
|
|
230
|
+
}
|
|
231
|
+
if (typeof orig.cancelAnimationFrame !== "function") {
|
|
232
|
+
throw new Error("installVirtualTime: cancelAnimationFrame not available");
|
|
233
|
+
}
|
|
219
234
|
let running = true;
|
|
220
|
-
let
|
|
221
|
-
|
|
222
|
-
const
|
|
235
|
+
let vNowMs = 0;
|
|
236
|
+
let nextRafId = 1;
|
|
237
|
+
const rafCallbacks = /* @__PURE__ */ new Map();
|
|
238
|
+
const rafQueue = [];
|
|
223
239
|
function hookedRAF(cb) {
|
|
224
|
-
if (!running) return
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
240
|
+
if (!running) return orig.requestAnimationFrame(cb);
|
|
241
|
+
if (typeof cb !== "function") return orig.requestAnimationFrame(cb);
|
|
242
|
+
const id = nextRafId++;
|
|
243
|
+
rafCallbacks.set(id, cb);
|
|
244
|
+
rafQueue.push(id);
|
|
228
245
|
return id;
|
|
229
246
|
}
|
|
230
247
|
function hookedCancelRAF(id) {
|
|
231
|
-
if (!running) return
|
|
232
|
-
|
|
248
|
+
if (!running) return orig.cancelAnimationFrame(id);
|
|
249
|
+
rafCallbacks.delete(id);
|
|
250
|
+
}
|
|
251
|
+
let nextTimerId = 1;
|
|
252
|
+
const timers = /* @__PURE__ */ new Map();
|
|
253
|
+
function normalizeDelay(ms) {
|
|
254
|
+
const n = Number(ms);
|
|
255
|
+
if (!Number.isFinite(n) || n < 0) return 0;
|
|
256
|
+
return n;
|
|
257
|
+
}
|
|
258
|
+
function hookedSetTimeout(fn, delay, ...args) {
|
|
259
|
+
if (!running) return orig.setTimeout(fn, delay, ...args);
|
|
260
|
+
if (typeof fn !== "function") return orig.setTimeout(fn, delay, ...args);
|
|
261
|
+
const id = nextTimerId++;
|
|
262
|
+
const d = normalizeDelay(delay);
|
|
263
|
+
timers.set(id, {
|
|
264
|
+
type: "timeout",
|
|
265
|
+
dueMs: vNowMs + d,
|
|
266
|
+
intervalMs: 0,
|
|
267
|
+
fn: () => fn(...args)
|
|
268
|
+
});
|
|
269
|
+
return id;
|
|
270
|
+
}
|
|
271
|
+
function hookedClearTimeout(id) {
|
|
272
|
+
if (!running) return orig.clearTimeout(id);
|
|
273
|
+
timers.delete(id);
|
|
274
|
+
}
|
|
275
|
+
function hookedSetInterval(fn, delay, ...args) {
|
|
276
|
+
if (!running) return orig.setInterval(fn, delay, ...args);
|
|
277
|
+
if (typeof fn !== "function") return orig.setInterval(fn, delay, ...args);
|
|
278
|
+
const id = nextTimerId++;
|
|
279
|
+
const d = normalizeDelay(delay);
|
|
280
|
+
timers.set(id, {
|
|
281
|
+
type: "interval",
|
|
282
|
+
dueMs: vNowMs + d,
|
|
283
|
+
intervalMs: d,
|
|
284
|
+
fn: () => fn(...args)
|
|
285
|
+
});
|
|
286
|
+
return id;
|
|
287
|
+
}
|
|
288
|
+
function hookedClearInterval(id) {
|
|
289
|
+
if (!running) return orig.clearInterval(id);
|
|
290
|
+
timers.delete(id);
|
|
233
291
|
}
|
|
234
292
|
window.requestAnimationFrame = hookedRAF;
|
|
235
293
|
window.cancelAnimationFrame = hookedCancelRAF;
|
|
294
|
+
if (hookTimers) {
|
|
295
|
+
if (typeof orig.setTimeout !== "function" || typeof orig.clearTimeout !== "function") {
|
|
296
|
+
throw new Error(
|
|
297
|
+
"installVirtualTime: setTimeout/clearTimeout not available"
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
if (typeof orig.setInterval !== "function" || typeof orig.clearInterval !== "function") {
|
|
301
|
+
throw new Error(
|
|
302
|
+
"installVirtualTime: setInterval/clearInterval not available"
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
window.setTimeout = hookedSetTimeout;
|
|
306
|
+
window.clearTimeout = hookedClearTimeout;
|
|
307
|
+
window.setInterval = hookedSetInterval;
|
|
308
|
+
window.clearInterval = hookedClearInterval;
|
|
309
|
+
}
|
|
236
310
|
if (hookDateNow) {
|
|
237
311
|
Date.now = function() {
|
|
238
|
-
return running ? vNowMs :
|
|
312
|
+
return running ? vNowMs : orig.dateNow();
|
|
239
313
|
};
|
|
240
314
|
}
|
|
241
315
|
if (hookPerformanceNow) {
|
|
242
316
|
performance.now = function() {
|
|
243
|
-
return running ? vNowMs :
|
|
317
|
+
return running ? vNowMs : orig.perfNow();
|
|
244
318
|
};
|
|
245
319
|
}
|
|
320
|
+
function flushDueTimers() {
|
|
321
|
+
let executed = 0;
|
|
322
|
+
while (true) {
|
|
323
|
+
if (executed > maxTimerCallbacksPerStep) {
|
|
324
|
+
throw new Error(
|
|
325
|
+
`VirtualTime: too many timer callbacks in a single step (>${maxTimerCallbacksPerStep}).`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
let nextDue = Infinity;
|
|
329
|
+
for (const t of timers.values()) {
|
|
330
|
+
if (t.dueMs < nextDue) nextDue = t.dueMs;
|
|
331
|
+
}
|
|
332
|
+
if (!(nextDue <= vNowMs)) break;
|
|
333
|
+
const due = [];
|
|
334
|
+
for (const [id, t] of timers.entries()) {
|
|
335
|
+
if (t.dueMs <= vNowMs) due.push([id, t]);
|
|
336
|
+
}
|
|
337
|
+
due.sort((a, b) => a[1].dueMs - b[1].dueMs || a[0] - b[0]);
|
|
338
|
+
for (const [id, t] of due) {
|
|
339
|
+
if (!timers.has(id)) continue;
|
|
340
|
+
executed++;
|
|
341
|
+
t.fn();
|
|
342
|
+
if (t.type === "timeout") {
|
|
343
|
+
timers.delete(id);
|
|
344
|
+
} else {
|
|
345
|
+
const interval = t.intervalMs;
|
|
346
|
+
let next = t.dueMs + interval;
|
|
347
|
+
if (next <= vNowMs) next = vNowMs;
|
|
348
|
+
t.dueMs = next;
|
|
349
|
+
timers.set(id, t);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
246
354
|
function flushRAFFrame() {
|
|
247
|
-
const ids =
|
|
355
|
+
const ids = rafQueue.splice(0, rafQueue.length);
|
|
248
356
|
for (const id of ids) {
|
|
249
|
-
const cb =
|
|
357
|
+
const cb = rafCallbacks.get(id);
|
|
250
358
|
if (!cb) continue;
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
cb(vNowMs);
|
|
254
|
-
} catch (e) {
|
|
255
|
-
console.error("Error in rAF callback:", e);
|
|
256
|
-
throw e;
|
|
257
|
-
}
|
|
359
|
+
rafCallbacks.delete(id);
|
|
360
|
+
cb(vNowMs);
|
|
258
361
|
}
|
|
259
362
|
}
|
|
260
363
|
function step(frames = 1) {
|
|
261
|
-
if (!running) throw new Error("
|
|
262
|
-
if (!Number.isInteger(frames) || frames < 1)
|
|
263
|
-
throw new TypeError("frames must be >= 1");
|
|
364
|
+
if (!running) throw new Error("VirtualTime: not running");
|
|
365
|
+
if (!Number.isInteger(frames) || frames < 1) {
|
|
366
|
+
throw new TypeError("VirtualTime.step: frames must be an integer >= 1");
|
|
367
|
+
}
|
|
264
368
|
for (let i = 0; i < frames; i++) {
|
|
265
369
|
vNowMs += dtMs;
|
|
370
|
+
if (hookTimers) flushDueTimers();
|
|
266
371
|
flushRAFFrame();
|
|
267
372
|
}
|
|
268
373
|
}
|
|
@@ -270,161 +375,297 @@ function installVirtualTime({
|
|
|
270
375
|
running = false;
|
|
271
376
|
}
|
|
272
377
|
function restore() {
|
|
273
|
-
window.requestAnimationFrame =
|
|
274
|
-
window.cancelAnimationFrame =
|
|
275
|
-
if (
|
|
276
|
-
|
|
378
|
+
window.requestAnimationFrame = orig.requestAnimationFrame;
|
|
379
|
+
window.cancelAnimationFrame = orig.cancelAnimationFrame;
|
|
380
|
+
if (hookTimers) {
|
|
381
|
+
window.setTimeout = orig.setTimeout;
|
|
382
|
+
window.clearTimeout = orig.clearTimeout;
|
|
383
|
+
window.setInterval = orig.setInterval;
|
|
384
|
+
window.clearInterval = orig.clearInterval;
|
|
385
|
+
}
|
|
386
|
+
if (hookDateNow) Date.now = orig.dateNow;
|
|
387
|
+
if (hookPerformanceNow) performance.now = orig.perfNow;
|
|
277
388
|
running = false;
|
|
278
|
-
|
|
279
|
-
|
|
389
|
+
rafCallbacks.clear();
|
|
390
|
+
rafQueue.length = 0;
|
|
391
|
+
timers.clear();
|
|
280
392
|
}
|
|
281
393
|
return {
|
|
394
|
+
step,
|
|
395
|
+
restore,
|
|
396
|
+
stopVirtualTime,
|
|
282
397
|
get nowMs() {
|
|
283
398
|
return vNowMs;
|
|
284
399
|
},
|
|
285
|
-
dtMs
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
restore
|
|
400
|
+
get dtMs() {
|
|
401
|
+
return dtMs;
|
|
402
|
+
}
|
|
289
403
|
};
|
|
290
404
|
}
|
|
291
405
|
|
|
292
406
|
// src/virtual_capture.js
|
|
293
|
-
function
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
const first = document.querySelector("canvas");
|
|
301
|
-
if (first instanceof HTMLCanvasElement) return first;
|
|
302
|
-
throw new Error("No canvas found on the page");
|
|
303
|
-
}
|
|
304
|
-
async function virtualTimeCapture({
|
|
305
|
-
// IMPORTANT: loadSketch must be called AFTER hooks are installed
|
|
306
|
-
loadSketch,
|
|
307
|
-
// () => import("...") (or any function that executes the sketch module)
|
|
308
|
-
canvas: canvasOrSelector,
|
|
309
|
-
// canvas element OR selector OR omit to use first canvas
|
|
310
|
-
fps = 60,
|
|
311
|
-
frames = 300,
|
|
312
|
-
warmupFrames = 0,
|
|
313
|
-
exporter,
|
|
314
|
-
concurrency = 2,
|
|
315
|
-
onProgress,
|
|
316
|
-
hookDateNow = true,
|
|
317
|
-
hookPerformanceNow = true,
|
|
318
|
-
signal
|
|
407
|
+
function findElementDeep({
|
|
408
|
+
doc,
|
|
409
|
+
selector,
|
|
410
|
+
includeShadow = true,
|
|
411
|
+
maxIframeDepth = 8,
|
|
412
|
+
_depth = 0
|
|
319
413
|
} = {}) {
|
|
320
|
-
if (
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
if (!exporter || typeof exporter.write !== "function" || typeof exporter.finalize !== "function") {
|
|
326
|
-
throw new TypeError("exporter must have write() and finalize()");
|
|
414
|
+
if (!doc || _depth > maxIframeDepth) return null;
|
|
415
|
+
try {
|
|
416
|
+
const el = doc.querySelector(selector);
|
|
417
|
+
if (el) return { el, doc };
|
|
418
|
+
} catch {
|
|
327
419
|
}
|
|
328
|
-
if (
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
var _a, _b;
|
|
343
|
-
if (((_a = ev.data) == null ? void 0 : _a.type) === "ready") {
|
|
344
|
-
worker.removeEventListener("message", onMsg);
|
|
345
|
-
resolve();
|
|
346
|
-
} else if (((_b = ev.data) == null ? void 0 : _b.type) === "error") {
|
|
347
|
-
worker.removeEventListener("message", onMsg);
|
|
348
|
-
reject(new Error(ev.data.message || "Worker error"));
|
|
420
|
+
if (includeShadow) {
|
|
421
|
+
let all = [];
|
|
422
|
+
try {
|
|
423
|
+
all = doc.querySelectorAll("*");
|
|
424
|
+
} catch {
|
|
425
|
+
all = [];
|
|
426
|
+
}
|
|
427
|
+
for (const host of all) {
|
|
428
|
+
const sr = host.shadowRoot;
|
|
429
|
+
if (!sr) continue;
|
|
430
|
+
try {
|
|
431
|
+
const el = sr.querySelector(selector);
|
|
432
|
+
if (el) return { el, doc };
|
|
433
|
+
} catch {
|
|
349
434
|
}
|
|
350
|
-
};
|
|
351
|
-
worker.addEventListener("message", onMsg);
|
|
352
|
-
worker.addEventListener("error", reject);
|
|
353
|
-
});
|
|
354
|
-
function throwIfAborted() {
|
|
355
|
-
if (signal == null ? void 0 : signal.aborted) {
|
|
356
|
-
throw signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
357
435
|
}
|
|
358
436
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
vt.step(1);
|
|
365
|
-
}
|
|
366
|
-
let inFlight = 0;
|
|
367
|
-
const waiters = [];
|
|
368
|
-
const ack = /* @__PURE__ */ new Map();
|
|
369
|
-
function acquireSlot() {
|
|
370
|
-
if (inFlight < concurrency) return Promise.resolve();
|
|
371
|
-
return new Promise((r) => waiters.push(r));
|
|
437
|
+
let iframes = [];
|
|
438
|
+
try {
|
|
439
|
+
iframes = doc.querySelectorAll("iframe");
|
|
440
|
+
} catch {
|
|
441
|
+
iframes = [];
|
|
372
442
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
443
|
+
for (const f of iframes) {
|
|
444
|
+
let childDoc = null;
|
|
445
|
+
try {
|
|
446
|
+
childDoc = f.contentDocument;
|
|
447
|
+
} catch {
|
|
448
|
+
childDoc = null;
|
|
449
|
+
}
|
|
450
|
+
if (!childDoc) continue;
|
|
451
|
+
const found = findElementDeep({
|
|
452
|
+
doc: childDoc,
|
|
453
|
+
selector,
|
|
454
|
+
includeShadow,
|
|
455
|
+
maxIframeDepth,
|
|
456
|
+
_depth: _depth + 1
|
|
457
|
+
});
|
|
458
|
+
if (found) return found;
|
|
377
459
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
function debugSnapshot(tag = "snapshot") {
|
|
463
|
+
const info = {
|
|
464
|
+
tag,
|
|
465
|
+
href: (() => {
|
|
383
466
|
try {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
} catch (e) {
|
|
388
|
-
const entry = ack.get(frameIndex);
|
|
389
|
-
if (entry) entry.reject(e);
|
|
390
|
-
} finally {
|
|
391
|
-
ack.delete(frameIndex);
|
|
392
|
-
releaseSlot();
|
|
467
|
+
return location.href;
|
|
468
|
+
} catch {
|
|
469
|
+
return "(no location)";
|
|
393
470
|
}
|
|
394
|
-
}
|
|
395
|
-
|
|
471
|
+
})(),
|
|
472
|
+
readyState: (() => {
|
|
473
|
+
try {
|
|
474
|
+
return document.readyState;
|
|
475
|
+
} catch {
|
|
476
|
+
return "(no document)";
|
|
477
|
+
}
|
|
478
|
+
})(),
|
|
479
|
+
canvasCount: (() => {
|
|
480
|
+
try {
|
|
481
|
+
return document.querySelectorAll("canvas").length;
|
|
482
|
+
} catch {
|
|
483
|
+
return -1;
|
|
484
|
+
}
|
|
485
|
+
})(),
|
|
486
|
+
iframeCount: (() => {
|
|
487
|
+
try {
|
|
488
|
+
return document.querySelectorAll("iframe").length;
|
|
489
|
+
} catch {
|
|
490
|
+
return -1;
|
|
491
|
+
}
|
|
492
|
+
})()
|
|
493
|
+
};
|
|
494
|
+
console.warn("[pa_encoder]", info);
|
|
495
|
+
}
|
|
496
|
+
function waitForCanvasLiveDeep({
|
|
497
|
+
selector = "canvas",
|
|
498
|
+
timeoutMs = 15e3,
|
|
499
|
+
pollMs = 50,
|
|
500
|
+
includeShadow = true
|
|
501
|
+
} = {}) {
|
|
502
|
+
const immediate = findElementDeep({ doc: document, selector, includeShadow });
|
|
503
|
+
if (immediate == null ? void 0 : immediate.el) return Promise.resolve(immediate);
|
|
504
|
+
return new Promise((resolve, reject) => {
|
|
505
|
+
const t0 = performance.now();
|
|
506
|
+
let done = false;
|
|
507
|
+
let intervalId = null;
|
|
508
|
+
const cleanup = (observer2) => {
|
|
509
|
+
if (done) return;
|
|
510
|
+
done = true;
|
|
511
|
+
try {
|
|
512
|
+
observer2 == null ? void 0 : observer2.disconnect();
|
|
513
|
+
} catch {
|
|
514
|
+
}
|
|
515
|
+
if (intervalId) clearInterval(intervalId);
|
|
516
|
+
};
|
|
517
|
+
const check = (observer2) => {
|
|
518
|
+
const found = findElementDeep({ doc: document, selector, includeShadow });
|
|
519
|
+
if (found == null ? void 0 : found.el) {
|
|
520
|
+
cleanup(observer2);
|
|
521
|
+
resolve(found);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (performance.now() - t0 > timeoutMs) {
|
|
525
|
+
cleanup(observer2);
|
|
526
|
+
debugSnapshot("waitForCanvasLiveDeep timeout");
|
|
527
|
+
reject(new Error(`Timed out waiting for canvas selector: ${selector}`));
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
const observer = new MutationObserver(() => check(observer));
|
|
531
|
+
observer.observe(document.documentElement, {
|
|
532
|
+
childList: true,
|
|
533
|
+
subtree: true
|
|
534
|
+
});
|
|
535
|
+
intervalId = setInterval(() => check(observer), pollMs);
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
function resolveCanvasFromFound(found) {
|
|
539
|
+
const el = found == null ? void 0 : found.el;
|
|
540
|
+
if (el instanceof HTMLCanvasElement) return el;
|
|
541
|
+
throw new Error("Found element is not a canvas");
|
|
542
|
+
}
|
|
543
|
+
async function resolveCanvasWithWaitDeep({
|
|
544
|
+
vt,
|
|
545
|
+
selector = "canvas",
|
|
546
|
+
canvasWaitFrames = 240,
|
|
547
|
+
includeShadow = true
|
|
548
|
+
} = {}) {
|
|
549
|
+
const tryResolve = () => {
|
|
550
|
+
const found = findElementDeep({ doc: document, selector, includeShadow });
|
|
551
|
+
if (!(found == null ? void 0 : found.el)) throw new Error("No canvas found on the page");
|
|
552
|
+
return resolveCanvasFromFound(found);
|
|
553
|
+
};
|
|
554
|
+
try {
|
|
555
|
+
return tryResolve();
|
|
556
|
+
} catch (e) {
|
|
557
|
+
console.warn("[pa_encoder] resolveCanvas failed:", (e == null ? void 0 : e.message) ?? e);
|
|
558
|
+
}
|
|
559
|
+
for (let i = 0; i < canvasWaitFrames; i++) {
|
|
560
|
+
vt.step(1);
|
|
561
|
+
try {
|
|
562
|
+
return tryResolve();
|
|
563
|
+
} catch (e) {
|
|
564
|
+
console.warn("[pa_encoder] resolveCanvas failed:", (e == null ? void 0 : e.message) ?? e);
|
|
396
565
|
}
|
|
566
|
+
}
|
|
567
|
+
throw new Error("No canvas found on the page");
|
|
568
|
+
}
|
|
569
|
+
async function virtualTimeCapture({
|
|
570
|
+
fps = 60,
|
|
571
|
+
canvasSelector = "canvas",
|
|
572
|
+
preWaitTimeoutMs = 15e3,
|
|
573
|
+
includeShadow = true,
|
|
574
|
+
canvasWaitFrames = 240,
|
|
575
|
+
hookDateNow = true,
|
|
576
|
+
hookPerformanceNow = true,
|
|
577
|
+
hookTimers = true,
|
|
578
|
+
frameCount = 60,
|
|
579
|
+
onFrame = null
|
|
580
|
+
} = {}) {
|
|
581
|
+
debugSnapshot("virtualTimeCapture start");
|
|
582
|
+
const foundLive = await waitForCanvasLiveDeep({
|
|
583
|
+
selector: canvasSelector || "canvas",
|
|
584
|
+
timeoutMs: preWaitTimeoutMs,
|
|
585
|
+
includeShadow
|
|
586
|
+
});
|
|
587
|
+
resolveCanvasFromFound(foundLive);
|
|
588
|
+
const vt = installVirtualTime({
|
|
589
|
+
fps,
|
|
590
|
+
hookDateNow,
|
|
591
|
+
hookPerformanceNow,
|
|
592
|
+
hookTimers
|
|
397
593
|
});
|
|
398
594
|
try {
|
|
399
|
-
|
|
400
|
-
|
|
595
|
+
let canvas = await resolveCanvasWithWaitDeep({
|
|
596
|
+
vt,
|
|
597
|
+
selector: canvasSelector || "canvas",
|
|
598
|
+
canvasWaitFrames,
|
|
599
|
+
includeShadow
|
|
600
|
+
});
|
|
601
|
+
for (let i = 0; i < frameCount; i++) {
|
|
401
602
|
vt.step(1);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
width: canvas.width,
|
|
414
|
-
height: canvas.height
|
|
415
|
-
},
|
|
416
|
-
[bitmap]
|
|
417
|
-
);
|
|
418
|
-
await done;
|
|
419
|
-
onProgress == null ? void 0 : onProgress(i + 1, frames, { nowMs: vt.nowMs });
|
|
603
|
+
if (!(canvas instanceof HTMLCanvasElement) || !canvas.isConnected) {
|
|
604
|
+
canvas = await resolveCanvasWithWaitDeep({
|
|
605
|
+
vt,
|
|
606
|
+
selector: canvasSelector || "canvas",
|
|
607
|
+
canvasWaitFrames: 30,
|
|
608
|
+
includeShadow
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
if (typeof onFrame === "function") {
|
|
612
|
+
await onFrame(canvas, i);
|
|
613
|
+
}
|
|
420
614
|
}
|
|
421
|
-
|
|
422
|
-
|
|
615
|
+
return { ok: true };
|
|
616
|
+
} finally {
|
|
617
|
+
vt.restore();
|
|
618
|
+
debugSnapshot("virtualTimeCapture end");
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
async function virtualTimeCaptureFromStart({
|
|
622
|
+
fps = 60,
|
|
623
|
+
// caller provides a start hook (e.g. import(entry))
|
|
624
|
+
start = null,
|
|
625
|
+
canvasSelector = "canvas",
|
|
626
|
+
includeShadow = true,
|
|
627
|
+
canvasWaitFrames = 600,
|
|
628
|
+
hookDateNow = true,
|
|
629
|
+
hookPerformanceNow = true,
|
|
630
|
+
hookTimers = true,
|
|
631
|
+
frameCount = 60,
|
|
632
|
+
onFrame = null
|
|
633
|
+
} = {}) {
|
|
634
|
+
debugSnapshot("virtualTimeCaptureFromStart begin");
|
|
635
|
+
const vt = installVirtualTime({
|
|
636
|
+
fps,
|
|
637
|
+
hookDateNow,
|
|
638
|
+
hookPerformanceNow,
|
|
639
|
+
hookTimers
|
|
640
|
+
});
|
|
641
|
+
try {
|
|
642
|
+
if (typeof start === "function") {
|
|
643
|
+
await start();
|
|
423
644
|
}
|
|
424
|
-
await
|
|
645
|
+
let canvas = await resolveCanvasWithWaitDeep({
|
|
646
|
+
vt,
|
|
647
|
+
selector: canvasSelector || "canvas",
|
|
648
|
+
canvasWaitFrames,
|
|
649
|
+
includeShadow
|
|
650
|
+
});
|
|
651
|
+
for (let i = 0; i < frameCount; i++) {
|
|
652
|
+
vt.step(1);
|
|
653
|
+
if (!(canvas instanceof HTMLCanvasElement) || !canvas.isConnected) {
|
|
654
|
+
canvas = await resolveCanvasWithWaitDeep({
|
|
655
|
+
vt,
|
|
656
|
+
selector: canvasSelector || "canvas",
|
|
657
|
+
canvasWaitFrames: 30,
|
|
658
|
+
includeShadow
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
if (typeof onFrame === "function") {
|
|
662
|
+
await onFrame(canvas, i);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return { ok: true };
|
|
425
666
|
} finally {
|
|
426
|
-
worker.terminate();
|
|
427
667
|
vt.restore();
|
|
668
|
+
debugSnapshot("virtualTimeCaptureFromStart end");
|
|
428
669
|
}
|
|
429
670
|
}
|
|
430
671
|
|
|
@@ -510,5 +751,6 @@ export {
|
|
|
510
751
|
createFsExporter,
|
|
511
752
|
createZipExporter,
|
|
512
753
|
startLiveCapture,
|
|
513
|
-
virtualTimeCapture
|
|
754
|
+
virtualTimeCapture,
|
|
755
|
+
virtualTimeCaptureFromStart
|
|
514
756
|
};
|