pa_encoder 0.2.8 → 0.3.2
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 +14 -7
- package/dist/browser/index.js +2 -2
- package/dist/index.js +219 -69
- package/dist/ui/encoder.html +38 -1
- package/dist/ui/encoder.js +121 -11
- package/dist/ui/preview.js +159 -18
- package/dist/worker.js +11 -2
- package/package.json +1 -1
package/dist/ui/preview.js
CHANGED
|
@@ -16,6 +16,47 @@ function post(type, data = {}) {
|
|
|
16
16
|
} catch {}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function toPositiveInt(v, fallback) {
|
|
20
|
+
const n = Number(v);
|
|
21
|
+
if (!Number.isInteger(n) || n <= 0) return fallback;
|
|
22
|
+
return n;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function createThrottledPoster(type, intervalMs = 160) {
|
|
26
|
+
let latest = null;
|
|
27
|
+
const timer = setInterval(() => {
|
|
28
|
+
if (!latest) return;
|
|
29
|
+
post(type, latest);
|
|
30
|
+
latest = null;
|
|
31
|
+
}, Math.max(50, intervalMs));
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
push(data) {
|
|
35
|
+
latest = data;
|
|
36
|
+
},
|
|
37
|
+
flush() {
|
|
38
|
+
if (!latest) return;
|
|
39
|
+
post(type, latest);
|
|
40
|
+
latest = null;
|
|
41
|
+
},
|
|
42
|
+
stop() {
|
|
43
|
+
clearInterval(timer);
|
|
44
|
+
latest = null;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function throttle(fn, waitMs = 120) {
|
|
50
|
+
let timeoutId = null;
|
|
51
|
+
return (...args) => {
|
|
52
|
+
if (timeoutId) return;
|
|
53
|
+
timeoutId = setTimeout(() => {
|
|
54
|
+
timeoutId = null;
|
|
55
|
+
fn(...args);
|
|
56
|
+
}, waitMs);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
19
60
|
async function importEntry(entry) {
|
|
20
61
|
await import(entry);
|
|
21
62
|
}
|
|
@@ -49,6 +90,76 @@ function sendCanvasList() {
|
|
|
49
90
|
post("pa_preview_canvas_list", { count: canvases.length, canvases });
|
|
50
91
|
}
|
|
51
92
|
|
|
93
|
+
function findCanvasDeep({
|
|
94
|
+
selector = "canvas",
|
|
95
|
+
includeShadow = true,
|
|
96
|
+
maxIframeDepth = 8,
|
|
97
|
+
doc = document,
|
|
98
|
+
_depth = 0,
|
|
99
|
+
} = {}) {
|
|
100
|
+
if (!doc || _depth > maxIframeDepth) return null;
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const el = doc.querySelector(selector);
|
|
104
|
+
if (el instanceof HTMLCanvasElement) return el;
|
|
105
|
+
} catch {}
|
|
106
|
+
|
|
107
|
+
if (includeShadow) {
|
|
108
|
+
let all = [];
|
|
109
|
+
try {
|
|
110
|
+
all = doc.querySelectorAll("*");
|
|
111
|
+
} catch {
|
|
112
|
+
all = [];
|
|
113
|
+
}
|
|
114
|
+
for (const host of all) {
|
|
115
|
+
const sr = host.shadowRoot;
|
|
116
|
+
if (!sr) continue;
|
|
117
|
+
try {
|
|
118
|
+
const el = sr.querySelector(selector);
|
|
119
|
+
if (el instanceof HTMLCanvasElement) return el;
|
|
120
|
+
} catch {}
|
|
121
|
+
try {
|
|
122
|
+
const fallback = sr.querySelector("canvas");
|
|
123
|
+
if (fallback instanceof HTMLCanvasElement) return fallback;
|
|
124
|
+
} catch {}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let iframes = [];
|
|
129
|
+
try {
|
|
130
|
+
iframes = doc.querySelectorAll("iframe");
|
|
131
|
+
} catch {
|
|
132
|
+
iframes = [];
|
|
133
|
+
}
|
|
134
|
+
for (const iframe of iframes) {
|
|
135
|
+
let childDoc = null;
|
|
136
|
+
try {
|
|
137
|
+
childDoc = iframe.contentDocument;
|
|
138
|
+
} catch {
|
|
139
|
+
childDoc = null;
|
|
140
|
+
}
|
|
141
|
+
if (!childDoc) continue;
|
|
142
|
+
const found = findCanvasDeep({
|
|
143
|
+
selector,
|
|
144
|
+
includeShadow,
|
|
145
|
+
maxIframeDepth,
|
|
146
|
+
doc: childDoc,
|
|
147
|
+
_depth: _depth + 1,
|
|
148
|
+
});
|
|
149
|
+
if (found) return found;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function resolveCanvas(selector = "canvas") {
|
|
156
|
+
try {
|
|
157
|
+
const direct = document.querySelector(selector);
|
|
158
|
+
if (direct instanceof HTMLCanvasElement) return direct;
|
|
159
|
+
} catch {}
|
|
160
|
+
return findCanvasDeep({ selector }) || findCanvasDeep({ selector: "canvas" });
|
|
161
|
+
}
|
|
162
|
+
|
|
52
163
|
function canvasToBlob(canvas, type = "image/png") {
|
|
53
164
|
return new Promise((resolve, reject) => {
|
|
54
165
|
canvas.toBlob((b) => {
|
|
@@ -103,7 +214,7 @@ window.addEventListener("message", async (ev) => {
|
|
|
103
214
|
|
|
104
215
|
if (msg.type === "pa_focus_canvas") {
|
|
105
216
|
const sel = msg.payload?.canvasSelector || "canvas";
|
|
106
|
-
const c =
|
|
217
|
+
const c = resolveCanvas(sel);
|
|
107
218
|
if (c instanceof HTMLCanvasElement) focusCanvasBestEffort(c);
|
|
108
219
|
return;
|
|
109
220
|
}
|
|
@@ -136,6 +247,14 @@ async function runFrameAutostart(payload, entry) {
|
|
|
136
247
|
let exporter = null;
|
|
137
248
|
let didFinalize = false;
|
|
138
249
|
let firstCanvasFocused = false;
|
|
250
|
+
const progressPoster = createThrottledPoster(
|
|
251
|
+
"pa_progress",
|
|
252
|
+
toPositiveInt(p?.statsIntervalMs, 120)
|
|
253
|
+
);
|
|
254
|
+
const statsPoster = createThrottledPoster(
|
|
255
|
+
"pa_stats",
|
|
256
|
+
toPositiveInt(p?.statsIntervalMs, 120)
|
|
257
|
+
);
|
|
139
258
|
|
|
140
259
|
try {
|
|
141
260
|
exporter = await createExporterFromPayload(p);
|
|
@@ -163,9 +282,7 @@ async function runFrameAutostart(payload, entry) {
|
|
|
163
282
|
start: async () => {
|
|
164
283
|
await importEntry(entry);
|
|
165
284
|
// after import, try focus if canvas already exists
|
|
166
|
-
const c =
|
|
167
|
-
document.querySelector(canvasSelector) ||
|
|
168
|
-
document.querySelector("canvas");
|
|
285
|
+
const c = resolveCanvas(canvasSelector);
|
|
169
286
|
if (c instanceof HTMLCanvasElement) {
|
|
170
287
|
focusCanvasBestEffort(c);
|
|
171
288
|
firstCanvasFocused = true;
|
|
@@ -186,8 +303,8 @@ async function runFrameAutostart(payload, entry) {
|
|
|
186
303
|
await exporter.write(frameIndex, blob);
|
|
187
304
|
|
|
188
305
|
written++;
|
|
189
|
-
|
|
190
|
-
|
|
306
|
+
progressPoster.push({ done: written, total: frames });
|
|
307
|
+
statsPoster.push({ written, captured: i + 1 });
|
|
191
308
|
},
|
|
192
309
|
});
|
|
193
310
|
} catch (e) {
|
|
@@ -202,6 +319,8 @@ async function runFrameAutostart(payload, entry) {
|
|
|
202
319
|
await exporter.finalize();
|
|
203
320
|
didFinalize = true;
|
|
204
321
|
}
|
|
322
|
+
progressPoster.flush();
|
|
323
|
+
statsPoster.flush();
|
|
205
324
|
|
|
206
325
|
post("pa_status", {
|
|
207
326
|
status: "idle",
|
|
@@ -216,6 +335,8 @@ async function runFrameAutostart(payload, entry) {
|
|
|
216
335
|
await exporter.finalize();
|
|
217
336
|
} catch {}
|
|
218
337
|
}
|
|
338
|
+
progressPoster.stop();
|
|
339
|
+
statsPoster.stop();
|
|
219
340
|
running = false;
|
|
220
341
|
stopFlag = false;
|
|
221
342
|
}
|
|
@@ -230,13 +351,15 @@ async function runLive(p) {
|
|
|
230
351
|
|
|
231
352
|
let exporter = null;
|
|
232
353
|
let didFinalize = false;
|
|
354
|
+
const statsPoster = createThrottledPoster(
|
|
355
|
+
"pa_stats",
|
|
356
|
+
toPositiveInt(p?.statsIntervalMs, 180)
|
|
357
|
+
);
|
|
233
358
|
|
|
234
359
|
try {
|
|
235
360
|
exporter = await createExporterFromPayload(p);
|
|
236
361
|
|
|
237
|
-
const canvas =
|
|
238
|
-
document.querySelector(canvasSelector) ||
|
|
239
|
-
document.querySelector("canvas");
|
|
362
|
+
const canvas = resolveCanvas(canvasSelector);
|
|
240
363
|
if (!(canvas instanceof HTMLCanvasElement)) {
|
|
241
364
|
throw new Error(`Canvas not found: ${canvasSelector}`);
|
|
242
365
|
}
|
|
@@ -250,28 +373,44 @@ async function runLive(p) {
|
|
|
250
373
|
}, 50);
|
|
251
374
|
|
|
252
375
|
try {
|
|
253
|
-
const
|
|
376
|
+
const session = await startLiveCapture({
|
|
254
377
|
canvas,
|
|
255
378
|
exporter,
|
|
256
379
|
fps: Number(p.fps ?? 30),
|
|
257
380
|
concurrency: Number(p.concurrency ?? 2),
|
|
258
381
|
maxQueue: Number(p.maxQueue ?? 8),
|
|
382
|
+
maxPendingCaptures: toPositiveInt(p.maxPendingCaptures, undefined),
|
|
259
383
|
policy: p.policy ?? "drop",
|
|
260
384
|
signal: ac.signal,
|
|
261
|
-
onProgress: (stats) =>
|
|
385
|
+
onProgress: (stats) => statsPoster.push(stats),
|
|
262
386
|
});
|
|
263
387
|
|
|
264
388
|
post("pa_status", { status: "running", message: "capturing..." });
|
|
265
389
|
|
|
266
|
-
await
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
390
|
+
const result = await Promise.race([
|
|
391
|
+
new Promise((resolve) => {
|
|
392
|
+
ac.signal.addEventListener(
|
|
393
|
+
"abort",
|
|
394
|
+
() => resolve({ kind: "abort", reason: ac.signal.reason }),
|
|
395
|
+
{ once: true }
|
|
396
|
+
);
|
|
397
|
+
}),
|
|
398
|
+
Promise.resolve(session.done).then((v) => ({ kind: "done", value: v })),
|
|
399
|
+
]);
|
|
400
|
+
|
|
401
|
+
if (result.kind === "abort") {
|
|
402
|
+
await session.stop();
|
|
270
403
|
didFinalize = true;
|
|
271
|
-
throw
|
|
272
|
-
}
|
|
404
|
+
throw result.reason ?? new Error("stopped");
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
didFinalize = true;
|
|
408
|
+
const err = result.value?.error;
|
|
409
|
+
if (err) throw err;
|
|
410
|
+
throw new Error("stopped");
|
|
273
411
|
} finally {
|
|
274
412
|
clearInterval(stopWatcher);
|
|
413
|
+
statsPoster.flush();
|
|
275
414
|
}
|
|
276
415
|
} catch (e) {
|
|
277
416
|
if ((e?.message ?? "") === "stopped") {
|
|
@@ -286,6 +425,7 @@ async function runLive(p) {
|
|
|
286
425
|
await exporter.finalize();
|
|
287
426
|
} catch {}
|
|
288
427
|
}
|
|
428
|
+
statsPoster.stop();
|
|
289
429
|
running = false;
|
|
290
430
|
stopFlag = false;
|
|
291
431
|
}
|
|
@@ -296,7 +436,8 @@ async function runLive(p) {
|
|
|
296
436
|
post("pa_preview_ready", { entry });
|
|
297
437
|
|
|
298
438
|
sendCanvasList();
|
|
299
|
-
const
|
|
439
|
+
const sendCanvasListThrottled = throttle(sendCanvasList, 120);
|
|
440
|
+
const mo = new MutationObserver(() => sendCanvasListThrottled());
|
|
300
441
|
mo.observe(document.documentElement, { childList: true, subtree: true });
|
|
301
442
|
setTimeout(sendCanvasList, 250);
|
|
302
443
|
setTimeout(sendCanvasList, 1000);
|
package/dist/worker.js
CHANGED
|
@@ -24,13 +24,22 @@ self.onmessage = async (ev) => {
|
|
|
24
24
|
|
|
25
25
|
ctx.clearRect(0, 0, width, height);
|
|
26
26
|
ctx.drawImage(bitmap, 0, 0);
|
|
27
|
-
if (typeof bitmap.close === "function") bitmap.close();
|
|
28
27
|
|
|
29
28
|
const blob = await offscreen.convertToBlob({ type: "image/png" });
|
|
30
29
|
const t1 = performance.now();
|
|
31
30
|
|
|
32
31
|
self.postMessage({ type: "frame", frameIndex, blob, encodeMs: t1 - t0 });
|
|
33
32
|
} catch (e) {
|
|
34
|
-
self.postMessage({
|
|
33
|
+
self.postMessage({
|
|
34
|
+
type: "error",
|
|
35
|
+
frameIndex,
|
|
36
|
+
message: e?.message ?? String(e),
|
|
37
|
+
});
|
|
38
|
+
} finally {
|
|
39
|
+
if (typeof bitmap?.close === "function") {
|
|
40
|
+
try {
|
|
41
|
+
bitmap.close();
|
|
42
|
+
} catch {}
|
|
43
|
+
}
|
|
35
44
|
}
|
|
36
45
|
};
|