mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-22 17:16:19 -07:00
- Add backend selection, setup gating, Anki integration, and external host support - Add launcher flags, documentation, packaging, and focused tests - Open on-demand overlay modals on the first attempt
64 lines
2.2 KiB
JavaScript
64 lines
2.2 KiB
JavaScript
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
export const CAPTURE_ENCODING_TIMEOUT_MS = 30_000;
|
|
|
|
export function encodeCapturedAnimation(frames, options, {
|
|
WorkerClass = globalThis.Worker,
|
|
timeoutMs = CAPTURE_ENCODING_TIMEOUT_MS,
|
|
onProgress = () => {},
|
|
signal,
|
|
} = {}) {
|
|
if (typeof WorkerClass !== "function") return Promise.reject(new Error("Media encoder workers are unavailable."));
|
|
const worker = new WorkerClass(new URL("./capture-encoder-worker.js", import.meta.url), {
|
|
type: "module",
|
|
name: "hachidori-capture-encoder",
|
|
});
|
|
const id = crypto.randomUUID();
|
|
return new Promise((resolve, reject) => {
|
|
let settled = false;
|
|
let timer;
|
|
const finish = (callback, value) => {
|
|
if (settled) return;
|
|
settled = true;
|
|
clearTimeout(timer);
|
|
signal?.removeEventListener("abort", onAbort);
|
|
worker.terminate();
|
|
callback(value);
|
|
};
|
|
const onAbort = () => finish(reject, new Error("Media encoding was cancelled."));
|
|
timer = setTimeout(() => {
|
|
finish(reject, new Error("Media encoding exceeded the 30-second deadline."));
|
|
}, timeoutMs);
|
|
if (signal?.aborted) {
|
|
onAbort();
|
|
return;
|
|
}
|
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
worker.addEventListener("error", event => {
|
|
finish(reject, event.error ?? new Error(event.message || "The media encoder worker stopped."));
|
|
});
|
|
worker.addEventListener("message", event => {
|
|
const response = event.data;
|
|
if (response?.id !== id) return;
|
|
if (response.type === "progress") {
|
|
onProgress(response.completed, response.total, response.heapBytes);
|
|
return;
|
|
}
|
|
if (response.type !== "result") return;
|
|
if (!response.ok) {
|
|
finish(reject, new Error(response.error || "Media encoding failed."));
|
|
return;
|
|
}
|
|
finish(resolve, new Uint8Array(response.data));
|
|
});
|
|
const transferable = [];
|
|
const payloadFrames = frames.map(frame => {
|
|
const data = frame.data instanceof Uint8Array ? frame.data.slice()
|
|
: new Uint8Array(frame.data).slice();
|
|
transferable.push(data.buffer);
|
|
return { ...frame, data: data.buffer };
|
|
});
|
|
worker.postMessage({ type: "encode", id, frames: payloadFrames, ...options }, transferable);
|
|
});
|
|
}
|