@motionscript/browser 0.1.0-alpha.7 → 0.1.0-alpha.8
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/CHANGELOG.md +6 -0
- package/NOTICE +15 -14
- package/README.md +66 -43
- package/dist/asset-bytes.d.ts +7 -4
- package/dist/asset-bytes.d.ts.map +1 -1
- package/dist/asset-bytes.js +9 -4
- package/dist/asset-bytes.js.map +1 -1
- package/dist/audio/bus-graph.d.ts.map +1 -1
- package/dist/audio/bus-graph.js +15 -6
- package/dist/audio/bus-graph.js.map +1 -1
- package/dist/audio/effect-graph.d.ts +62 -0
- package/dist/audio/effect-graph.d.ts.map +1 -0
- package/dist/audio/effect-graph.js +234 -0
- package/dist/audio/effect-graph.js.map +1 -0
- package/dist/audio/index.d.ts.map +1 -1
- package/dist/audio/index.js +2 -3
- package/dist/audio/index.js.map +1 -1
- package/dist/audio/mixer.d.ts +4 -4
- package/dist/audio/mixer.d.ts.map +1 -1
- package/dist/audio/mixer.js +18 -38
- package/dist/audio/mixer.js.map +1 -1
- package/dist/audio/player.d.ts +12 -6
- package/dist/audio/player.d.ts.map +1 -1
- package/dist/audio/player.js +49 -62
- package/dist/audio/player.js.map +1 -1
- package/dist/browser/THIRD_PARTY_LICENSES.txt +379 -27
- package/dist/browser/audio.js +1 -1
- package/dist/browser/chunks/chunk-IILVN4SK.js +2 -0
- package/dist/browser/chunks/chunk-IILVN4SK.js.map +7 -0
- package/dist/browser/chunks/chunk-KLXMRKTL.js +2 -0
- package/dist/browser/chunks/chunk-L5UFQACH.js +163 -0
- package/dist/browser/chunks/chunk-L5UFQACH.js.map +7 -0
- package/dist/browser/chunks/mediabunny-mp3-encoder-ZCIHVVTJ.js +14 -0
- package/dist/browser/chunks/mediabunny-mp3-encoder-ZCIHVVTJ.js.map +7 -0
- package/dist/browser/index.js +51 -1343
- package/dist/browser/index.js.map +4 -4
- package/dist/browser/manifest.json +8 -8
- package/dist/browser/ms_renderer_bg.wasm +0 -0
- package/dist/canvaskit-renderer.d.ts +8 -0
- package/dist/canvaskit-renderer.d.ts.map +1 -0
- package/dist/canvaskit-renderer.js +23 -0
- package/dist/canvaskit-renderer.js.map +1 -0
- package/dist/concat.d.ts +33 -7
- package/dist/concat.d.ts.map +1 -1
- package/dist/concat.js +117 -72
- package/dist/concat.js.map +1 -1
- package/dist/deliver.d.ts +10 -0
- package/dist/deliver.d.ts.map +1 -0
- package/dist/deliver.js +22 -0
- package/dist/deliver.js.map +1 -0
- package/dist/dynamic-nodes.d.ts.map +1 -1
- package/dist/dynamic-nodes.js +2 -3
- package/dist/dynamic-nodes.js.map +1 -1
- package/dist/engine.d.ts +37 -9
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +41 -32
- package/dist/engine.js.map +1 -1
- package/dist/export-audio.d.ts +36 -0
- package/dist/export-audio.d.ts.map +1 -0
- package/dist/export-audio.js +56 -0
- package/dist/export-audio.js.map +1 -0
- package/dist/exporter.d.ts +125 -14
- package/dist/exporter.d.ts.map +1 -1
- package/dist/exporter.js +133 -89
- package/dist/exporter.js.map +1 -1
- package/dist/formats.d.ts +36 -0
- package/dist/formats.d.ts.map +1 -0
- package/dist/formats.js +83 -0
- package/dist/formats.js.map +1 -0
- package/dist/frame-loop.d.ts +190 -0
- package/dist/frame-loop.d.ts.map +1 -0
- package/dist/frame-loop.js +304 -0
- package/dist/frame-loop.js.map +1 -0
- package/dist/getter.d.ts +4 -1
- package/dist/getter.d.ts.map +1 -1
- package/dist/getter.js +7 -6
- package/dist/getter.js.map +1 -1
- package/dist/index.d.ts +12 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -21
- package/dist/index.js.map +1 -1
- package/dist/mix-timeline-audio.d.ts +5 -5
- package/dist/mix-timeline-audio.d.ts.map +1 -1
- package/dist/mix-timeline-audio.js +10 -12
- package/dist/mix-timeline-audio.js.map +1 -1
- package/dist/parallel/export-parallel.d.ts +32 -0
- package/dist/parallel/export-parallel.d.ts.map +1 -0
- package/dist/parallel/export-parallel.js +114 -0
- package/dist/parallel/export-parallel.js.map +1 -0
- package/dist/parallel/governor.d.ts +57 -0
- package/dist/parallel/governor.d.ts.map +1 -0
- package/dist/parallel/governor.js +79 -0
- package/dist/parallel/governor.js.map +1 -0
- package/dist/parallel/lane.d.ts +45 -0
- package/dist/parallel/lane.d.ts.map +1 -0
- package/dist/parallel/lane.js +2 -0
- package/dist/parallel/lane.js.map +1 -0
- package/dist/parallel/lanes.d.ts +20 -0
- package/dist/parallel/lanes.d.ts.map +1 -0
- package/dist/parallel/lanes.js +152 -0
- package/dist/parallel/lanes.js.map +1 -0
- package/dist/parallel/plan.d.ts +23 -0
- package/dist/parallel/plan.d.ts.map +1 -0
- package/dist/parallel/plan.js +30 -0
- package/dist/parallel/plan.js.map +1 -0
- package/dist/parallel/run-lanes.d.ts +38 -0
- package/dist/parallel/run-lanes.d.ts.map +1 -0
- package/dist/parallel/run-lanes.js +164 -0
- package/dist/parallel/run-lanes.js.map +1 -0
- package/dist/parallel/session.d.ts +9 -0
- package/dist/parallel/session.d.ts.map +1 -0
- package/dist/parallel/session.js +79 -0
- package/dist/parallel/session.js.map +1 -0
- package/dist/render-context.d.ts +2 -0
- package/dist/render-context.d.ts.map +1 -1
- package/dist/render-context.js +8 -4
- package/dist/render-context.js.map +1 -1
- package/dist/renderer.d.ts +22 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +23 -0
- package/dist/renderer.js.map +1 -0
- package/dist/screenshot.d.ts +4 -4
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +3 -3
- package/dist/screenshot.js.map +1 -1
- package/dist/still.d.ts +17 -19
- package/dist/still.d.ts.map +1 -1
- package/dist/still.js +27 -34
- package/dist/still.js.map +1 -1
- package/dist/ticker.d.ts +22 -8
- package/dist/ticker.d.ts.map +1 -1
- package/dist/ticker.js +55 -20
- package/dist/ticker.js.map +1 -1
- package/package.json +9 -10
- package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js +0 -2
- package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js.map +0 -7
- package/dist/browser/chunks/SkeletonUtils-XYDMTGTM.js +0 -2
- package/dist/browser/chunks/chunk-2MAFJWBZ.js +0 -2
- package/dist/browser/chunks/chunk-2MAFJWBZ.js.map +0 -7
- package/dist/browser/chunks/chunk-CTLBXZSU.js +0 -4119
- package/dist/browser/chunks/chunk-CTLBXZSU.js.map +0 -7
- package/dist/browser/chunks/chunk-FQ32PDIU.js +0 -2
- package/dist/browser/chunks/chunk-FQ32PDIU.js.map +0 -7
- package/dist/browser/chunks/chunk-FY3BTKY4.js +0 -2
- package/dist/browser/chunks/chunk-FY3BTKY4.js.map +0 -7
- package/dist/browser/chunks/three.module-Z4EOVUWF.js +0 -2
- package/dist/browser/chunks/three.module-Z4EOVUWF.js.map +0 -7
- /package/dist/browser/chunks/{SkeletonUtils-XYDMTGTM.js.map → chunk-KLXMRKTL.js.map} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Registry, type AssetManifest, type ProjectDocument, type VisualDocument, type Size2D } from "@motionscript/core";
|
|
2
|
-
|
|
2
|
+
import { type RendererWasmOption } from "./renderer";
|
|
3
|
+
export interface MixTimelineAudioParams extends RendererWasmOption {
|
|
3
4
|
/** A project, or a lone still or composition as the one track of one. */
|
|
4
5
|
document: ProjectDocument | VisualDocument;
|
|
5
6
|
/** The vocabulary its clips compile against. Defaults to what the library ships. */
|
|
@@ -10,7 +11,6 @@ export interface MixTimelineAudioParams {
|
|
|
10
11
|
manifest?: AssetManifest;
|
|
11
12
|
/** Mixdown rate. Defaults to the mixer's own (44100). */
|
|
12
13
|
sampleRate?: number;
|
|
13
|
-
wasmUrl?: string;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* Mix a timeline's audio without rendering a single frame.
|
|
@@ -34,9 +34,9 @@ export interface MixTimelineAudioParams {
|
|
|
34
34
|
* that puts its nodes in the tree, so there is no way to know what plays when
|
|
35
35
|
* without it.
|
|
36
36
|
*
|
|
37
|
-
* Nothing is drawn, so this takes
|
|
38
|
-
* context: discovery lays each clip out, which needs text measured, not
|
|
39
|
-
* rasterized. No canvas, no surface, no GPU.
|
|
37
|
+
* Nothing is drawn, so this takes the renderer's measure context rather than a
|
|
38
|
+
* render context: discovery lays each clip out, which needs text measured, not
|
|
39
|
+
* rasterized. No canvas, no surface, no GPU, so WebGPU is not required.
|
|
40
40
|
*/
|
|
41
41
|
export declare function mixTimelineAudio(params: MixTimelineAudioParams): Promise<AudioBuffer | null>;
|
|
42
42
|
//# sourceMappingURL=mix-timeline-audio.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mix-timeline-audio.d.ts","sourceRoot":"","sources":["../src/mix-timeline-audio.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,QAAQ,EAER,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,MAAM,EACd,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"mix-timeline-audio.d.ts","sourceRoot":"","sources":["../src/mix-timeline-audio.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,QAAQ,EAER,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,MAAM,EACd,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAAgB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEnE,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAC9D,yEAAyE;IACzE,QAAQ,EAAE,eAAe,GAAG,cAAc,CAAC;IAC3C,oFAAoF;IACpF,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAID;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,gBAAgB,CAClC,MAAM,EAAE,sBAAsB,GAC/B,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA4B7B"}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { DEFAULT_OUTPUT, ManifestAssetCatalog, AssetTimeline, Registry, resolveProjectDocument, } from "@motionscript/core";
|
|
2
|
-
import { collectAudio } from "
|
|
2
|
+
import { collectAudio } from "./frame-loop";
|
|
3
3
|
import { mixAudio } from "./audio/mixer";
|
|
4
|
-
import {
|
|
5
|
-
import { WebMeasureContext } from "./measure-context";
|
|
6
|
-
import { WebStorageAdapter } from "./storage-adapter";
|
|
4
|
+
import { loadRenderer } from "./renderer";
|
|
7
5
|
const EMPTY_MANIFEST = { image: {}, video: {}, audio: {}, font: {} };
|
|
8
6
|
/**
|
|
9
7
|
* Mix a timeline's audio without rendering a single frame.
|
|
@@ -27,22 +25,22 @@ const EMPTY_MANIFEST = { image: {}, video: {}, audio: {}, font: {} };
|
|
|
27
25
|
* that puts its nodes in the tree, so there is no way to know what plays when
|
|
28
26
|
* without it.
|
|
29
27
|
*
|
|
30
|
-
* Nothing is drawn, so this takes
|
|
31
|
-
* context: discovery lays each clip out, which needs text measured, not
|
|
32
|
-
* rasterized. No canvas, no surface, no GPU.
|
|
28
|
+
* Nothing is drawn, so this takes the renderer's measure context rather than a
|
|
29
|
+
* render context: discovery lays each clip out, which needs text measured, not
|
|
30
|
+
* rasterized. No canvas, no surface, no GPU, so WebGPU is not required.
|
|
33
31
|
*/
|
|
34
32
|
export async function mixTimelineAudio(params) {
|
|
35
|
-
const { registry = new Registry(), manifest = EMPTY_MANIFEST, sampleRate
|
|
33
|
+
const { registry = new Registry(), manifest = EMPTY_MANIFEST, sampleRate } = params;
|
|
36
34
|
const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);
|
|
37
35
|
if (project.tracks.length === 0)
|
|
38
36
|
return null;
|
|
39
37
|
const viewport = project.viewport;
|
|
40
38
|
const fps = project.fps;
|
|
41
|
-
//
|
|
42
|
-
const
|
|
39
|
+
// The renderer only for its text shaper; nothing is drawn.
|
|
40
|
+
const renderer = await loadRenderer(params, { gpu: false });
|
|
43
41
|
const catalog = new ManifestAssetCatalog(manifest);
|
|
44
|
-
const storage =
|
|
45
|
-
const measurer =
|
|
42
|
+
const storage = renderer.createStorageAdapter({ catalog, viewport, fps });
|
|
43
|
+
const measurer = renderer.createMeasureContext(storage);
|
|
46
44
|
// Audio is declared from each node's arrival state, so no asset windows are refined.
|
|
47
45
|
const precomper = new AssetTimeline(project, { registry, viewport, fps, assets: catalog, measurer }, { assets: false });
|
|
48
46
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mix-timeline-audio.js","sourceRoot":"","sources":["../src/mix-timeline-audio.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,QAAQ,EACR,sBAAsB,GAKzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"mix-timeline-audio.js","sourceRoot":"","sources":["../src/mix-timeline-audio.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,QAAQ,EACR,sBAAsB,GAKzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAA2B,MAAM,YAAY,CAAC;AAenE,MAAM,cAAc,GAAkB,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAClC,MAA8B;IAE9B,MAAM,EAAE,QAAQ,GAAG,IAAI,QAAQ,EAAE,EAAE,QAAQ,GAAG,cAAc,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;IACpF,MAAM,OAAO,GAAG,sBAAsB,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IAChF,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAS,CAAC;IACnC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAI,CAAC;IAEzB,2DAA2D;IAC3D,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5D,MAAM,OAAO,GAAG,IAAI,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;IAC1E,MAAM,QAAQ,GAAG,QAAQ,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAExD,qFAAqF;IACrF,MAAM,SAAS,GAAG,IAAI,aAAa,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACxH,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC;QAEhC,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QACxC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAExC,OAAO,MAAM,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,aAAa,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;IACrF,CAAC;YAAS,CAAC;QACP,SAAS,CAAC,OAAO,EAAE,CAAC;QACpB,0EAA0E;QAC1E,uCAAuC;QACvC,OAAO,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC;AACL,CAAC","sourcesContent":["import {\r\n DEFAULT_OUTPUT,\r\n ManifestAssetCatalog,\r\n AssetTimeline,\r\n Registry,\r\n resolveProjectDocument,\r\n type AssetManifest,\r\n type ProjectDocument,\r\n type VisualDocument,\r\n type Size2D,\r\n} from \"@motionscript/core\";\r\nimport { collectAudio } from \"./frame-loop\";\r\n\r\nimport { mixAudio } from \"./audio/mixer\";\r\nimport { loadRenderer, type RendererWasmOption } from \"./renderer\";\r\n\r\nexport interface MixTimelineAudioParams extends RendererWasmOption {\r\n /** A project, or a lone still or composition as the one track of one. */\r\n document: ProjectDocument | VisualDocument;\r\n /** The vocabulary its clips compile against. Defaults to what the library ships. */\r\n registry?: Registry;\r\n /** Win over the document's own; 1920×1080 at 60 fps when neither says. */\r\n viewport?: Size2D;\r\n fps?: number;\r\n manifest?: AssetManifest;\r\n /** Mixdown rate. Defaults to the mixer's own (44100). */\r\n sampleRate?: number;\r\n}\r\n\r\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\r\n\r\n/**\r\n * Mix a timeline's audio without rendering a single frame.\r\n *\r\n * `exportVideo` normally does this as part of the render, and for a\r\n * single-threaded export that is exactly right. A **split** export cannot: its\r\n * video is rendered in pieces by separate workers, and concatenating\r\n * separately-encoded AAC puts the encoder's priming delay at every join — a click\r\n * at each boundary and a track that drifts further out of sync with every one. So\r\n * the coordinator mixes the whole timeline once, here, and the joined file gets a\r\n * single continuous audio track.\r\n *\r\n * Returns `null` when the timeline schedules no audio at all, which is the common\r\n * case and not a failure — the caller should then mux video only rather than\r\n * declare an empty track.\r\n *\r\n * ## Why this discovers\r\n *\r\n * Audio requests are an *output of discovery*: each node declares its clips as it\r\n * enters the tree, and the pass that finds what a clip's frames draw is the one\r\n * that puts its nodes in the tree, so there is no way to know what plays when\r\n * without it.\r\n *\r\n * Nothing is drawn, so this takes the renderer's measure context rather than a\r\n * render context: discovery lays each clip out, which needs text measured, not\r\n * rasterized. No canvas, no surface, no GPU, so WebGPU is not required.\r\n */\r\nexport async function mixTimelineAudio(\r\n params: MixTimelineAudioParams,\r\n): Promise<AudioBuffer | null> {\r\n const { registry = new Registry(), manifest = EMPTY_MANIFEST, sampleRate } = params;\r\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\r\n if (project.tracks.length === 0) return null;\r\n const viewport = project.viewport!;\r\n const fps = project.fps!;\r\n\r\n // The renderer only for its text shaper; nothing is drawn.\r\n const renderer = await loadRenderer(params, { gpu: false });\r\n const catalog = new ManifestAssetCatalog(manifest);\r\n const storage = renderer.createStorageAdapter({ catalog, viewport, fps });\r\n const measurer = renderer.createMeasureContext(storage);\r\n\r\n // Audio is declared from each node's arrival state, so no asset windows are refined.\r\n const precomper = new AssetTimeline(project, { registry, viewport, fps, assets: catalog, measurer }, { assets: false });\r\n try {\r\n const precomp = precomper.run();\r\n\r\n const scheduled = collectAudio(precomp);\r\n if (scheduled.length === 0) return null;\r\n\r\n return await mixAudio(scheduled, precomp.totalDuration, { sampleRate, catalog });\r\n } finally {\r\n precomper.dispose();\r\n // The adapter holds decode sessions and cached images; without this a mix\r\n // leaks them for the life of the page.\r\n storage.dispose();\r\n }\r\n}\r\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ConcatOptions } from "../concat";
|
|
2
|
+
import { type ExportParams } from "../exporter";
|
|
3
|
+
import type { ExportLane } from "./lane";
|
|
4
|
+
export interface ParallelExportParams extends Omit<ExportParams, "frames" | "returnBytes" | "filename"> {
|
|
5
|
+
/**
|
|
6
|
+
* Where the frames are drawn, most preferred first. Every run starts on the
|
|
7
|
+
* first alone and admits the next only while that raises throughput (see
|
|
8
|
+
* `LaneGovernor`), so the first should be the warmest: usually `localLane()`.
|
|
9
|
+
*/
|
|
10
|
+
lanes: readonly ExportLane[];
|
|
11
|
+
/** Write the file here as it is joined; the call then resolves to an empty array. See `ConcatOptions.stream`. */
|
|
12
|
+
stream?: ConcatOptions["stream"];
|
|
13
|
+
/** Seconds per chunk; by default from the timeline's length and the lane count. */
|
|
14
|
+
chunkSeconds?: number;
|
|
15
|
+
/** Handed to every lane in its spec, for what only the host knows (see `serveExportLane`). */
|
|
16
|
+
host?: unknown;
|
|
17
|
+
/** How many lanes are rendering, each time that changes. */
|
|
18
|
+
onLanes?: (active: number) => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* {@link exportVideo}, rendered in chunks across `lanes` and joined into one
|
|
22
|
+
* file without re-encoding.
|
|
23
|
+
*
|
|
24
|
+
* Each chunk is a frame range of the **whole** project, so layered and gapped
|
|
25
|
+
* timelines split like any other. Every chunk is encoded with one pinned
|
|
26
|
+
* configuration, the audio is mixed once over the whole timeline while the frames
|
|
27
|
+
* render (joined AAC would click at every seam), and each chunk is muxed the
|
|
28
|
+
* moment it and the ones before it are done. Progress is frames drawn across
|
|
29
|
+
* every lane, so it climbs smoothly and never goes back.
|
|
30
|
+
*/
|
|
31
|
+
export declare function exportVideoParallel(params: ParallelExportParams): Promise<Uint8Array>;
|
|
32
|
+
//# sourceMappingURL=export-parallel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export-parallel.d.ts","sourceRoot":"","sources":["../../src/parallel/export-parallel.ts"],"names":[],"mappings":"AACA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAA4D,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAG1G,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,QAAQ,CAAC;AAItD,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,YAAY,EAAE,QAAQ,GAAG,aAAa,GAAG,UAAU,CAAC;IACnG;;;;OAIG;IACH,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IAC7B,iHAAiH;IACjH,MAAM,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACjC,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8FAA8F;IAC9F,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,4DAA4D;IAC5D,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAOD;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,UAAU,CAAC,CA4F3F"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { DEFAULT_OUTPUT, placeTracks, Registry, renderIssueKey, resolveProjectDocument, totalFramesOf } from "@motionscript/core";
|
|
2
|
+
import { createSegmentJoiner } from "../concat";
|
|
3
|
+
import { exportGeometry, plainVideoEncoding, resolveVideoEncoding } from "../exporter";
|
|
4
|
+
import { formatSpec } from "../formats";
|
|
5
|
+
import { mixTimelineAudio } from "../mix-timeline-audio";
|
|
6
|
+
import { planChunks } from "./plan";
|
|
7
|
+
import { runLanes } from "./run-lanes";
|
|
8
|
+
const EMPTY_MANIFEST = { image: {}, video: {}, audio: {}, font: {} };
|
|
9
|
+
const DEFAULT_SUPERSAMPLE = 2;
|
|
10
|
+
/** The share of progress drawing takes; finishing the file is the rest. */
|
|
11
|
+
const DRAWN = 0.97;
|
|
12
|
+
/**
|
|
13
|
+
* {@link exportVideo}, rendered in chunks across `lanes` and joined into one
|
|
14
|
+
* file without re-encoding.
|
|
15
|
+
*
|
|
16
|
+
* Each chunk is a frame range of the **whole** project, so layered and gapped
|
|
17
|
+
* timelines split like any other. Every chunk is encoded with one pinned
|
|
18
|
+
* configuration, the audio is mixed once over the whole timeline while the frames
|
|
19
|
+
* render (joined AAC would click at every seam), and each chunk is muxed the
|
|
20
|
+
* moment it and the ones before it are done. Progress is frames drawn across
|
|
21
|
+
* every lane, so it climbs smoothly and never goes back.
|
|
22
|
+
*/
|
|
23
|
+
export async function exportVideoParallel(params) {
|
|
24
|
+
const { registry = new Registry(), scale = 1, format = "mp4", manifest = EMPTY_MANIFEST, includeAudio = true, audio = {}, signal, onProgress, supersample = DEFAULT_SUPERSAMPLE, } = params;
|
|
25
|
+
if (formatSpec(format).kind !== "video") {
|
|
26
|
+
throw new RangeError(`exportVideoParallel writes video; "${format}" is audio, which exportAudio writes.`);
|
|
27
|
+
}
|
|
28
|
+
const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);
|
|
29
|
+
if (project.tracks.length === 0)
|
|
30
|
+
throw new Error("exportVideoParallel: the project has no tracks.");
|
|
31
|
+
signal?.throwIfAborted();
|
|
32
|
+
const fps = project.fps;
|
|
33
|
+
const totalFrames = totalFramesOf(placeTracks(project.tracks, fps));
|
|
34
|
+
const { output } = exportGeometry(project.viewport, scale, supersample);
|
|
35
|
+
const encoding = plainVideoEncoding(await resolveVideoEncoding(params.video, format, output, fps), output);
|
|
36
|
+
const spec = {
|
|
37
|
+
project, manifest, theme: params.theme, scale, supersample, format, encoding, host: params.host,
|
|
38
|
+
};
|
|
39
|
+
const chunks = planChunks(totalFrames, {
|
|
40
|
+
fps, keyFrameInterval: encoding.keyFrameInterval, lanes: params.lanes.length, chunkSeconds: params.chunkSeconds,
|
|
41
|
+
});
|
|
42
|
+
// Started now so it is ready by the first join; failures surface there.
|
|
43
|
+
const mixed = includeAudio
|
|
44
|
+
? mixTimelineAudio({
|
|
45
|
+
document: project, registry, manifest, sampleRate: audio.sampleRate,
|
|
46
|
+
rendererWasmUrl: params.rendererWasmUrl,
|
|
47
|
+
})
|
|
48
|
+
: Promise.resolve(null);
|
|
49
|
+
mixed.catch(() => { });
|
|
50
|
+
let reported = 0;
|
|
51
|
+
const seenIssues = new Set();
|
|
52
|
+
const report = (p) => {
|
|
53
|
+
if (p <= reported)
|
|
54
|
+
return;
|
|
55
|
+
reported = p;
|
|
56
|
+
onProgress?.(p);
|
|
57
|
+
};
|
|
58
|
+
// One signal for both ways a run ends early: the caller cancelling, and the join failing.
|
|
59
|
+
const run = new AbortController();
|
|
60
|
+
const abortRun = () => run.abort(signal?.reason);
|
|
61
|
+
if (signal?.aborted)
|
|
62
|
+
abortRun();
|
|
63
|
+
signal?.addEventListener("abort", abortRun, { once: true });
|
|
64
|
+
// Keyed by first frame: a merged range spans several planned chunks.
|
|
65
|
+
const ready = new Map();
|
|
66
|
+
let joinedTo = 0;
|
|
67
|
+
let joining = Promise.resolve();
|
|
68
|
+
let joiner = null;
|
|
69
|
+
const pump = () => {
|
|
70
|
+
joining = joining.then(async () => {
|
|
71
|
+
while (ready.has(joinedTo)) {
|
|
72
|
+
joiner ??= createSegmentJoiner({ stream: params.stream, audio: await mixed, audioBitrate: audio.bitrate });
|
|
73
|
+
const { end, bytes } = ready.get(joinedTo);
|
|
74
|
+
ready.delete(joinedTo);
|
|
75
|
+
joinedTo = end;
|
|
76
|
+
await joiner.append(bytes);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
return joining;
|
|
80
|
+
};
|
|
81
|
+
try {
|
|
82
|
+
await runLanes({
|
|
83
|
+
lanes: params.lanes, spec, chunks, signal: run.signal,
|
|
84
|
+
onIssue: (issue) => {
|
|
85
|
+
const key = renderIssueKey(issue);
|
|
86
|
+
if (seenIssues.has(key))
|
|
87
|
+
return;
|
|
88
|
+
seenIssues.add(key);
|
|
89
|
+
params.onIssue?.(issue);
|
|
90
|
+
},
|
|
91
|
+
onFrames: (frames) => report(DRAWN * Math.min(1, frames / totalFrames)),
|
|
92
|
+
onActive: params.onLanes,
|
|
93
|
+
onChunk: (chunk, bytes) => {
|
|
94
|
+
ready.set(chunk.start, { end: chunk.end, bytes });
|
|
95
|
+
pump().catch((error) => run.abort(error));
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
await pump();
|
|
99
|
+
if (joinedTo !== totalFrames)
|
|
100
|
+
throw new Error(`exportVideoParallel: joined ${joinedTo} of ${totalFrames} frames.`);
|
|
101
|
+
const bytes = await joiner.finish();
|
|
102
|
+
report(1);
|
|
103
|
+
return bytes;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
await joining.catch(() => { });
|
|
107
|
+
await joiner?.cancel().catch(() => { });
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
signal?.removeEventListener("abort", abortRun);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=export-parallel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export-parallel.js","sourceRoot":"","sources":["../../src/parallel/export-parallel.ts"],"names":[],"mappings":"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","sourcesContent":["import { DEFAULT_OUTPUT, placeTracks, Registry, renderIssueKey, resolveProjectDocument, totalFramesOf, type AssetManifest } from \"@motionscript/core\";\r\nimport { createSegmentJoiner, type ConcatOptions } from \"../concat\";\r\nimport { exportGeometry, plainVideoEncoding, resolveVideoEncoding, type ExportParams } from \"../exporter\";\r\nimport { formatSpec } from \"../formats\";\r\nimport { mixTimelineAudio } from \"../mix-timeline-audio\";\r\nimport type { ExportLane, SegmentSpec } from \"./lane\";\r\nimport { planChunks } from \"./plan\";\r\nimport { runLanes } from \"./run-lanes\";\r\n\r\nexport interface ParallelExportParams extends Omit<ExportParams, \"frames\" | \"returnBytes\" | \"filename\"> {\r\n /**\r\n * Where the frames are drawn, most preferred first. Every run starts on the\r\n * first alone and admits the next only while that raises throughput (see\r\n * `LaneGovernor`), so the first should be the warmest: usually `localLane()`.\r\n */\r\n lanes: readonly ExportLane[];\r\n /** Write the file here as it is joined; the call then resolves to an empty array. See `ConcatOptions.stream`. */\r\n stream?: ConcatOptions[\"stream\"];\r\n /** Seconds per chunk; by default from the timeline's length and the lane count. */\r\n chunkSeconds?: number;\r\n /** Handed to every lane in its spec, for what only the host knows (see `serveExportLane`). */\r\n host?: unknown;\r\n /** How many lanes are rendering, each time that changes. */\r\n onLanes?: (active: number) => void;\r\n}\r\n\r\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\r\nconst DEFAULT_SUPERSAMPLE = 2;\r\n/** The share of progress drawing takes; finishing the file is the rest. */\r\nconst DRAWN = 0.97;\r\n\r\n/**\r\n * {@link exportVideo}, rendered in chunks across `lanes` and joined into one\r\n * file without re-encoding.\r\n *\r\n * Each chunk is a frame range of the **whole** project, so layered and gapped\r\n * timelines split like any other. Every chunk is encoded with one pinned\r\n * configuration, the audio is mixed once over the whole timeline while the frames\r\n * render (joined AAC would click at every seam), and each chunk is muxed the\r\n * moment it and the ones before it are done. Progress is frames drawn across\r\n * every lane, so it climbs smoothly and never goes back.\r\n */\r\nexport async function exportVideoParallel(params: ParallelExportParams): Promise<Uint8Array> {\r\n const {\r\n registry = new Registry(), scale = 1, format = \"mp4\", manifest = EMPTY_MANIFEST,\r\n includeAudio = true, audio = {}, signal, onProgress, supersample = DEFAULT_SUPERSAMPLE,\r\n } = params;\r\n if (formatSpec(format).kind !== \"video\") {\r\n throw new RangeError(`exportVideoParallel writes video; \"${format}\" is audio, which exportAudio writes.`);\r\n }\r\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\r\n if (project.tracks.length === 0) throw new Error(\"exportVideoParallel: the project has no tracks.\");\r\n signal?.throwIfAborted();\r\n const fps = project.fps!;\r\n const totalFrames = totalFramesOf(placeTracks(project.tracks, fps));\r\n\r\n const { output } = exportGeometry(project.viewport!, scale, supersample);\r\n const encoding = plainVideoEncoding(await resolveVideoEncoding(params.video, format, output, fps), output);\r\n const spec: SegmentSpec = {\r\n project, manifest, theme: params.theme, scale, supersample, format, encoding, host: params.host,\r\n };\r\n const chunks = planChunks(totalFrames, {\r\n fps, keyFrameInterval: encoding.keyFrameInterval, lanes: params.lanes.length, chunkSeconds: params.chunkSeconds,\r\n });\r\n\r\n // Started now so it is ready by the first join; failures surface there.\r\n const mixed = includeAudio\r\n ? mixTimelineAudio({\r\n document: project, registry, manifest, sampleRate: audio.sampleRate,\r\n rendererWasmUrl: params.rendererWasmUrl,\r\n })\r\n : Promise.resolve(null);\r\n mixed.catch(() => {});\r\n\r\n let reported = 0;\r\n const seenIssues = new Set<string>();\r\n const report = (p: number) => {\r\n if (p <= reported) return;\r\n reported = p;\r\n onProgress?.(p);\r\n };\r\n\r\n // One signal for both ways a run ends early: the caller cancelling, and the join failing.\r\n const run = new AbortController();\r\n const abortRun = () => run.abort(signal?.reason);\r\n if (signal?.aborted) abortRun();\r\n signal?.addEventListener(\"abort\", abortRun, { once: true });\r\n\r\n // Keyed by first frame: a merged range spans several planned chunks.\r\n const ready = new Map<number, { end: number; bytes: Uint8Array }>();\r\n let joinedTo = 0;\r\n let joining: Promise<void> = Promise.resolve();\r\n let joiner: ReturnType<typeof createSegmentJoiner> | null = null;\r\n const pump = () => {\r\n joining = joining.then(async () => {\r\n while (ready.has(joinedTo)) {\r\n joiner ??= createSegmentJoiner({ stream: params.stream, audio: await mixed, audioBitrate: audio.bitrate });\r\n const { end, bytes } = ready.get(joinedTo)!;\r\n ready.delete(joinedTo);\r\n joinedTo = end;\r\n await joiner.append(bytes);\r\n }\r\n });\r\n return joining;\r\n };\r\n\r\n try {\r\n await runLanes({\r\n lanes: params.lanes, spec, chunks, signal: run.signal,\r\n onIssue: (issue) => {\r\n const key = renderIssueKey(issue);\r\n if (seenIssues.has(key)) return;\r\n seenIssues.add(key);\r\n params.onIssue?.(issue);\r\n },\r\n onFrames: (frames) => report(DRAWN * Math.min(1, frames / totalFrames)),\r\n onActive: params.onLanes,\r\n onChunk: (chunk, bytes) => {\r\n ready.set(chunk.start, { end: chunk.end, bytes });\r\n pump().catch((error: unknown) => run.abort(error));\r\n },\r\n });\r\n await pump();\r\n if (joinedTo !== totalFrames) throw new Error(`exportVideoParallel: joined ${joinedTo} of ${totalFrames} frames.`);\r\n const bytes = await joiner!.finish();\r\n report(1);\r\n return bytes;\r\n } catch (error) {\r\n await joining.catch(() => {});\r\n await (joiner as ReturnType<typeof createSegmentJoiner> | null)?.cancel().catch(() => {});\r\n throw error;\r\n } finally {\r\n signal?.removeEventListener(\"abort\", abortRun);\r\n }\r\n}\r\n"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decides how many lanes render at once, from what the run itself measures.
|
|
3
|
+
*
|
|
4
|
+
* Lanes do not add throughput for free. Every lane's encoder, and on a WebGPU
|
|
5
|
+
* canvas its frame capture, run in the browser's one GPU process, so once a lane
|
|
6
|
+
* spends its time waiting on the encoder rather than drawing, a second lane only
|
|
7
|
+
* splits the same encoder between two sessions and the export gets slower. The
|
|
8
|
+
* governor starts with one lane, adds the next only while lanes are mostly
|
|
9
|
+
* drawing, keeps it only if total throughput rose, and then settles.
|
|
10
|
+
*/
|
|
11
|
+
export interface GovernorOptions {
|
|
12
|
+
/** Lanes available, the first being the one every run starts with. */
|
|
13
|
+
lanes: number;
|
|
14
|
+
/** How long a configuration is measured before it is judged, in ms. */
|
|
15
|
+
windowMs?: number;
|
|
16
|
+
/** Frames a window needs before it is judged. */
|
|
17
|
+
minFrames?: number;
|
|
18
|
+
/** Above this share of time waiting on the encoder, another lane cannot help. */
|
|
19
|
+
maxWaitShare?: number;
|
|
20
|
+
/** A lane is kept only if it raises throughput by at least this factor. */
|
|
21
|
+
minGain?: number;
|
|
22
|
+
/** How long the newest lane draws before it is measured: its first frames wait on loads and a cold encoder. */
|
|
23
|
+
warmupMs?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface FrameSample {
|
|
26
|
+
lane: number;
|
|
27
|
+
frames: number;
|
|
28
|
+
busyMs: number;
|
|
29
|
+
waitMs: number;
|
|
30
|
+
at: number;
|
|
31
|
+
}
|
|
32
|
+
export declare class LaneGovernor {
|
|
33
|
+
private readonly _options;
|
|
34
|
+
private _active;
|
|
35
|
+
private _settled;
|
|
36
|
+
private _window;
|
|
37
|
+
private _previousFps;
|
|
38
|
+
private _newestFirstAt;
|
|
39
|
+
private readonly _windowMs;
|
|
40
|
+
private readonly _minFrames;
|
|
41
|
+
private readonly _maxWaitShare;
|
|
42
|
+
private readonly _minGain;
|
|
43
|
+
private readonly _warmupMs;
|
|
44
|
+
constructor(_options: GovernorOptions);
|
|
45
|
+
/** Lanes `0..active-1` may take chunks. */
|
|
46
|
+
get active(): number;
|
|
47
|
+
get settled(): boolean;
|
|
48
|
+
/** Stop measuring once a lane dies: the run continues on at most `alive` lanes. */
|
|
49
|
+
laneLost(alive: number): void;
|
|
50
|
+
/**
|
|
51
|
+
* Record frames a lane drew. Returns the new active count when it changed.
|
|
52
|
+
* A window opens once the newest lane has drawn for `warmupMs`, so its
|
|
53
|
+
* start-up is not counted against it.
|
|
54
|
+
*/
|
|
55
|
+
record(sample: FrameSample): number | null;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=governor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"governor.d.ts","sourceRoot":"","sources":["../../src/parallel/governor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe;IAC5B,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+GAA+G;IAC/G,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;CACd;AASD,qBAAa,YAAY;IAYT,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAXrC,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEN,QAAQ,EAAE,eAAe;IAStD,2CAA2C;IAC3C,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK7B;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI;CA+B7C"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export class LaneGovernor {
|
|
2
|
+
_options;
|
|
3
|
+
_active = 1;
|
|
4
|
+
_settled;
|
|
5
|
+
_window = emptyWindow();
|
|
6
|
+
_previousFps = 0;
|
|
7
|
+
_newestFirstAt = null;
|
|
8
|
+
_windowMs;
|
|
9
|
+
_minFrames;
|
|
10
|
+
_maxWaitShare;
|
|
11
|
+
_minGain;
|
|
12
|
+
_warmupMs;
|
|
13
|
+
constructor(_options) {
|
|
14
|
+
this._options = _options;
|
|
15
|
+
this._windowMs = _options.windowMs ?? 1500;
|
|
16
|
+
this._minFrames = _options.minFrames ?? 24;
|
|
17
|
+
this._maxWaitShare = _options.maxWaitShare ?? 0.45;
|
|
18
|
+
this._minGain = _options.minGain ?? 1.1;
|
|
19
|
+
this._warmupMs = _options.warmupMs ?? 300;
|
|
20
|
+
this._settled = _options.lanes <= 1;
|
|
21
|
+
}
|
|
22
|
+
/** Lanes `0..active-1` may take chunks. */
|
|
23
|
+
get active() {
|
|
24
|
+
return this._active;
|
|
25
|
+
}
|
|
26
|
+
get settled() {
|
|
27
|
+
return this._settled;
|
|
28
|
+
}
|
|
29
|
+
/** Stop measuring once a lane dies: the run continues on at most `alive` lanes. */
|
|
30
|
+
laneLost(alive) {
|
|
31
|
+
this._active = Math.max(1, Math.min(this._active, alive));
|
|
32
|
+
this._settled = true;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Record frames a lane drew. Returns the new active count when it changed.
|
|
36
|
+
* A window opens once the newest lane has drawn for `warmupMs`, so its
|
|
37
|
+
* start-up is not counted against it.
|
|
38
|
+
*/
|
|
39
|
+
record(sample) {
|
|
40
|
+
if (this._settled || sample.lane >= this._active)
|
|
41
|
+
return null;
|
|
42
|
+
const w = this._window;
|
|
43
|
+
if (w.start === null) {
|
|
44
|
+
if (sample.lane !== this._active - 1)
|
|
45
|
+
return null;
|
|
46
|
+
this._newestFirstAt ??= sample.at;
|
|
47
|
+
if (sample.at - this._newestFirstAt >= this._warmupMs)
|
|
48
|
+
w.start = sample.at;
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
w.frames += sample.frames;
|
|
52
|
+
w.busyMs += sample.busyMs;
|
|
53
|
+
w.waitMs += sample.waitMs;
|
|
54
|
+
const elapsed = sample.at - w.start;
|
|
55
|
+
if (elapsed < this._windowMs || w.frames < this._minFrames)
|
|
56
|
+
return null;
|
|
57
|
+
const fps = (w.frames * 1000) / elapsed;
|
|
58
|
+
const waitShare = w.waitMs / Math.max(1, w.busyMs + w.waitMs);
|
|
59
|
+
const before = this._active;
|
|
60
|
+
if (this._active > 1 && fps < this._previousFps * this._minGain) {
|
|
61
|
+
this._active -= 1;
|
|
62
|
+
this._settled = true;
|
|
63
|
+
}
|
|
64
|
+
else if (waitShare <= this._maxWaitShare && this._active < this._options.lanes) {
|
|
65
|
+
this._previousFps = fps;
|
|
66
|
+
this._active += 1;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
this._settled = true;
|
|
70
|
+
}
|
|
71
|
+
this._window = emptyWindow();
|
|
72
|
+
this._newestFirstAt = null;
|
|
73
|
+
return this._active !== before ? this._active : null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function emptyWindow() {
|
|
77
|
+
return { start: null, frames: 0, busyMs: 0, waitMs: 0 };
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=governor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"governor.js","sourceRoot":"","sources":["../../src/parallel/governor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Decides how many lanes render at once, from what the run itself measures.\n *\n * Lanes do not add throughput for free. Every lane's encoder, and on a WebGPU\n * canvas its frame capture, run in the browser's one GPU process, so once a lane\n * spends its time waiting on the encoder rather than drawing, a second lane only\n * splits the same encoder between two sessions and the export gets slower. The\n * governor starts with one lane, adds the next only while lanes are mostly\n * drawing, keeps it only if total throughput rose, and then settles.\n */\nexport interface GovernorOptions {\n /** Lanes available, the first being the one every run starts with. */\n lanes: number;\n /** How long a configuration is measured before it is judged, in ms. */\n windowMs?: number;\n /** Frames a window needs before it is judged. */\n minFrames?: number;\n /** Above this share of time waiting on the encoder, another lane cannot help. */\n maxWaitShare?: number;\n /** A lane is kept only if it raises throughput by at least this factor. */\n minGain?: number;\n /** How long the newest lane draws before it is measured: its first frames wait on loads and a cold encoder. */\n warmupMs?: number;\n}\n\nexport interface FrameSample {\n lane: number;\n frames: number;\n busyMs: number;\n waitMs: number;\n at: number;\n}\n\ninterface Window {\n start: number | null;\n frames: number;\n busyMs: number;\n waitMs: number;\n}\n\nexport class LaneGovernor {\n private _active = 1;\n private _settled: boolean;\n private _window: Window = emptyWindow();\n private _previousFps = 0;\n private _newestFirstAt: number | null = null;\n private readonly _windowMs: number;\n private readonly _minFrames: number;\n private readonly _maxWaitShare: number;\n private readonly _minGain: number;\n private readonly _warmupMs: number;\n\n constructor(private readonly _options: GovernorOptions) {\n this._windowMs = _options.windowMs ?? 1500;\n this._minFrames = _options.minFrames ?? 24;\n this._maxWaitShare = _options.maxWaitShare ?? 0.45;\n this._minGain = _options.minGain ?? 1.1;\n this._warmupMs = _options.warmupMs ?? 300;\n this._settled = _options.lanes <= 1;\n }\n\n /** Lanes `0..active-1` may take chunks. */\n get active(): number {\n return this._active;\n }\n\n get settled(): boolean {\n return this._settled;\n }\n\n /** Stop measuring once a lane dies: the run continues on at most `alive` lanes. */\n laneLost(alive: number): void {\n this._active = Math.max(1, Math.min(this._active, alive));\n this._settled = true;\n }\n\n /**\n * Record frames a lane drew. Returns the new active count when it changed.\n * A window opens once the newest lane has drawn for `warmupMs`, so its\n * start-up is not counted against it.\n */\n record(sample: FrameSample): number | null {\n if (this._settled || sample.lane >= this._active) return null;\n const w = this._window;\n if (w.start === null) {\n if (sample.lane !== this._active - 1) return null;\n this._newestFirstAt ??= sample.at;\n if (sample.at - this._newestFirstAt >= this._warmupMs) w.start = sample.at;\n return null;\n }\n w.frames += sample.frames;\n w.busyMs += sample.busyMs;\n w.waitMs += sample.waitMs;\n const elapsed = sample.at - w.start;\n if (elapsed < this._windowMs || w.frames < this._minFrames) return null;\n\n const fps = (w.frames * 1000) / elapsed;\n const waitShare = w.waitMs / Math.max(1, w.busyMs + w.waitMs);\n const before = this._active;\n if (this._active > 1 && fps < this._previousFps * this._minGain) {\n this._active -= 1;\n this._settled = true;\n } else if (waitShare <= this._maxWaitShare && this._active < this._options.lanes) {\n this._previousFps = fps;\n this._active += 1;\n } else {\n this._settled = true;\n }\n this._window = emptyWindow();\n this._newestFirstAt = null;\n return this._active !== before ? this._active : null;\n }\n}\n\nfunction emptyWindow(): Window {\n return { start: null, frames: 0, busyMs: 0, waitMs: 0 };\n}\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { AssetManifest, ProjectDocument, Registry, RenderIssue, Theme } from "@motionscript/core";
|
|
2
|
+
import type { RendererWasmOption } from "../renderer";
|
|
3
|
+
import type { ResolvedVideoEncoding } from "../exporter";
|
|
4
|
+
import type { VideoFormat } from "../formats";
|
|
5
|
+
import type { ExportChunk } from "./plan";
|
|
6
|
+
/**
|
|
7
|
+
* Everything a lane needs to render chunks of one export. Plain data, so it
|
|
8
|
+
* crosses a `MessagePort` intact; the encoding is pinned here because segments
|
|
9
|
+
* encoded differently cannot be joined.
|
|
10
|
+
*/
|
|
11
|
+
export interface SegmentSpec {
|
|
12
|
+
/** Its fps and viewport resolved. */
|
|
13
|
+
project: ProjectDocument;
|
|
14
|
+
manifest: AssetManifest;
|
|
15
|
+
theme?: Theme;
|
|
16
|
+
scale: number;
|
|
17
|
+
supersample: number;
|
|
18
|
+
format: VideoFormat;
|
|
19
|
+
encoding: ResolvedVideoEncoding;
|
|
20
|
+
/** Passed through untouched to a remote lane's `resolve`, for what only its host knows. */
|
|
21
|
+
host?: unknown;
|
|
22
|
+
}
|
|
23
|
+
/** A chunk's frames so far, and how its time split between drawing and waiting on the encoder. */
|
|
24
|
+
export interface LaneReport {
|
|
25
|
+
frames: number;
|
|
26
|
+
busyMs: number;
|
|
27
|
+
waitMs: number;
|
|
28
|
+
/** Render issues since the lane's last report. */
|
|
29
|
+
issues?: RenderIssue[];
|
|
30
|
+
}
|
|
31
|
+
/** One lane, open on one export. */
|
|
32
|
+
export interface LaneSession {
|
|
33
|
+
/** Render the chunk's frames to a complete, silent segment file. */
|
|
34
|
+
render(chunk: ExportChunk, onReport: (report: LaneReport) => void, signal: AbortSignal): Promise<Uint8Array>;
|
|
35
|
+
close(): void;
|
|
36
|
+
}
|
|
37
|
+
/** Something that renders chunks: this window, or another realm across a port. */
|
|
38
|
+
export interface ExportLane {
|
|
39
|
+
open(spec: SegmentSpec, signal: AbortSignal): Promise<LaneSession>;
|
|
40
|
+
}
|
|
41
|
+
/** What a realm renders with: its own registry, and where its renderer's WASM is served. */
|
|
42
|
+
export interface LaneEnvironment extends RendererWasmOption {
|
|
43
|
+
registry?: Registry;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=lane.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lane.d.ts","sourceRoot":"","sources":["../../src/parallel/lane.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AACvG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAE1C;;;;GAIG;AACH,MAAM,WAAW,WAAW;IACxB,qCAAqC;IACrC,OAAO,EAAE,eAAe,CAAC;IACzB,QAAQ,EAAE,aAAa,CAAC;IACxB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,qBAAqB,CAAC;IAChC,2FAA2F;IAC3F,IAAI,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,kGAAkG;AAClG,MAAM,WAAW,UAAU;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;CAC1B;AAED,oCAAoC;AACpC,MAAM,WAAW,WAAW;IACxB,oEAAoE;IACpE,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7G,KAAK,IAAI,IAAI,CAAC;CACjB;AAED,kFAAkF;AAClF,MAAM,WAAW,UAAU;IACvB,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CACtE;AAED,4FAA4F;AAC5F,MAAM,WAAW,eAAgB,SAAQ,kBAAkB;IACvD,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACvB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lane.js","sourceRoot":"","sources":["../../src/parallel/lane.ts"],"names":[],"mappings":"","sourcesContent":["import type { AssetManifest, ProjectDocument, Registry, RenderIssue, Theme } from \"@motionscript/core\";\r\nimport type { RendererWasmOption } from \"../renderer\";\r\nimport type { ResolvedVideoEncoding } from \"../exporter\";\r\nimport type { VideoFormat } from \"../formats\";\r\nimport type { ExportChunk } from \"./plan\";\r\n\r\n/**\r\n * Everything a lane needs to render chunks of one export. Plain data, so it\r\n * crosses a `MessagePort` intact; the encoding is pinned here because segments\r\n * encoded differently cannot be joined.\r\n */\r\nexport interface SegmentSpec {\r\n /** Its fps and viewport resolved. */\r\n project: ProjectDocument;\r\n manifest: AssetManifest;\r\n theme?: Theme;\r\n scale: number;\r\n supersample: number;\r\n format: VideoFormat;\r\n encoding: ResolvedVideoEncoding;\r\n /** Passed through untouched to a remote lane's `resolve`, for what only its host knows. */\r\n host?: unknown;\r\n}\r\n\r\n/** A chunk's frames so far, and how its time split between drawing and waiting on the encoder. */\r\nexport interface LaneReport {\r\n frames: number;\r\n busyMs: number;\r\n waitMs: number;\r\n /** Render issues since the lane's last report. */\r\n issues?: RenderIssue[];\r\n}\r\n\r\n/** One lane, open on one export. */\r\nexport interface LaneSession {\r\n /** Render the chunk's frames to a complete, silent segment file. */\r\n render(chunk: ExportChunk, onReport: (report: LaneReport) => void, signal: AbortSignal): Promise<Uint8Array>;\r\n close(): void;\r\n}\r\n\r\n/** Something that renders chunks: this window, or another realm across a port. */\r\nexport interface ExportLane {\r\n open(spec: SegmentSpec, signal: AbortSignal): Promise<LaneSession>;\r\n}\r\n\r\n/** What a realm renders with: its own registry, and where its renderer's WASM is served. */\r\nexport interface LaneEnvironment extends RendererWasmOption {\r\n registry?: Registry;\r\n}\r\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ExportLane, LaneEnvironment, SegmentSpec } from "./lane";
|
|
2
|
+
/** A lane that renders in this realm, with its renderer, registry and warm caches. */
|
|
3
|
+
export declare function localLane(env?: LaneEnvironment): ExportLane;
|
|
4
|
+
export interface MessagePortLaneOptions {
|
|
5
|
+
/** Settles when the realm behind the port is gone, failing whatever it was doing. */
|
|
6
|
+
lost?: Promise<unknown>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A lane on the far side of `port`, which {@link serveExportLane} answers. The
|
|
10
|
+
* finished segment comes back as a transferred buffer, never a copy.
|
|
11
|
+
*/
|
|
12
|
+
export declare function messagePortLane(port: MessagePort, options?: MessagePortLaneOptions): ExportLane;
|
|
13
|
+
/**
|
|
14
|
+
* Answer a {@link messagePortLane} from this realm. `resolve` supplies what the
|
|
15
|
+
* realm renders with, given the spec (its `host` field carries anything the
|
|
16
|
+
* coordinator's host wanted this side to know). Returns a function that stops
|
|
17
|
+
* serving and closes every open session.
|
|
18
|
+
*/
|
|
19
|
+
export declare function serveExportLane(port: MessagePort, resolve: (spec: SegmentSpec) => LaneEnvironment | Promise<LaneEnvironment>): () => void;
|
|
20
|
+
//# sourceMappingURL=lanes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lanes.d.ts","sourceRoot":"","sources":["../../src/parallel/lanes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAA2B,WAAW,EAAE,MAAM,QAAQ,CAAC;AAIhG,sFAAsF;AACtF,wBAAgB,SAAS,CAAC,GAAG,GAAE,eAAoB,GAAG,UAAU,CAE/D;AAqBD,MAAM,WAAW,sBAAsB;IACnC,qFAAqF;IACrF,IAAI,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,GAAE,sBAA2B,GAAG,UAAU,CA4DnG;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC3B,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,GAC3E,MAAM,IAAI,CA6DZ"}
|