@motionscript/browser 0.0.0-stage → 0.1.0-alpha.10

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.
Files changed (133) hide show
  1. package/CHANGELOG.md +192 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +49 -0
  4. package/README.md +140 -3
  5. package/dist/asset-bytes.d.ts +59 -0
  6. package/dist/asset-bytes.d.ts.map +1 -0
  7. package/dist/asset-bytes.js +78 -0
  8. package/dist/asset-bytes.js.map +1 -0
  9. package/dist/audio/bus-graph.d.ts +52 -0
  10. package/dist/audio/bus-graph.d.ts.map +1 -0
  11. package/dist/audio/bus-graph.js +108 -0
  12. package/dist/audio/bus-graph.js.map +1 -0
  13. package/dist/audio/effect-graph.d.ts +62 -0
  14. package/dist/audio/effect-graph.d.ts.map +1 -0
  15. package/dist/audio/effect-graph.js +234 -0
  16. package/dist/audio/effect-graph.js.map +1 -0
  17. package/dist/audio/index.d.ts +5 -0
  18. package/dist/audio/index.d.ts.map +1 -0
  19. package/dist/audio/index.js +11 -0
  20. package/dist/audio/index.js.map +1 -0
  21. package/dist/audio/loop-region.d.ts +31 -0
  22. package/dist/audio/loop-region.d.ts.map +1 -0
  23. package/dist/audio/loop-region.js +43 -0
  24. package/dist/audio/loop-region.js.map +1 -0
  25. package/dist/audio/mixer.d.ts +59 -0
  26. package/dist/audio/mixer.d.ts.map +1 -0
  27. package/dist/audio/mixer.js +192 -0
  28. package/dist/audio/mixer.js.map +1 -0
  29. package/dist/audio/player.d.ts +55 -0
  30. package/dist/audio/player.d.ts.map +1 -0
  31. package/dist/audio/player.js +217 -0
  32. package/dist/audio/player.js.map +1 -0
  33. package/dist/browser/THIRD_PARTY_LICENSES.txt +784 -0
  34. package/dist/browser/audio.js +2 -0
  35. package/dist/browser/audio.js.map +7 -0
  36. package/dist/browser/chunks/chunk-F3AZKPHU.js +2 -0
  37. package/dist/browser/chunks/chunk-F3AZKPHU.js.map +7 -0
  38. package/dist/browser/chunks/chunk-KLXMRKTL.js +2 -0
  39. package/dist/browser/chunks/chunk-KLXMRKTL.js.map +7 -0
  40. package/dist/browser/chunks/chunk-L5UFQACH.js +163 -0
  41. package/dist/browser/chunks/chunk-L5UFQACH.js.map +7 -0
  42. package/dist/browser/chunks/mediabunny-mp3-encoder-ZCIHVVTJ.js +14 -0
  43. package/dist/browser/chunks/mediabunny-mp3-encoder-ZCIHVVTJ.js.map +7 -0
  44. package/dist/browser/esbuild.wasm +0 -0
  45. package/dist/browser/index.js +734 -0
  46. package/dist/browser/index.js.map +7 -0
  47. package/dist/browser/manifest.json +32 -0
  48. package/dist/browser/ms_renderer_bg.wasm +0 -0
  49. package/dist/concat.d.ts +121 -0
  50. package/dist/concat.d.ts.map +1 -0
  51. package/dist/concat.js +219 -0
  52. package/dist/concat.js.map +1 -0
  53. package/dist/deliver.d.ts +10 -0
  54. package/dist/deliver.d.ts.map +1 -0
  55. package/dist/deliver.js +22 -0
  56. package/dist/deliver.js.map +1 -0
  57. package/dist/dynamic-nodes.d.ts +9 -0
  58. package/dist/dynamic-nodes.d.ts.map +1 -0
  59. package/dist/dynamic-nodes.js +53 -0
  60. package/dist/dynamic-nodes.js.map +1 -0
  61. package/dist/engine.d.ts +87 -0
  62. package/dist/engine.d.ts.map +1 -0
  63. package/dist/engine.js +193 -0
  64. package/dist/engine.js.map +1 -0
  65. package/dist/export-audio.d.ts +36 -0
  66. package/dist/export-audio.d.ts.map +1 -0
  67. package/dist/export-audio.js +56 -0
  68. package/dist/export-audio.js.map +1 -0
  69. package/dist/exporter.d.ts +282 -0
  70. package/dist/exporter.d.ts.map +1 -0
  71. package/dist/exporter.js +308 -0
  72. package/dist/exporter.js.map +1 -0
  73. package/dist/formats.d.ts +36 -0
  74. package/dist/formats.d.ts.map +1 -0
  75. package/dist/formats.js +83 -0
  76. package/dist/formats.js.map +1 -0
  77. package/dist/frame-loop.d.ts +190 -0
  78. package/dist/frame-loop.d.ts.map +1 -0
  79. package/dist/frame-loop.js +304 -0
  80. package/dist/frame-loop.js.map +1 -0
  81. package/dist/index.d.ts +24 -0
  82. package/dist/index.d.ts.map +1 -0
  83. package/dist/index.js +43 -0
  84. package/dist/index.js.map +1 -0
  85. package/dist/mix-timeline-audio.d.ts +42 -0
  86. package/dist/mix-timeline-audio.d.ts.map +1 -0
  87. package/dist/mix-timeline-audio.js +60 -0
  88. package/dist/mix-timeline-audio.js.map +1 -0
  89. package/dist/parallel/export-parallel.d.ts +32 -0
  90. package/dist/parallel/export-parallel.d.ts.map +1 -0
  91. package/dist/parallel/export-parallel.js +114 -0
  92. package/dist/parallel/export-parallel.js.map +1 -0
  93. package/dist/parallel/governor.d.ts +57 -0
  94. package/dist/parallel/governor.d.ts.map +1 -0
  95. package/dist/parallel/governor.js +79 -0
  96. package/dist/parallel/governor.js.map +1 -0
  97. package/dist/parallel/lane.d.ts +45 -0
  98. package/dist/parallel/lane.d.ts.map +1 -0
  99. package/dist/parallel/lane.js +2 -0
  100. package/dist/parallel/lane.js.map +1 -0
  101. package/dist/parallel/lanes.d.ts +20 -0
  102. package/dist/parallel/lanes.d.ts.map +1 -0
  103. package/dist/parallel/lanes.js +152 -0
  104. package/dist/parallel/lanes.js.map +1 -0
  105. package/dist/parallel/plan.d.ts +23 -0
  106. package/dist/parallel/plan.d.ts.map +1 -0
  107. package/dist/parallel/plan.js +30 -0
  108. package/dist/parallel/plan.js.map +1 -0
  109. package/dist/parallel/run-lanes.d.ts +38 -0
  110. package/dist/parallel/run-lanes.d.ts.map +1 -0
  111. package/dist/parallel/run-lanes.js +164 -0
  112. package/dist/parallel/run-lanes.js.map +1 -0
  113. package/dist/parallel/session.d.ts +9 -0
  114. package/dist/parallel/session.d.ts.map +1 -0
  115. package/dist/parallel/session.js +79 -0
  116. package/dist/parallel/session.js.map +1 -0
  117. package/dist/renderer.d.ts +22 -0
  118. package/dist/renderer.d.ts.map +1 -0
  119. package/dist/renderer.js +23 -0
  120. package/dist/renderer.js.map +1 -0
  121. package/dist/screenshot.d.ts +45 -0
  122. package/dist/screenshot.d.ts.map +1 -0
  123. package/dist/screenshot.js +31 -0
  124. package/dist/screenshot.js.map +1 -0
  125. package/dist/still.d.ts +159 -0
  126. package/dist/still.d.ts.map +1 -0
  127. package/dist/still.js +222 -0
  128. package/dist/still.js.map +1 -0
  129. package/dist/ticker.d.ts +74 -0
  130. package/dist/ticker.d.ts.map +1 -0
  131. package/dist/ticker.js +215 -0
  132. package/dist/ticker.js.map +1 -0
  133. package/package.json +75 -3
