@motionscript/browser 0.1.0-alpha.6 → 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.
Files changed (148) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/NOTICE +15 -14
  3. package/README.md +66 -43
  4. package/dist/asset-bytes.d.ts +7 -4
  5. package/dist/asset-bytes.d.ts.map +1 -1
  6. package/dist/asset-bytes.js +9 -4
  7. package/dist/asset-bytes.js.map +1 -1
  8. package/dist/audio/bus-graph.d.ts.map +1 -1
  9. package/dist/audio/bus-graph.js +15 -6
  10. package/dist/audio/bus-graph.js.map +1 -1
  11. package/dist/audio/effect-graph.d.ts +62 -0
  12. package/dist/audio/effect-graph.d.ts.map +1 -0
  13. package/dist/audio/effect-graph.js +234 -0
  14. package/dist/audio/effect-graph.js.map +1 -0
  15. package/dist/audio/index.d.ts.map +1 -1
  16. package/dist/audio/index.js +2 -3
  17. package/dist/audio/index.js.map +1 -1
  18. package/dist/audio/mixer.d.ts +4 -4
  19. package/dist/audio/mixer.d.ts.map +1 -1
  20. package/dist/audio/mixer.js +18 -38
  21. package/dist/audio/mixer.js.map +1 -1
  22. package/dist/audio/player.d.ts +12 -6
  23. package/dist/audio/player.d.ts.map +1 -1
  24. package/dist/audio/player.js +49 -62
  25. package/dist/audio/player.js.map +1 -1
  26. package/dist/browser/THIRD_PARTY_LICENSES.txt +379 -27
  27. package/dist/browser/audio.js +1 -1
  28. package/dist/browser/chunks/chunk-IILVN4SK.js +2 -0
  29. package/dist/browser/chunks/chunk-IILVN4SK.js.map +7 -0
  30. package/dist/browser/chunks/chunk-KLXMRKTL.js +2 -0
  31. package/dist/browser/chunks/chunk-L5UFQACH.js +163 -0
  32. package/dist/browser/chunks/chunk-L5UFQACH.js.map +7 -0
  33. package/dist/browser/chunks/mediabunny-mp3-encoder-ZCIHVVTJ.js +14 -0
  34. package/dist/browser/chunks/mediabunny-mp3-encoder-ZCIHVVTJ.js.map +7 -0
  35. package/dist/browser/index.js +51 -1343
  36. package/dist/browser/index.js.map +4 -4
  37. package/dist/browser/manifest.json +8 -8
  38. package/dist/browser/ms_renderer_bg.wasm +0 -0
  39. package/dist/canvaskit-renderer.d.ts +8 -0
  40. package/dist/canvaskit-renderer.d.ts.map +1 -0
  41. package/dist/canvaskit-renderer.js +23 -0
  42. package/dist/canvaskit-renderer.js.map +1 -0
  43. package/dist/concat.d.ts +33 -7
  44. package/dist/concat.d.ts.map +1 -1
  45. package/dist/concat.js +117 -72
  46. package/dist/concat.js.map +1 -1
  47. package/dist/deliver.d.ts +10 -0
  48. package/dist/deliver.d.ts.map +1 -0
  49. package/dist/deliver.js +22 -0
  50. package/dist/deliver.js.map +1 -0
  51. package/dist/dynamic-nodes.d.ts.map +1 -1
  52. package/dist/dynamic-nodes.js +2 -3
  53. package/dist/dynamic-nodes.js.map +1 -1
  54. package/dist/engine.d.ts +37 -9
  55. package/dist/engine.d.ts.map +1 -1
  56. package/dist/engine.js +41 -32
  57. package/dist/engine.js.map +1 -1
  58. package/dist/export-audio.d.ts +36 -0
  59. package/dist/export-audio.d.ts.map +1 -0
  60. package/dist/export-audio.js +56 -0
  61. package/dist/export-audio.js.map +1 -0
  62. package/dist/exporter.d.ts +125 -14
  63. package/dist/exporter.d.ts.map +1 -1
  64. package/dist/exporter.js +133 -89
  65. package/dist/exporter.js.map +1 -1
  66. package/dist/formats.d.ts +36 -0
  67. package/dist/formats.d.ts.map +1 -0
  68. package/dist/formats.js +83 -0
  69. package/dist/formats.js.map +1 -0
  70. package/dist/frame-loop.d.ts +190 -0
  71. package/dist/frame-loop.d.ts.map +1 -0
  72. package/dist/frame-loop.js +304 -0
  73. package/dist/frame-loop.js.map +1 -0
  74. package/dist/getter.d.ts +4 -1
  75. package/dist/getter.d.ts.map +1 -1
  76. package/dist/getter.js +7 -6
  77. package/dist/getter.js.map +1 -1
  78. package/dist/index.d.ts +12 -11
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +17 -21
  81. package/dist/index.js.map +1 -1
  82. package/dist/mix-timeline-audio.d.ts +5 -5
  83. package/dist/mix-timeline-audio.d.ts.map +1 -1
  84. package/dist/mix-timeline-audio.js +10 -12
  85. package/dist/mix-timeline-audio.js.map +1 -1
  86. package/dist/parallel/export-parallel.d.ts +32 -0
  87. package/dist/parallel/export-parallel.d.ts.map +1 -0
  88. package/dist/parallel/export-parallel.js +114 -0
  89. package/dist/parallel/export-parallel.js.map +1 -0
  90. package/dist/parallel/governor.d.ts +57 -0
  91. package/dist/parallel/governor.d.ts.map +1 -0
  92. package/dist/parallel/governor.js +79 -0
  93. package/dist/parallel/governor.js.map +1 -0
  94. package/dist/parallel/lane.d.ts +45 -0
  95. package/dist/parallel/lane.d.ts.map +1 -0
  96. package/dist/parallel/lane.js +2 -0
  97. package/dist/parallel/lane.js.map +1 -0
  98. package/dist/parallel/lanes.d.ts +20 -0
  99. package/dist/parallel/lanes.d.ts.map +1 -0
  100. package/dist/parallel/lanes.js +152 -0
  101. package/dist/parallel/lanes.js.map +1 -0
  102. package/dist/parallel/plan.d.ts +23 -0
  103. package/dist/parallel/plan.d.ts.map +1 -0
  104. package/dist/parallel/plan.js +30 -0
  105. package/dist/parallel/plan.js.map +1 -0
  106. package/dist/parallel/run-lanes.d.ts +38 -0
  107. package/dist/parallel/run-lanes.d.ts.map +1 -0
  108. package/dist/parallel/run-lanes.js +164 -0
  109. package/dist/parallel/run-lanes.js.map +1 -0
  110. package/dist/parallel/session.d.ts +9 -0
  111. package/dist/parallel/session.d.ts.map +1 -0
  112. package/dist/parallel/session.js +79 -0
  113. package/dist/parallel/session.js.map +1 -0
  114. package/dist/render-context.d.ts +2 -0
  115. package/dist/render-context.d.ts.map +1 -1
  116. package/dist/render-context.js +8 -4
  117. package/dist/render-context.js.map +1 -1
  118. package/dist/renderer.d.ts +22 -0
  119. package/dist/renderer.d.ts.map +1 -0
  120. package/dist/renderer.js +23 -0
  121. package/dist/renderer.js.map +1 -0
  122. package/dist/screenshot.d.ts +4 -4
  123. package/dist/screenshot.d.ts.map +1 -1
  124. package/dist/screenshot.js +3 -3
  125. package/dist/screenshot.js.map +1 -1
  126. package/dist/still.d.ts +17 -19
  127. package/dist/still.d.ts.map +1 -1
  128. package/dist/still.js +27 -34
  129. package/dist/still.js.map +1 -1
  130. package/dist/ticker.d.ts +22 -8
  131. package/dist/ticker.d.ts.map +1 -1
  132. package/dist/ticker.js +55 -20
  133. package/dist/ticker.js.map +1 -1
  134. package/package.json +9 -10
  135. package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js +0 -2
  136. package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js.map +0 -7
  137. package/dist/browser/chunks/SkeletonUtils-XYDMTGTM.js +0 -2
  138. package/dist/browser/chunks/chunk-2MAFJWBZ.js +0 -2
  139. package/dist/browser/chunks/chunk-2MAFJWBZ.js.map +0 -7
  140. package/dist/browser/chunks/chunk-CTLBXZSU.js +0 -4119
  141. package/dist/browser/chunks/chunk-CTLBXZSU.js.map +0 -7
  142. package/dist/browser/chunks/chunk-FQ32PDIU.js +0 -2
  143. package/dist/browser/chunks/chunk-FQ32PDIU.js.map +0 -7
  144. package/dist/browser/chunks/chunk-FY3BTKY4.js +0 -2
  145. package/dist/browser/chunks/chunk-FY3BTKY4.js.map +0 -7
  146. package/dist/browser/chunks/three.module-Z4EOVUWF.js +0 -2
  147. package/dist/browser/chunks/three.module-Z4EOVUWF.js.map +0 -7
  148. /package/dist/browser/chunks/{SkeletonUtils-XYDMTGTM.js.map → chunk-KLXMRKTL.js.map} +0 -0
