Miraiclip SDK
Export

Web worker

Export without blocking the UI.

exportProjectInWorker runs exportProject in a dedicated worker. The total work is the same, but the page keeps rendering and responding while it runs.

import { exportProjectInWorker } from "@miraiclip/renderer";

const bytes = await exportProjectInWorker(project, {
  format: "mp4",
  quality: "standard",
  onProgress: ({ framesDone, totalFrames }) => console.log(`${framesDone}/${totalFrames}`),
});

It spawns the bundled worker with new Worker(new URL("./export.worker.js", import.meta.url), { type: "module" }), a pattern Vite, webpack and Rollup bundle. The worker is terminated when the export ends. For a full button with progress and cancel, see Export UI.

Options

The same as exportProject, minus the ones that are functions:

OptionNotes
format, quality, fps, width, height, range, audioChunkSecondsSame meaning and defaults
signalRejects with ExportAbortedError
onProgressSame ExportProgress events, relayed from the worker
targetSupported. The stream stays open: close it yourself
workerUse this Worker instead of spawning one. It is not terminated
workerUrlSpawn a module worker from this URL instead of the bundled entry
factories, openDemuxer, createDecoder, openAudioNot available: functions can't cross into a worker

Bring your own worker

If your bundler doesn't resolve the default URL, load the worker entry yourself. The package exports it as @miraiclip/renderer/export-worker.

// Vite
import ExportWorker from "@miraiclip/renderer/export-worker?worker";
import { exportViaWorker } from "@miraiclip/renderer";

const worker = new ExportWorker();
try {
  const bytes = await exportViaWorker(worker, project, { format: "webm" });
} finally {
  worker.terminate(); // exportViaWorker never terminates your worker
}
FunctionSpawns a workerTerminates it
exportProjectInWorker(project, options)Yes, unless you pass workerOnly the one it spawned
exportViaWorker(worker, project, options)NoNo

With workerUrl, point at a URL that serves the export-worker entry, for example one your bundler emits.

Stream to disk

import { exportProjectInWorker } from "@miraiclip/renderer";

declare function showSaveFilePicker(options?: { suggestedName?: string }): Promise<FileSystemFileHandle>;

const handle = await showSaveFilePicker({ suggestedName: "export.mp4" });
const writable = await handle.createWritable();
try {
  await exportProjectInWorker(project, { format: "mp4", target: writable });
  await writable.close(); // the worker export leaves the stream open
} catch (error) {
  await writable.abort(); // discard the partial file
  throw error;
}

Encoded chunks pass from the worker through the main thread into your stream, because a FileSystemWritableFileStream can't be transferred. Each chunk waits for its write to finish, so the stream's backpressure reaches the encoders.

What runs where

WorkThread
Decoding, compositing, encodingWorker
Fonts, captions, effects, transitionsWorker
Audio mixMain. OfflineAudioContext is window-only; the mix is sent to the worker as raw PCM
HTML clipsMain. They rasterize through the DOM. Static ones render before the export starts; animated ones render per frame on request
Writes into targetMain

So a worker export still uses some main-thread time for audio and HTML clips.

Limits

LimitWhy
Built-in clip kinds onlyCustom-kind factories are functions and can't cross into a worker. Use exportProject on the main thread for custom clip kinds
Media src must be reachable from a workerhttp(s): and blob: URLs and Blob/File objects work
A worker that fails to loadRejects with export worker failed: …

On this page