@cutcraft/player 0.0.0-stage → 0.2.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.
@@ -0,0 +1,892 @@
1
+ import { AssetEntry, AssetManifest, DeclaredFont, EASING_FN, FitMode, LayerClock, Placement, ResolvedState, ResolvedTrack, Transform } from "@cutcraft/kit";
2
+ import { Input, VideoSample } from "mediabunny";
3
+ interface PlayerErrorEvent {
4
+ readonly id?: string;
5
+ readonly asset?: string;
6
+ readonly error: Error;
7
+ readonly fatal: boolean;
8
+ }
9
+ interface PlayerEvents {
10
+ frameupdate: {
11
+ readonly time: number;
12
+ readonly frame: number;
13
+ };
14
+ play: {
15
+ readonly time: number;
16
+ };
17
+ pause: {
18
+ readonly time: number;
19
+ };
20
+ seeked: {
21
+ readonly time: number;
22
+ };
23
+ ended: {
24
+ readonly time: number;
25
+ };
26
+ ratechange: {
27
+ readonly rate: number;
28
+ };
29
+ volumechange: {
30
+ readonly volume: number;
31
+ readonly muted: boolean;
32
+ };
33
+ error: PlayerErrorEvent;
34
+ }
35
+ type Ctx2D = CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
36
+ type AnyCanvas = HTMLCanvasElement | OffscreenCanvas;
37
+ type CreateCanvas = (width: number, height: number) => AnyCanvas;
38
+ /**
39
+ * Where an asset's bytes come from. The player reads `blob` and `url`; `path` is a file on disk, which a Node host (the
40
+ * export's server) serves to the page as a URL.
41
+ */
42
+ type MediaHandle = {
43
+ readonly kind: "blob";
44
+ readonly blob: Blob;
45
+ } | {
46
+ readonly kind: "url";
47
+ readonly url: string;
48
+ } | {
49
+ readonly kind: "path";
50
+ readonly path: string;
51
+ };
52
+ interface MediaResolver {
53
+ resolve(assetId: string): Promise<MediaHandle>;
54
+ }
55
+ interface FrameSourceInfo {
56
+ readonly width: number;
57
+ readonly height: number;
58
+ readonly duration: number;
59
+ readonly fps?: number;
60
+ readonly decodable: boolean;
61
+ }
62
+ /** One decoded picture. Frames handed out by a source belong to the caller, who closes them. */
63
+ interface Frame {
64
+ readonly timestamp: number;
65
+ readonly duration: number;
66
+ readonly width: number;
67
+ readonly height: number;
68
+ draw(ctx: Ctx2D, dx: number, dy: number, dw: number, dh: number, crop?: {
69
+ x: number;
70
+ y: number;
71
+ w: number;
72
+ h: number;
73
+ }): void;
74
+ /** A copy that outlives the source's pools and the original's `close()`. */
75
+ retain(): Frame;
76
+ close(): void;
77
+ }
78
+ interface FrameSource {
79
+ open(): Promise<FrameSourceInfo>;
80
+ /** The last frame at or before `sourceTime`, or null before the first frame. */
81
+ frameAt(sourceTime: number): Promise<Frame | null>;
82
+ /** Frames from `from` (the frame covering it first) up to `to`, in order. */
83
+ stream(from: number, to: number): AsyncGenerator<Frame, void, unknown>;
84
+ close(): void;
85
+ }
86
+ type FrameSourceFactory = (assetId: string, handle: MediaHandle, kind: AssetEntry["kind"]) => FrameSource;
87
+ interface AudioSourceInfo {
88
+ readonly sampleRate: number;
89
+ readonly channels: number;
90
+ readonly duration: number;
91
+ readonly decodable: boolean;
92
+ }
93
+ interface AudioSource {
94
+ open(): Promise<AudioSourceInfo>;
95
+ /** PCM for [from, to) as one buffer whose first sample is `from`. */
96
+ range(from: number, to: number): Promise<AudioBuffer>;
97
+ close(): void;
98
+ }
99
+ type AudioSourceFactory = (assetId: string, handle: MediaHandle, context: BaseAudioContext) => AudioSource;
100
+ interface Clock {
101
+ /** Monotonic seconds. */
102
+ now(): number;
103
+ }
104
+ interface EnvelopePoint {
105
+ /** Audio context seconds. */
106
+ readonly at: number;
107
+ readonly value: number;
108
+ }
109
+ interface ScheduledSource {
110
+ /** `${placementId}#${chunk}#${session}`. */
111
+ readonly key: string;
112
+ readonly buffer: AudioBuffer;
113
+ /** Audio context seconds. */
114
+ readonly when: number;
115
+ readonly offset: number;
116
+ readonly duration: number;
117
+ /** The rate at `when`; `rate`, when set, automates it from there. */
118
+ readonly playbackRate: number;
119
+ readonly rate?: readonly EnvelopePoint[];
120
+ readonly envelope: readonly EnvelopePoint[];
121
+ readonly pan?: readonly EnvelopePoint[];
122
+ }
123
+ interface AudioOutput {
124
+ readonly contextTime: number;
125
+ readonly running: boolean;
126
+ schedule(src: ScheduledSource): void;
127
+ /** Stop and disconnect every source whose key starts with `keyPrefix`. */
128
+ cancel(keyPrefix: string): void;
129
+ cancelAll(): void;
130
+ setMaster(gain: number): void;
131
+ resume(): Promise<void>;
132
+ }
133
+ declare class ManualClock implements Clock {
134
+ private t;
135
+ now(): number;
136
+ advance(dt: number): void;
137
+ set(t: number): void;
138
+ }
139
+ declare class PerformanceClock implements Clock {
140
+ now(): number;
141
+ }
142
+ /** Seconds before a clip starts at which its decoder is mounted. */
143
+ declare const MOUNT_AHEAD_S = 2;
144
+ /** Seconds after a clip ends for which its decoder is kept. */
145
+ declare const UNMOUNT_BEHIND_S = 0.5;
146
+ /** Seconds an unreferenced mediabunny Input stays open before it is disposed. */
147
+ declare const INPUT_IDLE_CLOSE_S = 10;
148
+ /** Hard cap on open Inputs; idle ones are evicted LRU first. */
149
+ declare const MAX_OPEN_INPUTS = 6;
150
+ /** Decoded frames a clip decoder keeps queued ahead of the play head. */
151
+ declare const QUEUE_TARGET = 8;
152
+ declare const QUEUE_MAX = 10;
153
+ /** Effective speed (clip speed × rate) above which frames are fetched by seek per tick instead of streamed. */
154
+ declare const SEEK_MODE_SPEED = 4;
155
+ /** Reverse playback pre-decodes this window behind the cursor. */
156
+ declare const REVERSE_WINDOW_S = 1;
157
+ declare const REVERSE_WINDOW_FRAMES = 16;
158
+ /** Longest wait for a frame in `wait` mode (seek, export) before drawing without it. */
159
+ declare const FRAME_WAIT_MS = 250;
160
+ /** Audio scheduler period and horizon. */
161
+ declare const SCHEDULER_MS = 100;
162
+ declare const LOOKAHEAD_S = 0.2;
163
+ /** Source ranges longer than twice this are decoded in chunks of this length. */
164
+ declare const AUDIO_CHUNK_S = 30;
165
+ /** LRU cap, in decoded seconds, on cached PCM. */
166
+ declare const AUDIO_CACHE_S = 600;
167
+ /** Pre-decode window on play, and how long play() waits for it. */
168
+ declare const AUDIO_PRIME_S = 0.5;
169
+ declare const PLAY_PRIME_TIMEOUT_MS = 500;
170
+ /** Ducking defaults. */
171
+ declare const DUCK_ATTACK_S = 0.1;
172
+ declare const DUCK_RELEASE_S = 0.3;
173
+ /** Sampling step for multiplied gain ramps. */
174
+ declare const ENVELOPE_STEP_S = 0.05;
175
+ /** Playback rate clamp. */
176
+ declare const MIN_RATE = 0.25;
177
+ declare const MAX_RATE = 4;
178
+ /** Quiet time after the last setState before a paused preview fetches its exact frame. */
179
+ declare const SETTLE_MS = 120;
180
+ /** Tolerance when rounding a time to a frame index. */
181
+ declare const FRAME_EPS = 0.000001;
182
+ /**
183
+ * Container timestamps are often rounded to the millisecond, so the frame for
184
+ * the exact time n/fps can sit a fraction of a millisecond after it. A frame
185
+ * is picked when its timestamp is at most this far past the wanted time.
186
+ */
187
+ declare const PICK_EPS = 0.001;
188
+ interface AssetSourceDeps {
189
+ readonly media: MediaResolver;
190
+ readonly manifest: AssetManifest;
191
+ readonly frameSourceFactory: FrameSourceFactory;
192
+ readonly clock: Clock;
193
+ readonly onError: (e: PlayerErrorEvent) => void;
194
+ }
195
+ /**
196
+ * One FrameSource per asset, shared by every clip of that asset and by nested
197
+ * compositors. Ref-counted; an unreferenced source stays open for a while
198
+ * (a seek back is cheap) and is closed when idle too long or when too many
199
+ * are open.
200
+ */
201
+ declare class AssetSources {
202
+ private readonly deps;
203
+ private readonly entries;
204
+ private readonly errored;
205
+ constructor(deps: AssetSourceDeps);
206
+ /** Acquire a reference; the source opens lazily on first use. */
207
+ acquire(assetId: string): {
208
+ source: FrameSource;
209
+ info: Promise<FrameSourceInfo>;
210
+ };
211
+ release(assetId: string): void;
212
+ /** Close sources idle for too long, then the least recently used idle ones beyond the cap. */
213
+ sweep(): void;
214
+ get openCount(): number;
215
+ isOpen(assetId: string): boolean;
216
+ dispose(): void;
217
+ private close;
218
+ private open;
219
+ }
220
+ type DecodeMode = "forward" | "reverse" | "seek";
221
+ /**
222
+ * Decoded frames for one placement. Forward playback streams from the source
223
+ * into a short queue; reverse pre-decodes windows behind the cursor; seek
224
+ * mode (high speeds, scrubbing) fetches single frames. Freezes hold one
225
+ * retained frame. The decoder never blocks a playing tick: when the frame for
226
+ * `t` is not ready it returns the last frame it delivered.
227
+ */
228
+ declare class ClipDecoder {
229
+ private readonly source;
230
+ private readonly effectiveSpeed;
231
+ readonly id: string;
232
+ readonly sourceKey: string;
233
+ private queue;
234
+ private current;
235
+ private generation;
236
+ private gen;
237
+ private pumping;
238
+ private streamEnd;
239
+ private seekPending;
240
+ private reverseLow;
241
+ private reverseLoading;
242
+ private readonly freezes;
243
+ private disposed;
244
+ private primed;
245
+ private _placement;
246
+ private _children;
247
+ constructor(source: FrameSource, placement: Placement, children: readonly Placement[], effectiveSpeed: () => number);
248
+ /** Asset and direction: everything else (trim, speed, freezes) may change under a live decoder. */
249
+ static sourceKey(p: Placement): string;
250
+ get placement(): Placement;
251
+ get children(): readonly Placement[];
252
+ /**
253
+ * Take a new resolve of the same placement. A trim or speed change moves
254
+ * where source time lands; the queue is kept and the next `frameFor` reseeks
255
+ * only if the new source time falls outside what is queued.
256
+ */
257
+ update(placement: Placement, children: readonly Placement[]): void;
258
+ get mode(): DecodeMode;
259
+ get queued(): number;
260
+ get ready(): Promise<void>;
261
+ sourceTime(t: number): number;
262
+ /** Jump to composition time `t`: cancel streaming, fetch the frame, restart from there. */
263
+ prime(t: number): Promise<void>;
264
+ /**
265
+ * The frame to draw at composition time `t`. With `wait`, fetch it if it is
266
+ * not queued, for at most `FRAME_WAIT_MS`; with `exact` as well, for as long
267
+ * as it takes, so an export never draws a frame it did not wait for.
268
+ */
269
+ frameFor(t: number, wait: boolean, exact?: boolean): Promise<Frame | null>;
270
+ /** Close consumed frames; called after a frame was painted. */
271
+ advance(t: number): void;
272
+ dispose(): void;
273
+ private forwardFrame;
274
+ /** The newest queued frame at or before `st` becomes current; older ones are consumed. */
275
+ private forwardPick;
276
+ private primeAtSource;
277
+ private startStream;
278
+ private pump;
279
+ /** Pull frames until the queue is at target; a caller that finds a pump running awaits that one. */
280
+ private pumpOnce;
281
+ private seekFrame;
282
+ private reverseFrame;
283
+ private loadReverseWindow;
284
+ private activeFreeze;
285
+ private freezeFrame;
286
+ private setCurrent;
287
+ private cancelStream;
288
+ }
289
+ /**
290
+ * Keeps a ClipDecoder for every media placement near the play head and drops
291
+ * the rest. Asset sources are shared through `AssetSources`, so nested
292
+ * compositors handed the same registry never open an asset twice.
293
+ */
294
+ declare class DecoderPool {
295
+ readonly sources: AssetSources;
296
+ private readonly decoders;
297
+ private rate;
298
+ constructor(sources: AssetSources);
299
+ setRate(rate: number): void;
300
+ /** Mount decoders for placements within the window around `t`; unmount the rest. */
301
+ sync(state: ResolvedState, t: number): void;
302
+ decoderFor(id: string): ClipDecoder | undefined;
303
+ get mounted(): string[];
304
+ /** After a seek: every decoder jumps to `t`. */
305
+ seekAll(t: number): Promise<void>;
306
+ /** Resolves when every decoder has its first frame. */
307
+ settle(): Promise<void>;
308
+ dispose(): void;
309
+ private unmount;
310
+ }
311
+ /** A face to add to the page's fonts, from a URL or the file's bytes. */
312
+ interface FontFaceSource {
313
+ readonly family: string;
314
+ readonly weight?: number;
315
+ readonly style?: "normal" | "italic";
316
+ readonly source: string | ArrayBuffer;
317
+ }
318
+ /**
319
+ * Adds faces to `document.fonts` and resolves once every one has loaded. A URL source loads from that URL; bytes load as
320
+ * given. Does nothing where there is no `FontFace` (Node, tests).
321
+ */
322
+ declare function addFontFaces(faces: readonly FontFaceSource[]): Promise<void>;
323
+ /**
324
+ * Loads the faces a file declares (`state.fonts`) into the page: an `asset:` face is asked of `media` as an asset is,
325
+ * a Google face loads from Google's stylesheet. Resolves once all have loaded; a face that fails is reported through
326
+ * `onError` and the rest load. Does nothing where there is no `FontFace` (Node, tests).
327
+ */
328
+ declare function loadFileFonts(faces: readonly DeclaredFont[], media: MediaResolver, onError: (family: string, error: Error) => void): Promise<void>;
329
+ declare function defaultCreateCanvas(width: number, height: number): AnyCanvas;
330
+ type Crop$1 = {
331
+ x: number;
332
+ y: number;
333
+ w: number;
334
+ h: number;
335
+ };
336
+ /** A frame backed by an image we own (canvas or bitmap); `retain` is free. */
337
+ declare class ImageFrame implements Frame {
338
+ private readonly image;
339
+ readonly timestamp: number;
340
+ readonly duration: number;
341
+ private readonly owned;
342
+ constructor(image: CanvasImageSource & {
343
+ width: number;
344
+ height: number;
345
+ }, timestamp: number, duration: number, owned: boolean);
346
+ get width(): number;
347
+ get height(): number;
348
+ draw(ctx: Ctx2D, dx: number, dy: number, dw: number, dh: number, crop?: Crop$1): void;
349
+ retain(): Frame;
350
+ close(): void;
351
+ }
352
+ /** Copies any frame into a canvas we own. */
353
+ declare function copyFrame(frame: Frame, createCanvas: CreateCanvas): Frame;
354
+ /** Still images: one bitmap, returned for every time. */
355
+ declare class ImageFrameSource implements FrameSource {
356
+ private readonly load;
357
+ private bitmap;
358
+ constructor(load: () => Promise<ImageBitmap | HTMLImageElement>);
359
+ open(): Promise<FrameSourceInfo>;
360
+ frameAt(): Promise<Frame | null>;
361
+ stream(): AsyncGenerator<Frame, void, unknown>;
362
+ close(): void;
363
+ private frame;
364
+ }
365
+ /** Draws a grey box with a label; used for missing or undecodable assets. */
366
+ declare class ErrorFrame implements Frame {
367
+ readonly width: number;
368
+ readonly height: number;
369
+ private readonly label;
370
+ readonly timestamp = 0;
371
+ readonly duration: number;
372
+ constructor(width: number, height: number, label: string);
373
+ draw(ctx: Ctx2D, dx: number, dy: number, dw: number, dh: number): void;
374
+ retain(): Frame;
375
+ close(): void;
376
+ }
377
+ declare class ErrorFrameSource implements FrameSource {
378
+ private readonly frame;
379
+ constructor(label: string, width?: number, height?: number);
380
+ open(): Promise<FrameSourceInfo>;
381
+ frameAt(): Promise<Frame | null>;
382
+ stream(): AsyncGenerator<Frame, void, unknown>;
383
+ close(): void;
384
+ }
385
+ /** One mediabunny `Input` per asset, shared by the video and audio sources of that asset. */
386
+ declare class MediabunnyInputs {
387
+ private readonly entries;
388
+ acquire(assetId: string, handle: MediaHandle): Input;
389
+ release(assetId: string): void;
390
+ get openCount(): number;
391
+ dispose(): void;
392
+ }
393
+ /** PCM ranges of one audio track, decoded on demand into AudioBuffers of the track's sample rate. */
394
+ declare class MediabunnyAudioSource implements AudioSource {
395
+ private readonly assetId;
396
+ private readonly handle;
397
+ private readonly inputs;
398
+ private readonly context;
399
+ private readonly trackIndex;
400
+ private input;
401
+ private sink;
402
+ private sampleRate;
403
+ private channels;
404
+ constructor(assetId: string, handle: MediaHandle, inputs: MediabunnyInputs, context: BaseAudioContext, trackIndex?: number);
405
+ open(): Promise<AudioSourceInfo>;
406
+ range(from: number, to: number): Promise<AudioBuffer>;
407
+ close(): void;
408
+ }
409
+ declare function mediabunnyAudioSourceFactory(inputs: MediabunnyInputs): AudioSourceFactory;
410
+ type Crop = {
411
+ x: number;
412
+ y: number;
413
+ w: number;
414
+ h: number;
415
+ };
416
+ /** A decoded `VideoSample`: GPU-backed, drawn straight to the context, closed when consumed. */
417
+ declare class SampleFrame implements Frame {
418
+ private readonly sample;
419
+ private readonly createCanvas;
420
+ readonly timestamp: number;
421
+ readonly duration: number;
422
+ readonly width: number;
423
+ readonly height: number;
424
+ constructor(sample: VideoSample, createCanvas: CreateCanvas);
425
+ draw(ctx: Ctx2D, dx: number, dy: number, dw: number, dh: number, crop?: Crop): void;
426
+ retain(): Frame;
427
+ close(): void;
428
+ }
429
+ /** Frames of one video asset through a shared `VideoSampleSink`. */
430
+ declare class MediabunnyFrameSource implements FrameSource {
431
+ private readonly assetId;
432
+ private readonly handle;
433
+ private readonly inputs;
434
+ private readonly createCanvas;
435
+ private input;
436
+ private track;
437
+ private sink;
438
+ private stillSink;
439
+ constructor(assetId: string, handle: MediaHandle, inputs: MediabunnyInputs, createCanvas: CreateCanvas);
440
+ private wrap;
441
+ open(): Promise<FrameSourceInfo>;
442
+ frameAt(sourceTime: number): Promise<Frame | null>;
443
+ stream(from: number, to: number): AsyncGenerator<Frame, void, unknown>;
444
+ /** A canvas copy of the frame at `sourceTime`, for freeze frames; pooled canvases are copied right away. */
445
+ still(sourceTime: number): Promise<Frame | null>;
446
+ close(): void;
447
+ }
448
+ declare function mediabunnyFrameSourceFactory(inputs: MediabunnyInputs, createCanvas: CreateCanvas): FrameSourceFactory;
449
+ /** A resolver over a map of asset id → Blob or URL. */
450
+ declare function fromMap(map: Readonly<Record<string, Blob | string>>): MediaResolver;
451
+ declare function toResolver(media: MediaResolver | Readonly<Record<string, Blob | string>>): MediaResolver;
452
+ interface FilterResult {
453
+ readonly filter: string;
454
+ readonly unsupported: readonly string[];
455
+ }
456
+ type Params = Readonly<Record<string, unknown>>;
457
+ /** An effect's params with its keyframed ones (`effect.<id>.<param>` keyframes on the element) evaluated at element-relative `rel`. */
458
+ declare function paramsAt(children: readonly Placement[], effect: Placement, rel: number): Params;
459
+ /** A single canvas/CSS `filter` string for an element's effect and colour-grade children at element-relative `rel`; unsupported types are listed. */
460
+ declare function filterString(children: readonly Placement[], elementId: string, rel: number,
461
+ /** Effect types the file declares (`defineEffect()`), drawn as shaders rather than filters. */
462
+ declared?: ReadonlySet<string>): FilterResult;
463
+ type PostEffect = {
464
+ readonly type: "vignette";
465
+ readonly amount: number;
466
+ } | {
467
+ readonly type: "grain";
468
+ readonly amount: number;
469
+ readonly seed: number;
470
+ };
471
+ /** Vignette and grain, in order, with `amount` on 0..1; grain is seeded by the frame, so a frame always looks the same. */
472
+ declare function postEffects(children: readonly Placement[], frame: number, rel: number): PostEffect[] | undefined;
473
+ interface DrawRect {
474
+ readonly x: number;
475
+ readonly y: number;
476
+ readonly w: number;
477
+ readonly h: number;
478
+ }
479
+ /** Row-major 2D affine matrix in the canvas/CSS `matrix(a, b, c, d, e, f)` convention. */
480
+ interface Matrix2D {
481
+ readonly a: number;
482
+ readonly b: number;
483
+ readonly c: number;
484
+ readonly d: number;
485
+ readonly e: number;
486
+ readonly f: number;
487
+ }
488
+ interface Layout {
489
+ /** Where the element lands in composition pixels before its transform. */
490
+ readonly rect: DrawRect;
491
+ /** Maps local element coordinates (0..rect.w, 0..rect.h) to composition coordinates. */
492
+ readonly matrix: Matrix2D;
493
+ /** Source sub-rectangle in source pixels, when cropped. */
494
+ readonly crop?: DrawRect;
495
+ readonly opacity: number;
496
+ }
497
+ declare const IDENTITY: Matrix2D;
498
+ declare function multiply(m: Matrix2D, n: Matrix2D): Matrix2D;
499
+ declare const translate: (x: number, y: number) => Matrix2D;
500
+ declare const scaleM: (sx: number, sy: number) => Matrix2D;
501
+ declare function rotate(deg: number): Matrix2D;
502
+ declare function matrixToCss(m: Matrix2D): string;
503
+ interface Box {
504
+ readonly width: number;
505
+ readonly height: number;
506
+ }
507
+ /** The fitted rectangle of a source inside the composition, centred. */
508
+ declare function fitRect(comp: Box, src: Box, fit: FitMode): DrawRect;
509
+ /**
510
+ * Lay out a source of `src` pixels in the composition: crop, fit, then the
511
+ * element transform (position offset, scale, rotation about the anchor).
512
+ */
513
+ declare function layoutRect(comp: Box, src: Box, fit: FitMode, transform?: Transform | undefined): Layout;
514
+ /** The matrix mapping an element's local pixels to the composition, given its untransformed rect. */
515
+ declare function transformMatrix(rect: DrawRect, t: Transform | undefined): Matrix2D;
516
+ /** The upright box around `rect` after `m`: where a rectangle of an element's local pixels lands in the composition. */
517
+ declare function boundsOf(rect: DrawRect, m: Matrix2D): DrawRect;
518
+ /** The transform with its keyframed properties evaluated at element-relative time `rel`. */
519
+ declare function animatedTransform(base: Transform | undefined, children: readonly Placement[], rel: number): Transform;
520
+ /** CSS for a DOM element laid out like `layoutRect` does: it sits at the origin with `transform-origin: 0 0`. */
521
+ declare function cssTransform(layout: Layout): string;
522
+ /** An entrance or exit as the schema writes it. */
523
+ interface GraphicAnimation {
524
+ readonly type: string;
525
+ readonly duration?: number;
526
+ readonly easing?: string;
527
+ }
528
+ /** A rectangle in the element's own pixels. */
529
+ interface ClipRect {
530
+ readonly x: number;
531
+ readonly y: number;
532
+ readonly w: number;
533
+ readonly h: number;
534
+ }
535
+ /**
536
+ * What an element's entrance and exit do to it at one moment, in its own pixels:
537
+ * an opacity factor, a matrix applied before its layout, the part of it shown
538
+ * (null for all of it) and how much of its strokes are drawn (null when not drawing).
539
+ */
540
+ interface Motion {
541
+ readonly opacity: number;
542
+ readonly matrix: Matrix2D;
543
+ readonly clip: readonly ClipRect[] | null;
544
+ readonly draw: number | null;
545
+ }
546
+ declare const STILL: Motion;
547
+ /**
548
+ * The entrance and exit of an element `rel` seconds into its `duration`, for a
549
+ * `width` by `height` box. Both can play at once on a short element; their
550
+ * effects multiply. Pop, bounce and elastic carry their own curve; the others
551
+ * follow the animation's easing (default ease-out).
552
+ */
553
+ declare function motionAt(enter: GraphicAnimation | undefined, exit: GraphicAnimation | undefined, rel: number, duration: number, width: number, height: number): Motion;
554
+ /** One animation at raw progress `x` (0 hidden, 1 fully shown). */
555
+ declare function effect(anim: GraphicAnimation, x: number, w: number, h: number): Motion;
556
+ /** Both effects at once: opacities and draw amounts multiply, matrices compose, clips intersect. */
557
+ declare function combine(a: Motion, b: Motion): Motion;
558
+ interface TransitionRole {
559
+ readonly placement: Placement;
560
+ readonly role: "out" | "in";
561
+ /** 0..1 through the transition, eased by its curve. */
562
+ readonly progress: number;
563
+ }
564
+ interface VisibleItem {
565
+ readonly placement: Placement;
566
+ readonly track: ResolvedTrack;
567
+ readonly children: readonly Placement[];
568
+ readonly transition?: TransitionRole;
569
+ }
570
+ /** Visual, timed placements that show at `t`: tracks in document order (the first track is the bottom), items in track order. */
571
+ declare function visiblePlacements(state: ResolvedState, t: number): VisibleItem[];
572
+ declare function isVisibleAt(p: Placement, t: number, compositionDuration: number): boolean;
573
+ /** Placements that sit on a track, in track order; a multicam's generated clips follow their parent. */
574
+ declare function trackItems(state: ResolvedState, track: ResolvedTrack): Placement[];
575
+ /** One declared effect (`defineEffect()`) to run over an element's picture, with its params at this moment. */
576
+ interface ShaderPass {
577
+ readonly name: string;
578
+ readonly source: string;
579
+ readonly params: Readonly<Record<string, unknown>>;
580
+ /** Seconds into the element. */
581
+ readonly time: number;
582
+ }
583
+ /** A declared transition (`defineTransition()`) on one side of it at this moment. */
584
+ interface TransitionPass {
585
+ /** The transition's id, which pairs the outgoing side with the incoming one. */
586
+ readonly id: string;
587
+ readonly name: string;
588
+ readonly source: string;
589
+ readonly params: Readonly<Record<string, unknown>>;
590
+ /** 0..1 through the transition, eased by its curve. */
591
+ readonly progress: number;
592
+ readonly time: number;
593
+ readonly role: "out" | "in";
594
+ /** Between two clips, whose pictures the shader blends; false for one clip's edge, blended with nothing. */
595
+ readonly paired: boolean;
596
+ }
597
+ interface MaskSpec {
598
+ readonly shape?: Record<string, unknown> | undefined;
599
+ readonly path?: {
600
+ points: {
601
+ x: number;
602
+ y: number;
603
+ handleIn?: {
604
+ x: number;
605
+ y: number;
606
+ };
607
+ handleOut?: {
608
+ x: number;
609
+ y: number;
610
+ };
611
+ }[];
612
+ closed: boolean;
613
+ } | undefined;
614
+ readonly feather: number;
615
+ readonly inverted: boolean;
616
+ readonly opacity: number;
617
+ }
618
+ interface DrawOp {
619
+ readonly id: string;
620
+ readonly kind: "video" | "image" | "nested" | "error" | "adjustment";
621
+ readonly asset?: string;
622
+ readonly sourceTime?: number;
623
+ readonly nested?: {
624
+ readonly state: ResolvedState;
625
+ readonly time: number;
626
+ };
627
+ readonly rect: DrawRect;
628
+ readonly matrix: Matrix2D;
629
+ readonly crop?: DrawRect;
630
+ /** Dimensions used to express crop pixels; decoded proxies can have another resolution. */
631
+ readonly cropReference?: {
632
+ readonly width: number;
633
+ readonly height: number;
634
+ };
635
+ readonly fit: FitMode;
636
+ readonly opacity: number;
637
+ readonly blend: GlobalCompositeOperation;
638
+ readonly filter: string;
639
+ readonly borderRadius: number;
640
+ readonly mask?: MaskSpec;
641
+ /** The part shown, in composition pixels (a wipe). */
642
+ readonly clip?: readonly ClipRect[];
643
+ /** Vignette and grain, drawn over the element's own pixels. */
644
+ readonly post?: readonly PostEffect[];
645
+ /** Effects the file declares (`defineEffect()`), run over the element's picture in order. */
646
+ readonly shaders?: readonly ShaderPass[];
647
+ /** A transition the file declares (`defineTransition()`) on this side of it; replaces the built-in look. */
648
+ readonly transition?: TransitionPass;
649
+ }
650
+ /** A composition background gradient, as the schema writes it. CSS angles: 0 points up, 180 (the default) runs down. */
651
+ interface BackgroundGradient {
652
+ readonly type: "linear" | "radial" | "conic";
653
+ readonly angle?: number;
654
+ readonly center?: {
655
+ readonly x: number;
656
+ readonly y: number;
657
+ };
658
+ readonly stops: readonly {
659
+ readonly offset: number;
660
+ readonly color: string;
661
+ readonly opacity?: number;
662
+ }[];
663
+ }
664
+ interface FramePlan {
665
+ readonly time: number;
666
+ readonly frame: number;
667
+ readonly background: string;
668
+ /** A gradient that fills the frame instead of `background` (which is then its first colour). */
669
+ readonly backgroundGradient?: BackgroundGradient;
670
+ /** The footage goes over a transparent ground: overlays beneath it (see `backdropTracks`) and the background are drawn under the canvas. */
671
+ readonly clear?: true;
672
+ /** `background.mode: "blur"`: this op's picture, blurred to cover the frame, goes under everything. */
673
+ readonly backgroundBlur?: {
674
+ readonly id: string;
675
+ };
676
+ readonly ops: readonly DrawOp[];
677
+ readonly warnings: readonly string[];
678
+ }
679
+ type ResolveNested = (p: Placement) => ResolvedState | null;
680
+ declare function blendOf(value: unknown): GlobalCompositeOperation;
681
+ /**
682
+ * Everything the painter needs to draw composition time `t`, in draw order.
683
+ * Pure: the same state and time give a deep-equal plan. DOM-rendered kinds
684
+ * (title, shape, svg, sticker, layer, captions) are not in it; the DOM layer
685
+ * reads `visiblePlacements` itself.
686
+ */
687
+ declare function buildFramePlan(state: ResolvedState, t: number, resolveNested?: ResolveNested): FramePlan;
688
+ /** The composition's background as the painter draws it: a colour, or a gradient (whose first colour is `background`). */
689
+ declare function compositionBackground(state: ResolvedState): {
690
+ background: string;
691
+ backgroundGradient?: BackgroundGradient;
692
+ blur: boolean;
693
+ };
694
+ /** A gain breakpoint in composition seconds. */
695
+ type GainPoint = readonly [t: number, value: number];
696
+ interface AudioOccurrence {
697
+ readonly id: string;
698
+ readonly asset: string;
699
+ readonly chunk: number;
700
+ /** Composition range of this chunk, unclipped; the engine clips to "now". */
701
+ readonly compStart: number;
702
+ readonly compEnd: number;
703
+ /** Source seconds of the chunk's buffer; stable across windows so decoded PCM caches. */
704
+ readonly bufferFrom: number;
705
+ readonly bufferTo: number;
706
+ /** Placement speed; the engine multiplies in the transport rate. Ignored when `rate` is set. */
707
+ readonly speed: number;
708
+ /** Speed-key ramp: buffer seconds per composition second, piecewise linear over [compStart, compEnd]. */
709
+ readonly rate?: readonly GainPoint[];
710
+ readonly gain: readonly GainPoint[];
711
+ readonly pan?: readonly GainPoint[];
712
+ /** The buffer plays backwards: the engine reverses the decoded PCM. */
713
+ readonly reverse?: true;
714
+ }
715
+ /** Every audio-bearing chunk that overlaps [from, to). */
716
+ declare function buildAudioPlan(state: ResolvedState, from: number, to: number, resolveNested?: ResolveNested): AudioOccurrence[];
717
+ /**
718
+ * Gain over [winFrom, winTo] in composition seconds: volume × keyframed
719
+ * `audio.volume` × fades × automation × ducking. The factors are multiplied
720
+ * and sampled at their breakpoints plus a fixed step inside ramps, so the
721
+ * product stays close to piecewise linear.
722
+ */
723
+ declare function buildGainEnvelope(p: Placement, track: ResolvedTrack, state: ResolvedState, winFrom: number, winTo: number): GainPoint[];
724
+ /** Composition intervals where sound plays on the track `under` names by id, else on the tracks of that role; silent assets and muted clips do not duck. */
725
+ declare function duckIntervals(state: ResolvedState, under: string, exceptId: string): [number, number][];
726
+ /** Kinds the overlay plane draws (as DOM, in the preview and the export) rather than the canvas. */
727
+ declare const OVERLAY_KINDS: ReadonlySet<string>;
728
+ /**
729
+ * The tracks whose overlays draw beneath the footage: those below every
730
+ * visible track that holds footage, when one of them holds an overlay (a
731
+ * background graphic under the cut, say). Empty otherwise, and then every
732
+ * overlay draws above the footage as before. When it is not empty, the canvas
733
+ * holds only the footage over a transparent ground, and the composition
734
+ * background is painted beneath the backdrop instead.
735
+ */
736
+ declare function backdropTracks(state: ResolvedState): ReadonlySet<string>;
737
+ declare function ease(name: string | undefined, x: number): number;
738
+ /**
739
+ * The keyframed value of `property` at element-relative time `rel`. Holds
740
+ * before the first and after the last key; the segment between two keys uses
741
+ * the easing of the key it leaves. Non-numeric values step.
742
+ */
743
+ declare function keyframedValue<T>(children: readonly Placement[], property: string, rel: number, fallback: T): T;
744
+ declare function hasKeyframes(children: readonly Placement[], property: string): boolean;
745
+ declare function frameIndex(t: number, fps: number): number;
746
+ declare function frameTime(frame: number, fps: number): number;
747
+ /** `mm:ss.ff` for a composition time, what the controls show and the contact sheet labels. */
748
+ declare function timecode(t: number, fps: number): string;
749
+ /** 0..1 progress through a placement, what `useT()` returns inside a Layer. */
750
+ declare function localT(p: Placement, t: number): number;
751
+ /** What `useLayer()` returns inside a Layer: progress plus real seconds. */
752
+ declare function layerClock(p: Placement, t: number, fps: number): LayerClock;
753
+ /**
754
+ * Source seconds a media placement shows at composition time `t`: in/out
755
+ * trim, constant speed or a speed-key ramp, reverse, and freeze holds.
756
+ * A freeze is an insert hold: it shows `at` over its window, then the clip
757
+ * resumes from where it stopped; the result is clamped to the trimmed range.
758
+ */
759
+ declare function placementSourceTime(p: Placement, children: readonly Placement[], t: number): number;
760
+ declare function nestedSourceTime(p: Placement, t: number): number;
761
+ /**
762
+ * Resolves `<Nested of={Helper}>` through the kit's `resolveScene`, once per
763
+ * helper. File references (`of="file:..."`) have no loader yet and resolve
764
+ * to nothing.
765
+ */
766
+ declare function makeNestedResolver(parent: () => ResolvedState): ResolveNested;
767
+ interface CompositorDeps {
768
+ readonly sources: AssetSources;
769
+ readonly createCanvas: CreateCanvas;
770
+ readonly resolveNested?: ResolveNested | undefined;
771
+ /** Called after a frame was painted with a new frame index. */
772
+ readonly onFrame?: ((time: number, frame: number) => void) | undefined;
773
+ }
774
+ interface TickOptions {
775
+ /** Fetch missing frames before painting (seek, export). */
776
+ readonly wait: boolean;
777
+ /** With `wait`: no deadline. An export waits for every frame instead of drawing a stale one. */
778
+ readonly exact?: boolean;
779
+ }
780
+ /**
781
+ * One tick: plan the frame, sync decoders, gather frames, paint. A tick that
782
+ * arrives while another is in flight is coalesced into one re-run; a tick on
783
+ * the same frame index as the last paint only keeps decoders prefetching.
784
+ */
785
+ declare class Compositor {
786
+ private readonly deps;
787
+ readonly pool: DecoderPool;
788
+ private painter;
789
+ private ctx;
790
+ private state;
791
+ private inFlight;
792
+ private pending;
793
+ private lastFrame;
794
+ private lastPlan;
795
+ private readonly nested;
796
+ private disposed;
797
+ constructor(state: ResolvedState, deps: CompositorDeps);
798
+ attach(ctx: Ctx2D | null): void;
799
+ get renderedFrame(): number;
800
+ get attachedContext(): Ctx2D | null;
801
+ get lastPlanBuilt(): FramePlan | null;
802
+ get currentState(): ResolvedState;
803
+ setState(state: ResolvedState): void;
804
+ setRate(rate: number): void;
805
+ plan(t: number): FramePlan;
806
+ /** Jump every decoder (nested ones too) to `t`. */
807
+ seek(t: number): Promise<void>;
808
+ /**
809
+ * The next frame of a sequential render (export): decoders keep streaming
810
+ * from where they are instead of being re-primed, and every frame is waited
811
+ * for. Paint order and output are the same as `seek` then `tick`, at a
812
+ * fraction of the decode work.
813
+ */
814
+ step(t: number): Promise<void>;
815
+ tick(t: number, opts: TickOptions): Promise<void>;
816
+ dispose(): void;
817
+ private run;
818
+ private syncNested;
819
+ }
820
+ /**
821
+ * Trace the mask outline onto the context's current path, in local element
822
+ * pixels. Shape coordinates are fractions of the element box; bezier paths
823
+ * are in pixels with handles. Returns false when the spec has no usable shape.
824
+ */
825
+ declare function traceMask(ctx: Ctx2D, mask: MaskSpec, rect: DrawRect): boolean;
826
+ declare function roundedRect(ctx: Ctx2D, x: number, y: number, w: number, h: number, r: number): void;
827
+ interface PaintInputs {
828
+ readonly frames: ReadonlyMap<string, Frame | null>;
829
+ readonly nested: ReadonlyMap<string, AnyCanvas>;
830
+ }
831
+ /**
832
+ * Executes a FramePlan on a 2D context of composition size. Each op is
833
+ * drawn under its own transform, alpha, blend and filter; masks clip; an
834
+ * adjustment op re-filters everything drawn so far through a scratch canvas.
835
+ */
836
+ declare class CanvasPainter {
837
+ readonly width: number;
838
+ readonly height: number;
839
+ private readonly createCanvas;
840
+ private scratch;
841
+ /** An element drawn on its own before compositing: a feathered mask, vignette, grain. */
842
+ private layer;
843
+ private grain;
844
+ /** Runs the effects and transitions the file declares; made on first use. */
845
+ private shaders;
846
+ /** The outgoing side of a declared transition between two clips, waiting for the incoming side. */
847
+ private outgoing;
848
+ private outgoingPicture;
849
+ constructor(width: number, height: number, createCanvas: CreateCanvas);
850
+ paint(ctx: Ctx2D, plan: FramePlan, inputs: PaintInputs): void;
851
+ private drawOp;
852
+ /**
853
+ * An element with effects or a transition the file declares: drawn alone, run through its shaders, then composited.
854
+ * The outgoing side of a transition between two clips is kept until the incoming side arrives, and the two are
855
+ * blended in one pass.
856
+ */
857
+ private drawShaded;
858
+ private runner;
859
+ private outgoingCanvas;
860
+ /** The footage's picture, blurred and scaled to cover the frame, under everything; skipped when the footage covers the frame itself. */
861
+ private blurBackground;
862
+ private blit;
863
+ /** Applies the border radius and mask as a clip in local coordinates. Returns true when a mask clip was set. */
864
+ private clip;
865
+ /**
866
+ * The element alone on the layer canvas, then composited: a feathered mask punches it out with a
867
+ * blurred alpha there, and vignette and grain darken or speckle only its own pixels.
868
+ */
869
+ private drawLayered;
870
+ /** The element drawn alone on the layer canvas, at its place in the frame, its mask and vignette or grain applied. */
871
+ private alone;
872
+ /** A picture of the whole frame composited as the op: its opacity, blend, filter, wipe and rounded corners. */
873
+ private composite;
874
+ /** A tile of black and white specks, the same for the same seed; alpha up to `amount`. */
875
+ private noise;
876
+ private adjust;
877
+ /** An adjustment with declared effects: everything drawn so far, through the shaders, back over itself. */
878
+ private adjustShaded;
879
+ }
880
+ declare function setMatrix(ctx: Ctx2D, m: Matrix2D): void;
881
+ /** Whether a draw op puts its picture over the whole canvas as it is: no transform, crop, clip, fade, filter or blend. */
882
+ declare function coversCanvas(op: DrawOp, width: number, height: number): boolean;
883
+ /** The composition background (a colour or a gradient) over the whole frame. */
884
+ declare function paintBackground(ctx: Ctx2D, plan: Pick<FramePlan, "background" | "backgroundGradient">, w: number, h: number): void;
885
+ /** The same background as CSS, for the preview's ground under backdrop overlays. */
886
+ declare function backgroundCss(plan: Pick<FramePlan, "background" | "backgroundGradient">): string;
887
+ /** A canvas gradient for the frame, laid out the way CSS lays out the same gradient on a box of this size. */
888
+ declare function backgroundFill(ctx: Ctx2D, g: BackgroundGradient, w: number, h: number): CanvasGradient | string;
889
+ /** Warn once per key. Tests capture warnings with `captureWarnings`. */
890
+ declare function devWarn(key: string, message: string): void;
891
+ declare function captureWarnings(fn: ((message: string) => void) | null): void;
892
+ export { motionAt as $, MAX_OPEN_INPUTS as $t, GainPoint as A, PlayerErrorEvent as An, ErrorFrameSource as At, buildFramePlan as B, DecodeMode as Bt, EASING_FN as C, EnvelopePoint as Cn, MediabunnyFrameSource as Ct, OVERLAY_KINDS as D, FrameSourceInfo as Dn, mediabunnyAudioSourceFactory as Dt, keyframedValue as E, FrameSourceFactory as En, MediabunnyAudioSource as Et, DrawOp as F, FontFaceSource as Ft, trackItems as G, AUDIO_PRIME_S as Gt, TransitionRole as H, AssetSources as Ht, FramePlan as I, addFontFaces as It, GraphicAnimation as J, ENVELOPE_STEP_S as Jt, visiblePlacements as K, DUCK_ATTACK_S as Kt, MaskSpec as L, loadFileFonts as Lt, buildGainEnvelope as M, ScheduledSource as Mn, ImageFrameSource as Mt, duckIntervals as N, copyFrame as Nt, backdropTracks as O, MediaHandle as On, MediabunnyInputs as Ot, BackgroundGradient as P, defaultCreateCanvas as Pt, effect as Q, LOOKAHEAD_S as Qt, ResolveNested as R, DecoderPool as Rt, timecode as S, Ctx2D as Sn, toResolver as St, hasKeyframes as T, FrameSource as Tn, mediabunnyFrameSourceFactory as Tt, VisibleItem as U, AUDIO_CACHE_S as Ut, compositionBackground as V, AssetSourceDeps as Vt, isVisibleAt as W, AUDIO_CHUNK_S as Wt, STILL as X, FRAME_WAIT_MS as Xt, Motion as Y, FRAME_EPS as Yt, combine as Z, INPUT_IDLE_CLOSE_S as Zt, frameTime as _, AudioSource as _n, PostEffect as _t, backgroundCss as a, QUEUE_MAX as an, animatedTransform as at, nestedSourceTime as b, Clock as bn, postEffects as bt, paintBackground as c, REVERSE_WINDOW_S as cn, fitRect as ct, traceMask as d, SETTLE_MS as dn, multiply as dt, MAX_RATE as en, Box as et, Compositor as f, UNMOUNT_BEHIND_S as fn, rotate as ft, frameIndex as g, AudioOutput as gn, FilterResult as gt, makeNestedResolver as h, AnyCanvas as hn, translate as ht, PaintInputs as i, PLAY_PRIME_TIMEOUT_MS as in, Matrix2D as it, buildAudioPlan as j, PlayerEvents as jn, ImageFrame as jt, AudioOccurrence as k, MediaResolver as kn, ErrorFrame as kt, setMatrix as l, SCHEDULER_MS as ln, layoutRect as lt, TickOptions as m, PerformanceClock as mn, transformMatrix as mt, devWarn as n, MOUNT_AHEAD_S as nn, IDENTITY as nt, backgroundFill as o, QUEUE_TARGET as on, boundsOf as ot, CompositorDeps as p, ManualClock as pn, scaleM as pt, ClipRect as q, DUCK_RELEASE_S as qt, CanvasPainter as r, PICK_EPS as rn, Layout as rt, coversCanvas as s, REVERSE_WINDOW_FRAMES as sn, cssTransform as st, captureWarnings as t, MIN_RATE as tn, DrawRect as tt, roundedRect as u, SEEK_MODE_SPEED as un, matrixToCss as ut, layerClock as v, AudioSourceFactory as vn, filterString as vt, ease as w, Frame as wn, SampleFrame as wt, placementSourceTime as x, CreateCanvas as xn, fromMap as xt, localT as y, AudioSourceInfo as yn, paramsAt as yt, blendOf as z, ClipDecoder as zt };