@@ -37,6 +37,13 @@ export class WebRenderContext extends SkiaRenderContext {
37
37
  * for other formats.
38
38
  */
39
39
  screenshot(mime = "image/png", quality) {
40
+ // A data: URL rather than a blob: URL — both work as an <img> src and a
41
+ // data URL needs no revoke. `quality` is honored only by lossy formats
42
+ // (e.g. image/jpeg); the browser ignores it for image/png.
43
+ return this.snapshotCanvas()?.toDataURL(mime, quality);
44
+ }
45
+ /** The surface read back into a fresh 2D canvas, or `undefined` when unmounted or no 2D context could be made. */
46
+ snapshotCanvas() {
40
47
  const snapshot = this.snapshotPixels();
41
48
  if (!snapshot)
42
49
  return undefined;
@@ -50,10 +57,7 @@ export class WebRenderContext extends SkiaRenderContext {
50
57
  // `pixels` is already unpremultiplied RGBA8888 copied out of the wasm heap,
51
58
  // which is exactly ImageData's layout.
52
59
  ctx.putImageData(new ImageData(new Uint8ClampedArray(pixels), width, height), 0, 0);
53
- // A data: URL rather than a blob: URL — both work as an <img> src and a
54
- // data URL needs no revoke. `quality` is honored only by lossy formats
55
- // (e.g. image/jpeg); the browser ignores it for image/png.
56
- return canvas.toDataURL(mime, quality);
60
+ return canvas;
57
61
  }
58
62
  }
59
63
  //# sourceMappingURL=render-context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"render-context.js","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAE7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,gBAAiB,SAAQ,iBAAiB;IACnD,YAAY,SAAoB,EAAE,cAAiC;QAC/D,KAAK,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;QACjC,kBAAkB,EAAE,CAAC;IACzB,CAAC;IAED,sGAAsG;IACtG,KAAK,CAAC,MAAyB;QAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC;QAC9D,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACpE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAED;;;OAGG;IACM,OAAO;QACZ,IAAI,CAAC,MAAM,EAAE,CAAC;IAClB,CAAC;IAED;;;;OAIG;IACM,UAAU,CAAC,OAAe,WAAW,EAAE,OAAgB;QAC5D,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACvC,IAAI,CAAC,QAAQ;YAAE,OAAO,SAAS,CAAC;QAChC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC;QAE3C,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QACrB,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;QACvB,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAC;QAC3B,4EAA4E;QAC5E,uCAAuC;QACvC,GAAG,CAAC,YAAY,CAAC,IAAI,SAAS,CAAC,IAAI,iBAAiB,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACpF,wEAAwE;QACxE,uEAAuE;QACvE,2DAA2D;QAC3D,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;CACJ","sourcesContent":["import type { CanvasKit } from \"@motionscript/canvaskit\";\r\nimport { SkiaRenderContext } from \"@motionscript/skia-render/render-context\";\r\nimport type { WebStorageAdapter } from \"./storage-adapter\";\r\nimport { installWebCanvas3D } from \"./three/renderer\";\r\n\r\n/**\r\n * Browser bindings for {@link SkiaRenderContext}.\r\n *\r\n * Everything about *how a frame is drawn* — shapes, fills, strokes, text, effects,\r\n * clips, masks, the camera, the 3D composite — lives in\r\n * `@motionscript/skia-render` and is shared with every other backend. What is\r\n * genuinely browser-specific is only what is below: the WebGL renderer 3D draws\r\n * with (installed on construction, so every path that draws has it), creating a\r\n * WebGL-backed Skia surface over a `<canvas>`, and encoding a snapshot (this\r\n * CanvasKit build ships no wasm image encoders, so the encode has to come from\r\n * the host).\r\n */\r\nexport class WebRenderContext extends SkiaRenderContext {\r\n constructor(canvasKit: CanvasKit, storageAdapter: WebStorageAdapter) {\r\n super(canvasKit, storageAdapter);\r\n installWebCanvas3D();\r\n }\r\n\r\n /** Attaches a WebGL CanvasKit surface to `canvas`, remounting if already mounted (HMR/StrictMode). */\r\n mount(canvas: HTMLCanvasElement): void {\r\n const surface = this.canvasKit.MakeWebGLCanvasSurface(canvas);\r\n if (!surface) throw new Error(\"Failed to create CanvasKit surface\");\r\n this.attach(surface);\r\n }\r\n\r\n /**\r\n * Core declares `unmount()`; it is `detach()` on the portable base, since\r\n * \"unmount\" only means anything where there is a DOM element to unmount from.\r\n */\r\n override unmount(): void {\r\n this.detach();\r\n }\r\n\r\n /**\r\n * Snapshots the current surface and encodes it as an image data URL through a\r\n * 2D canvas. Defaults to PNG; pass `mime`/`quality` (e.g. `\"image/jpeg\", 0.9`)\r\n * for other formats.\r\n */\r\n override screenshot(mime: string = \"image/png\", quality?: number): string | undefined {\r\n const snapshot = this.snapshotPixels();\r\n if (!snapshot) return undefined;\r\n const { pixels, width, height } = snapshot;\r\n\r\n const canvas = document.createElement(\"canvas\");\r\n canvas.width = width;\r\n canvas.height = height;\r\n const ctx = canvas.getContext(\"2d\");\r\n if (!ctx) return undefined;\r\n // `pixels` is already unpremultiplied RGBA8888 copied out of the wasm heap,\r\n // which is exactly ImageData's layout.\r\n ctx.putImageData(new ImageData(new Uint8ClampedArray(pixels), width, height), 0, 0);\r\n // A data: URL rather than a blob: URL — both work as an <img> src and a\r\n // data URL needs no revoke. `quality` is honored only by lossy formats\r\n // (e.g. image/jpeg); the browser ignores it for image/png.\r\n return canvas.toDataURL(mime, quality);\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"render-context.js","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAE7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,gBAAiB,SAAQ,iBAAiB;IACnD,YAAY,SAAoB,EAAE,cAAiC;QAC/D,KAAK,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;QACjC,kBAAkB,EAAE,CAAC;IACzB,CAAC;IAED,sGAAsG;IACtG,KAAK,CAAC,MAAyB;QAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC;QAC9D,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACpE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAED;;;OAGG;IACM,OAAO;QACZ,IAAI,CAAC,MAAM,EAAE,CAAC;IAClB,CAAC;IAED;;;;OAIG;IACM,UAAU,CAAC,OAAe,WAAW,EAAE,OAAgB;QAC5D,wEAAwE;QACxE,uEAAuE;QACvE,2DAA2D;QAC3D,OAAO,IAAI,CAAC,cAAc,EAAE,EAAE,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC3D,CAAC;IAED,kHAAkH;IAClH,cAAc;QACV,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACvC,IAAI,CAAC,QAAQ;YAAE,OAAO,SAAS,CAAC;QAChC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC;QAE3C,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QACrB,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;QACvB,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAC;QAC3B,4EAA4E;QAC5E,uCAAuC;QACvC,GAAG,CAAC,YAAY,CAAC,IAAI,SAAS,CAAC,IAAI,iBAAiB,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACpF,OAAO,MAAM,CAAC;IAClB,CAAC;CACJ","sourcesContent":["import type { CanvasKit } from \"@motionscript/canvaskit\";\r\nimport { SkiaRenderContext } from \"@motionscript/skia-render/render-context\";\r\nimport type { WebStorageAdapter } from \"./storage-adapter\";\r\nimport { installWebCanvas3D } from \"./three/renderer\";\r\n\r\n/**\r\n * Browser bindings for {@link SkiaRenderContext}.\r\n *\r\n * Everything about *how a frame is drawn* — shapes, fills, strokes, text, effects,\r\n * clips, masks, the camera, the 3D composite — lives in\r\n * `@motionscript/skia-render` and is shared with every other backend. What is\r\n * genuinely browser-specific is only what is below: the WebGL renderer 3D draws\r\n * with (installed on construction, so every path that draws has it), creating a\r\n * WebGL-backed Skia surface over a `<canvas>`, and encoding a snapshot (this\r\n * CanvasKit build ships no wasm image encoders, so the encode has to come from\r\n * the host).\r\n */\r\nexport class WebRenderContext extends SkiaRenderContext {\r\n constructor(canvasKit: CanvasKit, storageAdapter: WebStorageAdapter) {\r\n super(canvasKit, storageAdapter);\r\n installWebCanvas3D();\r\n }\r\n\r\n /** Attaches a WebGL CanvasKit surface to `canvas`, remounting if already mounted (HMR/StrictMode). */\r\n mount(canvas: HTMLCanvasElement): void {\r\n const surface = this.canvasKit.MakeWebGLCanvasSurface(canvas);\r\n if (!surface) throw new Error(\"Failed to create CanvasKit surface\");\r\n this.attach(surface);\r\n }\r\n\r\n /**\r\n * Core declares `unmount()`; it is `detach()` on the portable base, since\r\n * \"unmount\" only means anything where there is a DOM element to unmount from.\r\n */\r\n override unmount(): void {\r\n this.detach();\r\n }\r\n\r\n /**\r\n * Snapshots the current surface and encodes it as an image data URL through a\r\n * 2D canvas. Defaults to PNG; pass `mime`/`quality` (e.g. `\"image/jpeg\", 0.9`)\r\n * for other formats.\r\n */\r\n override screenshot(mime: string = \"image/png\", quality?: number): string | undefined {\r\n // A data: URL rather than a blob: URL — both work as an <img> src and a\r\n // data URL needs no revoke. `quality` is honored only by lossy formats\r\n // (e.g. image/jpeg); the browser ignores it for image/png.\r\n return this.snapshotCanvas()?.toDataURL(mime, quality);\r\n }\r\n\r\n /** The surface read back into a fresh 2D canvas, or `undefined` when unmounted or no 2D context could be made. */\r\n snapshotCanvas(): HTMLCanvasElement | undefined {\r\n const snapshot = this.snapshotPixels();\r\n if (!snapshot) return undefined;\r\n const { pixels, width, height } = snapshot;\r\n\r\n const canvas = document.createElement(\"canvas\");\r\n canvas.width = width;\r\n canvas.height = height;\r\n const ctx = canvas.getContext(\"2d\");\r\n if (!ctx) return undefined;\r\n // `pixels` is already unpremultiplied RGBA8888 copied out of the wasm heap,\r\n // which is exactly ImageData's layout.\r\n ctx.putImageData(new ImageData(new Uint8ClampedArray(pixels), width, height), 0, 0);\r\n return canvas;\r\n }\r\n}\r\n"]}
@@ -0,0 +1,22 @@
1
+ import { type WgpuRenderer } from "@motionscript/renderer";
2
+ /** Where the renderer's WebAssembly is served, for every entry that draws. */
3
+ export interface RendererWasmOption {
4
+ /** URL of `ms_renderer_bg.wasm`; defaults to beside `@motionscript/renderer`'s module, which a bundler resolves. */
5
+ rendererWasmUrl?: string | URL;
6
+ }
7
+ /**
8
+ * This browser cannot run the WebGPU renderer: no `navigator.gpu`, or no
9
+ * adapter behind it. Every entry that draws (an engine, a still, an export,
10
+ * a timeline's audio mix) rejects with it before drawing anything.
11
+ */
12
+ export declare class WebGpuUnavailableError extends Error {
13
+ constructor();
14
+ }
15
+ /**
16
+ * The renderer every entry draws with, or {@link WebGpuUnavailableError}.
17
+ * `gpu: false` is for an entry that only measures text, which needs no device.
18
+ */
19
+ export declare function loadRenderer(options?: RendererWasmOption, { gpu }?: {
20
+ gpu?: boolean | undefined;
21
+ }): Promise<WgpuRenderer>;
22
+ //# sourceMappingURL=renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuC,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAGhG,8EAA8E;AAC9E,MAAM,WAAW,kBAAkB;IAC/B,oHAAoH;IACpH,eAAe,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;CAClC;AAED;;;;GAIG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;;CAKhD;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,OAAO,GAAE,kBAAuB,EAAE,EAAE,GAAU,EAAE;;CAAK,GAAG,OAAO,CAAC,YAAY,CAAC,CAG/G"}
@@ -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\";\r\nimport { loadAssetBytes } from \"./asset-bytes\";\r\n\r\n/** Where the renderer's WebAssembly is served, for every entry that draws. */\r\nexport interface RendererWasmOption {\r\n /** URL of `ms_renderer_bg.wasm`; defaults to beside `@motionscript/renderer`'s module, which a bundler resolves. */\r\n rendererWasmUrl?: string | URL;\r\n}\r\n\r\n/**\r\n * This browser cannot run the WebGPU renderer: no `navigator.gpu`, or no\r\n * adapter behind it. Every entry that draws (an engine, a still, an export,\r\n * a timeline's audio mix) rejects with it before drawing anything.\r\n */\r\nexport class WebGpuUnavailableError extends Error {\r\n constructor() {\r\n super(\"Motion Script draws with WebGPU, and this browser has no WebGPU adapter.\");\r\n this.name = \"WebGpuUnavailableError\";\r\n }\r\n}\r\n\r\n/**\r\n * The renderer every entry draws with, or {@link WebGpuUnavailableError}.\r\n * `gpu: false` is for an entry that only measures text, which needs no device.\r\n */\r\nexport async function loadRenderer(options: RendererWasmOption = {}, { gpu = true } = {}): Promise<WgpuRenderer> {\r\n if (gpu && !(await isWgpuSupported())) throw new WebGpuUnavailableError();\r\n return createWgpuRenderer({ wasmUrl: options.rendererWasmUrl, loadBytes: loadAssetBytes });\r\n}\r\n"]}
@@ -1,8 +1,9 @@
1
1
  import { type AssetManifest, type FrameSpec, type ProjectDocument, type Registry, type VisualDocument, type Size2D, type Theme } from "@motionscript/core";
