@motionscript/browser 0.1.0-alpha.7 → 0.1.0-alpha.9

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 (174) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +201 -201
  3. package/NOTICE +49 -48
  4. package/README.md +141 -118
  5. package/dist/asset-bytes.d.ts +7 -4
  6. package/dist/asset-bytes.d.ts.map +1 -1
  7. package/dist/asset-bytes.js +9 -4
  8. package/dist/asset-bytes.js.map +1 -1
  9. package/dist/audio/bus-graph.d.ts.map +1 -1
  10. package/dist/audio/bus-graph.js +15 -6
  11. package/dist/audio/bus-graph.js.map +1 -1
  12. package/dist/audio/effect-graph.d.ts +62 -0
  13. package/dist/audio/effect-graph.d.ts.map +1 -0
  14. package/dist/audio/effect-graph.js +234 -0
  15. package/dist/audio/effect-graph.js.map +1 -0
  16. package/dist/audio/index.d.ts.map +1 -1
  17. package/dist/audio/index.js +2 -3
  18. package/dist/audio/index.js.map +1 -1
  19. package/dist/audio/loop-region.js.map +1 -1
  20. package/dist/audio/mixer.d.ts +4 -4
  21. package/dist/audio/mixer.d.ts.map +1 -1
  22. package/dist/audio/mixer.js +18 -38
  23. package/dist/audio/mixer.js.map +1 -1
  24. package/dist/audio/player.d.ts +12 -6
  25. package/dist/audio/player.d.ts.map +1 -1
  26. package/dist/audio/player.js +49 -62
  27. package/dist/audio/player.js.map +1 -1
  28. package/dist/browser/THIRD_PARTY_LICENSES.txt +379 -27
  29. package/dist/browser/audio.js +1 -1
  30. package/dist/browser/chunks/chunk-F3AZKPHU.js +2 -0
  31. package/dist/browser/chunks/chunk-F3AZKPHU.js.map +7 -0
  32. package/dist/browser/chunks/chunk-KLXMRKTL.js +2 -0
  33. package/dist/browser/chunks/chunk-L5UFQACH.js +163 -0
  34. package/dist/browser/chunks/chunk-L5UFQACH.js.map +7 -0
  35. package/dist/browser/chunks/mediabunny-mp3-encoder-ZCIHVVTJ.js +14 -0
  36. package/dist/browser/chunks/mediabunny-mp3-encoder-ZCIHVVTJ.js.map +7 -0
  37. package/dist/browser/index.js +51 -1343
  38. package/dist/browser/index.js.map +4 -4
  39. package/dist/browser/manifest.json +8 -8
  40. package/dist/browser/ms_renderer_bg.wasm +0 -0
  41. package/dist/concat.d.ts +33 -7
  42. package/dist/concat.d.ts.map +1 -1
  43. package/dist/concat.js +117 -72
  44. package/dist/concat.js.map +1 -1
  45. package/dist/deliver.d.ts +10 -0
  46. package/dist/deliver.d.ts.map +1 -0
  47. package/dist/deliver.js +22 -0
  48. package/dist/deliver.js.map +1 -0
  49. package/dist/dynamic-nodes.d.ts.map +1 -1
  50. package/dist/dynamic-nodes.js +2 -3
  51. package/dist/dynamic-nodes.js.map +1 -1
  52. package/dist/engine.d.ts +37 -9
  53. package/dist/engine.d.ts.map +1 -1
  54. package/dist/engine.js +41 -32
  55. package/dist/engine.js.map +1 -1
  56. package/dist/export-audio.d.ts +36 -0
  57. package/dist/export-audio.d.ts.map +1 -0
  58. package/dist/export-audio.js +56 -0
  59. package/dist/export-audio.js.map +1 -0
  60. package/dist/exporter.d.ts +125 -14
  61. package/dist/exporter.d.ts.map +1 -1
  62. package/dist/exporter.js +133 -89
  63. package/dist/exporter.js.map +1 -1
  64. package/dist/formats.d.ts +36 -0
  65. package/dist/formats.d.ts.map +1 -0
  66. package/dist/formats.js +83 -0
  67. package/dist/formats.js.map +1 -0
  68. package/dist/frame-loop.d.ts +190 -0
  69. package/dist/frame-loop.d.ts.map +1 -0
  70. package/dist/frame-loop.js +304 -0
  71. package/dist/frame-loop.js.map +1 -0
  72. package/dist/index.d.ts +12 -11
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +17 -21
  75. package/dist/index.js.map +1 -1
  76. package/dist/mix-timeline-audio.d.ts +5 -5
  77. package/dist/mix-timeline-audio.d.ts.map +1 -1
  78. package/dist/mix-timeline-audio.js +10 -12
  79. package/dist/mix-timeline-audio.js.map +1 -1
  80. package/dist/parallel/export-parallel.d.ts +32 -0
  81. package/dist/parallel/export-parallel.d.ts.map +1 -0
  82. package/dist/parallel/export-parallel.js +114 -0
  83. package/dist/parallel/export-parallel.js.map +1 -0
  84. package/dist/parallel/governor.d.ts +57 -0
  85. package/dist/parallel/governor.d.ts.map +1 -0
  86. package/dist/parallel/governor.js +79 -0
  87. package/dist/parallel/governor.js.map +1 -0
  88. package/dist/parallel/lane.d.ts +45 -0
  89. package/dist/parallel/lane.d.ts.map +1 -0
  90. package/dist/parallel/lane.js +2 -0
  91. package/dist/parallel/lane.js.map +1 -0
  92. package/dist/parallel/lanes.d.ts +20 -0
  93. package/dist/parallel/lanes.d.ts.map +1 -0
  94. package/dist/parallel/lanes.js +152 -0
  95. package/dist/parallel/lanes.js.map +1 -0
  96. package/dist/parallel/plan.d.ts +23 -0
  97. package/dist/parallel/plan.d.ts.map +1 -0
  98. package/dist/parallel/plan.js +30 -0
  99. package/dist/parallel/plan.js.map +1 -0
  100. package/dist/parallel/run-lanes.d.ts +38 -0
  101. package/dist/parallel/run-lanes.d.ts.map +1 -0
  102. package/dist/parallel/run-lanes.js +164 -0
  103. package/dist/parallel/run-lanes.js.map +1 -0
  104. package/dist/parallel/session.d.ts +9 -0
  105. package/dist/parallel/session.d.ts.map +1 -0
  106. package/dist/parallel/session.js +79 -0
  107. package/dist/parallel/session.js.map +1 -0
  108. package/dist/renderer.d.ts +22 -0
  109. package/dist/renderer.d.ts.map +1 -0
  110. package/dist/renderer.js +23 -0
  111. package/dist/renderer.js.map +1 -0
  112. package/dist/screenshot.d.ts +4 -4
  113. package/dist/screenshot.d.ts.map +1 -1
  114. package/dist/screenshot.js +3 -3
  115. package/dist/screenshot.js.map +1 -1
  116. package/dist/still.d.ts +17 -19
  117. package/dist/still.d.ts.map +1 -1
  118. package/dist/still.js +27 -34
  119. package/dist/still.js.map +1 -1
  120. package/dist/ticker.d.ts +22 -8
  121. package/dist/ticker.d.ts.map +1 -1
  122. package/dist/ticker.js +55 -20
  123. package/dist/ticker.js.map +1 -1
  124. package/package.json +9 -10
  125. package/dist/audio/filter-graph.d.ts +0 -59
  126. package/dist/audio/filter-graph.d.ts.map +0 -1
  127. package/dist/audio/filter-graph.js +0 -241
  128. package/dist/audio/filter-graph.js.map +0 -1
  129. package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js +0 -2
  130. package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js.map +0 -7
  131. package/dist/browser/chunks/SkeletonUtils-XYDMTGTM.js +0 -2
  132. package/dist/browser/chunks/chunk-2MAFJWBZ.js +0 -2
  133. package/dist/browser/chunks/chunk-2MAFJWBZ.js.map +0 -7
  134. package/dist/browser/chunks/chunk-CTLBXZSU.js +0 -4119
  135. package/dist/browser/chunks/chunk-CTLBXZSU.js.map +0 -7
  136. package/dist/browser/chunks/chunk-FQ32PDIU.js +0 -2
  137. package/dist/browser/chunks/chunk-FQ32PDIU.js.map +0 -7
  138. package/dist/browser/chunks/chunk-FY3BTKY4.js +0 -2
  139. package/dist/browser/chunks/chunk-FY3BTKY4.js.map +0 -7
  140. package/dist/browser/chunks/three.module-Z4EOVUWF.js +0 -2
  141. package/dist/browser/chunks/three.module-Z4EOVUWF.js.map +0 -7
  142. package/dist/decode/image-levels.d.ts +0 -137
  143. package/dist/decode/image-levels.d.ts.map +0 -1
  144. package/dist/decode/image-levels.js +0 -477
  145. package/dist/decode/image-levels.js.map +0 -1
  146. package/dist/decode/levels.d.ts +0 -37
  147. package/dist/decode/levels.d.ts.map +0 -1
  148. package/dist/decode/levels.js +0 -68
  149. package/dist/decode/levels.js.map +0 -1
  150. package/dist/decode/video-levels.d.ts +0 -37
  151. package/dist/decode/video-levels.d.ts.map +0 -1
  152. package/dist/decode/video-levels.js +0 -95
  153. package/dist/decode/video-levels.js.map +0 -1
  154. package/dist/getter.d.ts +0 -10
  155. package/dist/getter.d.ts.map +0 -1
  156. package/dist/getter.js +0 -38
  157. package/dist/getter.js.map +0 -1
  158. package/dist/measure-context.d.ts +0 -12
  159. package/dist/measure-context.d.ts.map +0 -1
  160. package/dist/measure-context.js +0 -12
  161. package/dist/measure-context.js.map +0 -1
  162. package/dist/render-context.d.ts +0 -32
  163. package/dist/render-context.d.ts.map +0 -1
  164. package/dist/render-context.js +0 -59
  165. package/dist/render-context.js.map +0 -1
  166. package/dist/storage-adapter.d.ts +0 -367
  167. package/dist/storage-adapter.d.ts.map +0 -1
  168. package/dist/storage-adapter.js +0 -1280
  169. package/dist/storage-adapter.js.map +0 -1
  170. package/dist/three/renderer.d.ts +0 -47
  171. package/dist/three/renderer.d.ts.map +0 -1
  172. package/dist/three/renderer.js +0 -182
  173. package/dist/three/renderer.js.map +0 -1
  174. /package/dist/browser/chunks/{SkeletonUtils-XYDMTGTM.js.map → chunk-KLXMRKTL.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,27 +1,28 @@
1
- // @motionscript/browser — CanvasKit/Skia-based renderer for @motionscript/core,
2
- // running in the browser (canvas mount, video export, audio playback/clock).
3
- export { WebRenderContext } from "./render-context";
4
- export { WebStorageAdapter } from "./storage-adapter";
1
+ // @motionscript/browser — @motionscript/core in the browser: WebGPU surfaces
2
+ // (through @motionscript/renderer), video export, audio playback and clock.
5
3
  // The seam letting a host answer for an asset's bytes before the adapter goes
6
4
  // to the network — see `asset-bytes.ts` for why it is module-level.
7
- export { setAssetByteSource } from "./asset-bytes";
8
- export { getCanvasKit } from "./getter";
9
- // Re-exported from @motionscript/skia-render, whose canonical home these are —
10
- // kept on this barrel so the specifier consumers already use keeps resolving.
11
- export { EffectRegistry } from "@motionscript/skia-render";
5
+ export { loadAssetBytes, setAssetByteSource } from "./asset-bytes";
12
6
  export { exportVideo, } from "./exporter";
7
+ // Mixes the timeline's audio and encodes it alone, without rendering a frame.
8
+ export { exportAudio, NoAudioError } from "./export-audio";
9
+ export { EXPORT_FORMATS, } from "./formats";
13
10
  // mediabunny's resolution-aware quality levels, re-exported so a host can pick
14
11
  // one for `ExportParams.video.bitrate` without taking a direct dependency on the
15
12
  // encoder library this package happens to use.
16
13
  export { QUALITY_VERY_LOW, QUALITY_LOW, QUALITY_MEDIUM, QUALITY_HIGH, QUALITY_VERY_HIGH, } from "mediabunny";
17
14
  export { exportScreenshot } from "./screenshot";
18
15
  // Thrown by a still and an export alike for a frame whose every track failed to build.
19
- export { NothingToDrawError } from "@motionscript/skia-render";
20
- // Joins separately-encoded MP4s end to end without re-encoding. What makes it
16
+ export { NothingToDrawError } from "./frame-loop";
17
+ // Joins separately-encoded MP4s (or WebMs) end to end without re-encoding. What makes it
21
18
  // worth rendering an export in parallel at all: the parts still have to become
22
19
  // one file, and re-encoding to join them gives back everything the parallelism
23
20
  // won. See `concat.ts` for the two things a caller has to guarantee.
24
- export { concatVideoSegments, ConcatMismatchError } from "./concat";
21
+ export { concatVideoSegments, createSegmentJoiner, ConcatMismatchError } from "./concat";
22
+ // One export across several renderers: chunks of frames on lanes the run admits
23
+ // while they add throughput, joined as they land. See `parallel/export-parallel.ts`.
24
+ export { exportVideoParallel } from "./parallel/export-parallel";
25
+ export { localLane, messagePortLane, serveExportLane } from "./parallel/lanes";
25
26
  // Mixes a timeline's audio without rendering a frame — the other half of a split
26
27
  // export, whose video arrives in separately-encoded pieces that AAC cannot be
27
28
  // concatenated across. See `mix-timeline-audio.ts`.
@@ -32,16 +33,11 @@ export { mixTimelineAudio } from "./mix-timeline-audio";
32
33
  export { StillRenderer, createStillRenderer } from "./still";
33
34
  export { WebAudioSink as WebAudioPlayer } from "./audio/player";
34
35
  // Mixdown. Also on the `@motionscript/browser/audio` subpath, which is the
35
- // import to prefer — it reaches it without loading CanvasKit, since none of it
36
- // draws.
36
+ // import to prefer — it reaches it without loading the renderer.
37
37
  export { mixAudio, encodeWav } from "./audio/mixer";
38
- export { WebMeasureContext } from "./measure-context";
39
38
  export { WebTicker } from "./ticker";
40
39
  export { createBrowserEngine } from "./engine";
41
- // ─── 3D ──────────────────────────────────────────────────────────────────────
42
- // The reconciler, handlers and lazy `three` boundary all live in
43
- // @motionscript/skia-render now; this package supplies only the WebGL renderer
44
- // that rasterizes what they build. Re-exported here so the specifiers consumers
45
- // already use keep resolving.
46
- export { Canvas3DBackend, canvas3DBackend, loadCanvas3D, disposeCanvas3DBackend } from "@motionscript/skia-render";
40
+ export { WebGpuUnavailableError } from "./renderer";
41
+ // The renderer's host-facing surface, so a host needs no second import.
42
+ export { isWgpuSupported, onWgpuEvent, ShaderPreview, validateShader, WGSL_PRELUDE } from "@motionscript/renderer";
47
43
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,6EAA6E;AAE7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,8EAA8E;AAC9E,oEAAoE;AACpE,OAAO,EAAE,kBAAkB,EAAwB,MAAM,eAAe,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,+EAA+E;AAC/E,8EAA8E;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,OAAO,EACH,WAAW,GAKd,MAAM,YAAY,CAAC;AACpB,+EAA+E;AAC/E,iFAAiF;AACjF,+CAA+C;AAC/C,OAAO,EACH,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,YAAY,EACZ,iBAAiB,GAEpB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,gBAAgB,EAAuF,MAAM,cAAc,CAAC;AACrI,uFAAuF;AACvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,8EAA8E;AAC9E,+EAA+E;AAC/E,+EAA+E;AAC/E,qEAAqE;AACrE,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAsB,MAAM,UAAU,CAAC;AACxF,iFAAiF;AACjF,8EAA8E;AAC9E,oDAAoD;AACpD,OAAO,EAAE,gBAAgB,EAA+B,MAAM,sBAAsB,CAAC;AACrF,mFAAmF;AACnF,qFAAqF;AACrF,yCAAyC;AACzC,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAQ7D,OAAO,EAAE,YAAY,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChE,2EAA2E;AAC3E,+EAA+E;AAC/E,SAAS;AACT,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAwB,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAG/C,gFAAgF;AAEhF,iEAAiE;AACjE,+EAA+E;AAC/E,gFAAgF;AAChF,8BAA8B;AAC9B,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC","sourcesContent":["// @motionscript/browser — CanvasKit/Skia-based renderer for @motionscript/core,\r\n// running in the browser (canvas mount, video export, audio playback/clock).\r\n\r\nexport { WebRenderContext } from \"./render-context\";\r\nexport { WebStorageAdapter } from \"./storage-adapter\";\r\n// The seam letting a host answer for an asset's bytes before the adapter goes\r\n// to the network — see `asset-bytes.ts` for why it is module-level.\r\nexport { setAssetByteSource, type AssetByteSource } from \"./asset-bytes\";\r\nexport { getCanvasKit } from \"./getter\";\r\n// Re-exported from @motionscript/skia-render, whose canonical home these are —\r\n// kept on this barrel so the specifier consumers already use keeps resolving.\r\nexport { EffectRegistry } from \"@motionscript/skia-render\";\r\nexport type { EffectHandler, EffectGeometry, EffectResources, EffectTarget, RenderEffect } from \"@motionscript/skia-render\";\r\nexport {\r\n exportVideo,\r\n type ExportParams,\r\n type ExportProgressCallback,\r\n type VideoEncoderOptions,\r\n type AudioEncoderOptions,\r\n} from \"./exporter\";\r\n// mediabunny's resolution-aware quality levels, re-exported so a host can pick\r\n// one for `ExportParams.video.bitrate` without taking a direct dependency on the\r\n// encoder library this package happens to use.\r\nexport {\r\n QUALITY_VERY_LOW,\r\n QUALITY_LOW,\r\n QUALITY_MEDIUM,\r\n QUALITY_HIGH,\r\n QUALITY_VERY_HIGH,\r\n type Quality,\r\n} from \"mediabunny\";\r\nexport { exportScreenshot, type ScreenshotParams, type ScreenshotResult, type ScreenshotFormat, type FrameSpec } from \"./screenshot\";\r\n// Thrown by a still and an export alike for a frame whose every track failed to build.\r\nexport { NothingToDrawError } from \"@motionscript/skia-render\";\r\n// Joins separately-encoded MP4s end to end without re-encoding. What makes it\r\n// worth rendering an export in parallel at all: the parts still have to become\r\n// one file, and re-encoding to join them gives back everything the parallelism\r\n// won. See `concat.ts` for the two things a caller has to guarantee.\r\nexport { concatVideoSegments, ConcatMismatchError, type ConcatOptions } from \"./concat\";\r\n// Mixes a timeline's audio without rendering a frame — the other half of a split\r\n// export, whose video arrives in separately-encoded pieces that AAC cannot be\r\n// concatenated across. See `mix-timeline-audio.ts`.\r\nexport { mixTimelineAudio, type MixTimelineAudioParams } from \"./mix-timeline-audio\";\r\n// Single frames as a first-class capability: one long-lived renderer that repaints\r\n// into a canvas the host owns, for thumbnails and still previews. `exportScreenshot`\r\n// above is the one-shot wrapper over it.\r\nexport { StillRenderer, createStillRenderer } from \"./still\";\r\nexport type {\r\n StillRendererOptions,\r\n RenderStillOptions,\r\n EncodeStillOptions,\r\n StillSource,\r\n FrameRef,\r\n} from \"./still\";\r\nexport { WebAudioSink as WebAudioPlayer } from \"./audio/player\";\r\n// Mixdown. Also on the `@motionscript/browser/audio` subpath, which is the\r\n// import to prefer — it reaches it without loading CanvasKit, since none of it\r\n// draws.\r\nexport { mixAudio, encodeWav, type MixAudioOptions } from \"./audio/mixer\";\r\nexport { WebMeasureContext } from \"./measure-context\";\r\nexport { WebTicker } from \"./ticker\";\r\n\r\nexport { createBrowserEngine } from \"./engine\";\r\nexport type { BrowserEngineOptions, BrowserEncodeOptions } from \"./engine\";\r\n\r\n// ─── 3D ──────────────────────────────────────────────────────────────────────\r\n\r\n// The reconciler, handlers and lazy `three` boundary all live in\r\n// @motionscript/skia-render now; this package supplies only the WebGL renderer\r\n// that rasterizes what they build. Re-exported here so the specifiers consumers\r\n// already use keep resolving.\r\nexport { Canvas3DBackend, canvas3DBackend, loadCanvas3D, disposeCanvas3DBackend } from \"@motionscript/skia-render\";\r\nexport type { Canvas3DAssets, RenderedCanvas3D } from \"@motionscript/skia-render\";\r\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,4EAA4E;AAE5E,8EAA8E;AAC9E,oEAAoE;AACpE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAwB,MAAM,eAAe,CAAC;AACzF,OAAO,EACH,WAAW,GAKd,MAAM,YAAY,CAAC;AACpB,8EAA8E;AAC9E,OAAO,EAAE,WAAW,EAAE,YAAY,EAA0B,MAAM,gBAAgB,CAAC;AACnF,OAAO,EACH,cAAc,GAKjB,MAAM,WAAW,CAAC;AACnB,+EAA+E;AAC/E,iFAAiF;AACjF,+CAA+C;AAC/C,OAAO,EACH,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,YAAY,EACZ,iBAAiB,GAEpB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,gBAAgB,EAAuF,MAAM,cAAc,CAAC;AACrI,uFAAuF;AACvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,yFAAyF;AACzF,+EAA+E;AAC/E,+EAA+E;AAC/E,qEAAqE;AACrE,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,mBAAmB,EAA0C,MAAM,UAAU,CAAC;AACjI,gFAAgF;AAChF,qFAAqF;AACrF,OAAO,EAAE,mBAAmB,EAA6B,MAAM,4BAA4B,CAAC;AAC5F,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,eAAe,EAA+B,MAAM,kBAAkB,CAAC;AAG5G,iFAAiF;AACjF,8EAA8E;AAC9E,oDAAoD;AACpD,OAAO,EAAE,gBAAgB,EAA+B,MAAM,sBAAsB,CAAC;AACrF,mFAAmF;AACnF,qFAAqF;AACrF,yCAAyC;AACzC,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAQ7D,OAAO,EAAE,YAAY,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChE,2EAA2E;AAC3E,iEAAiE;AACjE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAwB,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAE/C,OAAO,EAAE,sBAAsB,EAA2B,MAAM,YAAY,CAAC;AAE7E,wEAAwE;AACxE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC","sourcesContent":["// @motionscript/browser — @motionscript/core in the browser: WebGPU surfaces\n// (through @motionscript/renderer), video export, audio playback and clock.\n\n// The seam letting a host answer for an asset's bytes before the adapter goes\n// to the network — see `asset-bytes.ts` for why it is module-level.\nexport { loadAssetBytes, setAssetByteSource, type AssetByteSource } from \"./asset-bytes\";\nexport {\n exportVideo,\n type ExportParams,\n type ExportProgressCallback,\n type VideoEncoderOptions,\n type AudioEncoderOptions,\n} from \"./exporter\";\n// Mixes the timeline's audio and encodes it alone, without rendering a frame.\nexport { exportAudio, NoAudioError, type ExportAudioParams } from \"./export-audio\";\nexport {\n EXPORT_FORMATS,\n type ExportFormat,\n type ExportFormatInfo,\n type VideoFormat,\n type AudioFormat,\n} from \"./formats\";\n// mediabunny's resolution-aware quality levels, re-exported so a host can pick\n// one for `ExportParams.video.bitrate` without taking a direct dependency on the\n// encoder library this package happens to use.\nexport {\n QUALITY_VERY_LOW,\n QUALITY_LOW,\n QUALITY_MEDIUM,\n QUALITY_HIGH,\n QUALITY_VERY_HIGH,\n type Quality,\n} from \"mediabunny\";\nexport { exportScreenshot, type ScreenshotParams, type ScreenshotResult, type ScreenshotFormat, type FrameSpec } from \"./screenshot\";\n// Thrown by a still and an export alike for a frame whose every track failed to build.\nexport { NothingToDrawError } from \"./frame-loop\";\n// Joins separately-encoded MP4s (or WebMs) end to end without re-encoding. What makes it\n// worth rendering an export in parallel at all: the parts still have to become\n// one file, and re-encoding to join them gives back everything the parallelism\n// won. See `concat.ts` for the two things a caller has to guarantee.\nexport { concatVideoSegments, createSegmentJoiner, ConcatMismatchError, type ConcatOptions, type SegmentJoiner } from \"./concat\";\n// One export across several renderers: chunks of frames on lanes the run admits\n// while they add throughput, joined as they land. See `parallel/export-parallel.ts`.\nexport { exportVideoParallel, type ParallelExportParams } from \"./parallel/export-parallel\";\nexport { localLane, messagePortLane, serveExportLane, type MessagePortLaneOptions } from \"./parallel/lanes\";\nexport type { ExportLane, LaneEnvironment, LaneReport, LaneSession, SegmentSpec } from \"./parallel/lane\";\nexport type { ExportChunk } from \"./parallel/plan\";\n// Mixes a timeline's audio without rendering a frame — the other half of a split\n// export, whose video arrives in separately-encoded pieces that AAC cannot be\n// concatenated across. See `mix-timeline-audio.ts`.\nexport { mixTimelineAudio, type MixTimelineAudioParams } from \"./mix-timeline-audio\";\n// Single frames as a first-class capability: one long-lived renderer that repaints\n// into a canvas the host owns, for thumbnails and still previews. `exportScreenshot`\n// above is the one-shot wrapper over it.\nexport { StillRenderer, createStillRenderer } from \"./still\";\nexport type {\n StillRendererOptions,\n RenderStillOptions,\n EncodeStillOptions,\n StillSource,\n FrameRef,\n} from \"./still\";\nexport { WebAudioSink as WebAudioPlayer } from \"./audio/player\";\n// Mixdown. Also on the `@motionscript/browser/audio` subpath, which is the\n// import to prefer — it reaches it without loading the renderer.\nexport { mixAudio, encodeWav, type MixAudioOptions } from \"./audio/mixer\";\nexport { WebTicker } from \"./ticker\";\n\nexport { createBrowserEngine } from \"./engine\";\nexport type { BrowserEngineOptions, BrowserEncodeOptions } from \"./engine\";\nexport { WebGpuUnavailableError, type RendererWasmOption } from \"./renderer\";\n\n// The renderer's host-facing surface, so a host needs no second import.\nexport { isWgpuSupported, onWgpuEvent, ShaderPreview, validateShader, WGSL_PRELUDE } from \"@motionscript/renderer\";\nexport type {\n WgpuEvent, ShaderDiagnostic, ShaderField, ShaderPreviewInput, ShaderReport, ShaderScope, ShaderStatus,\n} from \"@motionscript/renderer\";\n"]}
@@ -1,5 +1,6 @@
1
1
  import { Registry, type AssetManifest, type ProjectDocument, type VisualDocument, type Size2D } from "@motionscript/core";
2
- export interface MixTimelineAudioParams {
2
+ import { type RendererWasmOption } from "./renderer";
3
+ export interface MixTimelineAudioParams extends RendererWasmOption {
3
4
  /** A project, or a lone still or composition as the one track of one. */
4
5
  document: ProjectDocument | VisualDocument;
5
6
  /** The vocabulary its clips compile against. Defaults to what the library ships. */
@@ -10,7 +11,6 @@ export interface MixTimelineAudioParams {
10
11
  manifest?: AssetManifest;
11
12
  /** Mixdown rate. Defaults to the mixer's own (44100). */
12
13
  sampleRate?: number;
13
- wasmUrl?: string;
14
14
  }
15
15
  /**
16
16
  * Mix a timeline's audio without rendering a single frame.
@@ -34,9 +34,9 @@ export interface MixTimelineAudioParams {
34
34
  * that puts its nodes in the tree, so there is no way to know what plays when
35
35
  * without it.
36
36
  *
37
- * Nothing is drawn, so this takes a {@link WebMeasureContext} rather than a render
38
- * context: discovery lays each clip out, which needs text measured, not
39
- * rasterized. No canvas, no surface, no GPU.
37
+ * Nothing is drawn, so this takes the renderer's measure context rather than a
38
+ * render context: discovery lays each clip out, which needs text measured, not
39
+ * rasterized. No canvas, no surface, no GPU, so WebGPU is not required.
40
40
  */
41
41
  export declare function mixTimelineAudio(params: MixTimelineAudioParams): Promise<AudioBuffer | null>;
42
42
  //# sourceMappingURL=mix-timeline-audio.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mix-timeline-audio.d.ts","sourceRoot":"","sources":["../src/mix-timeline-audio.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,QAAQ,EAER,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,MAAM,EACd,MAAM,oBAAoB,CAAC;AAQ5B,MAAM,WAAW,sBAAsB;IACnC,yEAAyE;IACzE,QAAQ,EAAE,eAAe,GAAG,cAAc,CAAC;IAC3C,oFAAoF;IACpF,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAID;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,gBAAgB,CAClC,MAAM,EAAE,sBAAsB,GAC/B,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA4B7B"}
1
+ {"version":3,"file":"mix-timeline-audio.d.ts","sourceRoot":"","sources":["../src/mix-timeline-audio.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,QAAQ,EAER,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,MAAM,EACd,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAAgB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEnE,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAC9D,yEAAyE;IACzE,QAAQ,EAAE,eAAe,GAAG,cAAc,CAAC;IAC3C,oFAAoF;IACpF,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAID;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,gBAAgB,CAClC,MAAM,EAAE,sBAAsB,GAC/B,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA4B7B"}
@@ -1,9 +1,7 @@
1
1
  import { DEFAULT_OUTPUT, ManifestAssetCatalog, AssetTimeline, Registry, resolveProjectDocument, } from "@motionscript/core";
2
- import { collectAudio } from "@motionscript/skia-render/export";
2
+ import { collectAudio } from "./frame-loop";
3
3
  import { mixAudio } from "./audio/mixer";
4
- import { getCanvasKit } from "./getter";
5
- import { WebMeasureContext } from "./measure-context";
6
- import { WebStorageAdapter } from "./storage-adapter";
4
+ import { loadRenderer } from "./renderer";
7
5
  const EMPTY_MANIFEST = { image: {}, video: {}, audio: {}, font: {} };
8
6
  /**
9
7
  * Mix a timeline's audio without rendering a single frame.
@@ -27,22 +25,22 @@ const EMPTY_MANIFEST = { image: {}, video: {}, audio: {}, font: {} };
27
25
  * that puts its nodes in the tree, so there is no way to know what plays when
28
26
  * without it.
29
27
  *
30
- * Nothing is drawn, so this takes a {@link WebMeasureContext} rather than a render
31
- * context: discovery lays each clip out, which needs text measured, not
32
- * rasterized. No canvas, no surface, no GPU.
28
+ * Nothing is drawn, so this takes the renderer's measure context rather than a
29
+ * render context: discovery lays each clip out, which needs text measured, not
30
+ * rasterized. No canvas, no surface, no GPU, so WebGPU is not required.
33
31
  */
34
32
  export async function mixTimelineAudio(params) {
35
- const { registry = new Registry(), manifest = EMPTY_MANIFEST, sampleRate, wasmUrl } = params;
33
+ const { registry = new Registry(), manifest = EMPTY_MANIFEST, sampleRate } = params;
36
34
  const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);
37
35
  if (project.tracks.length === 0)
38
36
  return null;
39
37
  const viewport = project.viewport;
40
38
  const fps = project.fps;
41
- // CanvasKit only for its font manager — `WebMeasureContext` shapes text through it.
42
- const canvasKit = await getCanvasKit(wasmUrl);
39
+ // The renderer only for its text shaper; nothing is drawn.
40
+ const renderer = await loadRenderer(params, { gpu: false });
43
41
  const catalog = new ManifestAssetCatalog(manifest);
44
- const storage = new WebStorageAdapter(canvasKit, catalog, viewport, fps);
45
- const measurer = new WebMeasureContext(storage);
42
+ const storage = renderer.createStorageAdapter({ catalog, viewport, fps });
43
+ const measurer = renderer.createMeasureContext(storage);
46
44
  // Audio is declared from each node's arrival state, so no asset windows are refined.
47
45
  const precomper = new AssetTimeline(project, { registry, viewport, fps, assets: catalog, measurer }, { assets: false });
48
46
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"mix-timeline-audio.js","sourceRoot":"","sources":["../src/mix-timeline-audio.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,QAAQ,EACR,sBAAsB,GAKzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAEhE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAgBtD,MAAM,cAAc,GAAkB,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAClC,MAA8B;IAE9B,MAAM,EAAE,QAAQ,GAAG,IAAI,QAAQ,EAAE,EAAE,QAAQ,GAAG,cAAc,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IAC7F,MAAM,OAAO,GAAG,sBAAsB,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IAChF,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAS,CAAC;IACnC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAI,CAAC;IAEzB,oFAAoF;IACpF,MAAM,SAAS,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,IAAI,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,IAAI,iBAAiB,CAAC,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;IACzE,MAAM,QAAQ,GAAG,IAAI,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAEhD,qFAAqF;IACrF,MAAM,SAAS,GAAG,IAAI,aAAa,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACxH,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC;QAEhC,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QACxC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAExC,OAAO,MAAM,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,aAAa,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;IACrF,CAAC;YAAS,CAAC;QACP,SAAS,CAAC,OAAO,EAAE,CAAC;QACpB,0EAA0E;QAC1E,uCAAuC;QACvC,OAAO,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC;AACL,CAAC","sourcesContent":["import {\r\n DEFAULT_OUTPUT,\r\n ManifestAssetCatalog,\r\n AssetTimeline,\r\n Registry,\r\n resolveProjectDocument,\r\n type AssetManifest,\r\n type ProjectDocument,\r\n type VisualDocument,\r\n type Size2D,\r\n} from \"@motionscript/core\";\r\nimport { collectAudio } from \"@motionscript/skia-render/export\";\r\n\r\nimport { mixAudio } from \"./audio/mixer\";\r\nimport { getCanvasKit } from \"./getter\";\r\nimport { WebMeasureContext } from \"./measure-context\";\r\nimport { WebStorageAdapter } from \"./storage-adapter\";\r\n\r\nexport interface MixTimelineAudioParams {\r\n /** A project, or a lone still or composition as the one track of one. */\r\n document: ProjectDocument | VisualDocument;\r\n /** The vocabulary its clips compile against. Defaults to what the library ships. */\r\n registry?: Registry;\r\n /** Win over the document's own; 1920×1080 at 60 fps when neither says. */\r\n viewport?: Size2D;\r\n fps?: number;\r\n manifest?: AssetManifest;\r\n /** Mixdown rate. Defaults to the mixer's own (44100). */\r\n sampleRate?: number;\r\n wasmUrl?: string;\r\n}\r\n\r\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\r\n\r\n/**\r\n * Mix a timeline's audio without rendering a single frame.\r\n *\r\n * `exportVideo` normally does this as part of the render, and for a\r\n * single-threaded export that is exactly right. A **split** export cannot: its\r\n * video is rendered in pieces by separate workers, and concatenating\r\n * separately-encoded AAC puts the encoder's priming delay at every join — a click\r\n * at each boundary and a track that drifts further out of sync with every one. So\r\n * the coordinator mixes the whole timeline once, here, and the joined file gets a\r\n * single continuous audio track.\r\n *\r\n * Returns `null` when the timeline schedules no audio at all, which is the common\r\n * case and not a failure — the caller should then mux video only rather than\r\n * declare an empty track.\r\n *\r\n * ## Why this discovers\r\n *\r\n * Audio requests are an *output of discovery*: each node declares its clips as it\r\n * enters the tree, and the pass that finds what a clip's frames draw is the one\r\n * that puts its nodes in the tree, so there is no way to know what plays when\r\n * without it.\r\n *\r\n * Nothing is drawn, so this takes a {@link WebMeasureContext} rather than a render\r\n * context: discovery lays each clip out, which needs text measured, not\r\n * rasterized. No canvas, no surface, no GPU.\r\n */\r\nexport async function mixTimelineAudio(\r\n params: MixTimelineAudioParams,\r\n): Promise<AudioBuffer | null> {\r\n const { registry = new Registry(), manifest = EMPTY_MANIFEST, sampleRate, wasmUrl } = params;\r\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\r\n if (project.tracks.length === 0) return null;\r\n const viewport = project.viewport!;\r\n const fps = project.fps!;\r\n\r\n // CanvasKit only for its font manager — `WebMeasureContext` shapes text through it.\r\n const canvasKit = await getCanvasKit(wasmUrl);\r\n const catalog = new ManifestAssetCatalog(manifest);\r\n const storage = new WebStorageAdapter(canvasKit, catalog, viewport, fps);\r\n const measurer = new WebMeasureContext(storage);\r\n\r\n // Audio is declared from each node's arrival state, so no asset windows are refined.\r\n const precomper = new AssetTimeline(project, { registry, viewport, fps, assets: catalog, measurer }, { assets: false });\r\n try {\r\n const precomp = precomper.run();\r\n\r\n const scheduled = collectAudio(precomp);\r\n if (scheduled.length === 0) return null;\r\n\r\n return await mixAudio(scheduled, precomp.totalDuration, { sampleRate, catalog });\r\n } finally {\r\n precomper.dispose();\r\n // The adapter holds decode sessions and cached images; without this a mix\r\n // leaks them for the life of the page.\r\n storage.dispose();\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"mix-timeline-audio.js","sourceRoot":"","sources":["../src/mix-timeline-audio.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,QAAQ,EACR,sBAAsB,GAKzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAA2B,MAAM,YAAY,CAAC;AAenE,MAAM,cAAc,GAAkB,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAClC,MAA8B;IAE9B,MAAM,EAAE,QAAQ,GAAG,IAAI,QAAQ,EAAE,EAAE,QAAQ,GAAG,cAAc,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;IACpF,MAAM,OAAO,GAAG,sBAAsB,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IAChF,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAS,CAAC;IACnC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAI,CAAC;IAEzB,2DAA2D;IAC3D,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5D,MAAM,OAAO,GAAG,IAAI,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;IAC1E,MAAM,QAAQ,GAAG,QAAQ,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAExD,qFAAqF;IACrF,MAAM,SAAS,GAAG,IAAI,aAAa,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACxH,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC;QAEhC,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QACxC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAExC,OAAO,MAAM,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,aAAa,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;IACrF,CAAC;YAAS,CAAC;QACP,SAAS,CAAC,OAAO,EAAE,CAAC;QACpB,0EAA0E;QAC1E,uCAAuC;QACvC,OAAO,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC;AACL,CAAC","sourcesContent":["import {\n DEFAULT_OUTPUT,\n ManifestAssetCatalog,\n AssetTimeline,\n Registry,\n resolveProjectDocument,\n type AssetManifest,\n type ProjectDocument,\n type VisualDocument,\n type Size2D,\n} from \"@motionscript/core\";\nimport { collectAudio } from \"./frame-loop\";\n\nimport { mixAudio } from \"./audio/mixer\";\nimport { loadRenderer, type RendererWasmOption } from \"./renderer\";\n\nexport interface MixTimelineAudioParams extends 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 manifest?: AssetManifest;\n /** Mixdown rate. Defaults to the mixer's own (44100). */\n sampleRate?: number;\n}\n\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\n\n/**\n * Mix a timeline's audio without rendering a single frame.\n *\n * `exportVideo` normally does this as part of the render, and for a\n * single-threaded export that is exactly right. A **split** export cannot: its\n * video is rendered in pieces by separate workers, and concatenating\n * separately-encoded AAC puts the encoder's priming delay at every join — a click\n * at each boundary and a track that drifts further out of sync with every one. So\n * the coordinator mixes the whole timeline once, here, and the joined file gets a\n * single continuous audio track.\n *\n * Returns `null` when the timeline schedules no audio at all, which is the common\n * case and not a failure — the caller should then mux video only rather than\n * declare an empty track.\n *\n * ## Why this discovers\n *\n * Audio requests are an *output of discovery*: each node declares its clips as it\n * enters the tree, and the pass that finds what a clip's frames draw is the one\n * that puts its nodes in the tree, so there is no way to know what plays when\n * without it.\n *\n * Nothing is drawn, so this takes the renderer's measure context rather than a\n * render context: discovery lays each clip out, which needs text measured, not\n * rasterized. No canvas, no surface, no GPU, so WebGPU is not required.\n */\nexport async function mixTimelineAudio(\n params: MixTimelineAudioParams,\n): Promise<AudioBuffer | null> {\n const { registry = new Registry(), manifest = EMPTY_MANIFEST, sampleRate } = params;\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\n if (project.tracks.length === 0) return null;\n const viewport = project.viewport!;\n const fps = project.fps!;\n\n // The renderer only for its text shaper; nothing is drawn.\n const renderer = await loadRenderer(params, { gpu: false });\n const catalog = new ManifestAssetCatalog(manifest);\n const storage = renderer.createStorageAdapter({ catalog, viewport, fps });\n const measurer = renderer.createMeasureContext(storage);\n\n // Audio is declared from each node's arrival state, so no asset windows are refined.\n const precomper = new AssetTimeline(project, { registry, viewport, fps, assets: catalog, measurer }, { assets: false });\n try {\n const precomp = precomper.run();\n\n const scheduled = collectAudio(precomp);\n if (scheduled.length === 0) return null;\n\n return await mixAudio(scheduled, precomp.totalDuration, { sampleRate, catalog });\n } finally {\n precomper.dispose();\n // The adapter holds decode sessions and cached images; without this a mix\n // leaks them for the life of the page.\n storage.dispose();\n }\n}\n"]}
@@ -0,0 +1,32 @@
1
+ import { type ConcatOptions } from "../concat";
2
+ import { type ExportParams } from "../exporter";
3
+ import type { ExportLane } from "./lane";
4
+ export interface ParallelExportParams extends Omit<ExportParams, "frames" | "returnBytes" | "filename"> {
5
+ /**
6
+ * Where the frames are drawn, most preferred first. Every run starts on the
7
+ * first alone and admits the next only while that raises throughput (see
8
+ * `LaneGovernor`), so the first should be the warmest: usually `localLane()`.
9
+ */
10
+ lanes: readonly ExportLane[];
11
+ /** Write the file here as it is joined; the call then resolves to an empty array. See `ConcatOptions.stream`. */
12
+ stream?: ConcatOptions["stream"];
13
+ /** Seconds per chunk; by default from the timeline's length and the lane count. */
14
+ chunkSeconds?: number;
15
+ /** Handed to every lane in its spec, for what only the host knows (see `serveExportLane`). */
16
+ host?: unknown;
17
+ /** How many lanes are rendering, each time that changes. */
18
+ onLanes?: (active: number) => void;
19
+ }
20
+ /**
21
+ * {@link exportVideo}, rendered in chunks across `lanes` and joined into one
22
+ * file without re-encoding.
23
+ *
24
+ * Each chunk is a frame range of the **whole** project, so layered and gapped
25
+ * timelines split like any other. Every chunk is encoded with one pinned
26
+ * configuration, the audio is mixed once over the whole timeline while the frames
27
+ * render (joined AAC would click at every seam), and each chunk is muxed the
28
+ * moment it and the ones before it are done. Progress is frames drawn across
29
+ * every lane, so it climbs smoothly and never goes back.
30
+ */
31
+ export declare function exportVideoParallel(params: ParallelExportParams): Promise<Uint8Array>;
32
+ //# sourceMappingURL=export-parallel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-parallel.d.ts","sourceRoot":"","sources":["../../src/parallel/export-parallel.ts"],"names":[],"mappings":"AACA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAA4D,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAG1G,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,QAAQ,CAAC;AAItD,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,YAAY,EAAE,QAAQ,GAAG,aAAa,GAAG,UAAU,CAAC;IACnG;;;;OAIG;IACH,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IAC7B,iHAAiH;IACjH,MAAM,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACjC,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8FAA8F;IAC9F,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,4DAA4D;IAC5D,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAOD;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,UAAU,CAAC,CA4F3F"}
@@ -0,0 +1,114 @@
1
+ import { DEFAULT_OUTPUT, placeTracks, Registry, renderIssueKey, resolveProjectDocument, totalFramesOf } from "@motionscript/core";
2
+ import { createSegmentJoiner } from "../concat";
3
+ import { exportGeometry, plainVideoEncoding, resolveVideoEncoding } from "../exporter";
4
+ import { formatSpec } from "../formats";
5
+ import { mixTimelineAudio } from "../mix-timeline-audio";
6
+ import { planChunks } from "./plan";
7
+ import { runLanes } from "./run-lanes";
8
+ const EMPTY_MANIFEST = { image: {}, video: {}, audio: {}, font: {} };
9
+ const DEFAULT_SUPERSAMPLE = 2;
10
+ /** The share of progress drawing takes; finishing the file is the rest. */
11
+ const DRAWN = 0.97;
12
+ /**
13
+ * {@link exportVideo}, rendered in chunks across `lanes` and joined into one
14
+ * file without re-encoding.
15
+ *
16
+ * Each chunk is a frame range of the **whole** project, so layered and gapped
17
+ * timelines split like any other. Every chunk is encoded with one pinned
18
+ * configuration, the audio is mixed once over the whole timeline while the frames
19
+ * render (joined AAC would click at every seam), and each chunk is muxed the
20
+ * moment it and the ones before it are done. Progress is frames drawn across
21
+ * every lane, so it climbs smoothly and never goes back.
22
+ */
23
+ export async function exportVideoParallel(params) {
24
+ const { registry = new Registry(), scale = 1, format = "mp4", manifest = EMPTY_MANIFEST, includeAudio = true, audio = {}, signal, onProgress, supersample = DEFAULT_SUPERSAMPLE, } = params;
25
+ if (formatSpec(format).kind !== "video") {
26
+ throw new RangeError(`exportVideoParallel writes video; "${format}" is audio, which exportAudio writes.`);
27
+ }
28
+ const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);
29
+ if (project.tracks.length === 0)
30
+ throw new Error("exportVideoParallel: the project has no tracks.");
31
+ signal?.throwIfAborted();
32
+ const fps = project.fps;
33
+ const totalFrames = totalFramesOf(placeTracks(project.tracks, fps));
34
+ const { output } = exportGeometry(project.viewport, scale, supersample);
35
+ const encoding = plainVideoEncoding(await resolveVideoEncoding(params.video, format, output, fps), output);
36
+ const spec = {
37
+ project, manifest, theme: params.theme, scale, supersample, format, encoding, host: params.host,
38
+ };
39
+ const chunks = planChunks(totalFrames, {
40
+ fps, keyFrameInterval: encoding.keyFrameInterval, lanes: params.lanes.length, chunkSeconds: params.chunkSeconds,
41
+ });
42
+ // Started now so it is ready by the first join; failures surface there.
43
+ const mixed = includeAudio
44
+ ? mixTimelineAudio({
45
+ document: project, registry, manifest, sampleRate: audio.sampleRate,
46
+ rendererWasmUrl: params.rendererWasmUrl,
47
+ })
48
+ : Promise.resolve(null);
49
+ mixed.catch(() => { });
50
+ let reported = 0;
51
+ const seenIssues = new Set();
52
+ const report = (p) => {
53
+ if (p <= reported)
54
+ return;
55
+ reported = p;
56
+ onProgress?.(p);
57
+ };
58
+ // One signal for both ways a run ends early: the caller cancelling, and the join failing.
59
+ const run = new AbortController();
60
+ const abortRun = () => run.abort(signal?.reason);
61
+ if (signal?.aborted)
62
+ abortRun();
63
+ signal?.addEventListener("abort", abortRun, { once: true });
64
+ // Keyed by first frame: a merged range spans several planned chunks.
65
+ const ready = new Map();
66
+ let joinedTo = 0;
67
+ let joining = Promise.resolve();
68
+ let joiner = null;
69
+ const pump = () => {
70
+ joining = joining.then(async () => {
71
+ while (ready.has(joinedTo)) {
72
+ joiner ??= createSegmentJoiner({ stream: params.stream, audio: await mixed, audioBitrate: audio.bitrate });
73
+ const { end, bytes } = ready.get(joinedTo);
74
+ ready.delete(joinedTo);
75
+ joinedTo = end;
76
+ await joiner.append(bytes);
77
+ }
78
+ });
79
+ return joining;
80
+ };
81
+ try {
82
+ await runLanes({
83
+ lanes: params.lanes, spec, chunks, signal: run.signal,
84
+ onIssue: (issue) => {
85
+ const key = renderIssueKey(issue);
86
+ if (seenIssues.has(key))
87
+ return;
88
+ seenIssues.add(key);
89
+ params.onIssue?.(issue);
90
+ },
91
+ onFrames: (frames) => report(DRAWN * Math.min(1, frames / totalFrames)),
92
+ onActive: params.onLanes,
93
+ onChunk: (chunk, bytes) => {
94
+ ready.set(chunk.start, { end: chunk.end, bytes });
95
+ pump().catch((error) => run.abort(error));
96
+ },
97
+ });
98
+ await pump();
99
+ if (joinedTo !== totalFrames)
100
+ throw new Error(`exportVideoParallel: joined ${joinedTo} of ${totalFrames} frames.`);
101
+ const bytes = await joiner.finish();
102
+ report(1);
103
+ return bytes;
104
+ }
105
+ catch (error) {
106
+ await joining.catch(() => { });
107
+ await joiner?.cancel().catch(() => { });
108
+ throw error;
109
+ }
110
+ finally {
111
+ signal?.removeEventListener("abort", abortRun);
112
+ }
113
+ }
114
+ //# sourceMappingURL=export-parallel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-parallel.js","sourceRoot":"","sources":["../../src/parallel/export-parallel.ts"],"names":[],"mappings":"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","sourcesContent":["import { DEFAULT_OUTPUT, placeTracks, Registry, renderIssueKey, resolveProjectDocument, totalFramesOf, type AssetManifest } from \"@motionscript/core\";\nimport { createSegmentJoiner, type ConcatOptions } from \"../concat\";\nimport { exportGeometry, plainVideoEncoding, resolveVideoEncoding, type ExportParams } from \"../exporter\";\nimport { formatSpec } from \"../formats\";\nimport { mixTimelineAudio } from \"../mix-timeline-audio\";\nimport type { ExportLane, SegmentSpec } from \"./lane\";\nimport { planChunks } from \"./plan\";\nimport { runLanes } from \"./run-lanes\";\n\nexport interface ParallelExportParams extends Omit<ExportParams, \"frames\" | \"returnBytes\" | \"filename\"> {\n /**\n * Where the frames are drawn, most preferred first. Every run starts on the\n * first alone and admits the next only while that raises throughput (see\n * `LaneGovernor`), so the first should be the warmest: usually `localLane()`.\n */\n lanes: readonly ExportLane[];\n /** Write the file here as it is joined; the call then resolves to an empty array. See `ConcatOptions.stream`. */\n stream?: ConcatOptions[\"stream\"];\n /** Seconds per chunk; by default from the timeline's length and the lane count. */\n chunkSeconds?: number;\n /** Handed to every lane in its spec, for what only the host knows (see `serveExportLane`). */\n host?: unknown;\n /** How many lanes are rendering, each time that changes. */\n onLanes?: (active: number) => void;\n}\n\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\nconst DEFAULT_SUPERSAMPLE = 2;\n/** The share of progress drawing takes; finishing the file is the rest. */\nconst DRAWN = 0.97;\n\n/**\n * {@link exportVideo}, rendered in chunks across `lanes` and joined into one\n * file without re-encoding.\n *\n * Each chunk is a frame range of the **whole** project, so layered and gapped\n * timelines split like any other. Every chunk is encoded with one pinned\n * configuration, the audio is mixed once over the whole timeline while the frames\n * render (joined AAC would click at every seam), and each chunk is muxed the\n * moment it and the ones before it are done. Progress is frames drawn across\n * every lane, so it climbs smoothly and never goes back.\n */\nexport async function exportVideoParallel(params: ParallelExportParams): Promise<Uint8Array> {\n const {\n registry = new Registry(), scale = 1, format = \"mp4\", manifest = EMPTY_MANIFEST,\n includeAudio = true, audio = {}, signal, onProgress, supersample = DEFAULT_SUPERSAMPLE,\n } = params;\n if (formatSpec(format).kind !== \"video\") {\n throw new RangeError(`exportVideoParallel writes video; \"${format}\" is audio, which exportAudio writes.`);\n }\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\n if (project.tracks.length === 0) throw new Error(\"exportVideoParallel: the project has no tracks.\");\n signal?.throwIfAborted();\n const fps = project.fps!;\n const totalFrames = totalFramesOf(placeTracks(project.tracks, fps));\n\n const { output } = exportGeometry(project.viewport!, scale, supersample);\n const encoding = plainVideoEncoding(await resolveVideoEncoding(params.video, format, output, fps), output);\n const spec: SegmentSpec = {\n project, manifest, theme: params.theme, scale, supersample, format, encoding, host: params.host,\n };\n const chunks = planChunks(totalFrames, {\n fps, keyFrameInterval: encoding.keyFrameInterval, lanes: params.lanes.length, chunkSeconds: params.chunkSeconds,\n });\n\n // Started now so it is ready by the first join; failures surface there.\n const mixed = includeAudio\n ? mixTimelineAudio({\n document: project, registry, manifest, sampleRate: audio.sampleRate,\n rendererWasmUrl: params.rendererWasmUrl,\n })\n : Promise.resolve(null);\n mixed.catch(() => {});\n\n let reported = 0;\n const seenIssues = new Set<string>();\n const report = (p: number) => {\n if (p <= reported) return;\n reported = p;\n onProgress?.(p);\n };\n\n // One signal for both ways a run ends early: the caller cancelling, and the join failing.\n const run = new AbortController();\n const abortRun = () => run.abort(signal?.reason);\n if (signal?.aborted) abortRun();\n signal?.addEventListener(\"abort\", abortRun, { once: true });\n\n // Keyed by first frame: a merged range spans several planned chunks.\n const ready = new Map<number, { end: number; bytes: Uint8Array }>();\n let joinedTo = 0;\n let joining: Promise<void> = Promise.resolve();\n let joiner: ReturnType<typeof createSegmentJoiner> | null = null;\n const pump = () => {\n joining = joining.then(async () => {\n while (ready.has(joinedTo)) {\n joiner ??= createSegmentJoiner({ stream: params.stream, audio: await mixed, audioBitrate: audio.bitrate });\n const { end, bytes } = ready.get(joinedTo)!;\n ready.delete(joinedTo);\n joinedTo = end;\n await joiner.append(bytes);\n }\n });\n return joining;\n };\n\n try {\n await runLanes({\n lanes: params.lanes, spec, chunks, signal: run.signal,\n onIssue: (issue) => {\n const key = renderIssueKey(issue);\n if (seenIssues.has(key)) return;\n seenIssues.add(key);\n params.onIssue?.(issue);\n },\n onFrames: (frames) => report(DRAWN * Math.min(1, frames / totalFrames)),\n onActive: params.onLanes,\n onChunk: (chunk, bytes) => {\n ready.set(chunk.start, { end: chunk.end, bytes });\n pump().catch((error: unknown) => run.abort(error));\n },\n });\n await pump();\n if (joinedTo !== totalFrames) throw new Error(`exportVideoParallel: joined ${joinedTo} of ${totalFrames} frames.`);\n const bytes = await joiner!.finish();\n report(1);\n return bytes;\n } catch (error) {\n await joining.catch(() => {});\n await (joiner as ReturnType<typeof createSegmentJoiner> | null)?.cancel().catch(() => {});\n throw error;\n } finally {\n signal?.removeEventListener(\"abort\", abortRun);\n }\n}\n"]}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Decides how many lanes render at once, from what the run itself measures.
3
+ *
4
+ * Lanes do not add throughput for free. Every lane's encoder, and on a WebGPU
5
+ * canvas its frame capture, run in the browser's one GPU process, so once a lane
6
+ * spends its time waiting on the encoder rather than drawing, a second lane only
7
+ * splits the same encoder between two sessions and the export gets slower. The
8
+ * governor starts with one lane, adds the next only while lanes are mostly
9
+ * drawing, keeps it only if total throughput rose, and then settles.
10
+ */
11
+ export interface GovernorOptions {
12
+ /** Lanes available, the first being the one every run starts with. */
13
+ lanes: number;
14
+ /** How long a configuration is measured before it is judged, in ms. */
15
+ windowMs?: number;
16
+ /** Frames a window needs before it is judged. */
17
+ minFrames?: number;
18
+ /** Above this share of time waiting on the encoder, another lane cannot help. */
19
+ maxWaitShare?: number;
20
+ /** A lane is kept only if it raises throughput by at least this factor. */
21
+ minGain?: number;
22
+ /** How long the newest lane draws before it is measured: its first frames wait on loads and a cold encoder. */
23
+ warmupMs?: number;
24
+ }
25
+ export interface FrameSample {
26
+ lane: number;
27
+ frames: number;
28
+ busyMs: number;
29
+ waitMs: number;
30
+ at: number;
31
+ }
32
+ export declare class LaneGovernor {
33
+ private readonly _options;
34
+ private _active;
35
+ private _settled;
36
+ private _window;
37
+ private _previousFps;
38
+ private _newestFirstAt;
39
+ private readonly _windowMs;
40
+ private readonly _minFrames;
41
+ private readonly _maxWaitShare;
42
+ private readonly _minGain;
43
+ private readonly _warmupMs;
44
+ constructor(_options: GovernorOptions);
45
+ /** Lanes `0..active-1` may take chunks. */
46
+ get active(): number;
47
+ get settled(): boolean;
48
+ /** Stop measuring once a lane dies: the run continues on at most `alive` lanes. */
49
+ laneLost(alive: number): void;
50
+ /**
51
+ * Record frames a lane drew. Returns the new active count when it changed.
52
+ * A window opens once the newest lane has drawn for `warmupMs`, so its
53
+ * start-up is not counted against it.
54
+ */
55
+ record(sample: FrameSample): number | null;
56
+ }
57
+ //# sourceMappingURL=governor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"governor.d.ts","sourceRoot":"","sources":["../../src/parallel/governor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe;IAC5B,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+GAA+G;IAC/G,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;CACd;AASD,qBAAa,YAAY;IAYT,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAXrC,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEN,QAAQ,EAAE,eAAe;IAStD,2CAA2C;IAC3C,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK7B;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI;CA+B7C"}
@@ -0,0 +1,79 @@
1
+ export class LaneGovernor {
2
+ _options;
3
+ _active = 1;
4
+ _settled;
5
+ _window = emptyWindow();
6
+ _previousFps = 0;
7
+ _newestFirstAt = null;
8
+ _windowMs;
9
+ _minFrames;
10
+ _maxWaitShare;
11
+ _minGain;
12
+ _warmupMs;
13
+ constructor(_options) {
14
+ this._options = _options;
15
+ this._windowMs = _options.windowMs ?? 1500;
16
+ this._minFrames = _options.minFrames ?? 24;
17
+ this._maxWaitShare = _options.maxWaitShare ?? 0.45;
18
+ this._minGain = _options.minGain ?? 1.1;
19
+ this._warmupMs = _options.warmupMs ?? 300;
20
+ this._settled = _options.lanes <= 1;
21
+ }
22
+ /** Lanes `0..active-1` may take chunks. */
23
+ get active() {
24
+ return this._active;
25
+ }
26
+ get settled() {
27
+ return this._settled;
28
+ }
29
+ /** Stop measuring once a lane dies: the run continues on at most `alive` lanes. */
30
+ laneLost(alive) {
31
+ this._active = Math.max(1, Math.min(this._active, alive));
32
+ this._settled = true;
33
+ }
34
+ /**
35
+ * Record frames a lane drew. Returns the new active count when it changed.
36
+ * A window opens once the newest lane has drawn for `warmupMs`, so its
37
+ * start-up is not counted against it.
38
+ */
39
+ record(sample) {
40
+ if (this._settled || sample.lane >= this._active)
41
+ return null;
42
+ const w = this._window;
43
+ if (w.start === null) {
44
+ if (sample.lane !== this._active - 1)
45
+ return null;
46
+ this._newestFirstAt ??= sample.at;
47
+ if (sample.at - this._newestFirstAt >= this._warmupMs)
48
+ w.start = sample.at;
49
+ return null;
50
+ }
51
+ w.frames += sample.frames;
52
+ w.busyMs += sample.busyMs;
53
+ w.waitMs += sample.waitMs;
54
+ const elapsed = sample.at - w.start;
55
+ if (elapsed < this._windowMs || w.frames < this._minFrames)
56
+ return null;
57
+ const fps = (w.frames * 1000) / elapsed;
58
+ const waitShare = w.waitMs / Math.max(1, w.busyMs + w.waitMs);
59
+ const before = this._active;
60
+ if (this._active > 1 && fps < this._previousFps * this._minGain) {
61
+ this._active -= 1;
62
+ this._settled = true;
63
+ }
64
+ else if (waitShare <= this._maxWaitShare && this._active < this._options.lanes) {
65
+ this._previousFps = fps;
66
+ this._active += 1;
67
+ }
68
+ else {
69
+ this._settled = true;
70
+ }
71
+ this._window = emptyWindow();
72
+ this._newestFirstAt = null;
73
+ return this._active !== before ? this._active : null;
74
+ }
75
+ }
76
+ function emptyWindow() {
77
+ return { start: null, frames: 0, busyMs: 0, waitMs: 0 };
78
+ }
79
+ //# sourceMappingURL=governor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"governor.js","sourceRoot":"","sources":["../../src/parallel/governor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Decides how many lanes render at once, from what the run itself measures.\n *\n * Lanes do not add throughput for free. Every lane's encoder, and on a WebGPU\n * canvas its frame capture, run in the browser's one GPU process, so once a lane\n * spends its time waiting on the encoder rather than drawing, a second lane only\n * splits the same encoder between two sessions and the export gets slower. The\n * governor starts with one lane, adds the next only while lanes are mostly\n * drawing, keeps it only if total throughput rose, and then settles.\n */\nexport interface GovernorOptions {\n /** Lanes available, the first being the one every run starts with. */\n lanes: number;\n /** How long a configuration is measured before it is judged, in ms. */\n windowMs?: number;\n /** Frames a window needs before it is judged. */\n minFrames?: number;\n /** Above this share of time waiting on the encoder, another lane cannot help. */\n maxWaitShare?: number;\n /** A lane is kept only if it raises throughput by at least this factor. */\n minGain?: number;\n /** How long the newest lane draws before it is measured: its first frames wait on loads and a cold encoder. */\n warmupMs?: number;\n}\n\nexport interface FrameSample {\n lane: number;\n frames: number;\n busyMs: number;\n waitMs: number;\n at: number;\n}\n\ninterface Window {\n start: number | null;\n frames: number;\n busyMs: number;\n waitMs: number;\n}\n\nexport class LaneGovernor {\n private _active = 1;\n private _settled: boolean;\n private _window: Window = emptyWindow();\n private _previousFps = 0;\n private _newestFirstAt: number | null = null;\n private readonly _windowMs: number;\n private readonly _minFrames: number;\n private readonly _maxWaitShare: number;\n private readonly _minGain: number;\n private readonly _warmupMs: number;\n\n constructor(private readonly _options: GovernorOptions) {\n this._windowMs = _options.windowMs ?? 1500;\n this._minFrames = _options.minFrames ?? 24;\n this._maxWaitShare = _options.maxWaitShare ?? 0.45;\n this._minGain = _options.minGain ?? 1.1;\n this._warmupMs = _options.warmupMs ?? 300;\n this._settled = _options.lanes <= 1;\n }\n\n /** Lanes `0..active-1` may take chunks. */\n get active(): number {\n return this._active;\n }\n\n get settled(): boolean {\n return this._settled;\n }\n\n /** Stop measuring once a lane dies: the run continues on at most `alive` lanes. */\n laneLost(alive: number): void {\n this._active = Math.max(1, Math.min(this._active, alive));\n this._settled = true;\n }\n\n /**\n * Record frames a lane drew. Returns the new active count when it changed.\n * A window opens once the newest lane has drawn for `warmupMs`, so its\n * start-up is not counted against it.\n */\n record(sample: FrameSample): number | null {\n if (this._settled || sample.lane >= this._active) return null;\n const w = this._window;\n if (w.start === null) {\n if (sample.lane !== this._active - 1) return null;\n this._newestFirstAt ??= sample.at;\n if (sample.at - this._newestFirstAt >= this._warmupMs) w.start = sample.at;\n return null;\n }\n w.frames += sample.frames;\n w.busyMs += sample.busyMs;\n w.waitMs += sample.waitMs;\n const elapsed = sample.at - w.start;\n if (elapsed < this._windowMs || w.frames < this._minFrames) return null;\n\n const fps = (w.frames * 1000) / elapsed;\n const waitShare = w.waitMs / Math.max(1, w.busyMs + w.waitMs);\n const before = this._active;\n if (this._active > 1 && fps < this._previousFps * this._minGain) {\n this._active -= 1;\n this._settled = true;\n } else if (waitShare <= this._maxWaitShare && this._active < this._options.lanes) {\n this._previousFps = fps;\n this._active += 1;\n } else {\n this._settled = true;\n }\n this._window = emptyWindow();\n this._newestFirstAt = null;\n return this._active !== before ? this._active : null;\n }\n}\n\nfunction emptyWindow(): Window {\n return { start: null, frames: 0, busyMs: 0, waitMs: 0 };\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import type { AssetManifest, ProjectDocument, Registry, RenderIssue, Theme } from "@motionscript/core";
2
+ import type { RendererWasmOption } from "../renderer";
3
+ import type { ResolvedVideoEncoding } from "../exporter";
4
+ import type { VideoFormat } from "../formats";
5
+ import type { ExportChunk } from "./plan";
6
+ /**
7
+ * Everything a lane needs to render chunks of one export. Plain data, so it
8
+ * crosses a `MessagePort` intact; the encoding is pinned here because segments
9
+ * encoded differently cannot be joined.
10
+ */
11
+ export interface SegmentSpec {
12
+ /** Its fps and viewport resolved. */
13
+ project: ProjectDocument;
14
+ manifest: AssetManifest;
15
+ theme?: Theme;
16
+ scale: number;
17
+ supersample: number;
18
+ format: VideoFormat;
19
+ encoding: ResolvedVideoEncoding;
20
+ /** Passed through untouched to a remote lane's `resolve`, for what only its host knows. */
21
+ host?: unknown;
22
+ }
23
+ /** A chunk's frames so far, and how its time split between drawing and waiting on the encoder. */
24
+ export interface LaneReport {
25
+ frames: number;
26
+ busyMs: number;
27
+ waitMs: number;
28
+ /** Render issues since the lane's last report. */
29
+ issues?: RenderIssue[];
30
+ }
31
+ /** One lane, open on one export. */
32
+ export interface LaneSession {
33
+ /** Render the chunk's frames to a complete, silent segment file. */
34
+ render(chunk: ExportChunk, onReport: (report: LaneReport) => void, signal: AbortSignal): Promise<Uint8Array>;
35
+ close(): void;
36
+ }
37
+ /** Something that renders chunks: this window, or another realm across a port. */
38
+ export interface ExportLane {
39
+ open(spec: SegmentSpec, signal: AbortSignal): Promise<LaneSession>;
40
+ }
41
+ /** What a realm renders with: its own registry, and where its renderer's WASM is served. */
42
+ export interface LaneEnvironment extends RendererWasmOption {
43
+ registry?: Registry;
44
+ }
45
+ //# sourceMappingURL=lane.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lane.d.ts","sourceRoot":"","sources":["../../src/parallel/lane.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AACvG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAE1C;;;;GAIG;AACH,MAAM,WAAW,WAAW;IACxB,qCAAqC;IACrC,OAAO,EAAE,eAAe,CAAC;IACzB,QAAQ,EAAE,aAAa,CAAC;IACxB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,qBAAqB,CAAC;IAChC,2FAA2F;IAC3F,IAAI,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,kGAAkG;AAClG,MAAM,WAAW,UAAU;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;CAC1B;AAED,oCAAoC;AACpC,MAAM,WAAW,WAAW;IACxB,oEAAoE;IACpE,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7G,KAAK,IAAI,IAAI,CAAC;CACjB;AAED,kFAAkF;AAClF,MAAM,WAAW,UAAU;IACvB,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CACtE;AAED,4FAA4F;AAC5F,MAAM,WAAW,eAAgB,SAAQ,kBAAkB;IACvD,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACvB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=lane.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lane.js","sourceRoot":"","sources":["../../src/parallel/lane.ts"],"names":[],"mappings":"","sourcesContent":["import type { AssetManifest, ProjectDocument, Registry, RenderIssue, Theme } from \"@motionscript/core\";\nimport type { RendererWasmOption } from \"../renderer\";\nimport type { ResolvedVideoEncoding } from \"../exporter\";\nimport type { VideoFormat } from \"../formats\";\nimport type { ExportChunk } from \"./plan\";\n\n/**\n * Everything a lane needs to render chunks of one export. Plain data, so it\n * crosses a `MessagePort` intact; the encoding is pinned here because segments\n * encoded differently cannot be joined.\n */\nexport interface SegmentSpec {\n /** Its fps and viewport resolved. */\n project: ProjectDocument;\n manifest: AssetManifest;\n theme?: Theme;\n scale: number;\n supersample: number;\n format: VideoFormat;\n encoding: ResolvedVideoEncoding;\n /** Passed through untouched to a remote lane's `resolve`, for what only its host knows. */\n host?: unknown;\n}\n\n/** A chunk's frames so far, and how its time split between drawing and waiting on the encoder. */\nexport interface LaneReport {\n frames: number;\n busyMs: number;\n waitMs: number;\n /** Render issues since the lane's last report. */\n issues?: RenderIssue[];\n}\n\n/** One lane, open on one export. */\nexport interface LaneSession {\n /** Render the chunk's frames to a complete, silent segment file. */\n render(chunk: ExportChunk, onReport: (report: LaneReport) => void, signal: AbortSignal): Promise<Uint8Array>;\n close(): void;\n}\n\n/** Something that renders chunks: this window, or another realm across a port. */\nexport interface ExportLane {\n open(spec: SegmentSpec, signal: AbortSignal): Promise<LaneSession>;\n}\n\n/** What a realm renders with: its own registry, and where its renderer's WASM is served. */\nexport interface LaneEnvironment extends RendererWasmOption {\n registry?: Registry;\n}\n"]}