@motionscript/browser 0.0.0-stage → 0.1.0-alpha.0
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 +5 -0
- package/LICENSE +201 -0
- package/README.md +68 -3
- package/dist/asset-bytes.d.ts +50 -0
- package/dist/asset-bytes.d.ts.map +1 -0
- package/dist/asset-bytes.js +60 -0
- package/dist/asset-bytes.js.map +1 -0
- package/dist/audio/bus-graph.d.ts +43 -0
- package/dist/audio/bus-graph.d.ts.map +1 -0
- package/dist/audio/bus-graph.js +104 -0
- package/dist/audio/bus-graph.js.map +1 -0
- package/dist/audio/filter-graph.d.ts +58 -0
- package/dist/audio/filter-graph.d.ts.map +1 -0
- package/dist/audio/filter-graph.js +235 -0
- package/dist/audio/filter-graph.js.map +1 -0
- package/dist/audio/index.d.ts +5 -0
- package/dist/audio/index.d.ts.map +1 -0
- package/dist/audio/index.js +12 -0
- package/dist/audio/index.js.map +1 -0
- package/dist/audio/mixer.d.ts +59 -0
- package/dist/audio/mixer.d.ts.map +1 -0
- package/dist/audio/mixer.js +193 -0
- package/dist/audio/mixer.js.map +1 -0
- package/dist/audio/player.d.ts +49 -0
- package/dist/audio/player.d.ts.map +1 -0
- package/dist/audio/player.js +226 -0
- package/dist/audio/player.js.map +1 -0
- package/dist/browser/audio.js +2 -0
- package/dist/browser/audio.js.map +7 -0
- package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js +2 -0
- package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js.map +7 -0
- package/dist/browser/chunks/SkeletonUtils-XYDMTGTM.js +2 -0
- package/dist/browser/chunks/SkeletonUtils-XYDMTGTM.js.map +7 -0
- package/dist/browser/chunks/chunk-CTLBXZSU.js +4119 -0
- package/dist/browser/chunks/chunk-CTLBXZSU.js.map +7 -0
- package/dist/browser/chunks/chunk-FQ32PDIU.js +2 -0
- package/dist/browser/chunks/chunk-FQ32PDIU.js.map +7 -0
- package/dist/browser/chunks/chunk-FY3BTKY4.js +2 -0
- package/dist/browser/chunks/chunk-FY3BTKY4.js.map +7 -0
- package/dist/browser/chunks/chunk-NSFLRI4P.js +2 -0
- package/dist/browser/chunks/chunk-NSFLRI4P.js.map +7 -0
- package/dist/browser/chunks/three.module-Z4EOVUWF.js +2 -0
- package/dist/browser/chunks/three.module-Z4EOVUWF.js.map +7 -0
- package/dist/browser/esbuild.wasm +0 -0
- package/dist/browser/index.js +2172 -0
- package/dist/browser/index.js.map +7 -0
- package/dist/browser/manifest.json +15 -0
- package/dist/concat.d.ts +95 -0
- package/dist/concat.d.ts.map +1 -0
- package/dist/concat.js +174 -0
- package/dist/concat.js.map +1 -0
- package/dist/decode/image-levels.d.ts +137 -0
- package/dist/decode/image-levels.d.ts.map +1 -0
- package/dist/decode/image-levels.js +477 -0
- package/dist/decode/image-levels.js.map +1 -0
- package/dist/decode/levels.d.ts +37 -0
- package/dist/decode/levels.d.ts.map +1 -0
- package/dist/decode/levels.js +68 -0
- package/dist/decode/levels.js.map +1 -0
- package/dist/decode/video-levels.d.ts +37 -0
- package/dist/decode/video-levels.d.ts.map +1 -0
- package/dist/decode/video-levels.js +95 -0
- package/dist/decode/video-levels.js.map +1 -0
- package/dist/dynamic-nodes.d.ts +9 -0
- package/dist/dynamic-nodes.d.ts.map +1 -0
- package/dist/dynamic-nodes.js +56 -0
- package/dist/dynamic-nodes.js.map +1 -0
- package/dist/engine.d.ts +59 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +171 -0
- package/dist/engine.js.map +1 -0
- package/dist/exporter.d.ts +163 -0
- package/dist/exporter.d.ts.map +1 -0
- package/dist/exporter.js +263 -0
- package/dist/exporter.js.map +1 -0
- package/dist/getter.d.ts +10 -0
- package/dist/getter.d.ts.map +1 -0
- package/dist/getter.js +38 -0
- package/dist/getter.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/measure-context.d.ts +12 -0
- package/dist/measure-context.d.ts.map +1 -0
- package/dist/measure-context.js +12 -0
- package/dist/measure-context.js.map +1 -0
- package/dist/mix-timeline-audio.d.ts +42 -0
- package/dist/mix-timeline-audio.d.ts.map +1 -0
- package/dist/mix-timeline-audio.js +63 -0
- package/dist/mix-timeline-audio.js.map +1 -0
- package/dist/output.d.ts +7 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +6 -0
- package/dist/output.js.map +1 -0
- package/dist/render-context.d.ts +30 -0
- package/dist/render-context.d.ts.map +1 -0
- package/dist/render-context.js +55 -0
- package/dist/render-context.js.map +1 -0
- package/dist/screenshot.d.ts +45 -0
- package/dist/screenshot.d.ts.map +1 -0
- package/dist/screenshot.js +32 -0
- package/dist/screenshot.js.map +1 -0
- package/dist/still.d.ts +151 -0
- package/dist/still.d.ts.map +1 -0
- package/dist/still.js +222 -0
- package/dist/still.js.map +1 -0
- package/dist/storage-adapter.d.ts +367 -0
- package/dist/storage-adapter.d.ts.map +1 -0
- package/dist/storage-adapter.js +1278 -0
- package/dist/storage-adapter.js.map +1 -0
- package/dist/three/renderer.d.ts +48 -0
- package/dist/three/renderer.d.ts.map +1 -0
- package/dist/three/renderer.js +177 -0
- package/dist/three/renderer.js.map +1 -0
- package/dist/ticker.d.ts +42 -0
- package/dist/ticker.d.ts.map +1 -0
- package/dist/ticker.js +143 -0
- package/dist/ticker.js.map +1 -0
- package/package.json +74 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"still.d.ts","sourceRoot":"","sources":["../src/still.ts"],"names":[],"mappings":"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"}
|
package/dist/still.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { ManifestAssetCatalog, Registry, resolveProjectDocument, setTheme, } from "@motionscript/core";
|
|
2
|
+
import { drawFrameAt, } from "@motionscript/skia-render/export";
|
|
3
|
+
import { WebRenderContext } from "./render-context";
|
|
4
|
+
import { WebStorageAdapter } from "./storage-adapter";
|
|
5
|
+
import { getCanvasKit } from "./getter";
|
|
6
|
+
import { DEFAULT_OUTPUT } from "./output";
|
|
7
|
+
const EMPTY_MANIFEST = { image: {}, video: {}, audio: {}, font: {} };
|
|
8
|
+
/** Normalize the many ways a caller can name a frame into the engine's own form. */
|
|
9
|
+
export function toFrameSpec(options, fps) {
|
|
10
|
+
if (options?.time !== undefined) {
|
|
11
|
+
return { kind: "frame", frame: Math.round(options.time * fps) };
|
|
12
|
+
}
|
|
13
|
+
const frame = options?.frame;
|
|
14
|
+
if (frame === undefined || frame === "first")
|
|
15
|
+
return { kind: "first" };
|
|
16
|
+
if (frame === "last")
|
|
17
|
+
return { kind: "last" };
|
|
18
|
+
if (typeof frame === "number")
|
|
19
|
+
return { kind: "frame", frame };
|
|
20
|
+
return frame;
|
|
21
|
+
}
|
|
22
|
+
function mimeOf(format) {
|
|
23
|
+
return format === "jpeg" ? "image/jpeg" : "image/png";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A long-lived single-frame renderer.
|
|
27
|
+
*
|
|
28
|
+
* Where `exportScreenshot` is one frame from a standing start, this keeps the
|
|
29
|
+
* expensive things — the CanvasKit module, the WebGL Skia surface, and the
|
|
30
|
+
* storage adapter's decoded-asset cache — alive across calls, so re-rendering
|
|
31
|
+
* after an edit costs discovering one frame and a draw rather than a new GL
|
|
32
|
+
* context. That is
|
|
33
|
+
* the difference between a one-off capture and a builder that repaints while the
|
|
34
|
+
* user types.
|
|
35
|
+
*
|
|
36
|
+
* It is also the *same* draw: {@link render} goes through `drawFrameAt`, which is
|
|
37
|
+
* the pass the video exporter runs per frame. A preview and its exported image
|
|
38
|
+
* cannot drift apart, because there is only one path.
|
|
39
|
+
*
|
|
40
|
+
* ```ts
|
|
41
|
+
* const renderer = await createStillRenderer({ canvas, viewport, manifest, theme });
|
|
42
|
+
* await renderer.render({ kind: "still", nodes: [
|
|
43
|
+
* { id: "bg", type: "rect", parentId: null, layerOrder: 0, props: { width: 1920, height: 1080, fill: "bg" } },
|
|
44
|
+
* ] });
|
|
45
|
+
* const blob = await renderer.toBlob({ format: "png" });
|
|
46
|
+
* renderer.dispose();
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* **The theme is process-global in core.** {@link render} re-applies the
|
|
50
|
+
* document's (or this renderer's) before every draw, so two renderers with
|
|
51
|
+
* different themes still each draw correctly; but anything else that calls
|
|
52
|
+
* `setTheme` between a render and a read of the surface will have been
|
|
53
|
+
* overwritten by the next render.
|
|
54
|
+
*/
|
|
55
|
+
export class StillRenderer {
|
|
56
|
+
canvas;
|
|
57
|
+
renderContext;
|
|
58
|
+
storageAdapter;
|
|
59
|
+
catalog;
|
|
60
|
+
canvasKit;
|
|
61
|
+
/** True when we made the canvas and must clean it up. */
|
|
62
|
+
ownsCanvas;
|
|
63
|
+
viewportValue;
|
|
64
|
+
fpsValue;
|
|
65
|
+
scaleValue;
|
|
66
|
+
manifest;
|
|
67
|
+
theme;
|
|
68
|
+
registry;
|
|
69
|
+
disposed = false;
|
|
70
|
+
/** @internal Use {@link createStillRenderer}, which loads CanvasKit first. */
|
|
71
|
+
constructor(canvasKit, options) {
|
|
72
|
+
this.canvasKit = canvasKit;
|
|
73
|
+
this.viewportValue = options.viewport ?? DEFAULT_OUTPUT.viewport;
|
|
74
|
+
this.fpsValue = options.fps ?? DEFAULT_OUTPUT.fps;
|
|
75
|
+
this.scaleValue = options.scale ?? 1;
|
|
76
|
+
this.manifest = options.manifest ?? EMPTY_MANIFEST;
|
|
77
|
+
this.theme = options.theme;
|
|
78
|
+
this.registry = options.registry ?? new Registry();
|
|
79
|
+
this.ownsCanvas = options.canvas === undefined;
|
|
80
|
+
this.canvas = options.canvas ?? document.createElement("canvas");
|
|
81
|
+
if (this.ownsCanvas) {
|
|
82
|
+
this.canvas.style.display = "none";
|
|
83
|
+
document.body.appendChild(this.canvas);
|
|
84
|
+
}
|
|
85
|
+
this.sizeCanvas();
|
|
86
|
+
this.catalog = new ManifestAssetCatalog(this.manifest);
|
|
87
|
+
this.storageAdapter = new WebStorageAdapter(canvasKit, this.catalog, this.viewportValue, this.fpsValue);
|
|
88
|
+
this.renderContext = new WebRenderContext(canvasKit, this.storageAdapter);
|
|
89
|
+
this.renderContext.mount(this.canvas);
|
|
90
|
+
}
|
|
91
|
+
get viewport() { return this.viewportValue; }
|
|
92
|
+
get fps() { return this.fpsValue; }
|
|
93
|
+
get scale() { return this.scaleValue; }
|
|
94
|
+
/**
|
|
95
|
+
* Draw one frame of `source`, at this renderer's viewport — the surface is
|
|
96
|
+
* the output, so it wins over any size the document asks for.
|
|
97
|
+
*/
|
|
98
|
+
async render(source, options) {
|
|
99
|
+
this.assertLive();
|
|
100
|
+
const scale = options?.scale ?? this.scaleValue;
|
|
101
|
+
if (scale !== this.scaleValue) {
|
|
102
|
+
this.scaleValue = scale;
|
|
103
|
+
this.remount();
|
|
104
|
+
}
|
|
105
|
+
const project = resolveProjectDocument(source, { viewport: this.viewportValue }, { viewport: this.viewportValue, fps: this.fpsValue });
|
|
106
|
+
// Re-applied per render, not once at construction: it is a global
|
|
107
|
+
// registry, so a second renderer (or the player) may have replaced it
|
|
108
|
+
// since. Cheap — a map swap.
|
|
109
|
+
setTheme(project.theme ?? this.theme);
|
|
110
|
+
return drawFrameAt({
|
|
111
|
+
project,
|
|
112
|
+
registry: this.registry,
|
|
113
|
+
scale,
|
|
114
|
+
renderContext: this.renderContext,
|
|
115
|
+
storageAdapter: this.storageAdapter,
|
|
116
|
+
assetCatalog: this.catalog,
|
|
117
|
+
frame: toFrameSpec(options, project.fps),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/** Theme for a document that carries none of its own. */
|
|
121
|
+
setTheme(theme) { this.theme = theme; }
|
|
122
|
+
/** Swap the asset manifest, keeping every decode already cached. */
|
|
123
|
+
setManifest(manifest) {
|
|
124
|
+
this.assertLive();
|
|
125
|
+
this.manifest = manifest;
|
|
126
|
+
this.catalog = new ManifestAssetCatalog(manifest);
|
|
127
|
+
this.storageAdapter.setCatalog(this.catalog);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Change the output resolution.
|
|
131
|
+
*
|
|
132
|
+
* A full reset, unlike {@link setManifest}: the viewport clamps the resolution
|
|
133
|
+
* images are decoded *at*, so cached decodes made for the old one would be
|
|
134
|
+
* soft at the new one, and the render context's handlers captured the adapter
|
|
135
|
+
* when they were built — so both are rebuilt. Call this when the output format
|
|
136
|
+
* changes, not per edit.
|
|
137
|
+
*/
|
|
138
|
+
setViewport(viewport) {
|
|
139
|
+
this.assertLive();
|
|
140
|
+
if (viewport.width === this.viewportValue.width && viewport.height === this.viewportValue.height) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this.viewportValue = viewport;
|
|
144
|
+
// Disposing the context disposes the adapter it holds, so this releases
|
|
145
|
+
// the old surface, its handlers and every decode in one call.
|
|
146
|
+
this.renderContext.dispose();
|
|
147
|
+
this.storageAdapter = new WebStorageAdapter(this.canvasKit, this.catalog, viewport, this.fpsValue);
|
|
148
|
+
this.renderContext = new WebRenderContext(this.canvasKit, this.storageAdapter);
|
|
149
|
+
this.remount();
|
|
150
|
+
}
|
|
151
|
+
/** Encode the frame currently on the surface. */
|
|
152
|
+
async toBytes(options) {
|
|
153
|
+
const blob = await this.toBlob(options);
|
|
154
|
+
return new Uint8Array(await blob.arrayBuffer());
|
|
155
|
+
}
|
|
156
|
+
/** Encode the frame currently on the surface. */
|
|
157
|
+
toBlob(options) {
|
|
158
|
+
const canvas = this.snapshotToCanvas();
|
|
159
|
+
const { format = "png", quality = 0.92 } = options ?? {};
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
canvas.toBlob((blob) => blob
|
|
162
|
+
? resolve(blob)
|
|
163
|
+
: reject(new Error("Still encode produced no data.")), mimeOf(format), quality);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/** Encode the frame currently on the surface. */
|
|
167
|
+
toDataURL(options) {
|
|
168
|
+
const { format = "png", quality = 0.92 } = options ?? {};
|
|
169
|
+
return this.snapshotToCanvas().toDataURL(mimeOf(format), quality);
|
|
170
|
+
}
|
|
171
|
+
dispose() {
|
|
172
|
+
if (this.disposed)
|
|
173
|
+
return;
|
|
174
|
+
this.disposed = true;
|
|
175
|
+
this.renderContext.dispose();
|
|
176
|
+
if (this.ownsCanvas)
|
|
177
|
+
this.canvas.remove();
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Read the surface back into a 2D canvas, which is what actually encodes: this
|
|
181
|
+
* CanvasKit build ships no wasm image encoders, so the browser's own codecs are
|
|
182
|
+
* the only ones available.
|
|
183
|
+
*/
|
|
184
|
+
snapshotToCanvas() {
|
|
185
|
+
this.assertLive();
|
|
186
|
+
const snapshot = this.renderContext.snapshotPixels();
|
|
187
|
+
if (!snapshot)
|
|
188
|
+
throw new Error("Nothing has been rendered yet.");
|
|
189
|
+
const canvas = document.createElement("canvas");
|
|
190
|
+
canvas.width = snapshot.width;
|
|
191
|
+
canvas.height = snapshot.height;
|
|
192
|
+
const ctx = canvas.getContext("2d");
|
|
193
|
+
if (!ctx)
|
|
194
|
+
throw new Error("Still encode: could not get a 2d context.");
|
|
195
|
+
// `pixels` is unpremultiplied RGBA8888, which is exactly ImageData's layout.
|
|
196
|
+
ctx.putImageData(new ImageData(new Uint8ClampedArray(snapshot.pixels), snapshot.width, snapshot.height), 0, 0);
|
|
197
|
+
return canvas;
|
|
198
|
+
}
|
|
199
|
+
sizeCanvas() {
|
|
200
|
+
this.canvas.width = this.viewportValue.width * this.scaleValue;
|
|
201
|
+
this.canvas.height = this.viewportValue.height * this.scaleValue;
|
|
202
|
+
}
|
|
203
|
+
/** Resize the backing store and build a fresh Skia surface over it. */
|
|
204
|
+
remount() {
|
|
205
|
+
this.sizeCanvas();
|
|
206
|
+
this.renderContext.mount(this.canvas);
|
|
207
|
+
}
|
|
208
|
+
assertLive() {
|
|
209
|
+
if (this.disposed)
|
|
210
|
+
throw new Error("This StillRenderer has been disposed.");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Create a {@link StillRenderer}, loading CanvasKit if it isn't already.
|
|
215
|
+
*
|
|
216
|
+
* `getCanvasKit` memoizes, so a second renderer in the same page is cheap.
|
|
217
|
+
*/
|
|
218
|
+
export async function createStillRenderer(options = {}) {
|
|
219
|
+
const canvasKit = await getCanvasKit(options.wasmUrl);
|
|
220
|
+
return new StillRenderer(canvasKit, options);
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=still.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"still.js","sourceRoot":"","sources":["../src/still.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AssetCatalog, ManifestAssetCatalog,\n Registry,\n resolveProjectDocument,\n setTheme,\n type AssetManifest,\n type ProjectDocument,\n type SceneDocument,\n type Size2D,\n type Theme,\n} from \"@motionscript/core\";\nimport {\n drawFrameAt,\n type DrawnFrame,\n type FrameSpec,\n type ScreenshotFormat,\n} from \"@motionscript/skia-render/export\";\nimport { WebRenderContext } from \"./render-context\";\nimport { WebStorageAdapter } from \"./storage-adapter\";\nimport { getCanvasKit } from \"./getter\";\nimport { DEFAULT_OUTPUT } from \"./output\";\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 | SceneDocument;\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 {\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 wasmUrl?: string;\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 at this pixel ratio instead of the renderer's. **Resizes the canvas and\n * rebuilds the Skia surface**, so keep it off the per-edit path — render the\n * preview at its normal scale and pass this only when capturing at a higher one.\n */\n scale?: number;\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 toFrameSpec(options: RenderStillOptions | undefined, fps: number): FrameSpec {\n if (options?.time !== undefined) {\n return { kind: \"frame\", frame: Math.round(options.time * fps) };\n }\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 CanvasKit module, the WebGL Skia 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 */\nexport class StillRenderer {\n readonly canvas: HTMLCanvasElement;\n\n private renderContext: WebRenderContext;\n private storageAdapter: WebStorageAdapter;\n private catalog: AssetCatalog;\n\n private readonly canvasKit: Awaited<ReturnType<typeof getCanvasKit>>;\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 scaleValue: number;\n private manifest: AssetManifest;\n private theme?: Theme;\n private readonly registry: Registry;\n private disposed = false;\n\n /** @internal Use {@link createStillRenderer}, which loads CanvasKit first. */\n constructor(\n canvasKit: Awaited<ReturnType<typeof getCanvasKit>>,\n options: StillRendererOptions,\n ) {\n this.canvasKit = canvasKit;\n this.viewportValue = options.viewport ?? DEFAULT_OUTPUT.viewport;\n this.fpsValue = options.fps ?? DEFAULT_OUTPUT.fps;\n this.scaleValue = options.scale ?? 1;\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 = new WebStorageAdapter(\n canvasKit, this.catalog, this.viewportValue, this.fpsValue,\n );\n this.renderContext = new WebRenderContext(canvasKit, 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.scaleValue) {\n this.scaleValue = scale;\n this.remount();\n }\n\n const project = resolveProjectDocument(\n source, { viewport: this.viewportValue }, { viewport: this.viewportValue, fps: this.fpsValue },\n );\n // Re-applied per render, not once at construction: it is a global\n // registry, so a second renderer (or the player) may have replaced it\n // since. Cheap — a map swap.\n setTheme(project.theme ?? this.theme);\n\n return drawFrameAt({\n project,\n registry: this.registry,\n scale,\n renderContext: this.renderContext,\n storageAdapter: this.storageAdapter,\n assetCatalog: this.catalog,\n frame: toFrameSpec(options, project.fps!),\n });\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 = new WebStorageAdapter(\n this.canvasKit, this.catalog, viewport, this.fpsValue,\n );\n this.renderContext = new WebRenderContext(this.canvasKit, this.storageAdapter);\n this.remount();\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: this\n * CanvasKit build ships no wasm image encoders, so the browser's own codecs are\n * the only ones available.\n */\n private snapshotToCanvas(): HTMLCanvasElement {\n this.assertLive();\n const snapshot = this.renderContext.snapshotPixels();\n if (!snapshot) throw new Error(\"Nothing has been rendered yet.\");\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = snapshot.width;\n canvas.height = snapshot.height;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) throw new Error(\"Still encode: could not get a 2d context.\");\n // `pixels` is unpremultiplied RGBA8888, which is exactly ImageData's layout.\n ctx.putImageData(\n new ImageData(new Uint8ClampedArray(snapshot.pixels), snapshot.width, snapshot.height),\n 0, 0,\n );\n return canvas;\n }\n\n private sizeCanvas(): void {\n this.canvas.width = this.viewportValue.width * this.scaleValue;\n this.canvas.height = this.viewportValue.height * this.scaleValue;\n }\n\n /** Resize the backing store and build a fresh Skia surface over it. */\n private remount(): void {\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/**\n * Create a {@link StillRenderer}, loading CanvasKit if it isn't already.\n *\n * `getCanvasKit` memoizes, so a second renderer in the same page is cheap.\n */\nexport async function createStillRenderer(\n options: StillRendererOptions = {},\n): Promise<StillRenderer> {\n const canvasKit = await getCanvasKit(options.wasmUrl);\n return new StillRenderer(canvasKit, options);\n}\n"]}
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import type { CanvasKit, Image as CKImage, Surface, TypefaceFontProvider } from "@motionscript/canvaskit";
|
|
2
|
+
import { AssetCatalog, StorageAdapter, type Size2D } from "@motionscript/core";
|
|
3
|
+
import { ParagraphShapeCache } from "@motionscript/skia-render/shapes/paragraph-cache";
|
|
4
|
+
import type { SkiaAssets, SkiaTextureSource } from "@motionscript/skia-render/assets";
|
|
5
|
+
import { ImageLevelCache, type DecodePolicy } from "./decode/image-levels";
|
|
6
|
+
import { type PixelSize } from "./decode/levels";
|
|
7
|
+
/**
|
|
8
|
+
* Browser implementation of {@link StorageAdapter} — owns all async asset
|
|
9
|
+
* decoding (images, video, audio, fonts) so the render loop can stay
|
|
10
|
+
* synchronous.
|
|
11
|
+
*
|
|
12
|
+
* Images and video are held at **levels** chosen from the footprints the
|
|
13
|
+
* renderer's getters report, not from declared layout boxes: a 4K photo drawn
|
|
14
|
+
* as a thumbnail holds a thumbnail's pixels, and the same photo zoomed to fill
|
|
15
|
+
* the screen is re-decoded to match. See `decode/image-levels.ts`.
|
|
16
|
+
*/
|
|
17
|
+
export declare class WebStorageAdapter extends StorageAdapter implements SkiaAssets {
|
|
18
|
+
private canvasKit;
|
|
19
|
+
private fontMgr;
|
|
20
|
+
private disposed;
|
|
21
|
+
/**
|
|
22
|
+
* GPU surface the render context draws into; needed to upload decoded video
|
|
23
|
+
* frames straight to texture (Surface.makeImageFromTextureSource /
|
|
24
|
+
* updateTextureFromSource). Set by the render context on mount.
|
|
25
|
+
*/
|
|
26
|
+
private surface;
|
|
27
|
+
/** Open decode session per video src (opened once by loadVideo, deduped). */
|
|
28
|
+
private videoSessions;
|
|
29
|
+
/** Decoded frame bitmaps per src, keyed by quantized source timestamp; bounded to the forward/back window. */
|
|
30
|
+
private videoFrames;
|
|
31
|
+
/**
|
|
32
|
+
* Decoded past frames the Echo filter needs, kept in a SEPARATE cache so the
|
|
33
|
+
* forward/back window eviction (which is centered on the current playhead)
|
|
34
|
+
* never drops them — echo taps reach intentionally outside that window.
|
|
35
|
+
* Bounded by trimming to the most-recently requested {@link ECHO_CACHE_LIMIT}
|
|
36
|
+
* per src.
|
|
37
|
+
*/
|
|
38
|
+
private videoEchoFrames;
|
|
39
|
+
/** Last timestamp each src's playhead was asked to show, used to drive sequential decoding forward. */
|
|
40
|
+
private videoPlayhead;
|
|
41
|
+
/**
|
|
42
|
+
* Exact `(src, timestamp)` pairs a render asked for but couldn't satisfy from
|
|
43
|
+
* the warm window. A blocking caller drains these via
|
|
44
|
+
* {@link warmPendingDecodes} and re-renders.
|
|
45
|
+
*/
|
|
46
|
+
private pendingVideoFrames;
|
|
47
|
+
/** Like {@link pendingVideoFrames}, for the several past frames an echo or a second playhead needs at once. */
|
|
48
|
+
private pendingEchoFrames;
|
|
49
|
+
/** In-flight echo/extra-playhead decodes, keyed `src#quantizedTs`, so callers join one seek. */
|
|
50
|
+
private echoDecodes;
|
|
51
|
+
/**
|
|
52
|
+
* Quantized timestamps claimed per src during the current render pass, in claim
|
|
53
|
+
* order (see {@link claimVideoFrame}): index 0 holds the session's own texture,
|
|
54
|
+
* index *n* holds {@link videoAltSlots} slot *n−1*. Cleared by
|
|
55
|
+
* {@link beginRenderPass}.
|
|
56
|
+
*/
|
|
57
|
+
private videoFrameClaims;
|
|
58
|
+
/**
|
|
59
|
+
* Extra textures per src — one per *simultaneously visible* time of that clip
|
|
60
|
+
* beyond the first. Persistent and updated in place exactly like a session's
|
|
61
|
+
* own texture, so a second playhead costs a blit per frame, not an allocation.
|
|
62
|
+
*/
|
|
63
|
+
private videoAltSlots;
|
|
64
|
+
/** The last footprint each clip was drawn at, for sizing a session opened later. */
|
|
65
|
+
private videoDemand;
|
|
66
|
+
/**
|
|
67
|
+
* Whether playback is live. When false the adapter does no look-ahead
|
|
68
|
+
* prefetch, so a paused clip never drains a backlog of stale decodes.
|
|
69
|
+
*/
|
|
70
|
+
private playing;
|
|
71
|
+
private decodePolicy;
|
|
72
|
+
private readonly images;
|
|
73
|
+
private readonly imageSources;
|
|
74
|
+
private readonly reportedImageErrors;
|
|
75
|
+
private audioBuffers;
|
|
76
|
+
private audioCtx;
|
|
77
|
+
private fps;
|
|
78
|
+
/** Families already registered with the font provider (registered once, all weights at once). */
|
|
79
|
+
private registeredFontFamilies;
|
|
80
|
+
/**
|
|
81
|
+
* Families the catalog could not describe, so the warning is emitted once
|
|
82
|
+
* rather than on every `loadAt`. Cleared by {@link setCatalog}: a new
|
|
83
|
+
* manifest is exactly the event that can turn one of these into a real face.
|
|
84
|
+
*/
|
|
85
|
+
private unknownFontFamilies;
|
|
86
|
+
/** Cache of shaped straight-text runs; owns the cached Fonts. See {@link ParagraphShapeCache}. */
|
|
87
|
+
private paragraphCache;
|
|
88
|
+
/** Bumped whenever a new font family registers, invalidating runs shaped against a fallback. */
|
|
89
|
+
private fontEpoch;
|
|
90
|
+
/** Past frames the Echo cache holds per src; caps the renderer's MAX_ECHOES (32) with slack. */
|
|
91
|
+
private static readonly ECHO_CACHE_LIMIT;
|
|
92
|
+
/**
|
|
93
|
+
* Max extra textures per src (see {@link videoAltSlots}) — how many different
|
|
94
|
+
* times of one clip can be on screen together. Past it the last slot is reused.
|
|
95
|
+
*/
|
|
96
|
+
private static readonly ALT_SLOT_LIMIT;
|
|
97
|
+
constructor(canvasKit: CanvasKit, catalog: AssetCatalog, viewport: Size2D, fps: number);
|
|
98
|
+
getCanvasKit(): CanvasKit;
|
|
99
|
+
/**
|
|
100
|
+
* Adopt a new manifest, and forget which families it previously could not
|
|
101
|
+
* describe — installing a typeface folds it into the live manifest without
|
|
102
|
+
* rebuilding this adapter, and that family deserves a fresh attempt.
|
|
103
|
+
*/
|
|
104
|
+
setCatalog(catalog: AssetCatalog): void;
|
|
105
|
+
setDecodePolicy(policy: DecodePolicy): void;
|
|
106
|
+
getDecodePolicy(): DecodePolicy;
|
|
107
|
+
hasPendingDecodes(): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Settle what the last render could not draw acceptably: video frames, echo
|
|
110
|
+
* taps, the 3D runtime, and images — every exact level under `"exact"`, only
|
|
111
|
+
* a source drawn with nothing resident under `"interactive"`, whose upgrades
|
|
112
|
+
* are {@link warmPendingUpgrades}. Returns whether there was any, so a
|
|
113
|
+
* blocking caller re-renders only when something changed.
|
|
114
|
+
*/
|
|
115
|
+
warmPendingDecodes(): Promise<boolean>;
|
|
116
|
+
hasPendingUpgrades(): boolean;
|
|
117
|
+
warmPendingUpgrades(): Promise<boolean>;
|
|
118
|
+
/** @deprecated Use {@link warmPendingDecodes}. */
|
|
119
|
+
warmPendingVideo(): Promise<boolean>;
|
|
120
|
+
/** Resident decode memory, for tests and the e2e memory script. */
|
|
121
|
+
_decodeStats(): {
|
|
122
|
+
images: ReturnType<ImageLevelCache["stats"]>;
|
|
123
|
+
video: {
|
|
124
|
+
sessions: number;
|
|
125
|
+
windowFrames: number;
|
|
126
|
+
echoFrames: number;
|
|
127
|
+
frameBytes: number;
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Make `src` drawable: decode its first level. Which level that is follows the
|
|
132
|
+
* policy — see `ImageLevelCache.load`. Every later level follows the
|
|
133
|
+
* footprints the draws report.
|
|
134
|
+
*/
|
|
135
|
+
loadImage(src: string): Promise<void>;
|
|
136
|
+
/** GPU-resident image for `src` at the level this draw needs, or null until it has loaded. */
|
|
137
|
+
getCKImage(src: string, footprint?: number): CKImage | null;
|
|
138
|
+
/**
|
|
139
|
+
* Decoded RGBA8888 pixels for `src` at the level this draw needs, or null
|
|
140
|
+
* until it has loaded. The same object comes back for as long as that level
|
|
141
|
+
* is resident, which is what the 3D texture cache keys its uploads on.
|
|
142
|
+
*/
|
|
143
|
+
getImagePixels(src: string, footprint?: number): {
|
|
144
|
+
pixels: Uint8Array;
|
|
145
|
+
width: number;
|
|
146
|
+
height: number;
|
|
147
|
+
} | null;
|
|
148
|
+
getImageSize(src: string): PixelSize | null;
|
|
149
|
+
private catalogImageSize;
|
|
150
|
+
/**
|
|
151
|
+
* Decode one level of `src`.
|
|
152
|
+
*
|
|
153
|
+
* A raster decodes from its kept compressed bytes, resized by the browser's
|
|
154
|
+
* own high-quality scaler; with no manifest size it is decoded once at
|
|
155
|
+
* natural size to learn it. An SVG is rasterized from its kept `<img>` at
|
|
156
|
+
* any level, since it has no resolution of its own to cap at.
|
|
157
|
+
*/
|
|
158
|
+
private decodeImage;
|
|
159
|
+
private rasterSource;
|
|
160
|
+
/**
|
|
161
|
+
* Decodes an SVG through an `HTMLImageElement`, which every browser
|
|
162
|
+
* supports — `createImageBitmap` can't decode SVG reliably (Chrome throws on
|
|
163
|
+
* an SVG Blob). From a data URL rather than an object URL so a missing xmlns
|
|
164
|
+
* or external ref can't taint the image and fail the readback.
|
|
165
|
+
*/
|
|
166
|
+
private svgSource;
|
|
167
|
+
/** Long edge of the draw surface in device px, or the viewport before one is attached. */
|
|
168
|
+
private surfaceEdge;
|
|
169
|
+
/**
|
|
170
|
+
* The compressed bytes for `src`, from the host's local store if it has
|
|
171
|
+
* them and from the network otherwise. Every byte-reading path goes through
|
|
172
|
+
* this, so a source is local for all of images, video, audio and fonts or
|
|
173
|
+
* for none of them.
|
|
174
|
+
*/
|
|
175
|
+
private fetchBlob;
|
|
176
|
+
/**
|
|
177
|
+
* The address `src` is fetched from.
|
|
178
|
+
*
|
|
179
|
+
* A manifest key and the URL its bytes live at are the same string only
|
|
180
|
+
* while the host keys its manifest by URL; `meta.src` is the address. An
|
|
181
|
+
* undescribed source falls back to itself. `media` is audio-then-video
|
|
182
|
+
* because a video's own soundtrack is requested under the video's key.
|
|
183
|
+
*/
|
|
184
|
+
private assetUrl;
|
|
185
|
+
/** Persistent CanvasKit texture per 3D slot, so a 60 fps 3D scene doesn't allocate a texture a frame. */
|
|
186
|
+
private canvas3DTextures;
|
|
187
|
+
/**
|
|
188
|
+
* Upload a 3D renderer's canvas into a GPU-resident CanvasKit image, creating
|
|
189
|
+
* the texture on first use and updating it in place afterwards.
|
|
190
|
+
*
|
|
191
|
+
* **The source arrives unpremultiplied, even though the canvas is not.**
|
|
192
|
+
* Uploading a canvas *as a texture source* goes through the browser's unpack
|
|
193
|
+
* pipeline, where `UNPACK_PREMULTIPLY_ALPHA_WEBGL` is false by default, so
|
|
194
|
+
* what reaches Skia is straight colour. Declaring it `Premul` made Skia
|
|
195
|
+
* composite additively — invisible at full alpha, and read as `opacity`
|
|
196
|
+
* being ignored on a translucent surface. `packages/e2e` has a probe in a
|
|
197
|
+
* colour that separates the two.
|
|
198
|
+
*
|
|
199
|
+
* The returned image is owned by this adapter (keyed by slot): it must NOT
|
|
200
|
+
* be deleted after the draw, or the next frame loses its texture.
|
|
201
|
+
*/
|
|
202
|
+
upload3DFrame(key: string, source: SkiaTextureSource, width: number, height: number): CKImage | null;
|
|
203
|
+
/** Release a 3D node's texture — called when its node is swept. */
|
|
204
|
+
release3DTexture(key: string): void;
|
|
205
|
+
/**
|
|
206
|
+
* Receive the GPU surface so decoded video frames can upload straight to
|
|
207
|
+
* texture. A texture-backed image is only valid for the surface that made it,
|
|
208
|
+
* so swapping surfaces drops every one; they are recreated on the next frame.
|
|
209
|
+
* Image levels are raster images and survive.
|
|
210
|
+
*/
|
|
211
|
+
setSurface(surface: Surface | null): void;
|
|
212
|
+
/**
|
|
213
|
+
* Toggle live-playback mode. While playing, the adapter prefetches a window
|
|
214
|
+
* ahead; when paused it decodes only the exact frame the render needs, so
|
|
215
|
+
* pausing immediately quiesces.
|
|
216
|
+
*/
|
|
217
|
+
setPlaying(playing: boolean): void;
|
|
218
|
+
/** Start a render pass: forget which timestamp each clip's texture was claimed for. */
|
|
219
|
+
beginRenderPass(): void;
|
|
220
|
+
/**
|
|
221
|
+
* End a render pass: every draw has reported its footprint, so decide level
|
|
222
|
+
* changes now rather than chasing each draw.
|
|
223
|
+
*/
|
|
224
|
+
endRenderPass(): void;
|
|
225
|
+
/**
|
|
226
|
+
* Open a streaming decode session for `src` (deduped — runs once per clip)
|
|
227
|
+
* and warm the first frame around `trimStart` so the first synchronous render
|
|
228
|
+
* hits cache. The rest streams in as the playhead advances; the level
|
|
229
|
+
* follows the footprints claims report.
|
|
230
|
+
*/
|
|
231
|
+
loadVideo(src: string, _startFrame: number, _endFrame: number, trimStart?: number, _trimEnd?: number): Promise<void>;
|
|
232
|
+
getVideoSize(src: string): PixelSize | null;
|
|
233
|
+
private catalogVideoSize;
|
|
234
|
+
private trackDisplaySize;
|
|
235
|
+
/**
|
|
236
|
+
* Synchronously return the GPU image for the frame at `timestamp` (source
|
|
237
|
+
* seconds), uploaded in place into the session's one texture. On a miss it
|
|
238
|
+
* records the exact frame for a blocking re-render and, while playing, primes
|
|
239
|
+
* the sequential decoder; it returns the nearest decoded frame so the picture
|
|
240
|
+
* never goes blank mid-stream.
|
|
241
|
+
*
|
|
242
|
+
* A *drawing* caller should go through {@link claimVideoFrame}, which owns
|
|
243
|
+
* the rule for one frame needing two different times of the same clip.
|
|
244
|
+
*/
|
|
245
|
+
getVideoFrame(src: string, timestamp: number): CKImage | null;
|
|
246
|
+
/**
|
|
247
|
+
* The image to paint `src` at `timestamp` with, for one draw in the current
|
|
248
|
+
* render pass. **This, not {@link getVideoFrame}, is what a fill should call.**
|
|
249
|
+
*
|
|
250
|
+
* A session's texture is updated in place and Skia doesn't resolve a draw
|
|
251
|
+
* until the surface flushes, so two draws of the same clip at different times
|
|
252
|
+
* would both sample whatever was uploaded last. Each distinct timestamp a
|
|
253
|
+
* pass asks for therefore gets its own texture: the first claim takes the
|
|
254
|
+
* session's, the rest take {@link videoAltSlots} by claim order. Every
|
|
255
|
+
* returned image is adapter-owned.
|
|
256
|
+
*/
|
|
257
|
+
claimVideoFrame(src: string, timestamp: number, footprint?: number): CKImage | null;
|
|
258
|
+
/** Free every alt-slot texture (surface swap, teardown). */
|
|
259
|
+
private clearVideoAltSlots;
|
|
260
|
+
/** Upload the frame for a non-primary timestamp into its slot — see {@link claimVideoFrame}. */
|
|
261
|
+
private uploadAltFrame;
|
|
262
|
+
/**
|
|
263
|
+
* Upload `frame` into `slot`'s texture — in place when the size held, fresh
|
|
264
|
+
* when a level change resized the frames, since a texture is fixed-size.
|
|
265
|
+
*/
|
|
266
|
+
private uploadInto;
|
|
267
|
+
/**
|
|
268
|
+
* The decoded frame to show a non-primary playhead at `timestamp`: the exact
|
|
269
|
+
* one when it's warm at the clip's level, otherwise the nearest decoded, with
|
|
270
|
+
* the exact one queued. Null only when nothing at all is decoded yet.
|
|
271
|
+
*/
|
|
272
|
+
private decodedFrameFor;
|
|
273
|
+
/**
|
|
274
|
+
* The decoded length of `src` in seconds, or `0` when its session hasn't
|
|
275
|
+
* opened yet. The *container's* duration rather than the manifest's, so
|
|
276
|
+
* looping still works for a source whose manifest probe couldn't read one.
|
|
277
|
+
*/
|
|
278
|
+
getVideoDuration(src: string): number;
|
|
279
|
+
/**
|
|
280
|
+
* An INDEPENDENT, caller-owned image for a past `timestamp` — the Echo
|
|
281
|
+
* filter needs several frames resident at once, which the shared session
|
|
282
|
+
* texture can't give. The caller must push it to `transientImages`.
|
|
283
|
+
*
|
|
284
|
+
* On a miss it records the timestamp for {@link warmPendingDecodes} and
|
|
285
|
+
* returns null, so the tap is skipped this pass and a blocking caller lands it.
|
|
286
|
+
*/
|
|
287
|
+
getVideoFrameImage(src: string, timestamp: number, footprint?: number): CKImage | null;
|
|
288
|
+
/**
|
|
289
|
+
* Upload `frame`'s bitmap into the session's persistent texture and return it.
|
|
290
|
+
* No upload when that bitmap is already resident, so a parked playhead costs
|
|
291
|
+
* nothing per render.
|
|
292
|
+
*/
|
|
293
|
+
private uploadFrame;
|
|
294
|
+
/** Whether `frame` counts as the frame at the clip's current level. */
|
|
295
|
+
private accepts;
|
|
296
|
+
/** Decode every exact video frame and echo tap a prior render asked for, plus the 3D runtime. */
|
|
297
|
+
private warmVideo;
|
|
298
|
+
/**
|
|
299
|
+
* Sequentially decode forward from `timestamp` to fill the window ahead, using
|
|
300
|
+
* mediabunny's monotonic pipeline — far cheaper than a random seek per frame.
|
|
301
|
+
* At most one pass runs per src; it stops as soon as playback pauses or the
|
|
302
|
+
* playhead jumps away. Each sample is sized as it is snapshotted, so a level
|
|
303
|
+
* change mid-pass applies to the next frame without restarting the stream.
|
|
304
|
+
*/
|
|
305
|
+
private driveSequentialDecode;
|
|
306
|
+
/**
|
|
307
|
+
* Decode the single frame containing `timestampSec` (random access) and cache
|
|
308
|
+
* its bitmap — for cold warms, where streaming isn't appropriate. Idempotent
|
|
309
|
+
* per quantized ts at the clip's current level.
|
|
310
|
+
*/
|
|
311
|
+
private decodeAt;
|
|
312
|
+
/** Put `frame` in the window, closing what it replaces unless the echo cache shares it. */
|
|
313
|
+
private storeFrame;
|
|
314
|
+
/**
|
|
315
|
+
* Decode a single past frame into the dedicated {@link videoEchoFrames} cache.
|
|
316
|
+
* Like {@link decodeAt} but it does NOT window-evict — echo taps reach outside
|
|
317
|
+
* that window on purpose. Concurrent calls for the same frame join one decode.
|
|
318
|
+
*/
|
|
319
|
+
private decodeEchoFrameAt;
|
|
320
|
+
/** {@link decodeEchoFrameAt}'s body, past the dedup gate. */
|
|
321
|
+
private decodeEchoFrameNow;
|
|
322
|
+
private replaceEcho;
|
|
323
|
+
/** Bound the echo cache to ECHO_CACHE_LIMIT frames per src, dropping oldest-inserted first. */
|
|
324
|
+
private trimEchoCache;
|
|
325
|
+
/**
|
|
326
|
+
* Resize `sample` to the clip's level and snapshot it to an immutable bitmap,
|
|
327
|
+
* closing the sample. Drawn and snapshotted in one synchronous step, so the
|
|
328
|
+
* pooled canvas can be shared by concurrent decodes of the same clip.
|
|
329
|
+
*/
|
|
330
|
+
private snapshot;
|
|
331
|
+
private windowFrames;
|
|
332
|
+
/** Drop decoded frames (closing their bitmaps) outside the window around `timestamp` (source seconds). */
|
|
333
|
+
private evictVideoWindow;
|
|
334
|
+
/**
|
|
335
|
+
* Closest decoded frame to `timestamp`, or null if none are decoded yet — at
|
|
336
|
+
* any distance, so a not-yet-warm playhead paints the closest frame.
|
|
337
|
+
*/
|
|
338
|
+
private nearestDecoded;
|
|
339
|
+
/** Quantize a source timestamp to a stable integer ring key. */
|
|
340
|
+
private quantizeTs;
|
|
341
|
+
/**
|
|
342
|
+
* The frame in `store` closest to `timestamp`, but only within one frame step
|
|
343
|
+
* (so a request matches the sample whose grid-aligned timestamp quantized to
|
|
344
|
+
* an adjacent key, without returning a clearly wrong frame).
|
|
345
|
+
*/
|
|
346
|
+
private nearestWithin;
|
|
347
|
+
/** The first sample's duration and size; ~30fps when the track won't say. */
|
|
348
|
+
private probeFirstFrame;
|
|
349
|
+
private makeCKImageFromPixels;
|
|
350
|
+
/** Fetches and decodes `src` into a Web Audio `AudioBuffer`, cached for {@link getAudioBuffer}. No-op if already cached. */
|
|
351
|
+
loadAudio(src: string, _startFrame: number, _endFrame: number, _trimStart?: number, _trimEnd?: number): Promise<void>;
|
|
352
|
+
fetchAudioData(src: string): Promise<ArrayBuffer>;
|
|
353
|
+
getAudioBuffer(src: string): AudioBuffer | null;
|
|
354
|
+
/**
|
|
355
|
+
* Registers every weight/slant file for `fontFamily` under the bare family
|
|
356
|
+
* name, once per family, so CanvasKit's matcher can pick the closest file for
|
|
357
|
+
* any requested weight — which is what makes weights tweenable.
|
|
358
|
+
*/
|
|
359
|
+
loadFont(key: string, fontFamily: string, _fontWeight: number): Promise<boolean>;
|
|
360
|
+
getFontMgr(): TypefaceFontProvider;
|
|
361
|
+
/** The shared shaped-text cache (see {@link paragraphCache}). */
|
|
362
|
+
getParagraphCache(): ParagraphShapeCache;
|
|
363
|
+
/** Current font-registration epoch, folded into paragraph cache keys. */
|
|
364
|
+
getFontEpoch(): number;
|
|
365
|
+
dispose(): void;
|
|
366
|
+
}
|
|
367
|
+
//# sourceMappingURL=storage-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-adapter.d.ts","sourceRoot":"","sources":["../src/storage-adapter.ts"],"names":[],"mappings":"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"}
|