2
- import type { ScreenshotFormat } from "@motionscript/skia-render/export";
2
+ import type { ScreenshotFormat } from "./frame-loop";
3
+ import type { RendererWasmOption } from "./renderer";
3
4
  import { type FrameRef } from "./still";
4
5
  export type { ScreenshotFormat, FrameSpec };
5
- export type ScreenshotParams = {
6
+ export type ScreenshotParams = RendererWasmOption & {
6
7
  /** A project, or a lone still or composition as the one track of one. */
7
8
  document: ProjectDocument | VisualDocument;
8
9
  /** The vocabulary its clips compile against. Defaults to what the library ships. */
@@ -14,7 +15,6 @@ export type ScreenshotParams = {
14
15
  manifest?: AssetManifest;
15
16
  /** Theme for a document that carries none of its own. */
16
17
  theme?: Theme;
17
- wasmUrl?: string;
18
18
  /** Which frame to capture. Defaults to the timeline's first. */
19
19
  frame?: FrameRef;
20
20
  /** Encoding format (default "png"). */
@@ -38,7 +38,7 @@ export type ScreenshotResult = {
38
38
  *
39
39
  * For anything that captures **repeatedly** — a thumbnail builder, a preview that
40
40
  * repaints as the user edits — hold a {@link StillRenderer} instead. This function
41
- * builds and tears down a WebGL surface per call, which is the right trade for one
41
+ * builds and tears down a WebGPU surface per call, which is the right trade for one
42
42
  * capture and the wrong one for a hundred.
43
43
  */
44
44
  export declare function exportScreenshot(params: ScreenshotParams): Promise<ScreenshotResult>;
@@ -1 +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,kCAAkC,CAAC;AACzE,OAAO,EAAuB,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE7D,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC;AAE5C,MAAM,MAAM,gBAAgB,GAAG;IAC3B,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,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,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"}
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"}
@@ -9,16 +9,16 @@ import { createStillRenderer } from "./still";
9
9
  *
10
10
  * For anything that captures **repeatedly** — a thumbnail builder, a preview that
11
11
  * repaints as the user edits — hold a {@link StillRenderer} instead. This function
12
- * builds and tears down a WebGL surface per call, which is the right trade for one
12
+ * builds and tears down a WebGPU surface per call, which is the right trade for one
13
13
  * capture and the wrong one for a hundred.
14
14
  */
15
15
  export async function exportScreenshot(params) {
16
- const { registry, scale, manifest, theme, wasmUrl, frame, format = "png", quality = 0.92, } = params;
16
+ const { registry, scale, manifest, theme, rendererWasmUrl, frame, format = "png", quality = 0.92, } = params;
17
17
  const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);
18
18
  if (project.tracks.length === 0)
19
19
  throw new Error("The project has no tracks to screenshot.");
20
20
  const renderer = await createStillRenderer({
21
- viewport: project.viewport, fps: project.fps, scale, manifest, theme, wasmUrl, registry,
21
+ viewport: project.viewport, fps: project.fps, scale, manifest, theme, rendererWasmUrl, registry,
22
22
  });
23
23
  try {
24
24
  const drawn = await renderer.render(project, { frame });
@@ -1 +1 @@
1
- {"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,cAAc,EAAE,sBAAsB,GAEzC,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,mBAAmB,EAAiB,MAAM,SAAS,CAAC;AAiC7D;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAwB;IAC3D,MAAM,EACF,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EACzC,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,OAAO,EAAE,QAAQ;KAC1F,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 {\r\n DEFAULT_OUTPUT, resolveProjectDocument,\r\n type AssetManifest, type FrameSpec, type ProjectDocument, type Registry, type VisualDocument, type Size2D, type Theme,\r\n} from \"@motionscript/core\";\r\nimport type { ScreenshotFormat } from \"@motionscript/skia-render/export\";\r\nimport { createStillRenderer, type FrameRef } from \"./still\";\r\n\r\nexport type { ScreenshotFormat, FrameSpec };\r\n\r\nexport type ScreenshotParams = {\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 scale?: number;\r\n manifest?: AssetManifest;\r\n /** Theme for a document that carries none of its own. */\r\n theme?: Theme;\r\n wasmUrl?: string;\r\n /** Which frame to capture. Defaults to the timeline's first. */\r\n frame?: FrameRef;\r\n /** Encoding format (default \"png\"). */\r\n format?: ScreenshotFormat;\r\n /** JPEG quality in [0,1] (ignored for png; default 0.92). */\r\n quality?: number;\r\n};\r\n\r\nexport type ScreenshotResult = {\r\n /** The project frame that was captured, after clamping. */\r\n frame: number;\r\n totalFrames: number;\r\n /** Encoded image bytes. */\r\n bytes: Uint8Array;\r\n};\r\n\r\n/**\r\n * Renders a single frame of a document to an image.\r\n *\r\n * A one-shot wrapper over {@link StillRenderer}: create, render, encode, dispose.\r\n * It therefore takes the same path as a live preview, and as the video export at\r\n * that frame — there is one renderer, not three.\r\n *\r\n * For anything that captures **repeatedly** — a thumbnail builder, a preview that\r\n * repaints as the user edits — hold a {@link StillRenderer} instead. This function\r\n * builds and tears down a WebGL surface per call, which is the right trade for one\r\n * capture and the wrong one for a hundred.\r\n */\r\nexport async function exportScreenshot(params: ScreenshotParams): Promise<ScreenshotResult> {\r\n const {\r\n registry, scale, manifest, theme, wasmUrl,\r\n frame, format = \"png\", quality = 0.92,\r\n } = params;\r\n\r\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\r\n if (project.tracks.length === 0) throw new Error(\"The project has no tracks to screenshot.\");\r\n\r\n const renderer = await createStillRenderer({\r\n viewport: project.viewport, fps: project.fps, scale, manifest, theme, wasmUrl, registry,\r\n });\r\n\r\n try {\r\n const drawn = await renderer.render(project, { frame });\r\n return { ...drawn, bytes: await renderer.toBytes({ format, quality }) };\r\n } finally {\r\n renderer.dispose();\r\n }\r\n}\r\n"]}
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 {\r\n DEFAULT_OUTPUT, resolveProjectDocument,\r\n type AssetManifest, type FrameSpec, type ProjectDocument, type Registry, type VisualDocument, type Size2D, type Theme,\r\n} from \"@motionscript/core\";\r\nimport type { ScreenshotFormat } from \"./frame-loop\";\r\nimport type { RendererWasmOption } from \"./renderer\";\r\nimport { createStillRenderer, type FrameRef } from \"./still\";\r\n\r\nexport type { ScreenshotFormat, FrameSpec };\r\n\r\nexport type ScreenshotParams = 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 scale?: number;\r\n manifest?: AssetManifest;\r\n /** Theme for a document that carries none of its own. */\r\n theme?: Theme;\r\n /** Which frame to capture. Defaults to the timeline's first. */\r\n frame?: FrameRef;\r\n /** Encoding format (default \"png\"). */\r\n format?: ScreenshotFormat;\r\n /** JPEG quality in [0,1] (ignored for png; default 0.92). */\r\n quality?: number;\r\n};\r\n\r\nexport type ScreenshotResult = {\r\n /** The project frame that was captured, after clamping. */\r\n frame: number;\r\n totalFrames: number;\r\n /** Encoded image bytes. */\r\n bytes: Uint8Array;\r\n};\r\n\r\n/**\r\n * Renders a single frame of a document to an image.\r\n *\r\n * A one-shot wrapper over {@link StillRenderer}: create, render, encode, dispose.\r\n * It therefore takes the same path as a live preview, and as the video export at\r\n * that frame — there is one renderer, not three.\r\n *\r\n * For anything that captures **repeatedly** — a thumbnail builder, a preview that\r\n * repaints as the user edits — hold a {@link StillRenderer} instead. This function\r\n * builds and tears down a WebGPU surface per call, which is the right trade for one\r\n * capture and the wrong one for a hundred.\r\n */\r\nexport async function exportScreenshot(params: ScreenshotParams): Promise<ScreenshotResult> {\r\n const {\r\n registry, scale, manifest, theme, rendererWasmUrl,\r\n frame, format = \"png\", quality = 0.92,\r\n } = params;\r\n\r\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\r\n if (project.tracks.length === 0) throw new Error(\"The project has no tracks to screenshot.\");\r\n\r\n const renderer = await createStillRenderer({\r\n viewport: project.viewport, fps: project.fps, scale, manifest, theme, rendererWasmUrl, registry,\r\n });\r\n\r\n try {\r\n const drawn = await renderer.render(project, { frame });\r\n return { ...drawn, bytes: await renderer.toBytes({ format, quality }) };\r\n } finally {\r\n renderer.dispose();\r\n }\r\n}\r\n"]}
package/dist/still.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { Registry, type AssetManifest, type FrameSpec, type ProjectDocument, type VisualDocument, type Size2D, type Theme } from "@motionscript/core";
2
- import { type DrawnFrame, type ScreenshotFormat } from "@motionscript/skia-render/export";
3
- import { getCanvasKit } from "./getter";
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";
4
5
  /** Anything a still can be rendered from: a project, or a lone still or composition as the one track of one. */
5
6
  export type StillSource = ProjectDocument | VisualDocument;
6
7
  /**
@@ -8,7 +9,7 @@ export type StillSource = ProjectDocument | VisualDocument;
8
9
  * index, the timeline's first or last frame, or a `FrameSpec`.
9
10
  */
10
11
  export type FrameRef = FrameSpec | number | "first" | "last";
11
- export interface StillRendererOptions {
12
+ export interface StillRendererOptions extends RendererWasmOption {
12
13
  /**
13
14
  * The canvas to draw into. Omit and the renderer creates its own hidden one
14
15
  * and removes it on {@link StillRenderer.dispose} — which is what a one-shot
@@ -24,7 +25,6 @@ export interface StillRendererOptions {
24
25
  manifest?: AssetManifest;
25
26
  /** Theme for a document that carries none of its own. */
26
27
  theme?: Theme;
27
- wasmUrl?: string;
28
28
  /**
29
29
  * The vocabulary a document is compiled against.
30
30
  *
@@ -42,11 +42,13 @@ export interface RenderStillOptions {
42
42
  time?: number;
43
43
  /**
44
44
  * Draw this call at this pixel ratio instead of the renderer's. **Resizes the
45
- * canvas and rebuilds the Skia surface**, and the next call without it rebuilds
45
+ * canvas and rebuilds the surface**, and the next call without it rebuilds
46
46
  * them back, so keep it off the per-edit path — render the preview at its
47
47
  * normal scale and pass this only when capturing at a higher one.
48
48
  */
49
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;
50
52
  }
51
53
  export interface EncodeStillOptions {
52
54
  /** Defaults to `"png"`. */
@@ -60,7 +62,7 @@ export declare function stillFrameSpec(options: RenderStillOptions | undefined,
60
62
  * A long-lived single-frame renderer.
61
63
  *
62
64
  * Where `exportScreenshot` is one frame from a standing start, this keeps the
63
- * expensive things — the CanvasKit module, the WebGL Skia surface, and the
65
+ * expensive things — the renderer module, its GPU surface, and the
64
66
  * storage adapter's decoded-asset cache — alive across calls, so re-rendering
65
67
  * after an edit costs discovering one frame and a draw rather than a new GL
66
68
  * context. That is
@@ -96,7 +98,7 @@ export declare class StillRenderer {
96
98
  private renderContext;
97
99
  private storageAdapter;
98
100
  private catalog;
99
- private readonly canvasKit;
101
+ private readonly backend;
100
102
  /** True when we made the canvas and must clean it up. */
101
103
  private readonly ownsCanvas;
102
104
  private viewportValue;
@@ -110,8 +112,8 @@ export declare class StillRenderer {
110
112
  private disposed;
111
113
  /** False until a frame has been drawn onto the current surface; a remount clears it. */
112
114
  private drawn;
113
- /** @internal Use {@link createStillRenderer}, which loads CanvasKit first. */
114
- constructor(canvasKit: Awaited<ReturnType<typeof getCanvasKit>>, options: StillRendererOptions);
115
+ /** @internal Use {@link createStillRenderer}, which loads the renderer first. */
116
+ constructor(backend: BrowserRenderer, options: StillRendererOptions);
115
117
  get viewport(): Size2D;
116
118
  get fps(): number;
117
119
  get scale(): number;
@@ -142,20 +144,16 @@ export declare class StillRenderer {
142
144
  toDataURL(options?: EncodeStillOptions): string;
143
145
  dispose(): void;
144
146
  /**
145
- * Read the surface back into a 2D canvas, which is what actually encodes: this
146
- * CanvasKit build ships no wasm image encoders, so the browser's own codecs are
147
- * the only ones available.
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.
148
150
  */
149
151
  private snapshotToCanvas;
150
152
  private sizeCanvas;
151
- /** Resize the backing store and build a fresh Skia surface over it. */
153
+ /** Resize the backing store and mount a fresh surface over it. */
152
154
  private remount;
153
155
  private assertLive;
154
156
  }
155
- /**
156
- * Create a {@link StillRenderer}, loading CanvasKit if it isn't already.
157
- *
158
- * `getCanvasKit` memoizes, so a second renderer in the same page is cheap.
159
- */
157
+ /** Create a {@link StillRenderer}; rejects with `WebGpuUnavailableError` without WebGPU. */
160
158
  export declare function createStillRenderer(options?: StillRendererOptions): Promise<StillRenderer>;
161
159
  //# sourceMappingURL=still.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"still.d.ts","sourceRoot":"","sources":["../src/still.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,QAAQ,EAER,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,MAAM,EACX,KAAK,KAAK,EACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEH,KAAK,UAAU,EACf,KAAK,gBAAgB,EACxB,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAIxC,gHAAgH;AAChH,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG,cAAc,CAAC;AAE3D;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7D,MAAM,WAAW,oBAAoB;IACjC;;;;OAIG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,yDAAyD;IACzD,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IAC/B,6DAA6D;IAC7D,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,uFAAuF;IACvF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IAC/B,2BAA2B;IAC3B,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,OAAO,EAAE,kBAAkB,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAO9F;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,qBAAa,aAAa;IACtB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IAEnC,OAAO,CAAC,aAAa,CAAmB;IACxC,OAAO,CAAC,cAAc,CAAoB;IAC1C,OAAO,CAAC,OAAO,CAAe;IAE9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2C;IACrE,yDAAyD;IACzD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;IAErC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,2EAA2E;IAC3E,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,KAAK,CAAC,CAAQ;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IACpC,OAAO,CAAC,QAAQ,CAAS;IACzB,wFAAwF;IACxF,OAAO,CAAC,KAAK,CAAS;IAEtB,8EAA8E;gBAE1E,SAAS,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,EACnD,OAAO,EAAE,oBAAoB;IA2BjC,IAAI,QAAQ,IAAI,MAAM,CAA+B;IACrD,IAAI,GAAG,IAAI,MAAM,CAA0B;IAC3C,IAAI,KAAK,IAAI,MAAM,CAA4B;IAE/C;;;OAGG;IACG,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,CAAC;IAuBpF,yDAAyD;IACzD,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI;IAE7B,oEAAoE;IACpE,WAAW,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IAO1C;;;;;;;;OAQG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAgBnC,iDAAiD;IAC3C,OAAO,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,CAAC;IAKhE,iDAAiD;IACjD,MAAM,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAcnD,iDAAiD;IACjD,SAAS,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,MAAM;IAK/C,OAAO,IAAI,IAAI;IAOf;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,UAAU;IAKlB,uEAAuE;IACvE,OAAO,CAAC,OAAO;IAOf,OAAO,CAAC,UAAU;CAGrB;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACrC,OAAO,GAAE,oBAAyB,GACnC,OAAO,CAAC,aAAa,CAAC,CAGxB"}
1
+ {"version":3,"file":"still.d.ts","sourceRoot":"","sources":["../src/still.ts"],"names":[],"mappings":"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"}
package/dist/still.js CHANGED
@@ -1,8 +1,6 @@
1
1
  import { DEFAULT_OUTPUT, ManifestAssetCatalog, frameSpecAtTime, Registry, resolveProjectDocument, } from "@motionscript/core";
2
- import { drawFrameAt, } from "@motionscript/skia-render/export";
3
- import { WebRenderContext } from "./render-context";
4
- import { WebStorageAdapter } from "./storage-adapter";
5
- import { getCanvasKit } from "./getter";
2
+ import { drawFrameAt, } from "./frame-loop";
3
+ import { loadRenderer } from "./renderer";
6
4
  const EMPTY_MANIFEST = { image: {}, video: {}, audio: {}, font: {} };
7
5
  /** Normalize the many ways a caller can name a frame into the engine's own form. */
8
6
  export function stillFrameSpec(options, fps) {
@@ -24,7 +22,7 @@ function mimeOf(format) {
24
22
  * A long-lived single-frame renderer.
25
23
  *
26
24
  * Where `exportScreenshot` is one frame from a standing start, this keeps the
27
- * expensive things — the CanvasKit module, the WebGL Skia surface, and the
25
+ * expensive things — the renderer module, its GPU surface, and the
28
26
  * storage adapter's decoded-asset cache — alive across calls, so re-rendering
29
27
  * after an edit costs discovering one frame and a draw rather than a new GL
30
28
  * context. That is
@@ -60,7 +58,7 @@ export class StillRenderer {
60
58
  renderContext;
61
59
  storageAdapter;
62
60
  catalog;
63
- canvasKit;
61
+ backend;
64
62
  /** True when we made the canvas and must clean it up. */
65
63
  ownsCanvas;
66
64
  viewportValue;
@@ -74,9 +72,9 @@ export class StillRenderer {
74
72
  disposed = false;
75
73
  /** False until a frame has been drawn onto the current surface; a remount clears it. */
76
74
  drawn = false;
77
- /** @internal Use {@link createStillRenderer}, which loads CanvasKit first. */
78
- constructor(canvasKit, options) {
79
- this.canvasKit = canvasKit;
75
+ /** @internal Use {@link createStillRenderer}, which loads the renderer first. */
76
+ constructor(backend, options) {
77
+ this.backend = backend;
80
78
  this.viewportValue = options.viewport ?? DEFAULT_OUTPUT.viewport;
81
79
  this.fpsValue = options.fps ?? DEFAULT_OUTPUT.fps;
82
80
  this.scaleValue = options.scale ?? 1;
@@ -92,8 +90,10 @@ export class StillRenderer {
92
90
  }
93
91
  this.sizeCanvas();
94
92
  this.catalog = new ManifestAssetCatalog(this.manifest);
95
- this.storageAdapter = new WebStorageAdapter(canvasKit, this.catalog, this.viewportValue, this.fpsValue);
96
- this.renderContext = new WebRenderContext(canvasKit, this.storageAdapter);
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
97
  this.renderContext.mount(this.canvas);
98
98
  }
99
99
  get viewport() { return this.viewportValue; }
@@ -108,6 +108,7 @@ export class StillRenderer {
108
108
  const scale = options?.scale ?? this.scaleValue;
109
109
  if (scale !== this.surfaceScale)
110
110
  this.remount(scale);
111
+ await this.renderContext.whenMounted?.();
111
112
  const project = resolveProjectDocument(source, { viewport: this.viewportValue }, { viewport: this.viewportValue, fps: this.fpsValue });
112
113
  const frame = await drawFrameAt({
113
114
  project,
@@ -118,6 +119,7 @@ export class StillRenderer {
118
119
  storageAdapter: this.storageAdapter,
119
120
  assetCatalog: this.catalog,
120
121
  frame: stillFrameSpec(options, project.fps),
122
+ onIssue: options?.onIssue,
121
123
  });
122
124
  this.drawn = true;
123
125
  return frame;
@@ -149,8 +151,10 @@ export class StillRenderer {
149
151
  // Disposing the context disposes the adapter it holds, so this releases
150
152
  // the old surface, its handlers and every decode in one call.
151
153
  this.renderContext.dispose();
152
- this.storageAdapter = new WebStorageAdapter(this.canvasKit, this.catalog, viewport, this.fpsValue);
153
- this.renderContext = new WebRenderContext(this.canvasKit, this.storageAdapter);
154
+ this.storageAdapter = this.backend.createStorageAdapter({
155
+ catalog: this.catalog, viewport, fps: this.fpsValue,
156
+ });
157
+ this.renderContext = this.backend.createRenderContext(this.storageAdapter);
154
158
  this.remount(this.scaleValue);
155
159
  }
156
160
  /** Encode the frame currently on the surface. */
@@ -182,30 +186,24 @@ export class StillRenderer {
182
186
  this.canvas.remove();
183
187
  }
184
188
  /**
185
- * Read the surface back into a 2D canvas, which is what actually encodes: this
186
- * CanvasKit build ships no wasm image encoders, so the browser's own codecs are
187
- * the only ones available.
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.
188
192
  */
189
193
  snapshotToCanvas() {
190
194
  this.assertLive();
191
- const snapshot = this.drawn ? this.renderContext.snapshotPixels() : undefined;
192
- if (!snapshot)
195
+ if (!this.drawn)
193
196
  throw new Error("Nothing has been rendered yet.");
194
- const canvas = document.createElement("canvas");
195
- canvas.width = snapshot.width;
196
- canvas.height = snapshot.height;
197
- const ctx = canvas.getContext("2d");
198
- if (!ctx)
199
- throw new Error("Still encode: could not get a 2d context.");
200
- // `pixels` is unpremultiplied RGBA8888, which is exactly ImageData's layout.
201
- ctx.putImageData(new ImageData(new Uint8ClampedArray(snapshot.pixels), snapshot.width, snapshot.height), 0, 0);
197
+ const canvas = this.renderContext.snapshotCanvas?.();
198
+ if (!canvas)
199
+ throw new Error("Still encode: could not read the surface into a 2d canvas.");
202
200
  return canvas;
203
201
  }
204
202
  sizeCanvas() {
205
203
  this.canvas.width = this.viewportValue.width * this.surfaceScale;
206
204
  this.canvas.height = this.viewportValue.height * this.surfaceScale;
207
205
  }
208
- /** Resize the backing store and build a fresh Skia surface over it. */
206
+ /** Resize the backing store and mount a fresh surface over it. */
209
207
  remount(scale) {
210
208
  this.surfaceScale = scale;
211
209
  this.drawn = false;
@@ -217,13 +215,8 @@ export class StillRenderer {
217
215
  throw new Error("This StillRenderer has been disposed.");
218
216
  }
219
217
  }
220
- /**
221
- * Create a {@link StillRenderer}, loading CanvasKit if it isn't already.
222
- *
223
- * `getCanvasKit` memoizes, so a second renderer in the same page is cheap.
224
- */
218
+ /** Create a {@link StillRenderer}; rejects with `WebGpuUnavailableError` without WebGPU. */
225
219
  export async function createStillRenderer(options = {}) {
226
- const canvasKit = await getCanvasKit(options.wasmUrl);
227
- return new StillRenderer(canvasKit, options);
220
+ return new StillRenderer(await loadRenderer(options), options);
228
221
  }
229
222
  //# sourceMappingURL=still.js.map
package/dist/still.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"still.js","sourceRoot":"","sources":["../src/still.ts"],"names":[],"mappings":"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","sourcesContent":["import {\r\n AssetCatalog, DEFAULT_OUTPUT, ManifestAssetCatalog,\r\n frameSpecAtTime,\r\n Registry,\r\n resolveProjectDocument,\r\n type AssetManifest,\r\n type FrameSpec,\r\n type ProjectDocument,\r\n type VisualDocument,\r\n type Size2D,\r\n type Theme,\r\n} from \"@motionscript/core\";\r\nimport {\r\n drawFrameAt,\r\n type DrawnFrame,\r\n type ScreenshotFormat,\r\n} from \"@motionscript/skia-render/export\";\r\nimport { WebRenderContext } from \"./render-context\";\r\nimport { WebStorageAdapter } from \"./storage-adapter\";\r\nimport { getCanvasKit } from \"./getter\";\r\n\r\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\r\n\r\n/** Anything a still can be rendered from: a project, or a lone still or composition as the one track of one. */\r\nexport type StillSource = ProjectDocument | VisualDocument;\r\n\r\n/**\r\n * Which frame to draw, in whatever form is most convenient — a global frame\r\n * index, the timeline's first or last frame, or a `FrameSpec`.\r\n */\r\nexport type FrameRef = FrameSpec | number | \"first\" | \"last\";\r\n\r\nexport interface StillRendererOptions {\r\n /**\r\n * The canvas to draw into. Omit and the renderer creates its own hidden one\r\n * and removes it on {@link StillRenderer.dispose} — which is what a one-shot\r\n * capture wants, while a live preview passes the canvas it already shows.\r\n */\r\n canvas?: HTMLCanvasElement;\r\n /** Output resolution in pixels, whatever the document asks for. Defaults to 1920×1080. */\r\n viewport?: Size2D;\r\n /** Frame rate for a document that sets none. Defaults to 60. */\r\n fps?: number;\r\n /** Device pixel ratio the surface is sized at. Defaults to 1. */\r\n scale?: number;\r\n manifest?: AssetManifest;\r\n /** Theme for a document that carries none of its own. */\r\n theme?: Theme;\r\n wasmUrl?: string;\r\n /**\r\n * The vocabulary a document is compiled against.\r\n *\r\n * Defaults to a fresh one carrying only what the library ships — a renderer\r\n * owns its registry the way an engine does, so a host with custom node types\r\n * passes the same one it gave its engine rather than relying on whatever a\r\n * module happened to register.\r\n */\r\n registry?: Registry;\r\n}\r\n\r\nexport interface RenderStillOptions {\r\n /** Which frame to draw. Defaults to the timeline's first. */\r\n frame?: FrameRef;\r\n /** Seconds into the timeline, converted with the document's fps. Wins over `frame`. */\r\n time?: number;\r\n /**\r\n * Draw this call at this pixel ratio instead of the renderer's. **Resizes the\r\n * canvas and rebuilds the Skia surface**, and the next call without it rebuilds\r\n * them back, so keep it off the per-edit path — render the preview at its\r\n * normal scale and pass this only when capturing at a higher one.\r\n */\r\n scale?: number;\r\n}\r\n\r\nexport interface EncodeStillOptions {\r\n /** Defaults to `\"png\"`. */\r\n format?: ScreenshotFormat;\r\n /** JPEG quality in [0,1]; ignored for png. Defaults to 0.92. */\r\n quality?: number;\r\n}\r\n\r\n/** Normalize the many ways a caller can name a frame into the engine's own form. */\r\nexport function stillFrameSpec(options: RenderStillOptions | undefined, fps: number): FrameSpec {\r\n if (options?.time !== undefined) return frameSpecAtTime(options.time, fps);\r\n const frame = options?.frame;\r\n if (frame === undefined || frame === \"first\") return { kind: \"first\" };\r\n if (frame === \"last\") return { kind: \"last\" };\r\n if (typeof frame === \"number\") return { kind: \"frame\", frame };\r\n return frame;\r\n}\r\n\r\nfunction mimeOf(format: ScreenshotFormat): string {\r\n return format === \"jpeg\" ? \"image/jpeg\" : \"image/png\";\r\n}\r\n\r\n/**\r\n * A long-lived single-frame renderer.\r\n *\r\n * Where `exportScreenshot` is one frame from a standing start, this keeps the\r\n * expensive things — the CanvasKit module, the WebGL Skia surface, and the\r\n * storage adapter's decoded-asset cache — alive across calls, so re-rendering\r\n * after an edit costs discovering one frame and a draw rather than a new GL\r\n * context. That is\r\n * the difference between a one-off capture and a builder that repaints while the\r\n * user types.\r\n *\r\n * It is also the *same* draw: {@link render} goes through `drawFrameAt`, which is\r\n * the pass the video exporter runs per frame. A preview and its exported image\r\n * cannot drift apart, because there is only one path.\r\n *\r\n * ```ts\r\n * const renderer = await createStillRenderer({ canvas, viewport, manifest, theme });\r\n * await renderer.render({ kind: \"still\", nodes: [\r\n * { id: \"bg\", type: \"rect\", parentId: null, layerOrder: 0, props: { width: 1920, height: 1080, fill: \"bg\" } },\r\n * ] });\r\n * const blob = await renderer.toBlob({ format: \"png\" });\r\n * renderer.dispose();\r\n * ```\r\n *\r\n * **The theme is process-global in core.** {@link render} re-applies the\r\n * document's (or this renderer's) before every draw, so two renderers with\r\n * different themes still each draw correctly; but anything else that calls\r\n * `setTheme` between a render and a read of the surface will have been\r\n * overwritten by the next render.\r\n *\r\n * **One surface, one caller at a time.** A render and the encode of its result\r\n * are two calls, and the next render draws over the surface between them, so\r\n * callers that share a renderer without knowing of each other must be queued —\r\n * as `session.image()` queues its calls.\r\n */\r\nexport class StillRenderer {\r\n readonly canvas: HTMLCanvasElement;\r\n\r\n private renderContext: WebRenderContext;\r\n private storageAdapter: WebStorageAdapter;\r\n private catalog: AssetCatalog;\r\n\r\n private readonly canvasKit: Awaited<ReturnType<typeof getCanvasKit>>;\r\n /** True when we made the canvas and must clean it up. */\r\n private readonly ownsCanvas: boolean;\r\n\r\n private viewportValue: Size2D;\r\n private readonly fpsValue: number;\r\n private readonly scaleValue: number;\r\n /** What the canvas is sized at now, which a call's own `scale` changes. */\r\n private surfaceScale: number;\r\n private manifest: AssetManifest;\r\n private theme?: Theme;\r\n private readonly registry: Registry;\r\n private disposed = false;\r\n /** False until a frame has been drawn onto the current surface; a remount clears it. */\r\n private drawn = false;\r\n\r\n /** @internal Use {@link createStillRenderer}, which loads CanvasKit first. */\r\n constructor(\r\n canvasKit: Awaited<ReturnType<typeof getCanvasKit>>,\r\n options: StillRendererOptions,\r\n ) {\r\n this.canvasKit = canvasKit;\r\n this.viewportValue = options.viewport ?? DEFAULT_OUTPUT.viewport;\r\n this.fpsValue = options.fps ?? DEFAULT_OUTPUT.fps;\r\n this.scaleValue = options.scale ?? 1;\r\n this.surfaceScale = this.scaleValue;\r\n this.manifest = options.manifest ?? EMPTY_MANIFEST;\r\n this.theme = options.theme;\r\n this.registry = options.registry ?? new Registry();\r\n\r\n this.ownsCanvas = options.canvas === undefined;\r\n this.canvas = options.canvas ?? document.createElement(\"canvas\");\r\n if (this.ownsCanvas) {\r\n this.canvas.style.display = \"none\";\r\n document.body.appendChild(this.canvas);\r\n }\r\n this.sizeCanvas();\r\n\r\n this.catalog = new ManifestAssetCatalog(this.manifest);\r\n this.storageAdapter = new WebStorageAdapter(\r\n canvasKit, this.catalog, this.viewportValue, this.fpsValue,\r\n );\r\n this.renderContext = new WebRenderContext(canvasKit, this.storageAdapter);\r\n this.renderContext.mount(this.canvas);\r\n }\r\n\r\n get viewport(): Size2D { return this.viewportValue; }\r\n get fps(): number { return this.fpsValue; }\r\n get scale(): number { return this.scaleValue; }\r\n\r\n /**\r\n * Draw one frame of `source`, at this renderer's viewport — the surface is\r\n * the output, so it wins over any size the document asks for.\r\n */\r\n async render(source: StillSource, options?: RenderStillOptions): Promise<DrawnFrame> {\r\n this.assertLive();\r\n const scale = options?.scale ?? this.scaleValue;\r\n if (scale !== this.surfaceScale) this.remount(scale);\r\n\r\n const project = resolveProjectDocument(\r\n source, { viewport: this.viewportValue }, { viewport: this.viewportValue, fps: this.fpsValue },\r\n );\r\n\r\n const frame = await drawFrameAt({\r\n project,\r\n registry: this.registry,\r\n theme: this.theme,\r\n scale,\r\n renderContext: this.renderContext,\r\n storageAdapter: this.storageAdapter,\r\n assetCatalog: this.catalog,\r\n frame: stillFrameSpec(options, project.fps!),\r\n });\r\n this.drawn = true;\r\n return frame;\r\n }\r\n\r\n /** Theme for a document that carries none of its own. */\r\n setTheme(theme?: Theme): void { this.theme = theme; }\r\n\r\n /** Swap the asset manifest, keeping every decode already cached. */\r\n setManifest(manifest: AssetManifest): void {\r\n this.assertLive();\r\n this.manifest = manifest;\r\n this.catalog = new ManifestAssetCatalog(manifest);\r\n this.storageAdapter.setCatalog(this.catalog);\r\n }\r\n\r\n /**\r\n * Change the output resolution.\r\n *\r\n * A full reset, unlike {@link setManifest}: the viewport clamps the resolution\r\n * images are decoded *at*, so cached decodes made for the old one would be\r\n * soft at the new one, and the render context's handlers captured the adapter\r\n * when they were built — so both are rebuilt. Call this when the output format\r\n * changes, not per edit.\r\n */\r\n setViewport(viewport: Size2D): void {\r\n this.assertLive();\r\n if (viewport.width === this.viewportValue.width && viewport.height === this.viewportValue.height) {\r\n return;\r\n }\r\n this.viewportValue = viewport;\r\n // Disposing the context disposes the adapter it holds, so this releases\r\n // the old surface, its handlers and every decode in one call.\r\n this.renderContext.dispose();\r\n this.storageAdapter = new WebStorageAdapter(\r\n this.canvasKit, this.catalog, viewport, this.fpsValue,\r\n );\r\n this.renderContext = new WebRenderContext(this.canvasKit, this.storageAdapter);\r\n this.remount(this.scaleValue);\r\n }\r\n\r\n /** Encode the frame currently on the surface. */\r\n async toBytes(options?: EncodeStillOptions): Promise<Uint8Array> {\r\n const blob = await this.toBlob(options);\r\n return new Uint8Array(await blob.arrayBuffer());\r\n }\r\n\r\n /** Encode the frame currently on the surface. */\r\n toBlob(options?: EncodeStillOptions): Promise<Blob> {\r\n const canvas = this.snapshotToCanvas();\r\n const { format = \"png\", quality = 0.92 } = options ?? {};\r\n return new Promise((resolve, reject) => {\r\n canvas.toBlob(\r\n (blob) => blob\r\n ? resolve(blob)\r\n : reject(new Error(\"Still encode produced no data.\")),\r\n mimeOf(format),\r\n quality,\r\n );\r\n });\r\n }\r\n\r\n /** Encode the frame currently on the surface. */\r\n toDataURL(options?: EncodeStillOptions): string {\r\n const { format = \"png\", quality = 0.92 } = options ?? {};\r\n return this.snapshotToCanvas().toDataURL(mimeOf(format), quality);\r\n }\r\n\r\n dispose(): void {\r\n if (this.disposed) return;\r\n this.disposed = true;\r\n this.renderContext.dispose();\r\n if (this.ownsCanvas) this.canvas.remove();\r\n }\r\n\r\n /**\r\n * Read the surface back into a 2D canvas, which is what actually encodes: this\r\n * CanvasKit build ships no wasm image encoders, so the browser's own codecs are\r\n * the only ones available.\r\n */\r\n private snapshotToCanvas(): HTMLCanvasElement {\r\n this.assertLive();\r\n const snapshot = this.drawn ? this.renderContext.snapshotPixels() : undefined;\r\n if (!snapshot) throw new Error(\"Nothing has been rendered yet.\");\r\n\r\n const canvas = document.createElement(\"canvas\");\r\n canvas.width = snapshot.width;\r\n canvas.height = snapshot.height;\r\n const ctx = canvas.getContext(\"2d\");\r\n if (!ctx) throw new Error(\"Still encode: could not get a 2d context.\");\r\n // `pixels` is unpremultiplied RGBA8888, which is exactly ImageData's layout.\r\n ctx.putImageData(\r\n new ImageData(new Uint8ClampedArray(snapshot.pixels), snapshot.width, snapshot.height),\r\n 0, 0,\r\n );\r\n return canvas;\r\n }\r\n\r\n private sizeCanvas(): void {\r\n this.canvas.width = this.viewportValue.width * this.surfaceScale;\r\n this.canvas.height = this.viewportValue.height * this.surfaceScale;\r\n }\r\n\r\n /** Resize the backing store and build a fresh Skia surface over it. */\r\n private remount(scale: number): void {\r\n this.surfaceScale = scale;\r\n this.drawn = false;\r\n this.sizeCanvas();\r\n this.renderContext.mount(this.canvas);\r\n }\r\n\r\n private assertLive(): void {\r\n if (this.disposed) throw new Error(\"This StillRenderer has been disposed.\");\r\n }\r\n}\r\n\r\n/**\r\n * Create a {@link StillRenderer}, loading CanvasKit if it isn't already.\r\n *\r\n * `getCanvasKit` memoizes, so a second renderer in the same page is cheap.\r\n */\r\nexport async function createStillRenderer(\r\n options: StillRendererOptions = {},\r\n): Promise<StillRenderer> {\r\n const canvasKit = await getCanvasKit(options.wasmUrl);\r\n return new StillRenderer(canvasKit, options);\r\n}\r\n"]}
1
+ {"version":3,"file":"still.js","sourceRoot":"","sources":["../src/still.ts"],"names":[],"mappings":"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","sourcesContent":["import {\r\n AssetCatalog, DEFAULT_OUTPUT, ManifestAssetCatalog,\r\n frameSpecAtTime,\r\n Registry,\r\n resolveProjectDocument,\r\n type AssetManifest,\r\n type FrameSpec,\r\n type RenderIssue,\r\n type ProjectDocument,\r\n type VisualDocument,\r\n type Size2D,\r\n type StorageAdapter,\r\n type Theme,\r\n} from \"@motionscript/core\";\r\nimport {\r\n drawFrameAt,\r\n type DrawnFrame,\r\n type ScreenshotFormat,\r\n} from \"./frame-loop\";\r\nimport type { BrowserRenderContext, BrowserRenderer } from \"./engine\";\r\nimport { loadRenderer, type RendererWasmOption } from \"./renderer\";\r\n\r\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\r\n\r\n/** Anything a still can be rendered from: a project, or a lone still or composition as the one track of one. */\r\nexport type StillSource = ProjectDocument | VisualDocument;\r\n\r\n/**\r\n * Which frame to draw, in whatever form is most convenient — a global frame\r\n * index, the timeline's first or last frame, or a `FrameSpec`.\r\n */\r\nexport type FrameRef = FrameSpec | number | \"first\" | \"last\";\r\n\r\nexport interface StillRendererOptions extends RendererWasmOption {\r\n /**\r\n * The canvas to draw into. Omit and the renderer creates its own hidden one\r\n * and removes it on {@link StillRenderer.dispose} — which is what a one-shot\r\n * capture wants, while a live preview passes the canvas it already shows.\r\n */\r\n canvas?: HTMLCanvasElement;\r\n /** Output resolution in pixels, whatever the document asks for. Defaults to 1920×1080. */\r\n viewport?: Size2D;\r\n /** Frame rate for a document that sets none. Defaults to 60. */\r\n fps?: number;\r\n /** Device pixel ratio the surface is sized at. Defaults to 1. */\r\n scale?: number;\r\n manifest?: AssetManifest;\r\n /** Theme for a document that carries none of its own. */\r\n theme?: Theme;\r\n /**\r\n * The vocabulary a document is compiled against.\r\n *\r\n * Defaults to a fresh one carrying only what the library ships — a renderer\r\n * owns its registry the way an engine does, so a host with custom node types\r\n * passes the same one it gave its engine rather than relying on whatever a\r\n * module happened to register.\r\n */\r\n registry?: Registry;\r\n}\r\n\r\nexport interface RenderStillOptions {\r\n /** Which frame to draw. Defaults to the timeline's first. */\r\n frame?: FrameRef;\r\n /** Seconds into the timeline, converted with the document's fps. Wins over `frame`. */\r\n time?: number;\r\n /**\r\n * Draw this call at this pixel ratio instead of the renderer's. **Resizes the\r\n * canvas and rebuilds the surface**, and the next call without it rebuilds\r\n * them back, so keep it off the per-edit path — render the preview at its\r\n * normal scale and pass this only when capturing at a higher one.\r\n */\r\n scale?: number;\r\n /** What the renderer drew but not as the document says; a lost GPU device throws `RenderBackendError` instead. */\r\n onIssue?: (issue: RenderIssue) => void;\r\n}\r\n\r\nexport interface EncodeStillOptions {\r\n /** Defaults to `\"png\"`. */\r\n format?: ScreenshotFormat;\r\n /** JPEG quality in [0,1]; ignored for png. Defaults to 0.92. */\r\n quality?: number;\r\n}\r\n\r\n/** Normalize the many ways a caller can name a frame into the engine's own form. */\r\nexport function stillFrameSpec(options: RenderStillOptions | undefined, fps: number): FrameSpec {\r\n if (options?.time !== undefined) return frameSpecAtTime(options.time, fps);\r\n const frame = options?.frame;\r\n if (frame === undefined || frame === \"first\") return { kind: \"first\" };\r\n if (frame === \"last\") return { kind: \"last\" };\r\n if (typeof frame === \"number\") return { kind: \"frame\", frame };\r\n return frame;\r\n}\r\n\r\nfunction mimeOf(format: ScreenshotFormat): string {\r\n return format === \"jpeg\" ? \"image/jpeg\" : \"image/png\";\r\n}\r\n\r\n/**\r\n * A long-lived single-frame renderer.\r\n *\r\n * Where `exportScreenshot` is one frame from a standing start, this keeps the\r\n * expensive things — the renderer module, its GPU surface, and the\r\n * storage adapter's decoded-asset cache — alive across calls, so re-rendering\r\n * after an edit costs discovering one frame and a draw rather than a new GL\r\n * context. That is\r\n * the difference between a one-off capture and a builder that repaints while the\r\n * user types.\r\n *\r\n * It is also the *same* draw: {@link render} goes through `drawFrameAt`, which is\r\n * the pass the video exporter runs per frame. A preview and its exported image\r\n * cannot drift apart, because there is only one path.\r\n *\r\n * ```ts\r\n * const renderer = await createStillRenderer({ canvas, viewport, manifest, theme });\r\n * await renderer.render({ kind: \"still\", nodes: [\r\n * { id: \"bg\", type: \"rect\", parentId: null, layerOrder: 0, props: { width: 1920, height: 1080, fill: \"bg\" } },\r\n * ] });\r\n * const blob = await renderer.toBlob({ format: \"png\" });\r\n * renderer.dispose();\r\n * ```\r\n *\r\n * **The theme is process-global in core.** {@link render} re-applies the\r\n * document's (or this renderer's) before every draw, so two renderers with\r\n * different themes still each draw correctly; but anything else that calls\r\n * `setTheme` between a render and a read of the surface will have been\r\n * overwritten by the next render.\r\n *\r\n * **One surface, one caller at a time.** A render and the encode of its result\r\n * are two calls, and the next render draws over the surface between them, so\r\n * callers that share a renderer without knowing of each other must be queued —\r\n * as `session.image()` queues its calls.\r\n */\r\nexport class StillRenderer {\r\n readonly canvas: HTMLCanvasElement;\r\n\r\n private renderContext: BrowserRenderContext;\r\n private storageAdapter: StorageAdapter;\r\n private catalog: AssetCatalog;\r\n\r\n private readonly backend: BrowserRenderer;\r\n /** True when we made the canvas and must clean it up. */\r\n private readonly ownsCanvas: boolean;\r\n\r\n private viewportValue: Size2D;\r\n private readonly fpsValue: number;\r\n private readonly scaleValue: number;\r\n /** What the canvas is sized at now, which a call's own `scale` changes. */\r\n private surfaceScale: number;\r\n private manifest: AssetManifest;\r\n private theme?: Theme;\r\n private readonly registry: Registry;\r\n private disposed = false;\r\n /** False until a frame has been drawn onto the current surface; a remount clears it. */\r\n private drawn = false;\r\n\r\n /** @internal Use {@link createStillRenderer}, which loads the renderer first. */\r\n constructor(\r\n backend: BrowserRenderer,\r\n options: StillRendererOptions,\r\n ) {\r\n this.backend = backend;\r\n this.viewportValue = options.viewport ?? DEFAULT_OUTPUT.viewport;\r\n this.fpsValue = options.fps ?? DEFAULT_OUTPUT.fps;\r\n this.scaleValue = options.scale ?? 1;\r\n this.surfaceScale = this.scaleValue;\r\n this.manifest = options.manifest ?? EMPTY_MANIFEST;\r\n this.theme = options.theme;\r\n this.registry = options.registry ?? new Registry();\r\n\r\n this.ownsCanvas = options.canvas === undefined;\r\n this.canvas = options.canvas ?? document.createElement(\"canvas\");\r\n if (this.ownsCanvas) {\r\n this.canvas.style.display = \"none\";\r\n document.body.appendChild(this.canvas);\r\n }\r\n this.sizeCanvas();\r\n\r\n this.catalog = new ManifestAssetCatalog(this.manifest);\r\n this.storageAdapter = this.backend.createStorageAdapter({\r\n catalog: this.catalog, viewport: this.viewportValue, fps: this.fpsValue,\r\n });\r\n this.renderContext = this.backend.createRenderContext(this.storageAdapter);\r\n this.renderContext.mount(this.canvas);\r\n }\r\n\r\n get viewport(): Size2D { return this.viewportValue; }\r\n get fps(): number { return this.fpsValue; }\r\n get scale(): number { return this.scaleValue; }\r\n\r\n /**\r\n * Draw one frame of `source`, at this renderer's viewport — the surface is\r\n * the output, so it wins over any size the document asks for.\r\n */\r\n async render(source: StillSource, options?: RenderStillOptions): Promise<DrawnFrame> {\r\n this.assertLive();\r\n const scale = options?.scale ?? this.scaleValue;\r\n if (scale !== this.surfaceScale) this.remount(scale);\r\n await this.renderContext.whenMounted?.();\r\n\r\n const project = resolveProjectDocument(\r\n source, { viewport: this.viewportValue }, { viewport: this.viewportValue, fps: this.fpsValue },\r\n );\r\n\r\n const frame = await drawFrameAt({\r\n project,\r\n registry: this.registry,\r\n theme: this.theme,\r\n scale,\r\n renderContext: this.renderContext,\r\n storageAdapter: this.storageAdapter,\r\n assetCatalog: this.catalog,\r\n frame: stillFrameSpec(options, project.fps!),\r\n onIssue: options?.onIssue,\r\n });\r\n this.drawn = true;\r\n return frame;\r\n }\r\n\r\n /** Theme for a document that carries none of its own. */\r\n setTheme(theme?: Theme): void { this.theme = theme; }\r\n\r\n /** Swap the asset manifest, keeping every decode already cached. */\r\n setManifest(manifest: AssetManifest): void {\r\n this.assertLive();\r\n this.manifest = manifest;\r\n this.catalog = new ManifestAssetCatalog(manifest);\r\n this.storageAdapter.setCatalog(this.catalog);\r\n }\r\n\r\n /**\r\n * Change the output resolution.\r\n *\r\n * A full reset, unlike {@link setManifest}: the viewport clamps the resolution\r\n * images are decoded *at*, so cached decodes made for the old one would be\r\n * soft at the new one, and the render context's handlers captured the adapter\r\n * when they were built — so both are rebuilt. Call this when the output format\r\n * changes, not per edit.\r\n */\r\n setViewport(viewport: Size2D): void {\r\n this.assertLive();\r\n if (viewport.width === this.viewportValue.width && viewport.height === this.viewportValue.height) {\r\n return;\r\n }\r\n this.viewportValue = viewport;\r\n // Disposing the context disposes the adapter it holds, so this releases\r\n // the old surface, its handlers and every decode in one call.\r\n this.renderContext.dispose();\r\n this.storageAdapter = this.backend.createStorageAdapter({\r\n catalog: this.catalog, viewport, fps: this.fpsValue,\r\n });\r\n this.renderContext = this.backend.createRenderContext(this.storageAdapter);\r\n this.remount(this.scaleValue);\r\n }\r\n\r\n /** Encode the frame currently on the surface. */\r\n async toBytes(options?: EncodeStillOptions): Promise<Uint8Array> {\r\n const blob = await this.toBlob(options);\r\n return new Uint8Array(await blob.arrayBuffer());\r\n }\r\n\r\n /** Encode the frame currently on the surface. */\r\n toBlob(options?: EncodeStillOptions): Promise<Blob> {\r\n const canvas = this.snapshotToCanvas();\r\n const { format = \"png\", quality = 0.92 } = options ?? {};\r\n return new Promise((resolve, reject) => {\r\n canvas.toBlob(\r\n (blob) => blob\r\n ? resolve(blob)\r\n : reject(new Error(\"Still encode produced no data.\")),\r\n mimeOf(format),\r\n quality,\r\n );\r\n });\r\n }\r\n\r\n /** Encode the frame currently on the surface. */\r\n toDataURL(options?: EncodeStillOptions): string {\r\n const { format = \"png\", quality = 0.92 } = options ?? {};\r\n return this.snapshotToCanvas().toDataURL(mimeOf(format), quality);\r\n }\r\n\r\n dispose(): void {\r\n if (this.disposed) return;\r\n this.disposed = true;\r\n this.renderContext.dispose();\r\n if (this.ownsCanvas) this.canvas.remove();\r\n }\r\n\r\n /**\r\n * Read the surface back into a 2D canvas, which is what actually encodes: the\r\n * renderers ship no image encoders, so the browser's own codecs are the only\r\n * ones available.\r\n */\r\n private snapshotToCanvas(): HTMLCanvasElement {\r\n this.assertLive();\r\n if (!this.drawn) throw new Error(\"Nothing has been rendered yet.\");\r\n const canvas = this.renderContext.snapshotCanvas?.();\r\n if (!canvas) throw new Error(\"Still encode: could not read the surface into a 2d canvas.\");\r\n return canvas;\r\n }\r\n\r\n private sizeCanvas(): void {\r\n this.canvas.width = this.viewportValue.width * this.surfaceScale;\r\n this.canvas.height = this.viewportValue.height * this.surfaceScale;\r\n }\r\n\r\n /** Resize the backing store and mount a fresh surface over it. */\r\n private remount(scale: number): void {\r\n this.surfaceScale = scale;\r\n this.drawn = false;\r\n this.sizeCanvas();\r\n this.renderContext.mount(this.canvas);\r\n }\r\n\r\n private assertLive(): void {\r\n if (this.disposed) throw new Error(\"This StillRenderer has been disposed.\");\r\n }\r\n}\r\n\r\n/** Create a {@link StillRenderer}; rejects with `WebGpuUnavailableError` without WebGPU. */\r\nexport async function createStillRenderer(\r\n options: StillRendererOptions = {},\r\n): Promise<StillRenderer> {\r\n return new StillRenderer(await loadRenderer(options), options);\r\n}\r\n"]}
package/dist/ticker.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { Ticker } from "@motionscript/core";
2
2
  /**
3
- * Ticker backed by a Web Audio AudioContext. Clip time advances in
4
- * lock-step with `audioContext.currentTime`, so any AudioBufferSourceNodes
5
- * scheduled on the same context play perfectly in sync.
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
6
  *
7
7
  * When paused we suspend the context (audio stops, currentTime freezes);
8
8
  * when playing we resume and a rAF loop publishes ticks at the target fps.
@@ -12,8 +12,9 @@ import { Ticker } from "@motionscript/core";
12
12
  * graph to schedule against even on scenes that contain no audio nodes.
13
13
  */
14
14
  export declare class WebTicker extends Ticker {
15
- private context;
16
- private ownsContext;
15
+ private readonly contextOf;
16
+ private readonly ownsContext;
17
+ private _context;
17
18
  private fps;
18
19
  private playStartCtxTime;
19
20
  private seekOffset;
@@ -23,11 +24,17 @@ export declare class WebTicker extends Ticker {
23
24
  /** Bumped by every `seek`, so the loop can tell that a listener or tick callback moved the playhead under it. */
24
25
  private seekEpoch;
25
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
+ */
26
32
  constructor(opts: {
27
- context?: AudioContext;
33
+ context?: AudioContext | (() => AudioContext);
28
34
  fps: number;
29
35
  });
30
36
  getContext(): AudioContext;
37
+ private get context();
31
38
  seek(t: number): void;
32
39
  /**
33
40
  * Anchors elapsed-time tracking to the current `context.currentTime`, starts the silent ticker
@@ -46,8 +53,15 @@ export declare class WebTicker extends Ticker {
46
53
  /** Where the playhead is now, from elapsed `context.currentTime` (so it stays sample-accurate), and whether that is the end of the run. */
47
54
  private position;
48
55
  /**
49
- * Drives ticks off `requestAnimationFrame`, firing `tick()` only when a full frame interval
50
- * has elapsed — and once more at the end of the run, whether or not it landed on a boundary.
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.
51
65
  *
52
66
  * A listener may `seek` (a host looping wraps to 0 when it hears the end), so the end is
53
67
  * re-read after the listeners and the tick callbacks have run: a run that was seeked out of
@@ -1 +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,OAAO,CAAe;IAC9B,OAAO,CAAC,WAAW,CAAU;IAC7B,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;gBAE9C,IAAI,EAAE;QAAE,OAAO,CAAC,EAAE,YAAY,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE;IAOzD,UAAU,IAAI,YAAY;IAI1B,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAQrB;;;;;;;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;;;;;;;OAOG;IACH,OAAO,CAAC,SAAS;IA+CjB,OAAO,CAAC,QAAQ;IAOhB,OAAO,IAAI,IAAI;CAQlB"}
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"}