@@ -0,0 +1,23 @@
1
+ import { createWgpuRenderer, isWgpuSupported } from "@motionscript/renderer";
2
+ import { loadAssetBytes } from "./asset-bytes";
3
+ /**
4
+ * This browser cannot run the WebGPU renderer: no `navigator.gpu`, or no
5
+ * adapter behind it. Every entry that draws (an engine, a still, an export,
6
+ * a timeline's audio mix) rejects with it before drawing anything.
7
+ */
8
+ export class WebGpuUnavailableError extends Error {
9
+ constructor() {
10
+ super("Motion Script draws with WebGPU, and this browser has no WebGPU adapter.");
11
+ this.name = "WebGpuUnavailableError";
12
+ }
13
+ }
14
+ /**
15
+ * The renderer every entry draws with, or {@link WebGpuUnavailableError}.
16
+ * `gpu: false` is for an entry that only measures text, which needs no device.
17
+ */
18
+ export async function loadRenderer(options = {}, { gpu = true } = {}) {
19
+ if (gpu && !(await isWgpuSupported()))
20
+ throw new WebGpuUnavailableError();
21
+ return createWgpuRenderer({ wasmUrl: options.rendererWasmUrl, loadBytes: loadAssetBytes });
22
+ }
23
+ //# sourceMappingURL=renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.js","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAqB,MAAM,wBAAwB,CAAC;AAChG,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAQ/C;;;;GAIG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC7C;QACI,KAAK,CAAC,0EAA0E,CAAC,CAAC;QAClF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACzC,CAAC;CACJ;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,UAA8B,EAAE,EAAE,EAAE,GAAG,GAAG,IAAI,EAAE,GAAG,EAAE;IACpF,IAAI,GAAG,IAAI,CAAC,CAAC,MAAM,eAAe,EAAE,CAAC;QAAE,MAAM,IAAI,sBAAsB,EAAE,CAAC;IAC1E,OAAO,kBAAkB,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,eAAe,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CAAC;AAC/F,CAAC","sourcesContent":["import { createWgpuRenderer, isWgpuSupported, type WgpuRenderer } from \"@motionscript/renderer\";\nimport { loadAssetBytes } from \"./asset-bytes\";\n\n/** Where the renderer's WebAssembly is served, for every entry that draws. */\nexport interface RendererWasmOption {\n /** URL of `ms_renderer_bg.wasm`; defaults to beside `@motionscript/renderer`'s module, which a bundler resolves. */\n rendererWasmUrl?: string | URL;\n}\n\n/**\n * This browser cannot run the WebGPU renderer: no `navigator.gpu`, or no\n * adapter behind it. Every entry that draws (an engine, a still, an export,\n * a timeline's audio mix) rejects with it before drawing anything.\n */\nexport class WebGpuUnavailableError extends Error {\n constructor() {\n super(\"Motion Script draws with WebGPU, and this browser has no WebGPU adapter.\");\n this.name = \"WebGpuUnavailableError\";\n }\n}\n\n/**\n * The renderer every entry draws with, or {@link WebGpuUnavailableError}.\n * `gpu: false` is for an entry that only measures text, which needs no device.\n */\nexport async function loadRenderer(options: RendererWasmOption = {}, { gpu = true } = {}): Promise<WgpuRenderer> {\n if (gpu && !(await isWgpuSupported())) throw new WebGpuUnavailableError();\n return createWgpuRenderer({ wasmUrl: options.rendererWasmUrl, loadBytes: loadAssetBytes });\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import { type AssetManifest, type FrameSpec, type ProjectDocument, type Registry, type VisualDocument, type Size2D, type Theme } from "@motionscript/core";
2
+ import type { ScreenshotFormat } from "./frame-loop";
3
+ import type { RendererWasmOption } from "./renderer";
4
+ import { type FrameRef } from "./still";
5
+ export type { ScreenshotFormat, FrameSpec };
6
+ export type ScreenshotParams = RendererWasmOption & {
7
+ /** A project, or a lone still or composition as the one track of one. */
8
+ document: ProjectDocument | VisualDocument;
9
+ /** The vocabulary its clips compile against. Defaults to what the library ships. */
10
+ registry?: Registry;
11
+ /** Win over the document's own; 1920×1080 at 60 fps when neither says. */
12
+ viewport?: Size2D;
13
+ fps?: number;
14
+ scale?: number;
15
+ manifest?: AssetManifest;
16
+ /** Theme for a document that carries none of its own. */
17
+ theme?: Theme;
18
+ /** Which frame to capture. Defaults to the timeline's first. */
19
+ frame?: FrameRef;
20
+ /** Encoding format (default "png"). */
21
+ format?: ScreenshotFormat;
22
+ /** JPEG quality in [0,1] (ignored for png; default 0.92). */
23
+ quality?: number;
24
+ };
25
+ export type ScreenshotResult = {
26
+ /** The project frame that was captured, after clamping. */
27
+ frame: number;
28
+ totalFrames: number;
29
+ /** Encoded image bytes. */
30
+ bytes: Uint8Array;
31
+ };
32
+ /**
33
+ * Renders a single frame of a document to an image.
34
+ *
35
+ * A one-shot wrapper over {@link StillRenderer}: create, render, encode, dispose.
36
+ * It therefore takes the same path as a live preview, and as the video export at
37
+ * that frame — there is one renderer, not three.
38
+ *
39
+ * For anything that captures **repeatedly** — a thumbnail builder, a preview that
40
+ * repaints as the user edits — hold a {@link StillRenderer} instead. This function
41
+ * builds and tears down a WebGPU surface per call, which is the right trade for one
42
+ * capture and the wrong one for a hundred.
43
+ */
44
+ export declare function exportScreenshot(params: ScreenshotParams): Promise<ScreenshotResult>;
45
+ //# sourceMappingURL=screenshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,KAAK,MAAM,EAAE,KAAK,KAAK,EACxH,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAuB,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE7D,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC;AAE5C,MAAM,MAAM,gBAAgB,GAAG,kBAAkB,GAAG;IAChD,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,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,yDAAyD;IACzD,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,gEAAgE;IAChE,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,uCAAuC;IACvC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC3B,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,2BAA2B;IAC3B,KAAK,EAAE,UAAU,CAAC;CACrB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmB1F"}
@@ -0,0 +1,31 @@
1
+ import { DEFAULT_OUTPUT, resolveProjectDocument, } from "@motionscript/core";
2
+ import { createStillRenderer } from "./still";
3
+ /**
4
+ * Renders a single frame of a document to an image.
5
+ *
6
+ * A one-shot wrapper over {@link StillRenderer}: create, render, encode, dispose.
7
+ * It therefore takes the same path as a live preview, and as the video export at
8
+ * that frame — there is one renderer, not three.
9
+ *
10
+ * For anything that captures **repeatedly** — a thumbnail builder, a preview that
11
+ * repaints as the user edits — hold a {@link StillRenderer} instead. This function
12
+ * builds and tears down a WebGPU surface per call, which is the right trade for one
13
+ * capture and the wrong one for a hundred.
14
+ */
15
+ export async function exportScreenshot(params) {
16
+ const { registry, scale, manifest, theme, rendererWasmUrl, frame, format = "png", quality = 0.92, } = params;
17
+ const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);
18
+ if (project.tracks.length === 0)
19
+ throw new Error("The project has no tracks to screenshot.");
20
+ const renderer = await createStillRenderer({
21
+ viewport: project.viewport, fps: project.fps, scale, manifest, theme, rendererWasmUrl, registry,
22
+ });
23
+ try {
24
+ const drawn = await renderer.render(project, { frame });
25
+ return { ...drawn, bytes: await renderer.toBytes({ format, quality }) };
26
+ }
27
+ finally {
28
+ renderer.dispose();
29
+ }
30
+ }
31
+ //# sourceMappingURL=screenshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,cAAc,EAAE,sBAAsB,GAEzC,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,mBAAmB,EAAiB,MAAM,SAAS,CAAC;AAgC7D;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAwB;IAC3D,MAAM,EACF,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,EACjD,KAAK,EAAE,MAAM,GAAG,KAAK,EAAE,OAAO,GAAG,IAAI,GACxC,GAAG,MAAM,CAAC;IAEX,MAAM,OAAO,GAAG,sBAAsB,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IAChF,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAE7F,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC;QACvC,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,EAAE,QAAQ;KAClG,CAAC,CAAC;IAEH,IAAI,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACxD,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAC5E,CAAC;YAAS,CAAC;QACP,QAAQ,CAAC,OAAO,EAAE,CAAC;IACvB,CAAC;AACL,CAAC","sourcesContent":["import {\n DEFAULT_OUTPUT, resolveProjectDocument,\n type AssetManifest, type FrameSpec, type ProjectDocument, type Registry, type VisualDocument, type Size2D, type Theme,\n} from \"@motionscript/core\";\nimport type { ScreenshotFormat } from \"./frame-loop\";\nimport type { RendererWasmOption } from \"./renderer\";\nimport { createStillRenderer, type FrameRef } from \"./still\";\n\nexport type { ScreenshotFormat, FrameSpec };\n\nexport type ScreenshotParams = RendererWasmOption & {\n /** A project, or a lone still or composition as the one track of one. */\n document: ProjectDocument | VisualDocument;\n /** The vocabulary its clips compile against. Defaults to what the library ships. */\n registry?: Registry;\n /** Win over the document's own; 1920×1080 at 60 fps when neither says. */\n viewport?: Size2D;\n fps?: number;\n scale?: number;\n manifest?: AssetManifest;\n /** Theme for a document that carries none of its own. */\n theme?: Theme;\n /** Which frame to capture. Defaults to the timeline's first. */\n frame?: FrameRef;\n /** Encoding format (default \"png\"). */\n format?: ScreenshotFormat;\n /** JPEG quality in [0,1] (ignored for png; default 0.92). */\n quality?: number;\n};\n\nexport type ScreenshotResult = {\n /** The project frame that was captured, after clamping. */\n frame: number;\n totalFrames: number;\n /** Encoded image bytes. */\n bytes: Uint8Array;\n};\n\n/**\n * Renders a single frame of a document to an image.\n *\n * A one-shot wrapper over {@link StillRenderer}: create, render, encode, dispose.\n * It therefore takes the same path as a live preview, and as the video export at\n * that frame — there is one renderer, not three.\n *\n * For anything that captures **repeatedly** — a thumbnail builder, a preview that\n * repaints as the user edits — hold a {@link StillRenderer} instead. This function\n * builds and tears down a WebGPU surface per call, which is the right trade for one\n * capture and the wrong one for a hundred.\n */\nexport async function exportScreenshot(params: ScreenshotParams): Promise<ScreenshotResult> {\n const {\n registry, scale, manifest, theme, rendererWasmUrl,\n frame, format = \"png\", quality = 0.92,\n } = params;\n\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\n if (project.tracks.length === 0) throw new Error(\"The project has no tracks to screenshot.\");\n\n const renderer = await createStillRenderer({\n viewport: project.viewport, fps: project.fps, scale, manifest, theme, rendererWasmUrl, registry,\n });\n\n try {\n const drawn = await renderer.render(project, { frame });\n return { ...drawn, bytes: await renderer.toBytes({ format, quality }) };\n } finally {\n renderer.dispose();\n }\n}\n"]}
@@ -0,0 +1,159 @@
1
+ import { Registry, type AssetManifest, type FrameSpec, type RenderIssue, type ProjectDocument, type VisualDocument, type Size2D, type Theme } from "@motionscript/core";
2
+ import { type DrawnFrame, type ScreenshotFormat } from "./frame-loop";
3
+ import type { BrowserRenderer } from "./engine";
4
+ import { type RendererWasmOption } from "./renderer";
5
+ /** Anything a still can be rendered from: a project, or a lone still or composition as the one track of one. */
6
+ export type StillSource = ProjectDocument | VisualDocument;
7
+ /**
8
+ * Which frame to draw, in whatever form is most convenient — a global frame
9
+ * index, the timeline's first or last frame, or a `FrameSpec`.
10
+ */
11
+ export type FrameRef = FrameSpec | number | "first" | "last";
12
+ export interface StillRendererOptions extends RendererWasmOption {
13
+ /**
14
+ * The canvas to draw into. Omit and the renderer creates its own hidden one
15
+ * and removes it on {@link StillRenderer.dispose} — which is what a one-shot
16
+ * capture wants, while a live preview passes the canvas it already shows.
17
+ */
18
+ canvas?: HTMLCanvasElement;
19
+ /** Output resolution in pixels, whatever the document asks for. Defaults to 1920×1080. */
20
+ viewport?: Size2D;
21
+ /** Frame rate for a document that sets none. Defaults to 60. */
22
+ fps?: number;
23
+ /** Device pixel ratio the surface is sized at. Defaults to 1. */
24
+ scale?: number;
25
+ manifest?: AssetManifest;
26
+ /** Theme for a document that carries none of its own. */
27
+ theme?: Theme;
28
+ /**
29
+ * The vocabulary a document is compiled against.
30
+ *
31
+ * Defaults to a fresh one carrying only what the library ships — a renderer
32
+ * owns its registry the way an engine does, so a host with custom node types
33
+ * passes the same one it gave its engine rather than relying on whatever a
34
+ * module happened to register.
35
+ */
36
+ registry?: Registry;
37
+ }
38
+ export interface RenderStillOptions {
39
+ /** Which frame to draw. Defaults to the timeline's first. */
40
+ frame?: FrameRef;
41
+ /** Seconds into the timeline, converted with the document's fps. Wins over `frame`. */
42
+ time?: number;
43
+ /**
44
+ * Draw this call at this pixel ratio instead of the renderer's. **Resizes the
45
+ * canvas and rebuilds the surface**, and the next call without it rebuilds
46
+ * them back, so keep it off the per-edit path — render the preview at its
47
+ * normal scale and pass this only when capturing at a higher one.
48
+ */
49
+ scale?: number;
50
+ /** What the renderer drew but not as the document says; a lost GPU device throws `RenderBackendError` instead. */
51
+ onIssue?: (issue: RenderIssue) => void;
52
+ }
53
+ export interface EncodeStillOptions {
54
+ /** Defaults to `"png"`. */
55
+ format?: ScreenshotFormat;
56
+ /** JPEG quality in [0,1]; ignored for png. Defaults to 0.92. */
57
+ quality?: number;
58
+ }
59
+ /** Normalize the many ways a caller can name a frame into the engine's own form. */
60
+ export declare function stillFrameSpec(options: RenderStillOptions | undefined, fps: number): FrameSpec;
61
+ /**
62
+ * A long-lived single-frame renderer.
63
+ *
64
+ * Where `exportScreenshot` is one frame from a standing start, this keeps the
65
+ * expensive things — the renderer module, its GPU surface, and the
66
+ * storage adapter's decoded-asset cache — alive across calls, so re-rendering
67
+ * after an edit costs discovering one frame and a draw rather than a new GL
68
+ * context. That is
69
+ * the difference between a one-off capture and a builder that repaints while the
70
+ * user types.
71
+ *
72
+ * It is also the *same* draw: {@link render} goes through `drawFrameAt`, which is
73
+ * the pass the video exporter runs per frame. A preview and its exported image
74
+ * cannot drift apart, because there is only one path.
75
+ *
76
+ * ```ts
77
+ * const renderer = await createStillRenderer({ canvas, viewport, manifest, theme });
78
+ * await renderer.render({ kind: "still", nodes: [
79
+ * { id: "bg", type: "rect", parentId: null, layerOrder: 0, props: { width: 1920, height: 1080, fill: "bg" } },
80
+ * ] });
81
+ * const blob = await renderer.toBlob({ format: "png" });
82
+ * renderer.dispose();
83
+ * ```
84
+ *
85
+ * **The theme is process-global in core.** {@link render} re-applies the
86
+ * document's (or this renderer's) before every draw, so two renderers with
87
+ * different themes still each draw correctly; but anything else that calls
88
+ * `setTheme` between a render and a read of the surface will have been
89
+ * overwritten by the next render.
90
+ *
91
+ * **One surface, one caller at a time.** A render and the encode of its result
92
+ * are two calls, and the next render draws over the surface between them, so
93
+ * callers that share a renderer without knowing of each other must be queued —
94
+ * as `session.image()` queues its calls.
95
+ */
96
+ export declare class StillRenderer {
97
+ readonly canvas: HTMLCanvasElement;
98
+ private renderContext;
99
+ private storageAdapter;
100
+ private catalog;
101
+ private readonly backend;
102
+ /** True when we made the canvas and must clean it up. */
103
+ private readonly ownsCanvas;
104
+ private viewportValue;
105
+ private readonly fpsValue;
106
+ private readonly scaleValue;
107
+ /** What the canvas is sized at now, which a call's own `scale` changes. */
108
+ private surfaceScale;
109
+ private manifest;
110
+ private theme?;
111
+ private readonly registry;
112
+ private disposed;
113
+ /** False until a frame has been drawn onto the current surface; a remount clears it. */
114
+ private drawn;
115
+ /** @internal Use {@link createStillRenderer}, which loads the renderer first. */
116
+ constructor(backend: BrowserRenderer, options: StillRendererOptions);
117
+ get viewport(): Size2D;
118
+ get fps(): number;
119
+ get scale(): number;
120
+ /**
121
+ * Draw one frame of `source`, at this renderer's viewport — the surface is
122
+ * the output, so it wins over any size the document asks for.
123
+ */
124
+ render(source: StillSource, options?: RenderStillOptions): Promise<DrawnFrame>;
125
+ /** Theme for a document that carries none of its own. */
126
+ setTheme(theme?: Theme): void;
127
+ /** Swap the asset manifest, keeping every decode already cached. */
128
+ setManifest(manifest: AssetManifest): void;
129
+ /**
130
+ * Change the output resolution.
131
+ *
132
+ * A full reset, unlike {@link setManifest}: the viewport clamps the resolution
133
+ * images are decoded *at*, so cached decodes made for the old one would be
134
+ * soft at the new one, and the render context's handlers captured the adapter
135
+ * when they were built — so both are rebuilt. Call this when the output format
136
+ * changes, not per edit.
137
+ */
138
+ setViewport(viewport: Size2D): void;
139
+ /** Encode the frame currently on the surface. */
140
+ toBytes(options?: EncodeStillOptions): Promise<Uint8Array>;
141
+ /** Encode the frame currently on the surface. */
142
+ toBlob(options?: EncodeStillOptions): Promise<Blob>;
143
+ /** Encode the frame currently on the surface. */
144
+ toDataURL(options?: EncodeStillOptions): string;
145
+ dispose(): void;
146
+ /**
147
+ * Read the surface back into a 2D canvas, which is what actually encodes: the
148
+ * renderers ship no image encoders, so the browser's own codecs are the only
149
+ * ones available.
150
+ */
151
+ private snapshotToCanvas;
152
+ private sizeCanvas;
153
+ /** Resize the backing store and mount a fresh surface over it. */
154
+ private remount;
155
+ private assertLive;
156
+ }
157
+ /** Create a {@link StillRenderer}; rejects with `WebGpuUnavailableError` without WebGPU. */
158
+ export declare function createStillRenderer(options?: StillRendererOptions): Promise<StillRenderer>;
159
+ //# sourceMappingURL=still.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"still.d.ts","sourceRoot":"","sources":["../src/still.ts"],"names":[],"mappings":"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"}
package/dist/still.js ADDED
@@ -0,0 +1,222 @@
1
+ import { DEFAULT_OUTPUT, ManifestAssetCatalog, frameSpecAtTime, Registry, resolveProjectDocument, } from "@motionscript/core";
2
+ import { drawFrameAt, } from "./frame-loop";
3
+ import { loadRenderer } from "./renderer";
4
+ const EMPTY_MANIFEST = { image: {}, video: {}, audio: {}, font: {} };
5
+ /** Normalize the many ways a caller can name a frame into the engine's own form. */
6
+ export function stillFrameSpec(options, fps) {
7
+ if (options?.time !== undefined)
8
+ return frameSpecAtTime(options.time, fps);
9
+ const frame = options?.frame;
10
+ if (frame === undefined || frame === "first")
11
+ return { kind: "first" };
12
+ if (frame === "last")
13
+ return { kind: "last" };
14
+ if (typeof frame === "number")
15
+ return { kind: "frame", frame };
16
+ return frame;
17
+ }
18
+ function mimeOf(format) {
19
+ return format === "jpeg" ? "image/jpeg" : "image/png";
20
+ }
21
+ /**
22
+ * A long-lived single-frame renderer.
23
+ *
24
+ * Where `exportScreenshot` is one frame from a standing start, this keeps the
25
+ * expensive things — the renderer module, its GPU surface, and the
26
+ * storage adapter's decoded-asset cache — alive across calls, so re-rendering
27
+ * after an edit costs discovering one frame and a draw rather than a new GL
28
+ * context. That is
29
+ * the difference between a one-off capture and a builder that repaints while the
30
+ * user types.
31
+ *
32
+ * It is also the *same* draw: {@link render} goes through `drawFrameAt`, which is
33
+ * the pass the video exporter runs per frame. A preview and its exported image
34
+ * cannot drift apart, because there is only one path.
35
+ *
36
+ * ```ts
37
+ * const renderer = await createStillRenderer({ canvas, viewport, manifest, theme });
38
+ * await renderer.render({ kind: "still", nodes: [
39
+ * { id: "bg", type: "rect", parentId: null, layerOrder: 0, props: { width: 1920, height: 1080, fill: "bg" } },
40
+ * ] });
41
+ * const blob = await renderer.toBlob({ format: "png" });
42
+ * renderer.dispose();
43
+ * ```
44
+ *
45
+ * **The theme is process-global in core.** {@link render} re-applies the
46
+ * document's (or this renderer's) before every draw, so two renderers with
47
+ * different themes still each draw correctly; but anything else that calls
48
+ * `setTheme` between a render and a read of the surface will have been
49
+ * overwritten by the next render.
50
+ *
51
+ * **One surface, one caller at a time.** A render and the encode of its result
52
+ * are two calls, and the next render draws over the surface between them, so
53
+ * callers that share a renderer without knowing of each other must be queued —
54
+ * as `session.image()` queues its calls.
55
+ */
56
+ export class StillRenderer {
57
+ canvas;
58
+ renderContext;
59
+ storageAdapter;
60
+ catalog;
61
+ backend;
62
+ /** True when we made the canvas and must clean it up. */
63
+ ownsCanvas;
64
+ viewportValue;
65
+ fpsValue;
66
+ scaleValue;
67
+ /** What the canvas is sized at now, which a call's own `scale` changes. */
68
+ surfaceScale;
69
+ manifest;
70
+ theme;
71
+ registry;
72
+ disposed = false;
73
+ /** False until a frame has been drawn onto the current surface; a remount clears it. */
74
+ drawn = false;
75
+ /** @internal Use {@link createStillRenderer}, which loads the renderer first. */
76
+ constructor(backend, options) {
77
+ this.backend = backend;
78
+ this.viewportValue = options.viewport ?? DEFAULT_OUTPUT.viewport;
79
+ this.fpsValue = options.fps ?? DEFAULT_OUTPUT.fps;
80
+ this.scaleValue = options.scale ?? 1;
81
+ this.surfaceScale = this.scaleValue;
82
+ this.manifest = options.manifest ?? EMPTY_MANIFEST;
83
+ this.theme = options.theme;
84
+ this.registry = options.registry ?? new Registry();
85
+ this.ownsCanvas = options.canvas === undefined;
86
+ this.canvas = options.canvas ?? document.createElement("canvas");
87
+ if (this.ownsCanvas) {
88
+ this.canvas.style.display = "none";
89
+ document.body.appendChild(this.canvas);
90
+ }
91
+ this.sizeCanvas();
92
+ this.catalog = new ManifestAssetCatalog(this.manifest);
93
+ this.storageAdapter = this.backend.createStorageAdapter({
94
+ catalog: this.catalog, viewport: this.viewportValue, fps: this.fpsValue,
95
+ });
96
+ this.renderContext = this.backend.createRenderContext(this.storageAdapter);
97
+ this.renderContext.mount(this.canvas);
98
+ }
99
+ get viewport() { return this.viewportValue; }
100
+ get fps() { return this.fpsValue; }
101
+ get scale() { return this.scaleValue; }
102
+ /**
103
+ * Draw one frame of `source`, at this renderer's viewport — the surface is
104
+ * the output, so it wins over any size the document asks for.
105
+ */
106
+ async render(source, options) {
107
+ this.assertLive();
108
+ const scale = options?.scale ?? this.scaleValue;
109
+ if (scale !== this.surfaceScale)
110
+ this.remount(scale);
111
+ await this.renderContext.whenMounted?.();
112
+ const project = resolveProjectDocument(source, { viewport: this.viewportValue }, { viewport: this.viewportValue, fps: this.fpsValue });
113
+ const frame = await drawFrameAt({
114
+ project,
115
+ registry: this.registry,
116
+ theme: this.theme,
117
+ scale,
118
+ renderContext: this.renderContext,
119
+ storageAdapter: this.storageAdapter,
120
+ assetCatalog: this.catalog,
121
+ frame: stillFrameSpec(options, project.fps),
122
+ onIssue: options?.onIssue,
123
+ });
124
+ this.drawn = true;
125
+ return frame;
126
+ }
127
+ /** Theme for a document that carries none of its own. */
128
+ setTheme(theme) { this.theme = theme; }
129
+ /** Swap the asset manifest, keeping every decode already cached. */
130
+ setManifest(manifest) {
131
+ this.assertLive();
132
+ this.manifest = manifest;
133
+ this.catalog = new ManifestAssetCatalog(manifest);
134
+ this.storageAdapter.setCatalog(this.catalog);
135
+ }
136
+ /**
137
+ * Change the output resolution.
138
+ *
139
+ * A full reset, unlike {@link setManifest}: the viewport clamps the resolution
140
+ * images are decoded *at*, so cached decodes made for the old one would be
141
+ * soft at the new one, and the render context's handlers captured the adapter
142
+ * when they were built — so both are rebuilt. Call this when the output format
143
+ * changes, not per edit.
144
+ */
145
+ setViewport(viewport) {
146
+ this.assertLive();
147
+ if (viewport.width === this.viewportValue.width && viewport.height === this.viewportValue.height) {
148
+ return;
149
+ }
150
+ this.viewportValue = viewport;
151
+ // Disposing the context disposes the adapter it holds, so this releases
152
+ // the old surface, its handlers and every decode in one call.
153
+ this.renderContext.dispose();
154
+ this.storageAdapter = this.backend.createStorageAdapter({
155
+ catalog: this.catalog, viewport, fps: this.fpsValue,
156
+ });
157
+ this.renderContext = this.backend.createRenderContext(this.storageAdapter);
158
+ this.remount(this.scaleValue);
159
+ }
160
+ /** Encode the frame currently on the surface. */
161
+ async toBytes(options) {
162
+ const blob = await this.toBlob(options);
163
+ return new Uint8Array(await blob.arrayBuffer());
164
+ }
165
+ /** Encode the frame currently on the surface. */
166
+ toBlob(options) {
167
+ const canvas = this.snapshotToCanvas();
168
+ const { format = "png", quality = 0.92 } = options ?? {};
169
+ return new Promise((resolve, reject) => {
170
+ canvas.toBlob((blob) => blob
171
+ ? resolve(blob)
172
+ : reject(new Error("Still encode produced no data.")), mimeOf(format), quality);
173
+ });
174
+ }
175
+ /** Encode the frame currently on the surface. */
176
+ toDataURL(options) {
177
+ const { format = "png", quality = 0.92 } = options ?? {};
178
+ return this.snapshotToCanvas().toDataURL(mimeOf(format), quality);
179
+ }
180
+ dispose() {
181
+ if (this.disposed)
182
+ return;
183
+ this.disposed = true;
184
+ this.renderContext.dispose();
185
+ if (this.ownsCanvas)
186
+ this.canvas.remove();
187
+ }
188
+ /**
189
+ * Read the surface back into a 2D canvas, which is what actually encodes: the
190
+ * renderers ship no image encoders, so the browser's own codecs are the only
191
+ * ones available.
192
+ */
193
+ snapshotToCanvas() {
194
+ this.assertLive();
195
+ if (!this.drawn)
196
+ throw new Error("Nothing has been rendered yet.");
197
+ const canvas = this.renderContext.snapshotCanvas?.();
198
+ if (!canvas)
199
+ throw new Error("Still encode: could not read the surface into a 2d canvas.");
200
+ return canvas;
201
+ }
202
+ sizeCanvas() {
203
+ this.canvas.width = this.viewportValue.width * this.surfaceScale;
204
+ this.canvas.height = this.viewportValue.height * this.surfaceScale;
205
+ }
206
+ /** Resize the backing store and mount a fresh surface over it. */
207
+ remount(scale) {
208
+ this.surfaceScale = scale;
209
+ this.drawn = false;
210
+ this.sizeCanvas();
211
+ this.renderContext.mount(this.canvas);
212
+ }
213
+ assertLive() {
214
+ if (this.disposed)
215
+ throw new Error("This StillRenderer has been disposed.");
216
+ }
217
+ }
218
+ /** Create a {@link StillRenderer}; rejects with `WebGpuUnavailableError` without WebGPU. */
219
+ export async function createStillRenderer(options = {}) {
220
+ return new StillRenderer(await loadRenderer(options), options);
221
+ }
222
+ //# sourceMappingURL=still.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"still.js","sourceRoot":"","sources":["../src/still.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AssetCatalog, DEFAULT_OUTPUT, ManifestAssetCatalog,\n frameSpecAtTime,\n Registry,\n resolveProjectDocument,\n type AssetManifest,\n type FrameSpec,\n type RenderIssue,\n type ProjectDocument,\n type VisualDocument,\n type Size2D,\n type StorageAdapter,\n type Theme,\n} from \"@motionscript/core\";\nimport {\n drawFrameAt,\n type DrawnFrame,\n type ScreenshotFormat,\n} from \"./frame-loop\";\nimport type { BrowserRenderContext, BrowserRenderer } from \"./engine\";\nimport { loadRenderer, type RendererWasmOption } from \"./renderer\";\n\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\n\n/** Anything a still can be rendered from: a project, or a lone still or composition as the one track of one. */\nexport type StillSource = ProjectDocument | VisualDocument;\n\n/**\n * Which frame to draw, in whatever form is most convenient — a global frame\n * index, the timeline's first or last frame, or a `FrameSpec`.\n */\nexport type FrameRef = FrameSpec | number | \"first\" | \"last\";\n\nexport interface StillRendererOptions extends RendererWasmOption {\n /**\n * The canvas to draw into. Omit and the renderer creates its own hidden one\n * and removes it on {@link StillRenderer.dispose} — which is what a one-shot\n * capture wants, while a live preview passes the canvas it already shows.\n */\n canvas?: HTMLCanvasElement;\n /** Output resolution in pixels, whatever the document asks for. Defaults to 1920×1080. */\n viewport?: Size2D;\n /** Frame rate for a document that sets none. Defaults to 60. */\n fps?: number;\n /** Device pixel ratio the surface is sized at. Defaults to 1. */\n scale?: number;\n manifest?: AssetManifest;\n /** Theme for a document that carries none of its own. */\n theme?: Theme;\n /**\n * The vocabulary a document is compiled against.\n *\n * Defaults to a fresh one carrying only what the library ships — a renderer\n * owns its registry the way an engine does, so a host with custom node types\n * passes the same one it gave its engine rather than relying on whatever a\n * module happened to register.\n */\n registry?: Registry;\n}\n\nexport interface RenderStillOptions {\n /** Which frame to draw. Defaults to the timeline's first. */\n frame?: FrameRef;\n /** Seconds into the timeline, converted with the document's fps. Wins over `frame`. */\n time?: number;\n /**\n * Draw this call at this pixel ratio instead of the renderer's. **Resizes the\n * canvas and rebuilds the surface**, and the next call without it rebuilds\n * them back, so keep it off the per-edit path — render the preview at its\n * normal scale and pass this only when capturing at a higher one.\n */\n scale?: number;\n /** What the renderer drew but not as the document says; a lost GPU device throws `RenderBackendError` instead. */\n onIssue?: (issue: RenderIssue) => void;\n}\n\nexport interface EncodeStillOptions {\n /** Defaults to `\"png\"`. */\n format?: ScreenshotFormat;\n /** JPEG quality in [0,1]; ignored for png. Defaults to 0.92. */\n quality?: number;\n}\n\n/** Normalize the many ways a caller can name a frame into the engine's own form. */\nexport function stillFrameSpec(options: RenderStillOptions | undefined, fps: number): FrameSpec {\n if (options?.time !== undefined) return frameSpecAtTime(options.time, fps);\n const frame = options?.frame;\n if (frame === undefined || frame === \"first\") return { kind: \"first\" };\n if (frame === \"last\") return { kind: \"last\" };\n if (typeof frame === \"number\") return { kind: \"frame\", frame };\n return frame;\n}\n\nfunction mimeOf(format: ScreenshotFormat): string {\n return format === \"jpeg\" ? \"image/jpeg\" : \"image/png\";\n}\n\n/**\n * A long-lived single-frame renderer.\n *\n * Where `exportScreenshot` is one frame from a standing start, this keeps the\n * expensive things — the renderer module, its GPU surface, and the\n * storage adapter's decoded-asset cache — alive across calls, so re-rendering\n * after an edit costs discovering one frame and a draw rather than a new GL\n * context. That is\n * the difference between a one-off capture and a builder that repaints while the\n * user types.\n *\n * It is also the *same* draw: {@link render} goes through `drawFrameAt`, which is\n * the pass the video exporter runs per frame. A preview and its exported image\n * cannot drift apart, because there is only one path.\n *\n * ```ts\n * const renderer = await createStillRenderer({ canvas, viewport, manifest, theme });\n * await renderer.render({ kind: \"still\", nodes: [\n * { id: \"bg\", type: \"rect\", parentId: null, layerOrder: 0, props: { width: 1920, height: 1080, fill: \"bg\" } },\n * ] });\n * const blob = await renderer.toBlob({ format: \"png\" });\n * renderer.dispose();\n * ```\n *\n * **The theme is process-global in core.** {@link render} re-applies the\n * document's (or this renderer's) before every draw, so two renderers with\n * different themes still each draw correctly; but anything else that calls\n * `setTheme` between a render and a read of the surface will have been\n * overwritten by the next render.\n *\n * **One surface, one caller at a time.** A render and the encode of its result\n * are two calls, and the next render draws over the surface between them, so\n * callers that share a renderer without knowing of each other must be queued —\n * as `session.image()` queues its calls.\n */\nexport class StillRenderer {\n readonly canvas: HTMLCanvasElement;\n\n private renderContext: BrowserRenderContext;\n private storageAdapter: StorageAdapter;\n private catalog: AssetCatalog;\n\n private readonly backend: BrowserRenderer;\n /** True when we made the canvas and must clean it up. */\n private readonly ownsCanvas: boolean;\n\n private viewportValue: Size2D;\n private readonly fpsValue: number;\n private readonly scaleValue: number;\n /** What the canvas is sized at now, which a call's own `scale` changes. */\n private surfaceScale: number;\n private manifest: AssetManifest;\n private theme?: Theme;\n private readonly registry: Registry;\n private disposed = false;\n /** False until a frame has been drawn onto the current surface; a remount clears it. */\n private drawn = false;\n\n /** @internal Use {@link createStillRenderer}, which loads the renderer first. */\n constructor(\n backend: BrowserRenderer,\n options: StillRendererOptions,\n ) {\n this.backend = backend;\n this.viewportValue = options.viewport ?? DEFAULT_OUTPUT.viewport;\n this.fpsValue = options.fps ?? DEFAULT_OUTPUT.fps;\n this.scaleValue = options.scale ?? 1;\n this.surfaceScale = this.scaleValue;\n this.manifest = options.manifest ?? EMPTY_MANIFEST;\n this.theme = options.theme;\n this.registry = options.registry ?? new Registry();\n\n this.ownsCanvas = options.canvas === undefined;\n this.canvas = options.canvas ?? document.createElement(\"canvas\");\n if (this.ownsCanvas) {\n this.canvas.style.display = \"none\";\n document.body.appendChild(this.canvas);\n }\n this.sizeCanvas();\n\n this.catalog = new ManifestAssetCatalog(this.manifest);\n this.storageAdapter = this.backend.createStorageAdapter({\n catalog: this.catalog, viewport: this.viewportValue, fps: this.fpsValue,\n });\n this.renderContext = this.backend.createRenderContext(this.storageAdapter);\n this.renderContext.mount(this.canvas);\n }\n\n get viewport(): Size2D { return this.viewportValue; }\n get fps(): number { return this.fpsValue; }\n get scale(): number { return this.scaleValue; }\n\n /**\n * Draw one frame of `source`, at this renderer's viewport — the surface is\n * the output, so it wins over any size the document asks for.\n */\n async render(source: StillSource, options?: RenderStillOptions): Promise<DrawnFrame> {\n this.assertLive();\n const scale = options?.scale ?? this.scaleValue;\n if (scale !== this.surfaceScale) this.remount(scale);\n await this.renderContext.whenMounted?.();\n\n const project = resolveProjectDocument(\n source, { viewport: this.viewportValue }, { viewport: this.viewportValue, fps: this.fpsValue },\n );\n\n const frame = await drawFrameAt({\n project,\n registry: this.registry,\n theme: this.theme,\n scale,\n renderContext: this.renderContext,\n storageAdapter: this.storageAdapter,\n assetCatalog: this.catalog,\n frame: stillFrameSpec(options, project.fps!),\n onIssue: options?.onIssue,\n });\n this.drawn = true;\n return frame;\n }\n\n /** Theme for a document that carries none of its own. */\n setTheme(theme?: Theme): void { this.theme = theme; }\n\n /** Swap the asset manifest, keeping every decode already cached. */\n setManifest(manifest: AssetManifest): void {\n this.assertLive();\n this.manifest = manifest;\n this.catalog = new ManifestAssetCatalog(manifest);\n this.storageAdapter.setCatalog(this.catalog);\n }\n\n /**\n * Change the output resolution.\n *\n * A full reset, unlike {@link setManifest}: the viewport clamps the resolution\n * images are decoded *at*, so cached decodes made for the old one would be\n * soft at the new one, and the render context's handlers captured the adapter\n * when they were built — so both are rebuilt. Call this when the output format\n * changes, not per edit.\n */\n setViewport(viewport: Size2D): void {\n this.assertLive();\n if (viewport.width === this.viewportValue.width && viewport.height === this.viewportValue.height) {\n return;\n }\n this.viewportValue = viewport;\n // Disposing the context disposes the adapter it holds, so this releases\n // the old surface, its handlers and every decode in one call.\n this.renderContext.dispose();\n this.storageAdapter = this.backend.createStorageAdapter({\n catalog: this.catalog, viewport, fps: this.fpsValue,\n });\n this.renderContext = this.backend.createRenderContext(this.storageAdapter);\n this.remount(this.scaleValue);\n }\n\n /** Encode the frame currently on the surface. */\n async toBytes(options?: EncodeStillOptions): Promise<Uint8Array> {\n const blob = await this.toBlob(options);\n return new Uint8Array(await blob.arrayBuffer());\n }\n\n /** Encode the frame currently on the surface. */\n toBlob(options?: EncodeStillOptions): Promise<Blob> {\n const canvas = this.snapshotToCanvas();\n const { format = \"png\", quality = 0.92 } = options ?? {};\n return new Promise((resolve, reject) => {\n canvas.toBlob(\n (blob) => blob\n ? resolve(blob)\n : reject(new Error(\"Still encode produced no data.\")),\n mimeOf(format),\n quality,\n );\n });\n }\n\n /** Encode the frame currently on the surface. */\n toDataURL(options?: EncodeStillOptions): string {\n const { format = \"png\", quality = 0.92 } = options ?? {};\n return this.snapshotToCanvas().toDataURL(mimeOf(format), quality);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.renderContext.dispose();\n if (this.ownsCanvas) this.canvas.remove();\n }\n\n /**\n * Read the surface back into a 2D canvas, which is what actually encodes: the\n * renderers ship no image encoders, so the browser's own codecs are the only\n * ones available.\n */\n private snapshotToCanvas(): HTMLCanvasElement {\n this.assertLive();\n if (!this.drawn) throw new Error(\"Nothing has been rendered yet.\");\n const canvas = this.renderContext.snapshotCanvas?.();\n if (!canvas) throw new Error(\"Still encode: could not read the surface into a 2d canvas.\");\n return canvas;\n }\n\n private sizeCanvas(): void {\n this.canvas.width = this.viewportValue.width * this.surfaceScale;\n this.canvas.height = this.viewportValue.height * this.surfaceScale;\n }\n\n /** Resize the backing store and mount a fresh surface over it. */\n private remount(scale: number): void {\n this.surfaceScale = scale;\n this.drawn = false;\n this.sizeCanvas();\n this.renderContext.mount(this.canvas);\n }\n\n private assertLive(): void {\n if (this.disposed) throw new Error(\"This StillRenderer has been disposed.\");\n }\n}\n\n/** Create a {@link StillRenderer}; rejects with `WebGpuUnavailableError` without WebGPU. */\nexport async function createStillRenderer(\n options: StillRendererOptions = {},\n): Promise<StillRenderer> {\n return new StillRenderer(await loadRenderer(options), options);\n}\n"]}
@@ -0,0 +1,74 @@
1
+ import { Ticker } from "@motionscript/core";
2
+ /**
3
+ * Ticker backed by a Web Audio AudioContext. Clip time follows
4
+ * `audioContext.currentTime` (within a few ms, see `startLoop`), so any
5
+ * AudioBufferSourceNodes scheduled on the same context play in sync.
6
+ *
7
+ * When paused we suspend the context (audio stops, currentTime freezes);
8
+ * when playing we resume and a rAF loop publishes ticks at the target fps.
9
+ *
10
+ * A single silent buffer covering the full timeline is started on play(),
11
+ * which keeps the context active and gives the browser a stable audio
12
+ * graph to schedule against even on scenes that contain no audio nodes.
13
+ */
14
+ export declare class WebTicker extends Ticker {
15
+ private readonly contextOf;
16
+ private readonly ownsContext;
17
+ private _context;
18
+ private fps;
19
+ private playStartCtxTime;
20
+ private seekOffset;
21
+ private speed;
22
+ private reverse;
23
+ private rafId;
24
+ /** Bumped by every `seek`, so the loop can tell that a listener or tick callback moved the playhead under it. */
25
+ private seekEpoch;
26
+ private silentSource;
27
+ /**
28
+ * `context` may be a getter, called on the first `play`: a session hands
29
+ * over its audio sink's, which is created on first use, so a session that
30
+ * never plays never opens the audio device.
31
+ */
32
+ constructor(opts: {
33
+ context?: AudioContext | (() => AudioContext);
34
+ fps: number;
35
+ });
36
+ getContext(): AudioContext;
37
+ private get context();
38
+ seek(t: number): void;
39
+ /**
40
+ * Anchors elapsed-time tracking to the current `context.currentTime`, starts the silent ticker
41
+ * + rAF tick loop and asks a suspended context to resume.
42
+ *
43
+ * Synchronous, as {@link Ticker.play} is: a `pause` or `seek` in the same turn must find a
44
+ * running clock to stop. A suspended context's `currentTime` is frozen, so the clock holds at
45
+ * the anchor until the resume lands — outside a user gesture that is never.
46
+ */
47
+ play(speed?: number, reverse?: boolean): void;
48
+ /** Stops the tick loop and silent ticker, then suspends the context — freezing `currentTime` so elapsed-time math stays correct across the pause. */
49
+ pause(): void;
50
+ /** (Re)starts the silent buffer source covering the remaining timeline — keeps the AudioContext active and gives a stable graph for other AudioBufferSourceNodes to schedule against, per the class-level note. */
51
+ private startSilentTicker;
52
+ private stopSilentTicker;
53
+ /** Where the playhead is now, from elapsed `context.currentTime` (so it stays sample-accurate), and whether that is the end of the run. */
54
+ private position;
55
+ /**
56
+ * Drives ticks off `requestAnimationFrame`, firing `tick()` when the playhead reaches another
57
+ * project frame — and once more at the end of the run, whether or not it landed on a boundary.
58
+ *
59
+ * `context.currentTime` moves in whole render quanta handed over in bursts: at 48 kHz on a
60
+ * 60 Hz display it advances 8, 10.67, 18.67 or 21.33 ms between animation frames, and a
61
+ * playhead read straight off it skipped up to a third of the frames. So the playhead advances
62
+ * by each animation frame's own interval, pulled 5% of the way to the audio clock every frame
63
+ * and snapped to it when they disagree by more than 50 ms (a stall, a seek). It stays within
64
+ * a few ms of the audio and moves one frame per frame.
65
+ *
66
+ * A listener may `seek` (a host looping wraps to 0 when it hears the end), so the end is
67
+ * re-read after the listeners and the tick callbacks have run: a run that was seeked out of
68
+ * its end carries on, and one that was not pauses itself.
69
+ */
70
+ private startLoop;
71
+ private stopLoop;
72
+ dispose(): void;
73
+ }
74
+ //# sourceMappingURL=ticker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ticker.d.ts","sourceRoot":"","sources":["../src/ticker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C;;;;;;;;;;;GAWG;AACH,qBAAa,SAAU,SAAQ,MAAM;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;IAC/C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAU;IACtC,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,GAAG,CAAS;IAEpB,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,OAAO,CAAkB;IAEjC,OAAO,CAAC,KAAK,CAAuB;IACpC,iHAAiH;IACjH,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,YAAY,CAAsC;IAE1D;;;;OAIG;gBACS,IAAI,EAAE;QAAE,OAAO,CAAC,EAAE,YAAY,GAAG,CAAC,MAAM,YAAY,CAAC,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE;IAQhF,UAAU,IAAI,YAAY;IAK1B,OAAO,KAAK,OAAO,GAElB;IAED,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IASrB;;;;;;;OAOG;IACH,IAAI,CAAC,KAAK,GAAE,MAAU,EAAE,OAAO,GAAE,OAAe,GAAG,IAAI;IAwBvD,qJAAqJ;IACrJ,KAAK,IAAI,IAAI;IAUb,mNAAmN;IACnN,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,gBAAgB;IAaxB,2IAA2I;IAC3I,OAAO,CAAC,QAAQ;IAShB;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,SAAS;IA6DjB,OAAO,CAAC,QAAQ;IAOhB,OAAO,IAAI,IAAI;CAQlB"}