@motionscript/browser 0.1.0-alpha.8 → 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.
- package/CHANGELOG.md +6 -0
- package/LICENSE +201 -201
- package/NOTICE +49 -49
- package/README.md +141 -141
- package/dist/asset-bytes.js.map +1 -1
- package/dist/audio/bus-graph.js.map +1 -1
- package/dist/audio/effect-graph.js.map +1 -1
- package/dist/audio/index.js.map +1 -1
- package/dist/audio/loop-region.js.map +1 -1
- package/dist/audio/mixer.js.map +1 -1
- package/dist/audio/player.js.map +1 -1
- package/dist/browser/audio.js +1 -1
- package/dist/browser/chunks/{chunk-IILVN4SK.js → chunk-F3AZKPHU.js} +1 -1
- package/dist/browser/chunks/chunk-F3AZKPHU.js.map +7 -0
- package/dist/browser/index.js +2 -2
- package/dist/browser/index.js.map +3 -3
- package/dist/browser/ms_renderer_bg.wasm +0 -0
- package/dist/concat.js.map +1 -1
- package/dist/dynamic-nodes.js.map +1 -1
- package/dist/engine.js.map +1 -1
- package/dist/export-audio.js.map +1 -1
- package/dist/exporter.js.map +1 -1
- package/dist/frame-loop.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mix-timeline-audio.js.map +1 -1
- package/dist/parallel/export-parallel.js.map +1 -1
- package/dist/parallel/lane.js.map +1 -1
- package/dist/parallel/run-lanes.js.map +1 -1
- package/dist/parallel/session.js.map +1 -1
- package/dist/renderer.js.map +1 -1
- package/dist/screenshot.js.map +1 -1
- package/dist/still.js.map +1 -1
- package/dist/ticker.js.map +1 -1
- package/package.json +4 -4
- package/dist/audio/filter-graph.d.ts +0 -59
- package/dist/audio/filter-graph.d.ts.map +0 -1
- package/dist/audio/filter-graph.js +0 -241
- package/dist/audio/filter-graph.js.map +0 -1
- package/dist/browser/chunks/chunk-IILVN4SK.js.map +0 -7
- package/dist/canvaskit-renderer.d.ts +0 -8
- package/dist/canvaskit-renderer.d.ts.map +0 -1
- package/dist/canvaskit-renderer.js +0 -23
- package/dist/canvaskit-renderer.js.map +0 -1
- package/dist/decode/image-levels.d.ts +0 -137
- package/dist/decode/image-levels.d.ts.map +0 -1
- package/dist/decode/image-levels.js +0 -477
- package/dist/decode/image-levels.js.map +0 -1
- package/dist/decode/levels.d.ts +0 -37
- package/dist/decode/levels.d.ts.map +0 -1
- package/dist/decode/levels.js +0 -68
- package/dist/decode/levels.js.map +0 -1
- package/dist/decode/video-levels.d.ts +0 -37
- package/dist/decode/video-levels.d.ts.map +0 -1
- package/dist/decode/video-levels.js +0 -95
- package/dist/decode/video-levels.js.map +0 -1
- package/dist/getter.d.ts +0 -13
- package/dist/getter.d.ts.map +0 -1
- package/dist/getter.js +0 -39
- package/dist/getter.js.map +0 -1
- package/dist/measure-context.d.ts +0 -12
- package/dist/measure-context.d.ts.map +0 -1
- package/dist/measure-context.js +0 -12
- package/dist/measure-context.js.map +0 -1
- package/dist/render-context.d.ts +0 -34
- package/dist/render-context.d.ts.map +0 -1
- package/dist/render-context.js +0 -63
- package/dist/render-context.js.map +0 -1
- package/dist/storage-adapter.d.ts +0 -367
- package/dist/storage-adapter.d.ts.map +0 -1
- package/dist/storage-adapter.js +0 -1280
- package/dist/storage-adapter.js.map +0 -1
- package/dist/three/renderer.d.ts +0 -47
- package/dist/three/renderer.d.ts.map +0 -1
- package/dist/three/renderer.js +0 -182
- package/dist/three/renderer.js.map +0 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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\
|
|
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 +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,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 {\
|
|
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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"export-parallel.js","sourceRoot":"","sources":["../../src/parallel/export-parallel.ts"],"names":[],"mappings":"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","sourcesContent":["import { DEFAULT_OUTPUT, placeTracks, Registry, renderIssueKey, resolveProjectDocument, totalFramesOf, type AssetManifest } from \"@motionscript/core\";\r\nimport { createSegmentJoiner, type ConcatOptions } from \"../concat\";\r\nimport { exportGeometry, plainVideoEncoding, resolveVideoEncoding, type ExportParams } from \"../exporter\";\r\nimport { formatSpec } from \"../formats\";\r\nimport { mixTimelineAudio } from \"../mix-timeline-audio\";\r\nimport type { ExportLane, SegmentSpec } from \"./lane\";\r\nimport { planChunks } from \"./plan\";\r\nimport { runLanes } from \"./run-lanes\";\r\n\r\nexport interface ParallelExportParams extends Omit<ExportParams, \"frames\" | \"returnBytes\" | \"filename\"> {\r\n /**\r\n * Where the frames are drawn, most preferred first. Every run starts on the\r\n * first alone and admits the next only while that raises throughput (see\r\n * `LaneGovernor`), so the first should be the warmest: usually `localLane()`.\r\n */\r\n lanes: readonly ExportLane[];\r\n /** Write the file here as it is joined; the call then resolves to an empty array. See `ConcatOptions.stream`. */\r\n stream?: ConcatOptions[\"stream\"];\r\n /** Seconds per chunk; by default from the timeline's length and the lane count. */\r\n chunkSeconds?: number;\r\n /** Handed to every lane in its spec, for what only the host knows (see `serveExportLane`). */\r\n host?: unknown;\r\n /** How many lanes are rendering, each time that changes. */\r\n onLanes?: (active: number) => void;\r\n}\r\n\r\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\r\nconst DEFAULT_SUPERSAMPLE = 2;\r\n/** The share of progress drawing takes; finishing the file is the rest. */\r\nconst DRAWN = 0.97;\r\n\r\n/**\r\n * {@link exportVideo}, rendered in chunks across `lanes` and joined into one\r\n * file without re-encoding.\r\n *\r\n * Each chunk is a frame range of the **whole** project, so layered and gapped\r\n * timelines split like any other. Every chunk is encoded with one pinned\r\n * configuration, the audio is mixed once over the whole timeline while the frames\r\n * render (joined AAC would click at every seam), and each chunk is muxed the\r\n * moment it and the ones before it are done. Progress is frames drawn across\r\n * every lane, so it climbs smoothly and never goes back.\r\n */\r\nexport async function exportVideoParallel(params: ParallelExportParams): Promise<Uint8Array> {\r\n const {\r\n registry = new Registry(), scale = 1, format = \"mp4\", manifest = EMPTY_MANIFEST,\r\n includeAudio = true, audio = {}, signal, onProgress, supersample = DEFAULT_SUPERSAMPLE,\r\n } = params;\r\n if (formatSpec(format).kind !== \"video\") {\r\n throw new RangeError(`exportVideoParallel writes video; \"${format}\" is audio, which exportAudio writes.`);\r\n }\r\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\r\n if (project.tracks.length === 0) throw new Error(\"exportVideoParallel: the project has no tracks.\");\r\n signal?.throwIfAborted();\r\n const fps = project.fps!;\r\n const totalFrames = totalFramesOf(placeTracks(project.tracks, fps));\r\n\r\n const { output } = exportGeometry(project.viewport!, scale, supersample);\r\n const encoding = plainVideoEncoding(await resolveVideoEncoding(params.video, format, output, fps), output);\r\n const spec: SegmentSpec = {\r\n project, manifest, theme: params.theme, scale, supersample, format, encoding, host: params.host,\r\n };\r\n const chunks = planChunks(totalFrames, {\r\n fps, keyFrameInterval: encoding.keyFrameInterval, lanes: params.lanes.length, chunkSeconds: params.chunkSeconds,\r\n });\r\n\r\n // Started now so it is ready by the first join; failures surface there.\r\n const mixed = includeAudio\r\n ? mixTimelineAudio({\r\n document: project, registry, manifest, sampleRate: audio.sampleRate,\r\n rendererWasmUrl: params.rendererWasmUrl,\r\n })\r\n : Promise.resolve(null);\r\n mixed.catch(() => {});\r\n\r\n let reported = 0;\r\n const seenIssues = new Set<string>();\r\n const report = (p: number) => {\r\n if (p <= reported) return;\r\n reported = p;\r\n onProgress?.(p);\r\n };\r\n\r\n // One signal for both ways a run ends early: the caller cancelling, and the join failing.\r\n const run = new AbortController();\r\n const abortRun = () => run.abort(signal?.reason);\r\n if (signal?.aborted) abortRun();\r\n signal?.addEventListener(\"abort\", abortRun, { once: true });\r\n\r\n // Keyed by first frame: a merged range spans several planned chunks.\r\n const ready = new Map<number, { end: number; bytes: Uint8Array }>();\r\n let joinedTo = 0;\r\n let joining: Promise<void> = Promise.resolve();\r\n let joiner: ReturnType<typeof createSegmentJoiner> | null = null;\r\n const pump = () => {\r\n joining = joining.then(async () => {\r\n while (ready.has(joinedTo)) {\r\n joiner ??= createSegmentJoiner({ stream: params.stream, audio: await mixed, audioBitrate: audio.bitrate });\r\n const { end, bytes } = ready.get(joinedTo)!;\r\n ready.delete(joinedTo);\r\n joinedTo = end;\r\n await joiner.append(bytes);\r\n }\r\n });\r\n return joining;\r\n };\r\n\r\n try {\r\n await runLanes({\r\n lanes: params.lanes, spec, chunks, signal: run.signal,\r\n onIssue: (issue) => {\r\n const key = renderIssueKey(issue);\r\n if (seenIssues.has(key)) return;\r\n seenIssues.add(key);\r\n params.onIssue?.(issue);\r\n },\r\n onFrames: (frames) => report(DRAWN * Math.min(1, frames / totalFrames)),\r\n onActive: params.onLanes,\r\n onChunk: (chunk, bytes) => {\r\n ready.set(chunk.start, { end: chunk.end, bytes });\r\n pump().catch((error: unknown) => run.abort(error));\r\n },\r\n });\r\n await pump();\r\n if (joinedTo !== totalFrames) throw new Error(`exportVideoParallel: joined ${joinedTo} of ${totalFrames} frames.`);\r\n const bytes = await joiner!.finish();\r\n report(1);\r\n return bytes;\r\n } catch (error) {\r\n await joining.catch(() => {});\r\n await (joiner as ReturnType<typeof createSegmentJoiner> | null)?.cancel().catch(() => {});\r\n throw error;\r\n } finally {\r\n signal?.removeEventListener(\"abort\", abortRun);\r\n }\r\n}\r\n"]}
|
|
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"]}
|
|
@@ -1 +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\";\
|
|
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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-lanes.js","sourceRoot":"","sources":["../../src/parallel/run-lanes.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RenderIssue } from \"@motionscript/core\";\r\nimport { LaneGovernor, type GovernorOptions } from \"./governor\";\r\nimport type { ExportLane, LaneReport, LaneSession, SegmentSpec } from \"./lane\";\r\nimport type { ExportChunk } from \"./plan\";\r\n\r\nexport interface RunLanesOptions {\r\n lanes: readonly ExportLane[];\r\n spec: SegmentSpec;\r\n chunks: readonly ExportChunk[];\r\n /**\r\n * A finished range, in whatever order ranges finish. Once the governor\r\n * settles, adjacent chunks still queued are merged, so a range can span\r\n * several of the planned chunks; it is named by the first.\r\n */\r\n onChunk: (chunk: ExportChunk, bytes: Uint8Array) => void;\r\n /** Frames drawn so far across every lane. Drops when a failed range's frames are discarded. */\r\n onFrames?: (frames: number) => void;\r\n /** The active lane count, each time it changes. */\r\n onActive?: (active: number) => void;\r\n /** A render issue a lane reported (a range retried elsewhere may report one twice). */\r\n onIssue?: (issue: RenderIssue) => void;\r\n governor?: Omit<GovernorOptions, \"lanes\">;\r\n now?: () => number;\r\n signal?: AbortSignal;\r\n}\r\n\r\n/** Tries a range gets before the run fails: once on its lane, once more on another. */\r\nconst ATTEMPTS = 2;\r\n\r\ninterface Job {\r\n chunk: ExportChunk;\r\n attempts: number;\r\n}\r\n\r\n/**\r\n * Render every chunk across the lanes the governor admits, pulling from one queue\r\n * in timeline order, so the earliest finish first and the join rarely waits.\r\n *\r\n * Small chunks are what let the governor measure early and lanes share the work,\r\n * but each costs a fresh encoder and scene build; once the governor settles, a\r\n * lane takes its share of what is left as one merged range instead (all of it,\r\n * when one lane is running). A lane opens only once admitted, so a window that\r\n * is not needed never competes with the first for the GPU. A range that fails is\r\n * retried once on another lane and its lane is retired; a second failure, or the\r\n * caller's abort, ends the run.\r\n */\r\nexport async function runLanes(options: RunLanesOptions): Promise<void> {\r\n const { lanes, spec, chunks, signal } = options;\r\n const now = options.now ?? (() => performance.now());\r\n if (lanes.length === 0) throw new Error(\"runLanes: no lanes\");\r\n if (chunks.length === 0) return;\r\n\r\n const controller = new AbortController();\r\n const abort = () => controller.abort(signal?.reason);\r\n if (signal?.aborted) abort();\r\n signal?.addEventListener(\"abort\", abort, { once: true });\r\n\r\n const governor = new LaneGovernor({ lanes: lanes.length, ...options.governor });\r\n const queue: Job[] = chunks.map((chunk) => ({ chunk, attempts: 0 }));\r\n const inFlight = new Map<Job, number>();\r\n const dead = new Set<number>();\r\n let doneFrames = 0;\r\n let remaining = chunks.reduce((sum, c) => sum + (c.end - c.start), 0);\r\n let failure: unknown = null;\r\n\r\n let wake: () => void = () => {};\r\n let woken = new Promise<void>((resolve) => (wake = resolve));\r\n const notify = () => {\r\n const resolve = wake;\r\n woken = new Promise<void>((r) => (wake = r));\r\n resolve();\r\n };\r\n controller.signal.addEventListener(\"abort\", notify, { once: true });\r\n\r\n const alive = () => lanes.length - dead.size;\r\n const admitted = (i: number) => {\r\n let rank = 0;\r\n for (let j = 0; j < i; j++) if (!dead.has(j)) rank++;\r\n return rank < governor.active;\r\n };\r\n const retire = (i: number) => {\r\n dead.add(i);\r\n governor.laneLost(alive());\r\n options.onActive?.(governor.active);\r\n notify();\r\n };\r\n const framesDrawn = () => {\r\n let frames = doneFrames;\r\n for (const f of inFlight.values()) frames += f;\r\n return frames;\r\n };\r\n const take = (): Job => {\r\n const job = queue.shift()!;\r\n if (!governor.settled || job.attempts > 0) return job;\r\n const queued = queue.reduce((sum, j) => sum + (j.chunk.end - j.chunk.start), job.chunk.end - job.chunk.start);\r\n const share = Math.ceil(queued / (governor.active === 1 ? 1 : governor.active * 2));\r\n let { end } = job.chunk;\r\n while (queue.length > 0 && queue[0].attempts === 0 && queue[0].chunk.start === end && end - job.chunk.start < share) {\r\n end = queue.shift()!.chunk.end;\r\n }\r\n return { chunk: { ...job.chunk, end }, attempts: 0 };\r\n };\r\n\r\n const runLane = async (i: number): Promise<void> => {\r\n while (!admitted(i) && remaining > 0 && !controller.signal.aborted && !dead.has(i)) await woken;\r\n if (remaining === 0 || controller.signal.aborted || dead.has(i)) return;\r\n\r\n let opened: LaneSession;\r\n try {\r\n opened = await lanes[i].open(spec, controller.signal);\r\n } catch (error) {\r\n if (controller.signal.aborted) return;\r\n if (alive() <= 1) throw error;\r\n console.warn(`[motionscript] export lane ${i} failed to open; continuing without it.`, error);\r\n retire(i);\r\n return;\r\n }\r\n try {\r\n for (;;) {\r\n controller.signal.throwIfAborted();\r\n if (remaining === 0) return;\r\n if (!admitted(i) || queue.length === 0) {\r\n await woken;\r\n continue;\r\n }\r\n const job = take();\r\n const frames = job.chunk.end - job.chunk.start;\r\n const reported: LaneReport = { frames: 0, busyMs: 0, waitMs: 0 };\r\n inFlight.set(job, 0);\r\n try {\r\n const bytes = await opened.render(job.chunk, (report) => {\r\n const changed = governor.record({\r\n lane: i,\r\n frames: report.frames - reported.frames,\r\n busyMs: report.busyMs - reported.busyMs,\r\n waitMs: report.waitMs - reported.waitMs,\r\n at: now(),\r\n });\r\n for (const issue of report.issues ?? []) options.onIssue?.(issue);\r\n Object.assign(reported, { frames: report.frames, busyMs: report.busyMs, waitMs: report.waitMs });\r\n inFlight.set(job, report.frames);\r\n options.onFrames?.(framesDrawn());\r\n if (changed !== null) {\r\n options.onActive?.(changed);\r\n notify();\r\n }\r\n }, controller.signal);\r\n inFlight.delete(job);\r\n doneFrames += frames;\r\n remaining -= frames;\r\n options.onFrames?.(framesDrawn());\r\n options.onChunk(job.chunk, bytes);\r\n notify();\r\n } catch (error) {\r\n inFlight.delete(job);\r\n if (controller.signal.aborted) throw error;\r\n if (++job.attempts >= ATTEMPTS || alive() <= 1) throw error;\r\n console.warn(`[motionscript] export lane ${i} failed frames ${job.chunk.start} to ${job.chunk.end}; retrying on another lane.`, error);\r\n queue.unshift(job);\r\n options.onFrames?.(framesDrawn());\r\n retire(i);\r\n return;\r\n }\r\n }\r\n } finally {\r\n opened.close();\r\n }\r\n };\r\n\r\n try {\r\n await Promise.all(lanes.map((_, i) => runLane(i).catch((error: unknown) => {\r\n failure ??= error;\r\n controller.abort(error);\r\n })));\r\n if (failure) throw failure;\r\n if (remaining > 0) throw new Error(`runLanes: ${remaining} frames were never rendered.`);\r\n } finally {\r\n signal?.removeEventListener(\"abort\", abort);\r\n }\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"file":"run-lanes.js","sourceRoot":"","sources":["../../src/parallel/run-lanes.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RenderIssue } from \"@motionscript/core\";\nimport { LaneGovernor, type GovernorOptions } from \"./governor\";\nimport type { ExportLane, LaneReport, LaneSession, SegmentSpec } from \"./lane\";\nimport type { ExportChunk } from \"./plan\";\n\nexport interface RunLanesOptions {\n lanes: readonly ExportLane[];\n spec: SegmentSpec;\n chunks: readonly ExportChunk[];\n /**\n * A finished range, in whatever order ranges finish. Once the governor\n * settles, adjacent chunks still queued are merged, so a range can span\n * several of the planned chunks; it is named by the first.\n */\n onChunk: (chunk: ExportChunk, bytes: Uint8Array) => void;\n /** Frames drawn so far across every lane. Drops when a failed range's frames are discarded. */\n onFrames?: (frames: number) => void;\n /** The active lane count, each time it changes. */\n onActive?: (active: number) => void;\n /** A render issue a lane reported (a range retried elsewhere may report one twice). */\n onIssue?: (issue: RenderIssue) => void;\n governor?: Omit<GovernorOptions, \"lanes\">;\n now?: () => number;\n signal?: AbortSignal;\n}\n\n/** Tries a range gets before the run fails: once on its lane, once more on another. */\nconst ATTEMPTS = 2;\n\ninterface Job {\n chunk: ExportChunk;\n attempts: number;\n}\n\n/**\n * Render every chunk across the lanes the governor admits, pulling from one queue\n * in timeline order, so the earliest finish first and the join rarely waits.\n *\n * Small chunks are what let the governor measure early and lanes share the work,\n * but each costs a fresh encoder and scene build; once the governor settles, a\n * lane takes its share of what is left as one merged range instead (all of it,\n * when one lane is running). A lane opens only once admitted, so a window that\n * is not needed never competes with the first for the GPU. A range that fails is\n * retried once on another lane and its lane is retired; a second failure, or the\n * caller's abort, ends the run.\n */\nexport async function runLanes(options: RunLanesOptions): Promise<void> {\n const { lanes, spec, chunks, signal } = options;\n const now = options.now ?? (() => performance.now());\n if (lanes.length === 0) throw new Error(\"runLanes: no lanes\");\n if (chunks.length === 0) return;\n\n const controller = new AbortController();\n const abort = () => controller.abort(signal?.reason);\n if (signal?.aborted) abort();\n signal?.addEventListener(\"abort\", abort, { once: true });\n\n const governor = new LaneGovernor({ lanes: lanes.length, ...options.governor });\n const queue: Job[] = chunks.map((chunk) => ({ chunk, attempts: 0 }));\n const inFlight = new Map<Job, number>();\n const dead = new Set<number>();\n let doneFrames = 0;\n let remaining = chunks.reduce((sum, c) => sum + (c.end - c.start), 0);\n let failure: unknown = null;\n\n let wake: () => void = () => {};\n let woken = new Promise<void>((resolve) => (wake = resolve));\n const notify = () => {\n const resolve = wake;\n woken = new Promise<void>((r) => (wake = r));\n resolve();\n };\n controller.signal.addEventListener(\"abort\", notify, { once: true });\n\n const alive = () => lanes.length - dead.size;\n const admitted = (i: number) => {\n let rank = 0;\n for (let j = 0; j < i; j++) if (!dead.has(j)) rank++;\n return rank < governor.active;\n };\n const retire = (i: number) => {\n dead.add(i);\n governor.laneLost(alive());\n options.onActive?.(governor.active);\n notify();\n };\n const framesDrawn = () => {\n let frames = doneFrames;\n for (const f of inFlight.values()) frames += f;\n return frames;\n };\n const take = (): Job => {\n const job = queue.shift()!;\n if (!governor.settled || job.attempts > 0) return job;\n const queued = queue.reduce((sum, j) => sum + (j.chunk.end - j.chunk.start), job.chunk.end - job.chunk.start);\n const share = Math.ceil(queued / (governor.active === 1 ? 1 : governor.active * 2));\n let { end } = job.chunk;\n while (queue.length > 0 && queue[0].attempts === 0 && queue[0].chunk.start === end && end - job.chunk.start < share) {\n end = queue.shift()!.chunk.end;\n }\n return { chunk: { ...job.chunk, end }, attempts: 0 };\n };\n\n const runLane = async (i: number): Promise<void> => {\n while (!admitted(i) && remaining > 0 && !controller.signal.aborted && !dead.has(i)) await woken;\n if (remaining === 0 || controller.signal.aborted || dead.has(i)) return;\n\n let opened: LaneSession;\n try {\n opened = await lanes[i].open(spec, controller.signal);\n } catch (error) {\n if (controller.signal.aborted) return;\n if (alive() <= 1) throw error;\n console.warn(`[motionscript] export lane ${i} failed to open; continuing without it.`, error);\n retire(i);\n return;\n }\n try {\n for (;;) {\n controller.signal.throwIfAborted();\n if (remaining === 0) return;\n if (!admitted(i) || queue.length === 0) {\n await woken;\n continue;\n }\n const job = take();\n const frames = job.chunk.end - job.chunk.start;\n const reported: LaneReport = { frames: 0, busyMs: 0, waitMs: 0 };\n inFlight.set(job, 0);\n try {\n const bytes = await opened.render(job.chunk, (report) => {\n const changed = governor.record({\n lane: i,\n frames: report.frames - reported.frames,\n busyMs: report.busyMs - reported.busyMs,\n waitMs: report.waitMs - reported.waitMs,\n at: now(),\n });\n for (const issue of report.issues ?? []) options.onIssue?.(issue);\n Object.assign(reported, { frames: report.frames, busyMs: report.busyMs, waitMs: report.waitMs });\n inFlight.set(job, report.frames);\n options.onFrames?.(framesDrawn());\n if (changed !== null) {\n options.onActive?.(changed);\n notify();\n }\n }, controller.signal);\n inFlight.delete(job);\n doneFrames += frames;\n remaining -= frames;\n options.onFrames?.(framesDrawn());\n options.onChunk(job.chunk, bytes);\n notify();\n } catch (error) {\n inFlight.delete(job);\n if (controller.signal.aborted) throw error;\n if (++job.attempts >= ATTEMPTS || alive() <= 1) throw error;\n console.warn(`[motionscript] export lane ${i} failed frames ${job.chunk.start} to ${job.chunk.end}; retrying on another lane.`, error);\n queue.unshift(job);\n options.onFrames?.(framesDrawn());\n retire(i);\n return;\n }\n }\n } finally {\n opened.close();\n }\n };\n\n try {\n await Promise.all(lanes.map((_, i) => runLane(i).catch((error: unknown) => {\n failure ??= error;\n controller.abort(error);\n })));\n if (failure) throw failure;\n if (remaining > 0) throw new Error(`runLanes: ${remaining} frames were never rendered.`);\n } finally {\n signal?.removeEventListener(\"abort\", abort);\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/parallel/session.ts"],"names":[],"mappings":"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","sourcesContent":["import { Registry, type RenderIssue } from \"@motionscript/core\";\
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/parallel/session.ts"],"names":[],"mappings":"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","sourcesContent":["import { Registry, type RenderIssue } from \"@motionscript/core\";\nimport { renderTimeline, type VideoFrameSink } from \"../frame-loop\";\nimport { exportGeometry, MediabunnyVideoSink, openExportSurface } from \"../exporter\";\nimport type { LaneEnvironment, LaneReport, LaneSession, SegmentSpec } from \"./lane\";\n\n/** How often a lane reports its frames, in ms. */\nconst REPORT_EVERY_MS = 100;\n\n/**\n * Open one surface for the whole export and render chunk after chunk onto it.\n * The surface is the expensive part (a WebGPU one takes up to a second and a\n * half to start), and decoded fonts and pictures stay in its storage between\n * chunks; each chunk still gets its own encoder, so it is its own file.\n */\nexport async function openSegmentSession(spec: SegmentSpec, env: LaneEnvironment): Promise<LaneSession> {\n const { project } = spec;\n const viewport = project.viewport!;\n const fps = project.fps!;\n const registry = env.registry ?? new Registry();\n const { output, oversample, renderScale } = exportGeometry(viewport, spec.scale, spec.supersample);\n const surface = await openExportSurface({\n canvasSize: { width: output.width * oversample, height: output.height * oversample },\n viewport, fps, manifest: spec.manifest, rendererWasmUrl: env.rendererWasmUrl,\n });\n\n return {\n async render(chunk, onReport, signal) {\n const sink = new TimedSink(new MediabunnyVideoSink(surface.canvas, output, spec.format, spec.encoding), onReport);\n const bytes = await renderTimeline<AudioBuffer>({\n project, registry, theme: spec.theme, scale: renderScale, includeAudio: false,\n frames: { start: chunk.start, end: chunk.end },\n renderContext: surface.renderContext, storageAdapter: surface.storageAdapter, assetCatalog: surface.assetCatalog,\n sink, signal,\n onIssue: (issue) => sink.issues.push(issue),\n });\n sink.flush();\n if (!bytes) throw new Error(`Frames ${chunk.start} to ${chunk.end} produced no bytes.`);\n return bytes;\n },\n close: () => surface.dispose(),\n };\n}\n\n/** Times each frame: drawing is the gap between frames, waiting is the encoder's backpressure. */\nclass TimedSink implements VideoFrameSink<AudioBuffer> {\n readonly issues: RenderIssue[] = [];\n private _frames = 0;\n private _busyMs = 0;\n private _waitMs = 0;\n private _lastEnd = 0;\n private _lastReport = 0;\n\n constructor(\n private readonly _inner: MediabunnyVideoSink,\n private readonly _onReport: (report: LaneReport) => void,\n ) { }\n\n async start(info: Parameters<MediabunnyVideoSink[\"start\"]>[0]): Promise<void> {\n await this._inner.start(info);\n this._lastEnd = this._lastReport = performance.now();\n }\n\n async addFrame(timestamp: number, duration: number): Promise<void> {\n const before = performance.now();\n this._busyMs += before - this._lastEnd;\n await this._inner.addFrame(timestamp, duration);\n const after = performance.now();\n this._waitMs += after - before;\n this._lastEnd = after;\n this._frames++;\n if (after - this._lastReport >= REPORT_EVERY_MS) {\n this._lastReport = after;\n this.flush();\n }\n }\n\n finalize(): Promise<Uint8Array> {\n return this._inner.finalize();\n }\n\n flush(): void {\n const issues = this.issues.splice(0);\n this._onReport({ frames: this._frames, busyMs: this._busyMs, waitMs: this._waitMs, ...(issues.length > 0 && { issues }) });\n }\n}\n"]}
|
package/dist/renderer.js.map
CHANGED
|
@@ -1 +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\";\
|
|
1
|
+
{"version":3,"file":"renderer.js","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAqB,MAAM,wBAAwB,CAAC;AAChG,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAQ/C;;;;GAIG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC7C;QACI,KAAK,CAAC,0EAA0E,CAAC,CAAC;QAClF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACzC,CAAC;CACJ;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,UAA8B,EAAE,EAAE,EAAE,GAAG,GAAG,IAAI,EAAE,GAAG,EAAE;IACpF,IAAI,GAAG,IAAI,CAAC,CAAC,MAAM,eAAe,EAAE,CAAC;QAAE,MAAM,IAAI,sBAAsB,EAAE,CAAC;IAC1E,OAAO,kBAAkB,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,eAAe,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CAAC;AAC/F,CAAC","sourcesContent":["import { createWgpuRenderer, isWgpuSupported, type WgpuRenderer } from \"@motionscript/renderer\";\nimport { loadAssetBytes } from \"./asset-bytes\";\n\n/** Where the renderer's WebAssembly is served, for every entry that draws. */\nexport interface RendererWasmOption {\n /** URL of `ms_renderer_bg.wasm`; defaults to beside `@motionscript/renderer`'s module, which a bundler resolves. */\n rendererWasmUrl?: string | URL;\n}\n\n/**\n * This browser cannot run the WebGPU renderer: no `navigator.gpu`, or no\n * adapter behind it. Every entry that draws (an engine, a still, an export,\n * a timeline's audio mix) rejects with it before drawing anything.\n */\nexport class WebGpuUnavailableError extends Error {\n constructor() {\n super(\"Motion Script draws with WebGPU, and this browser has no WebGPU adapter.\");\n this.name = \"WebGpuUnavailableError\";\n }\n}\n\n/**\n * The renderer every entry draws with, or {@link WebGpuUnavailableError}.\n * `gpu: false` is for an entry that only measures text, which needs no device.\n */\nexport async function loadRenderer(options: RendererWasmOption = {}, { gpu = true } = {}): Promise<WgpuRenderer> {\n if (gpu && !(await isWgpuSupported())) throw new WebGpuUnavailableError();\n return createWgpuRenderer({ wasmUrl: options.rendererWasmUrl, loadBytes: loadAssetBytes });\n}\n"]}
|
package/dist/screenshot.js.map
CHANGED
|
@@ -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;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 {\
|
|
1
|
+
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,cAAc,EAAE,sBAAsB,GAEzC,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,mBAAmB,EAAiB,MAAM,SAAS,CAAC;AAgC7D;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAwB;IAC3D,MAAM,EACF,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,EACjD,KAAK,EAAE,MAAM,GAAG,KAAK,EAAE,OAAO,GAAG,IAAI,GACxC,GAAG,MAAM,CAAC;IAEX,MAAM,OAAO,GAAG,sBAAsB,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IAChF,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAE7F,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC;QACvC,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,EAAE,QAAQ;KAClG,CAAC,CAAC;IAEH,IAAI,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACxD,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAC5E,CAAC;YAAS,CAAC;QACP,QAAQ,CAAC,OAAO,EAAE,CAAC;IACvB,CAAC;AACL,CAAC","sourcesContent":["import {\n DEFAULT_OUTPUT, resolveProjectDocument,\n type AssetManifest, type FrameSpec, type ProjectDocument, type Registry, type VisualDocument, type Size2D, type Theme,\n} from \"@motionscript/core\";\nimport type { ScreenshotFormat } from \"./frame-loop\";\nimport type { RendererWasmOption } from \"./renderer\";\nimport { createStillRenderer, type FrameRef } from \"./still\";\n\nexport type { ScreenshotFormat, FrameSpec };\n\nexport type ScreenshotParams = RendererWasmOption & {\n /** A project, or a lone still or composition as the one track of one. */\n document: ProjectDocument | VisualDocument;\n /** The vocabulary its clips compile against. Defaults to what the library ships. */\n registry?: Registry;\n /** Win over the document's own; 1920×1080 at 60 fps when neither says. */\n viewport?: Size2D;\n fps?: number;\n scale?: number;\n manifest?: AssetManifest;\n /** Theme for a document that carries none of its own. */\n theme?: Theme;\n /** Which frame to capture. Defaults to the timeline's first. */\n frame?: FrameRef;\n /** Encoding format (default \"png\"). */\n format?: ScreenshotFormat;\n /** JPEG quality in [0,1] (ignored for png; default 0.92). */\n quality?: number;\n};\n\nexport type ScreenshotResult = {\n /** The project frame that was captured, after clamping. */\n frame: number;\n totalFrames: number;\n /** Encoded image bytes. */\n bytes: Uint8Array;\n};\n\n/**\n * Renders a single frame of a document to an image.\n *\n * A one-shot wrapper over {@link StillRenderer}: create, render, encode, dispose.\n * It therefore takes the same path as a live preview, and as the video export at\n * that frame — there is one renderer, not three.\n *\n * For anything that captures **repeatedly** — a thumbnail builder, a preview that\n * repaints as the user edits — hold a {@link StillRenderer} instead. This function\n * builds and tears down a WebGPU surface per call, which is the right trade for one\n * capture and the wrong one for a hundred.\n */\nexport async function exportScreenshot(params: ScreenshotParams): Promise<ScreenshotResult> {\n const {\n registry, scale, manifest, theme, rendererWasmUrl,\n frame, format = \"png\", quality = 0.92,\n } = params;\n\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\n if (project.tracks.length === 0) throw new Error(\"The project has no tracks to screenshot.\");\n\n const renderer = await createStillRenderer({\n viewport: project.viewport, fps: project.fps, scale, manifest, theme, rendererWasmUrl, registry,\n });\n\n try {\n const drawn = await renderer.render(project, { frame });\n return { ...drawn, bytes: await renderer.toBytes({ format, quality }) };\n } finally {\n renderer.dispose();\n }\n}\n"]}
|
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 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"]}
|
|
1
|
+
{"version":3,"file":"still.js","sourceRoot":"","sources":["../src/still.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AssetCatalog, DEFAULT_OUTPUT, ManifestAssetCatalog,\n frameSpecAtTime,\n Registry,\n resolveProjectDocument,\n type AssetManifest,\n type FrameSpec,\n type RenderIssue,\n type ProjectDocument,\n type VisualDocument,\n type Size2D,\n type StorageAdapter,\n type Theme,\n} from \"@motionscript/core\";\nimport {\n drawFrameAt,\n type DrawnFrame,\n type ScreenshotFormat,\n} from \"./frame-loop\";\nimport type { BrowserRenderContext, BrowserRenderer } from \"./engine\";\nimport { loadRenderer, type RendererWasmOption } from \"./renderer\";\n\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\n\n/** Anything a still can be rendered from: a project, or a lone still or composition as the one track of one. */\nexport type StillSource = ProjectDocument | VisualDocument;\n\n/**\n * Which frame to draw, in whatever form is most convenient — a global frame\n * index, the timeline's first or last frame, or a `FrameSpec`.\n */\nexport type FrameRef = FrameSpec | number | \"first\" | \"last\";\n\nexport interface StillRendererOptions extends RendererWasmOption {\n /**\n * The canvas to draw into. Omit and the renderer creates its own hidden one\n * and removes it on {@link StillRenderer.dispose} — which is what a one-shot\n * capture wants, while a live preview passes the canvas it already shows.\n */\n canvas?: HTMLCanvasElement;\n /** Output resolution in pixels, whatever the document asks for. Defaults to 1920×1080. */\n viewport?: Size2D;\n /** Frame rate for a document that sets none. Defaults to 60. */\n fps?: number;\n /** Device pixel ratio the surface is sized at. Defaults to 1. */\n scale?: number;\n manifest?: AssetManifest;\n /** Theme for a document that carries none of its own. */\n theme?: Theme;\n /**\n * The vocabulary a document is compiled against.\n *\n * Defaults to a fresh one carrying only what the library ships — a renderer\n * owns its registry the way an engine does, so a host with custom node types\n * passes the same one it gave its engine rather than relying on whatever a\n * module happened to register.\n */\n registry?: Registry;\n}\n\nexport interface RenderStillOptions {\n /** Which frame to draw. Defaults to the timeline's first. */\n frame?: FrameRef;\n /** Seconds into the timeline, converted with the document's fps. Wins over `frame`. */\n time?: number;\n /**\n * Draw this call at this pixel ratio instead of the renderer's. **Resizes the\n * canvas and rebuilds the surface**, and the next call without it rebuilds\n * them back, so keep it off the per-edit path — render the preview at its\n * normal scale and pass this only when capturing at a higher one.\n */\n scale?: number;\n /** What the renderer drew but not as the document says; a lost GPU device throws `RenderBackendError` instead. */\n onIssue?: (issue: RenderIssue) => void;\n}\n\nexport interface EncodeStillOptions {\n /** Defaults to `\"png\"`. */\n format?: ScreenshotFormat;\n /** JPEG quality in [0,1]; ignored for png. Defaults to 0.92. */\n quality?: number;\n}\n\n/** Normalize the many ways a caller can name a frame into the engine's own form. */\nexport function stillFrameSpec(options: RenderStillOptions | undefined, fps: number): FrameSpec {\n if (options?.time !== undefined) return frameSpecAtTime(options.time, fps);\n const frame = options?.frame;\n if (frame === undefined || frame === \"first\") return { kind: \"first\" };\n if (frame === \"last\") return { kind: \"last\" };\n if (typeof frame === \"number\") return { kind: \"frame\", frame };\n return frame;\n}\n\nfunction mimeOf(format: ScreenshotFormat): string {\n return format === \"jpeg\" ? \"image/jpeg\" : \"image/png\";\n}\n\n/**\n * A long-lived single-frame renderer.\n *\n * Where `exportScreenshot` is one frame from a standing start, this keeps the\n * expensive things — the renderer module, its GPU surface, and the\n * storage adapter's decoded-asset cache — alive across calls, so re-rendering\n * after an edit costs discovering one frame and a draw rather than a new GL\n * context. That is\n * the difference between a one-off capture and a builder that repaints while the\n * user types.\n *\n * It is also the *same* draw: {@link render} goes through `drawFrameAt`, which is\n * the pass the video exporter runs per frame. A preview and its exported image\n * cannot drift apart, because there is only one path.\n *\n * ```ts\n * const renderer = await createStillRenderer({ canvas, viewport, manifest, theme });\n * await renderer.render({ kind: \"still\", nodes: [\n * { id: \"bg\", type: \"rect\", parentId: null, layerOrder: 0, props: { width: 1920, height: 1080, fill: \"bg\" } },\n * ] });\n * const blob = await renderer.toBlob({ format: \"png\" });\n * renderer.dispose();\n * ```\n *\n * **The theme is process-global in core.** {@link render} re-applies the\n * document's (or this renderer's) before every draw, so two renderers with\n * different themes still each draw correctly; but anything else that calls\n * `setTheme` between a render and a read of the surface will have been\n * overwritten by the next render.\n *\n * **One surface, one caller at a time.** A render and the encode of its result\n * are two calls, and the next render draws over the surface between them, so\n * callers that share a renderer without knowing of each other must be queued —\n * as `session.image()` queues its calls.\n */\nexport class StillRenderer {\n readonly canvas: HTMLCanvasElement;\n\n private renderContext: BrowserRenderContext;\n private storageAdapter: StorageAdapter;\n private catalog: AssetCatalog;\n\n private readonly backend: BrowserRenderer;\n /** True when we made the canvas and must clean it up. */\n private readonly ownsCanvas: boolean;\n\n private viewportValue: Size2D;\n private readonly fpsValue: number;\n private readonly scaleValue: number;\n /** What the canvas is sized at now, which a call's own `scale` changes. */\n private surfaceScale: number;\n private manifest: AssetManifest;\n private theme?: Theme;\n private readonly registry: Registry;\n private disposed = false;\n /** False until a frame has been drawn onto the current surface; a remount clears it. */\n private drawn = false;\n\n /** @internal Use {@link createStillRenderer}, which loads the renderer first. */\n constructor(\n backend: BrowserRenderer,\n options: StillRendererOptions,\n ) {\n this.backend = backend;\n this.viewportValue = options.viewport ?? DEFAULT_OUTPUT.viewport;\n this.fpsValue = options.fps ?? DEFAULT_OUTPUT.fps;\n this.scaleValue = options.scale ?? 1;\n this.surfaceScale = this.scaleValue;\n this.manifest = options.manifest ?? EMPTY_MANIFEST;\n this.theme = options.theme;\n this.registry = options.registry ?? new Registry();\n\n this.ownsCanvas = options.canvas === undefined;\n this.canvas = options.canvas ?? document.createElement(\"canvas\");\n if (this.ownsCanvas) {\n this.canvas.style.display = \"none\";\n document.body.appendChild(this.canvas);\n }\n this.sizeCanvas();\n\n this.catalog = new ManifestAssetCatalog(this.manifest);\n this.storageAdapter = this.backend.createStorageAdapter({\n catalog: this.catalog, viewport: this.viewportValue, fps: this.fpsValue,\n });\n this.renderContext = this.backend.createRenderContext(this.storageAdapter);\n this.renderContext.mount(this.canvas);\n }\n\n get viewport(): Size2D { return this.viewportValue; }\n get fps(): number { return this.fpsValue; }\n get scale(): number { return this.scaleValue; }\n\n /**\n * Draw one frame of `source`, at this renderer's viewport — the surface is\n * the output, so it wins over any size the document asks for.\n */\n async render(source: StillSource, options?: RenderStillOptions): Promise<DrawnFrame> {\n this.assertLive();\n const scale = options?.scale ?? this.scaleValue;\n if (scale !== this.surfaceScale) this.remount(scale);\n await this.renderContext.whenMounted?.();\n\n const project = resolveProjectDocument(\n source, { viewport: this.viewportValue }, { viewport: this.viewportValue, fps: this.fpsValue },\n );\n\n const frame = await drawFrameAt({\n project,\n registry: this.registry,\n theme: this.theme,\n scale,\n renderContext: this.renderContext,\n storageAdapter: this.storageAdapter,\n assetCatalog: this.catalog,\n frame: stillFrameSpec(options, project.fps!),\n onIssue: options?.onIssue,\n });\n this.drawn = true;\n return frame;\n }\n\n /** Theme for a document that carries none of its own. */\n setTheme(theme?: Theme): void { this.theme = theme; }\n\n /** Swap the asset manifest, keeping every decode already cached. */\n setManifest(manifest: AssetManifest): void {\n this.assertLive();\n this.manifest = manifest;\n this.catalog = new ManifestAssetCatalog(manifest);\n this.storageAdapter.setCatalog(this.catalog);\n }\n\n /**\n * Change the output resolution.\n *\n * A full reset, unlike {@link setManifest}: the viewport clamps the resolution\n * images are decoded *at*, so cached decodes made for the old one would be\n * soft at the new one, and the render context's handlers captured the adapter\n * when they were built — so both are rebuilt. Call this when the output format\n * changes, not per edit.\n */\n setViewport(viewport: Size2D): void {\n this.assertLive();\n if (viewport.width === this.viewportValue.width && viewport.height === this.viewportValue.height) {\n return;\n }\n this.viewportValue = viewport;\n // Disposing the context disposes the adapter it holds, so this releases\n // the old surface, its handlers and every decode in one call.\n this.renderContext.dispose();\n this.storageAdapter = this.backend.createStorageAdapter({\n catalog: this.catalog, viewport, fps: this.fpsValue,\n });\n this.renderContext = this.backend.createRenderContext(this.storageAdapter);\n this.remount(this.scaleValue);\n }\n\n /** Encode the frame currently on the surface. */\n async toBytes(options?: EncodeStillOptions): Promise<Uint8Array> {\n const blob = await this.toBlob(options);\n return new Uint8Array(await blob.arrayBuffer());\n }\n\n /** Encode the frame currently on the surface. */\n toBlob(options?: EncodeStillOptions): Promise<Blob> {\n const canvas = this.snapshotToCanvas();\n const { format = \"png\", quality = 0.92 } = options ?? {};\n return new Promise((resolve, reject) => {\n canvas.toBlob(\n (blob) => blob\n ? resolve(blob)\n : reject(new Error(\"Still encode produced no data.\")),\n mimeOf(format),\n quality,\n );\n });\n }\n\n /** Encode the frame currently on the surface. */\n toDataURL(options?: EncodeStillOptions): string {\n const { format = \"png\", quality = 0.92 } = options ?? {};\n return this.snapshotToCanvas().toDataURL(mimeOf(format), quality);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.renderContext.dispose();\n if (this.ownsCanvas) this.canvas.remove();\n }\n\n /**\n * Read the surface back into a 2D canvas, which is what actually encodes: the\n * renderers ship no image encoders, so the browser's own codecs are the only\n * ones available.\n */\n private snapshotToCanvas(): HTMLCanvasElement {\n this.assertLive();\n if (!this.drawn) throw new Error(\"Nothing has been rendered yet.\");\n const canvas = this.renderContext.snapshotCanvas?.();\n if (!canvas) throw new Error(\"Still encode: could not read the surface into a 2d canvas.\");\n return canvas;\n }\n\n private sizeCanvas(): void {\n this.canvas.width = this.viewportValue.width * this.surfaceScale;\n this.canvas.height = this.viewportValue.height * this.surfaceScale;\n }\n\n /** Resize the backing store and mount a fresh surface over it. */\n private remount(scale: number): void {\n this.surfaceScale = scale;\n this.drawn = false;\n this.sizeCanvas();\n this.renderContext.mount(this.canvas);\n }\n\n private assertLive(): void {\n if (this.disposed) throw new Error(\"This StillRenderer has been disposed.\");\n }\n}\n\n/** Create a {@link StillRenderer}; rejects with `WebGpuUnavailableError` without WebGPU. */\nexport async function createStillRenderer(\n options: StillRendererOptions = {},\n): Promise<StillRenderer> {\n return new StillRenderer(await loadRenderer(options), options);\n}\n"]}
|
package/dist/ticker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ticker.js","sourceRoot":"","sources":["../src/ticker.ts"],"names":[],"mappings":"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","sourcesContent":["import { Ticker } from \"@motionscript/core\";\r\n\r\n/**\r\n * Ticker backed by a Web Audio AudioContext. Clip time follows\r\n * `audioContext.currentTime` (within a few ms, see `startLoop`), so any\r\n * AudioBufferSourceNodes scheduled on the same context play in sync.\r\n *\r\n * When paused we suspend the context (audio stops, currentTime freezes);\r\n * when playing we resume and a rAF loop publishes ticks at the target fps.\r\n *\r\n * A single silent buffer covering the full timeline is started on play(),\r\n * which keeps the context active and gives the browser a stable audio\r\n * graph to schedule against even on scenes that contain no audio nodes.\r\n */\r\nexport class WebTicker extends Ticker {\r\n private readonly contextOf: () => AudioContext;\r\n private readonly ownsContext: boolean;\r\n private _context: AudioContext | null = null;\r\n private fps: number;\r\n\r\n private playStartCtxTime: number = 0;\r\n private seekOffset: number = 0;\r\n private speed: number = 1;\r\n private reverse: boolean = false;\r\n\r\n private rafId: number | null = null;\r\n /** Bumped by every `seek`, so the loop can tell that a listener or tick callback moved the playhead under it. */\r\n private seekEpoch: number = 0;\r\n private silentSource: AudioBufferSourceNode | null = null;\r\n\r\n /**\r\n * `context` may be a getter, called on the first `play`: a session hands\r\n * over its audio sink's, which is created on first use, so a session that\r\n * never plays never opens the audio device.\r\n */\r\n constructor(opts: { context?: AudioContext | (() => AudioContext); fps: number }) {\r\n super();\r\n const { context } = opts;\r\n this.contextOf = typeof context === \"function\" ? context : () => context ?? new AudioContext();\r\n this.ownsContext = !context;\r\n this.fps = opts.fps;\r\n }\r\n\r\n getContext(): AudioContext {\r\n this._context ??= this.contextOf();\r\n return this._context;\r\n }\r\n\r\n private get context(): AudioContext {\r\n return this.getContext();\r\n }\r\n\r\n seek(t: number): void {\r\n const clamped = Math.max(0, Math.min(t, this.duration));\r\n this.seekOffset = clamped;\r\n // The clock is read only while playing; `play` anchors it afresh.\r\n if (this.isPlaying) this.playStartCtxTime = this.context.currentTime;\r\n this.seekEpoch++;\r\n this.setCurrentTime(clamped);\r\n }\r\n\r\n /**\r\n * Anchors elapsed-time tracking to the current `context.currentTime`, starts the silent ticker\r\n * + rAF tick loop and asks a suspended context to resume.\r\n *\r\n * Synchronous, as {@link Ticker.play} is: a `pause` or `seek` in the same turn must find a\r\n * running clock to stop. A suspended context's `currentTime` is frozen, so the clock holds at\r\n * the anchor until the resume lands — outside a user gesture that is never.\r\n */\r\n play(speed: number = 1, reverse: boolean = false): void {\r\n this.speed = speed;\r\n this.reverse = reverse;\r\n this.seekOffset = this.currentTime;\r\n\r\n if (this.context.state === \"suspended\") {\r\n this.context.resume().then(\r\n () => {\r\n // A pause that arrived while the resume was pending could not suspend it.\r\n if (!this.isPlaying && this.context.state === \"running\") void this.context.suspend();\r\n },\r\n (error) => {\r\n console.error(\"[WebTicker] The audio context could not resume:\", error);\r\n this.pause();\r\n },\r\n );\r\n }\r\n\r\n this.playStartCtxTime = this.context.currentTime;\r\n this.startSilentTicker();\r\n super.play(speed, reverse);\r\n this.startLoop();\r\n }\r\n\r\n /** Stops the tick loop and silent ticker, then suspends the context — freezing `currentTime` so elapsed-time math stays correct across the pause. */\r\n pause(): void {\r\n this.seekOffset = this.currentTime;\r\n this.stopLoop();\r\n this.stopSilentTicker();\r\n if (this._context?.state === \"running\") {\r\n this._context.suspend();\r\n }\r\n super.pause();\r\n }\r\n\r\n /** (Re)starts the silent buffer source covering the remaining timeline — keeps the AudioContext active and gives a stable graph for other AudioBufferSourceNodes to schedule against, per the class-level note. */\r\n private startSilentTicker(): void {\r\n this.stopSilentTicker();\r\n const remaining = Math.max(0.001, this.duration - this.seekOffset);\r\n const sampleRate = this.context.sampleRate;\r\n const buffer = this.context.createBuffer(1, Math.ceil(remaining * sampleRate), sampleRate);\r\n const source = this.context.createBufferSource();\r\n source.buffer = buffer;\r\n source.connect(this.context.destination);\r\n source.start();\r\n this.silentSource = source;\r\n }\r\n\r\n private stopSilentTicker(): void {\r\n if (this.silentSource) {\r\n try {\r\n this.silentSource.onended = null;\r\n this.silentSource.stop();\r\n this.silentSource.disconnect();\r\n } catch {\r\n // already stopped\r\n }\r\n this.silentSource = null;\r\n }\r\n }\r\n\r\n /** Where the playhead is now, from elapsed `context.currentTime` (so it stays sample-accurate), and whether that is the end of the run. */\r\n private position(): { time: number; ended: boolean } {\r\n const elapsed = (this.context.currentTime - this.playStartCtxTime) * this.speed;\r\n const next = this.reverse\r\n ? this.seekOffset - elapsed\r\n : this.seekOffset + elapsed;\r\n const time = Math.max(0, Math.min(next, this.duration));\r\n return { time, ended: this.reverse ? time <= 0 : time >= this.duration };\r\n }\r\n\r\n /**\r\n * Drives ticks off `requestAnimationFrame`, firing `tick()` when the playhead reaches another\r\n * project frame — and once more at the end of the run, whether or not it landed on a boundary.\r\n *\r\n * `context.currentTime` moves in whole render quanta handed over in bursts: at 48 kHz on a\r\n * 60 Hz display it advances 8, 10.67, 18.67 or 21.33 ms between animation frames, and a\r\n * playhead read straight off it skipped up to a third of the frames. So the playhead advances\r\n * by each animation frame's own interval, pulled 5% of the way to the audio clock every frame\r\n * and snapped to it when they disagree by more than 50 ms (a stall, a seek). It stays within\r\n * a few ms of the audio and moves one frame per frame.\r\n *\r\n * A listener may `seek` (a host looping wraps to 0 when it hears the end), so the end is\r\n * re-read after the listeners and the tick callbacks have run: a run that was seeked out of\r\n * its end carries on, and one that was not pauses itself.\r\n */\r\n private startLoop(): void {\r\n if (this.rafId !== null) return;\r\n const frameOf = (t: number) => Math.floor(t * this.fps + 1e-6);\r\n let lastFrame = frameOf(this.currentTime);\r\n let steady: { time: number; at: number; epoch: number } | null = null;\r\n const steadied = (now: number): { time: number; ended: boolean } => {\r\n const audio = this.position();\r\n let time = audio.time;\r\n if (!audio.ended && steady && steady.epoch === this.seekEpoch) {\r\n const step = ((now - steady.at) / 1000) * this.speed;\r\n const predicted = this.reverse ? steady.time - step : steady.time + step;\r\n const drift = audio.time - predicted;\r\n const next = predicted + drift * 0.05;\r\n if (Math.abs(drift) <= 0.05 && next > 0 && next < this.duration) time = next;\r\n }\r\n steady = { time, at: now, epoch: this.seekEpoch };\r\n return { time, ended: audio.ended };\r\n };\r\n\r\n const loop = async (now: number) => {\r\n let epoch = this.seekEpoch;\r\n const position = steadied(now);\r\n let ended = position.ended;\r\n // This loop runs at the display's refresh rate, which on a 120 or 144Hz panel is two\r\n // or three times the rate anything is rendered at — so the clock advances every time\r\n // round (the elapsed maths in `play`/`pause` depend on it being exact) while listeners\r\n // hear about it once per frame. The end of the run always reports, or the final\r\n // position would never reach a host. See `Ticker.setCurrentTime`.\r\n const crossed = frameOf(position.time) !== lastFrame;\r\n const report = crossed || ended;\r\n this.setCurrentTime(position.time, report);\r\n\r\n const reread = () => {\r\n if (this.seekEpoch === epoch) return;\r\n epoch = this.seekEpoch;\r\n lastFrame = frameOf(this.currentTime);\r\n ended = this.position().ended;\r\n };\r\n reread();\r\n\r\n if (report) {\r\n lastFrame = frameOf(this.currentTime);\r\n // Await so that a slow tick (asset load on a clip\r\n // boundary) doesn't queue a second concurrent tick.\r\n await this.tick();\r\n reread();\r\n }\r\n\r\n // If pause() cleared the rafId while we were awaiting tick(),\r\n // do not resume the loop.\r\n if (this.rafId === null) return;\r\n if (ended) {\r\n this.pause();\r\n return;\r\n }\r\n this.rafId = requestAnimationFrame(loop);\r\n };\r\n\r\n this.rafId = requestAnimationFrame(loop);\r\n }\r\n\r\n private stopLoop(): void {\r\n if (this.rafId !== null) {\r\n cancelAnimationFrame(this.rafId);\r\n this.rafId = null;\r\n }\r\n }\r\n\r\n dispose(): void {\r\n this.stopLoop();\r\n this.stopSilentTicker();\r\n if (this.ownsContext) {\r\n void this._context?.close();\r\n }\r\n super.dispose();\r\n }\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"file":"ticker.js","sourceRoot":"","sources":["../src/ticker.ts"],"names":[],"mappings":"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","sourcesContent":["import { Ticker } from \"@motionscript/core\";\n\n/**\n * Ticker backed by a Web Audio AudioContext. Clip time follows\n * `audioContext.currentTime` (within a few ms, see `startLoop`), so any\n * AudioBufferSourceNodes scheduled on the same context play in sync.\n *\n * When paused we suspend the context (audio stops, currentTime freezes);\n * when playing we resume and a rAF loop publishes ticks at the target fps.\n *\n * A single silent buffer covering the full timeline is started on play(),\n * which keeps the context active and gives the browser a stable audio\n * graph to schedule against even on scenes that contain no audio nodes.\n */\nexport class WebTicker extends Ticker {\n private readonly contextOf: () => AudioContext;\n private readonly ownsContext: boolean;\n private _context: AudioContext | null = null;\n private fps: number;\n\n private playStartCtxTime: number = 0;\n private seekOffset: number = 0;\n private speed: number = 1;\n private reverse: boolean = false;\n\n private rafId: number | null = null;\n /** Bumped by every `seek`, so the loop can tell that a listener or tick callback moved the playhead under it. */\n private seekEpoch: number = 0;\n private silentSource: AudioBufferSourceNode | null = null;\n\n /**\n * `context` may be a getter, called on the first `play`: a session hands\n * over its audio sink's, which is created on first use, so a session that\n * never plays never opens the audio device.\n */\n constructor(opts: { context?: AudioContext | (() => AudioContext); fps: number }) {\n super();\n const { context } = opts;\n this.contextOf = typeof context === \"function\" ? context : () => context ?? new AudioContext();\n this.ownsContext = !context;\n this.fps = opts.fps;\n }\n\n getContext(): AudioContext {\n this._context ??= this.contextOf();\n return this._context;\n }\n\n private get context(): AudioContext {\n return this.getContext();\n }\n\n seek(t: number): void {\n const clamped = Math.max(0, Math.min(t, this.duration));\n this.seekOffset = clamped;\n // The clock is read only while playing; `play` anchors it afresh.\n if (this.isPlaying) this.playStartCtxTime = this.context.currentTime;\n this.seekEpoch++;\n this.setCurrentTime(clamped);\n }\n\n /**\n * Anchors elapsed-time tracking to the current `context.currentTime`, starts the silent ticker\n * + rAF tick loop and asks a suspended context to resume.\n *\n * Synchronous, as {@link Ticker.play} is: a `pause` or `seek` in the same turn must find a\n * running clock to stop. A suspended context's `currentTime` is frozen, so the clock holds at\n * the anchor until the resume lands — outside a user gesture that is never.\n */\n play(speed: number = 1, reverse: boolean = false): void {\n this.speed = speed;\n this.reverse = reverse;\n this.seekOffset = this.currentTime;\n\n if (this.context.state === \"suspended\") {\n this.context.resume().then(\n () => {\n // A pause that arrived while the resume was pending could not suspend it.\n if (!this.isPlaying && this.context.state === \"running\") void this.context.suspend();\n },\n (error) => {\n console.error(\"[WebTicker] The audio context could not resume:\", error);\n this.pause();\n },\n );\n }\n\n this.playStartCtxTime = this.context.currentTime;\n this.startSilentTicker();\n super.play(speed, reverse);\n this.startLoop();\n }\n\n /** Stops the tick loop and silent ticker, then suspends the context — freezing `currentTime` so elapsed-time math stays correct across the pause. */\n pause(): void {\n this.seekOffset = this.currentTime;\n this.stopLoop();\n this.stopSilentTicker();\n if (this._context?.state === \"running\") {\n this._context.suspend();\n }\n super.pause();\n }\n\n /** (Re)starts the silent buffer source covering the remaining timeline — keeps the AudioContext active and gives a stable graph for other AudioBufferSourceNodes to schedule against, per the class-level note. */\n private startSilentTicker(): void {\n this.stopSilentTicker();\n const remaining = Math.max(0.001, this.duration - this.seekOffset);\n const sampleRate = this.context.sampleRate;\n const buffer = this.context.createBuffer(1, Math.ceil(remaining * sampleRate), sampleRate);\n const source = this.context.createBufferSource();\n source.buffer = buffer;\n source.connect(this.context.destination);\n source.start();\n this.silentSource = source;\n }\n\n private stopSilentTicker(): void {\n if (this.silentSource) {\n try {\n this.silentSource.onended = null;\n this.silentSource.stop();\n this.silentSource.disconnect();\n } catch {\n // already stopped\n }\n this.silentSource = null;\n }\n }\n\n /** Where the playhead is now, from elapsed `context.currentTime` (so it stays sample-accurate), and whether that is the end of the run. */\n private position(): { time: number; ended: boolean } {\n const elapsed = (this.context.currentTime - this.playStartCtxTime) * this.speed;\n const next = this.reverse\n ? this.seekOffset - elapsed\n : this.seekOffset + elapsed;\n const time = Math.max(0, Math.min(next, this.duration));\n return { time, ended: this.reverse ? time <= 0 : time >= this.duration };\n }\n\n /**\n * Drives ticks off `requestAnimationFrame`, firing `tick()` when the playhead reaches another\n * project frame — and once more at the end of the run, whether or not it landed on a boundary.\n *\n * `context.currentTime` moves in whole render quanta handed over in bursts: at 48 kHz on a\n * 60 Hz display it advances 8, 10.67, 18.67 or 21.33 ms between animation frames, and a\n * playhead read straight off it skipped up to a third of the frames. So the playhead advances\n * by each animation frame's own interval, pulled 5% of the way to the audio clock every frame\n * and snapped to it when they disagree by more than 50 ms (a stall, a seek). It stays within\n * a few ms of the audio and moves one frame per frame.\n *\n * A listener may `seek` (a host looping wraps to 0 when it hears the end), so the end is\n * re-read after the listeners and the tick callbacks have run: a run that was seeked out of\n * its end carries on, and one that was not pauses itself.\n */\n private startLoop(): void {\n if (this.rafId !== null) return;\n const frameOf = (t: number) => Math.floor(t * this.fps + 1e-6);\n let lastFrame = frameOf(this.currentTime);\n let steady: { time: number; at: number; epoch: number } | null = null;\n const steadied = (now: number): { time: number; ended: boolean } => {\n const audio = this.position();\n let time = audio.time;\n if (!audio.ended && steady && steady.epoch === this.seekEpoch) {\n const step = ((now - steady.at) / 1000) * this.speed;\n const predicted = this.reverse ? steady.time - step : steady.time + step;\n const drift = audio.time - predicted;\n const next = predicted + drift * 0.05;\n if (Math.abs(drift) <= 0.05 && next > 0 && next < this.duration) time = next;\n }\n steady = { time, at: now, epoch: this.seekEpoch };\n return { time, ended: audio.ended };\n };\n\n const loop = async (now: number) => {\n let epoch = this.seekEpoch;\n const position = steadied(now);\n let ended = position.ended;\n // This loop runs at the display's refresh rate, which on a 120 or 144Hz panel is two\n // or three times the rate anything is rendered at — so the clock advances every time\n // round (the elapsed maths in `play`/`pause` depend on it being exact) while listeners\n // hear about it once per frame. The end of the run always reports, or the final\n // position would never reach a host. See `Ticker.setCurrentTime`.\n const crossed = frameOf(position.time) !== lastFrame;\n const report = crossed || ended;\n this.setCurrentTime(position.time, report);\n\n const reread = () => {\n if (this.seekEpoch === epoch) return;\n epoch = this.seekEpoch;\n lastFrame = frameOf(this.currentTime);\n ended = this.position().ended;\n };\n reread();\n\n if (report) {\n lastFrame = frameOf(this.currentTime);\n // Await so that a slow tick (asset load on a clip\n // boundary) doesn't queue a second concurrent tick.\n await this.tick();\n reread();\n }\n\n // If pause() cleared the rafId while we were awaiting tick(),\n // do not resume the loop.\n if (this.rafId === null) return;\n if (ended) {\n this.pause();\n return;\n }\n this.rafId = requestAnimationFrame(loop);\n };\n\n this.rafId = requestAnimationFrame(loop);\n }\n\n private stopLoop(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n }\n\n dispose(): void {\n this.stopLoop();\n this.stopSilentTicker();\n if (this.ownsContext) {\n void this._context?.close();\n }\n super.dispose();\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motionscript/browser",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Motion Script in the browser: WebGPU rendering through @motionscript/renderer, video and still export, audio playback.",
|
|
6
6
|
"repository": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@motionscript/core": "^0.1.0-alpha.
|
|
41
|
+
"@motionscript/core": "^0.1.0-alpha.9"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@vitest/browser": "4.1.7",
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
"typescript": "^6.0.3",
|
|
54
54
|
"typescript-eslint": "^8.48.0",
|
|
55
55
|
"vitest": "^4.1.5",
|
|
56
|
-
"@motionscript/core": "0.1.0-alpha.
|
|
56
|
+
"@motionscript/core": "0.1.0-alpha.9"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@mediabunny/mp3-encoder": "^1.49.0",
|
|
60
60
|
"esbuild-wasm": "^0.28.1",
|
|
61
61
|
"mediabunny": "^1.40.1",
|
|
62
|
-
"@motionscript/renderer": "^0.1.0-alpha.
|
|
62
|
+
"@motionscript/renderer": "^0.1.0-alpha.9"
|
|
63
63
|
},
|
|
64
64
|
"motionscriptBrowser": {
|
|
65
65
|
"assets": {
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { AudioFilterItem } from "@motionscript/core";
|
|
2
|
-
/**
|
|
3
|
-
* Result of building an audio-filter graph for one source.
|
|
4
|
-
*
|
|
5
|
-
* - `output` is the last node in the chain; connect it downstream (e.g. to the
|
|
6
|
-
* per-request gain node).
|
|
7
|
-
* - `nodes` are every intermediate node, so callers can `disconnect()` them on
|
|
8
|
-
* teardown.
|
|
9
|
-
* - `oscillators` are LFOs (from `tremolo`) the caller must `start()` — and may
|
|
10
|
-
* `stop()` — alongside the source, since oscillators need an explicit start.
|
|
11
|
-
*/
|
|
12
|
-
export interface AudioFilterGraph {
|
|
13
|
-
output: AudioNode;
|
|
14
|
-
nodes: AudioNode[];
|
|
15
|
-
oscillators: OscillatorNode[];
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Builds the Web Audio graph for a clip's filter chain, returning the node to
|
|
19
|
-
* connect downstream. Shared by live playback ({@link WebAudioSink}) and
|
|
20
|
-
* offline export (`mixAudio`) so both paths sound identical.
|
|
21
|
-
*
|
|
22
|
-
* Filters apply in array order (index 0 closest to the source). `speed` filters
|
|
23
|
-
* are intentionally ignored here — they're realized via `source.playbackRate`
|
|
24
|
-
* by the caller (and affect timeline scheduling), not as a graph node.
|
|
25
|
-
*
|
|
26
|
-
* `startTime` is the AudioContext time the source starts; `clipDuration` is the
|
|
27
|
-
* clip-time length the curves run over (a clip's, or a bus's span); `elapsed` is
|
|
28
|
-
* clip-seconds already played at a mid-clip seek (0 for a clean start, negative
|
|
29
|
-
* before the span begins). These let curve-valued params schedule and align.
|
|
30
|
-
*
|
|
31
|
-
* Pass the already-created `source` so its `playbackRate` can be set by the
|
|
32
|
-
* caller; this function only wires nodes *after* the source.
|
|
33
|
-
*/
|
|
34
|
-
export declare function buildAudioFilterGraph(ctx: BaseAudioContext, source: AudioNode, filters: readonly AudioFilterItem[], startTime?: number, clipDuration?: number, elapsed?: number): AudioFilterGraph;
|
|
35
|
-
/**
|
|
36
|
-
* Net *constant* playback-rate multiplier from any scalar `speed` filters in the
|
|
37
|
-
* chain (product of all), defaulting to 1. Curve-valued speeds contribute 1 here
|
|
38
|
-
* and are scheduled separately by {@link applySpeedToPlaybackRate}. Mirrors
|
|
39
|
-
* `Sound.effectiveSpeed()` so the renderer applies the same rate the core timeline
|
|
40
|
-
* reserved time for.
|
|
41
|
-
*/
|
|
42
|
-
export declare function effectiveSpeed(filters: readonly AudioFilterItem[] | undefined): number;
|
|
43
|
-
/** The first curve-valued `speed` filter in the chain, if any. */
|
|
44
|
-
export declare function speedCurveOf(filters: readonly AudioFilterItem[] | undefined): import("@motionscript/core").Curve | undefined;
|
|
45
|
-
/**
|
|
46
|
-
* Schedule a time-varying speed curve onto an `AudioBufferSourceNode.playbackRate`.
|
|
47
|
-
*
|
|
48
|
-
* The curve is authored over **source time** (rate as a function of how far through
|
|
49
|
-
* the source we've played). `setValueAtTime`/`linearRampToValueAtTime` schedule in
|
|
50
|
-
* **context time**, so we walk clip-time forward in small steps, convert each to a
|
|
51
|
-
* source-time offset via the integral inverse, read the curve there, and lay down a
|
|
52
|
-
* value point at the matching context time. `scalar` folds in any constant speed
|
|
53
|
-
* filters (they scale the whole rate uniformly).
|
|
54
|
-
*
|
|
55
|
-
* `elapsed` is clip-seconds already played at a mid-clip seek, so the schedule
|
|
56
|
-
* starts at the seek point and aligns the playbackRate to the clip's true position.
|
|
57
|
-
*/
|
|
58
|
-
export declare function applySpeedToPlaybackRate(rate: AudioParam, curve: ReturnType<typeof speedCurveOf>, sourceLength: number, clipDuration: number, scalar: number, startTime: number, elapsed: number): void;
|
|
59
|
-
//# sourceMappingURL=filter-graph.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"filter-graph.d.ts","sourceRoot":"","sources":["../../src/audio/filter-graph.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAA2C,MAAM,oBAAoB,CAAC;AAE9F;;;;;;;;;GASG;AACH,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,WAAW,EAAE,cAAc,EAAE,CAAC;CACjC;AA8FD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CACjC,GAAG,EAAE,gBAAgB,EACrB,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,SAAS,eAAe,EAAE,EACnC,SAAS,GAAE,MAAwB,EACnC,YAAY,GAAE,MAAU,EACxB,OAAO,GAAE,MAAU,GACpB,gBAAgB,CAwFlB;AAYD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,GAAG,SAAS,GAAG,MAAM,CAOtF;AAED,kEAAkE;AAClE,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,GAAG,SAAS,kDAM3E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CACpC,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,EACtC,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GAChB,IAAI,CAgBN"}
|