@frame-by-frame/core 1.0.0-rc.1
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/LICENSE +21 -0
- package/README.md +123 -0
- package/dist/canvas.d.ts +8 -0
- package/dist/canvas.d.ts.map +1 -0
- package/dist/canvas.js +535 -0
- package/dist/canvas.js.map +1 -0
- package/dist/index-DhdyeigP.d.ts +7 -0
- package/dist/index-DhdyeigP.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/public-controller-jGcw6iqQ.js +2825 -0
- package/dist/public-controller-jGcw6iqQ.js.map +1 -0
- package/dist/timeline-DDuRBPRJ.d.ts +17 -0
- package/dist/timeline-DDuRBPRJ.d.ts.map +1 -0
- package/dist/types-BYfMRnsj.d.ts +451 -0
- package/dist/types-BYfMRnsj.d.ts.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +0 -0
- package/dist/video.d.ts +4 -0
- package/dist/video.js +4 -0
- package/package.json +98 -0
package/dist/canvas.js
ADDED
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
import { a as videoTargetRegistry, c as isMediaHtmlElement, d as createNativeVideoRenderer, f as createController, i as sourceRegistry, l as isMediaVideoElement, m as FrameByFrameError, n as createVideoRenderer, o as resolveScrollSource, p as createTimeline, r as reportAsyncError, s as isMediaDocument, u as resolveMediaReference } from "./public-controller-jGcw6iqQ.js";
|
|
2
|
+
|
|
3
|
+
//#region src/media/canvas-target.ts
|
|
4
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
5
|
+
const getElementName = (value) => {
|
|
6
|
+
const localName = value["localName"];
|
|
7
|
+
if (typeof localName === "string") return localName.toLowerCase();
|
|
8
|
+
const tagName = value["tagName"];
|
|
9
|
+
return typeof tagName === "string" ? tagName.toLowerCase() : "";
|
|
10
|
+
};
|
|
11
|
+
const isCanvasElement = (value) => isRecord(value) && value["nodeType"] === 1 && getElementName(value) === "canvas" && typeof value["getContext"] === "function" && isMediaDocument(value["ownerDocument"]);
|
|
12
|
+
const targetError = (bindingId, message, cause) => {
|
|
13
|
+
throw new FrameByFrameError("INVALID_TARGET_TYPE", message, {
|
|
14
|
+
cause,
|
|
15
|
+
details: { bindingId }
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
/** Prevents more than one mounted binding from drawing to the same canvas. */
|
|
19
|
+
var CanvasTargetRegistry = class {
|
|
20
|
+
#owners = /* @__PURE__ */ new WeakMap();
|
|
21
|
+
acquire(target, owner, bindingId) {
|
|
22
|
+
if (this.#owners.has(target)) throw new FrameByFrameError("TARGET_CONFLICT", "The canvas target is already controlled by another mounted binding.", { details: { bindingId } });
|
|
23
|
+
this.#owners.set(target, owner);
|
|
24
|
+
let active = true;
|
|
25
|
+
return () => {
|
|
26
|
+
if (!active) return;
|
|
27
|
+
active = false;
|
|
28
|
+
if (this.#owners.get(target) === owner) this.#owners.delete(target);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const createOwnedCanvas = (mountTo, bindingId) => {
|
|
33
|
+
const document = mountTo.ownerDocument;
|
|
34
|
+
if (!isMediaDocument(document)) throw new FrameByFrameError("ENVIRONMENT_UNAVAILABLE", "The canvas mount container does not expose an owner document.", { details: { bindingId } });
|
|
35
|
+
const target = document.createElement("canvas");
|
|
36
|
+
if (!isCanvasElement(target)) return targetError(bindingId, "The owner document could not create an HTMLCanvasElement.", target);
|
|
37
|
+
mountTo.appendChild(target);
|
|
38
|
+
return target;
|
|
39
|
+
};
|
|
40
|
+
/** Resolves, creates, and globally claims one binding's visible canvas. */
|
|
41
|
+
const resolveCanvasTarget = (config, registry) => {
|
|
42
|
+
const owner = {};
|
|
43
|
+
let target;
|
|
44
|
+
let owned = false;
|
|
45
|
+
if (config.target !== void 0) {
|
|
46
|
+
const candidate = resolveMediaReference(config.target, config.id, "canvas target");
|
|
47
|
+
if (!isCanvasElement(candidate)) return targetError(config.id, "The resolved target must be an HTMLCanvasElement.", candidate);
|
|
48
|
+
target = candidate;
|
|
49
|
+
} else {
|
|
50
|
+
const candidate = resolveMediaReference(config.mountTo, config.id, "canvas mount container");
|
|
51
|
+
if (!isMediaHtmlElement(candidate)) return targetError(config.id, "The resolved mountTo value must be an HTMLElement.", candidate);
|
|
52
|
+
target = createOwnedCanvas(candidate, config.id);
|
|
53
|
+
owned = true;
|
|
54
|
+
}
|
|
55
|
+
let releaseOwnership = null;
|
|
56
|
+
try {
|
|
57
|
+
releaseOwnership = registry.acquire(target, owner, config.id);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (owned) target.parentNode?.removeChild(target);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
let active = true;
|
|
63
|
+
return {
|
|
64
|
+
target,
|
|
65
|
+
owned,
|
|
66
|
+
release: () => {
|
|
67
|
+
if (!active) return;
|
|
68
|
+
active = false;
|
|
69
|
+
releaseOwnership();
|
|
70
|
+
if (owned) target.parentNode?.removeChild(target);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
/** Resolves or creates the video decoder used behind one visible canvas. */
|
|
75
|
+
const resolveCanvasDecoder = (config, canvas, registry) => {
|
|
76
|
+
const canvasOptions = config.canvas;
|
|
77
|
+
if (canvasOptions === null) throw new FrameByFrameError("INVALID_MEDIA_CONFIG", "Canvas renderer options are missing.", { details: { bindingId: config.id } });
|
|
78
|
+
const owner = {};
|
|
79
|
+
const supplied = canvasOptions.decoderTarget !== void 0;
|
|
80
|
+
let target;
|
|
81
|
+
if (supplied) {
|
|
82
|
+
const candidate = resolveMediaReference(canvasOptions.decoderTarget, config.id, "canvas decoder target");
|
|
83
|
+
if (!isMediaVideoElement(candidate)) return targetError(config.id, "The resolved canvas decoderTarget must be an HTMLVideoElement.", candidate);
|
|
84
|
+
target = candidate;
|
|
85
|
+
} else {
|
|
86
|
+
const document = canvas.ownerDocument;
|
|
87
|
+
if (!isMediaDocument(document)) throw new FrameByFrameError("ENVIRONMENT_UNAVAILABLE", "The canvas target does not expose an owner document for its video decoder.", { details: { bindingId: config.id } });
|
|
88
|
+
const candidate = document.createElement("video");
|
|
89
|
+
if (!isMediaVideoElement(candidate)) return targetError(config.id, "The canvas owner document could not create an HTMLVideoElement decoder.", candidate);
|
|
90
|
+
target = candidate;
|
|
91
|
+
}
|
|
92
|
+
const releaseOwnership = registry.acquire(target, owner, config.id);
|
|
93
|
+
let active = true;
|
|
94
|
+
return {
|
|
95
|
+
target,
|
|
96
|
+
owned: !supplied,
|
|
97
|
+
release: () => {
|
|
98
|
+
if (!active) return;
|
|
99
|
+
active = false;
|
|
100
|
+
releaseOwnership();
|
|
101
|
+
if (!supplied) target.parentNode?.removeChild(target);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/media/canvas-layout.ts
|
|
108
|
+
const centered = (outer, inner) => (outer - inner) / 2;
|
|
109
|
+
/** Computes centered object-fit-like rectangles without reading layout or touching a canvas. */
|
|
110
|
+
const calculateCanvasDrawPlan = (sourceWidth, sourceHeight, targetWidth, targetHeight, fit, pixelRatio) => {
|
|
111
|
+
if (![
|
|
112
|
+
sourceWidth,
|
|
113
|
+
sourceHeight,
|
|
114
|
+
targetWidth,
|
|
115
|
+
targetHeight,
|
|
116
|
+
pixelRatio
|
|
117
|
+
].every((value) => Number.isFinite(value) && value > 0)) return null;
|
|
118
|
+
if (fit === "fill") return {
|
|
119
|
+
sourceX: 0,
|
|
120
|
+
sourceY: 0,
|
|
121
|
+
sourceWidth,
|
|
122
|
+
sourceHeight,
|
|
123
|
+
destinationX: 0,
|
|
124
|
+
destinationY: 0,
|
|
125
|
+
destinationWidth: targetWidth,
|
|
126
|
+
destinationHeight: targetHeight
|
|
127
|
+
};
|
|
128
|
+
if (fit === "none") {
|
|
129
|
+
const destinationWidth = sourceWidth * pixelRatio;
|
|
130
|
+
const destinationHeight = sourceHeight * pixelRatio;
|
|
131
|
+
return {
|
|
132
|
+
sourceX: 0,
|
|
133
|
+
sourceY: 0,
|
|
134
|
+
sourceWidth,
|
|
135
|
+
sourceHeight,
|
|
136
|
+
destinationX: centered(targetWidth, destinationWidth),
|
|
137
|
+
destinationY: centered(targetHeight, destinationHeight),
|
|
138
|
+
destinationWidth,
|
|
139
|
+
destinationHeight
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const sourceRatio = sourceWidth / sourceHeight;
|
|
143
|
+
const targetRatio = targetWidth / targetHeight;
|
|
144
|
+
if (fit === "cover") {
|
|
145
|
+
if (sourceRatio > targetRatio) {
|
|
146
|
+
const croppedWidth = sourceHeight * targetRatio;
|
|
147
|
+
return {
|
|
148
|
+
sourceX: centered(sourceWidth, croppedWidth),
|
|
149
|
+
sourceY: 0,
|
|
150
|
+
sourceWidth: croppedWidth,
|
|
151
|
+
sourceHeight,
|
|
152
|
+
destinationX: 0,
|
|
153
|
+
destinationY: 0,
|
|
154
|
+
destinationWidth: targetWidth,
|
|
155
|
+
destinationHeight: targetHeight
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const croppedHeight = sourceWidth / targetRatio;
|
|
159
|
+
return {
|
|
160
|
+
sourceX: 0,
|
|
161
|
+
sourceY: centered(sourceHeight, croppedHeight),
|
|
162
|
+
sourceWidth,
|
|
163
|
+
sourceHeight: croppedHeight,
|
|
164
|
+
destinationX: 0,
|
|
165
|
+
destinationY: 0,
|
|
166
|
+
destinationWidth: targetWidth,
|
|
167
|
+
destinationHeight: targetHeight
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const scale = Math.min(targetWidth / sourceWidth, targetHeight / sourceHeight);
|
|
171
|
+
const destinationWidth = sourceWidth * scale;
|
|
172
|
+
const destinationHeight = sourceHeight * scale;
|
|
173
|
+
return {
|
|
174
|
+
sourceX: 0,
|
|
175
|
+
sourceY: 0,
|
|
176
|
+
sourceWidth,
|
|
177
|
+
sourceHeight,
|
|
178
|
+
destinationX: centered(targetWidth, destinationWidth),
|
|
179
|
+
destinationY: centered(targetHeight, destinationHeight),
|
|
180
|
+
destinationWidth,
|
|
181
|
+
destinationHeight
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/media/canvas-renderer.ts
|
|
187
|
+
const initialState = () => ({
|
|
188
|
+
loadState: "idle",
|
|
189
|
+
loadProgress: Object.freeze({}),
|
|
190
|
+
activeClipId: null,
|
|
191
|
+
selectedSource: null,
|
|
192
|
+
duration: null,
|
|
193
|
+
appliedTime: null,
|
|
194
|
+
presentedTime: null,
|
|
195
|
+
seeking: false,
|
|
196
|
+
error: null
|
|
197
|
+
});
|
|
198
|
+
const decoderConfig = (config) => Object.freeze({
|
|
199
|
+
...config,
|
|
200
|
+
clips: Object.freeze(config.clips.map((clip) => clip.preload === "metadata" ? Object.freeze({
|
|
201
|
+
...clip,
|
|
202
|
+
preload: "auto"
|
|
203
|
+
}) : clip))
|
|
204
|
+
});
|
|
205
|
+
const canvasError = (code, message, bindingId, cause) => new FrameByFrameError(code, message, {
|
|
206
|
+
cause,
|
|
207
|
+
details: { bindingId }
|
|
208
|
+
});
|
|
209
|
+
const isSecurityError = (cause) => typeof cause === "object" && cause !== null && "name" in cause && cause.name === "SecurityError";
|
|
210
|
+
const resolveDevicePixelRatio = (target) => {
|
|
211
|
+
const ownerRatio = target.ownerDocument.defaultView?.devicePixelRatio;
|
|
212
|
+
const globalRatio = globalThis.devicePixelRatio;
|
|
213
|
+
const ratio = typeof ownerRatio === "number" ? ownerRatio : globalRatio;
|
|
214
|
+
return typeof ratio === "number" && Number.isFinite(ratio) && ratio > 0 ? ratio : 1;
|
|
215
|
+
};
|
|
216
|
+
var CanvasRenderer = class {
|
|
217
|
+
#config;
|
|
218
|
+
#canvasHandle;
|
|
219
|
+
#decoderHandle;
|
|
220
|
+
#canvas;
|
|
221
|
+
#decoder;
|
|
222
|
+
#context;
|
|
223
|
+
#onEvent;
|
|
224
|
+
#drawWaiters = /* @__PURE__ */ new Set();
|
|
225
|
+
#video;
|
|
226
|
+
#removeDecoderListeners;
|
|
227
|
+
#fallbackState = initialState();
|
|
228
|
+
#canvasFailure = null;
|
|
229
|
+
#presentedTime = null;
|
|
230
|
+
#lastDrawSignature = null;
|
|
231
|
+
#drawGeneration = 0;
|
|
232
|
+
#activity;
|
|
233
|
+
#destroyed = false;
|
|
234
|
+
constructor(config, canvasHandle, decoderHandle, onEvent, activity, dependencies) {
|
|
235
|
+
this.#config = config;
|
|
236
|
+
this.#canvasHandle = canvasHandle;
|
|
237
|
+
this.#decoderHandle = decoderHandle;
|
|
238
|
+
this.#canvas = canvasHandle.target;
|
|
239
|
+
this.#decoder = decoderHandle.target;
|
|
240
|
+
this.#onEvent = onEvent;
|
|
241
|
+
this.#activity = activity;
|
|
242
|
+
let context = null;
|
|
243
|
+
try {
|
|
244
|
+
context = this.#canvas.getContext("2d");
|
|
245
|
+
} catch (cause) {
|
|
246
|
+
this.#canvasFailure = canvasError("CANVAS_CONTEXT_UNAVAILABLE", "The canvas target could not create a 2D rendering context.", config.id, cause);
|
|
247
|
+
}
|
|
248
|
+
if (context === null && this.#canvasFailure === null) this.#canvasFailure = canvasError("CANVAS_CONTEXT_UNAVAILABLE", "The canvas target does not provide a 2D rendering context.", config.id);
|
|
249
|
+
this.#context = context;
|
|
250
|
+
if (context === null) {
|
|
251
|
+
this.#video = null;
|
|
252
|
+
this.#decoderHandle.release();
|
|
253
|
+
this.#fallbackState = {
|
|
254
|
+
...this.#fallbackState,
|
|
255
|
+
loadState: "error",
|
|
256
|
+
error: this.#canvasFailure
|
|
257
|
+
};
|
|
258
|
+
this.#removeDecoderListeners = () => void 0;
|
|
259
|
+
globalThis.queueMicrotask(() => {
|
|
260
|
+
if (!this.#destroyed && this.#canvasFailure !== null) this.#onEvent({
|
|
261
|
+
type: "error",
|
|
262
|
+
error: this.#canvasFailure
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
this.#video = createNativeVideoRenderer(decoderConfig(config), decoderHandle, (event) => {
|
|
268
|
+
this.#handleVideoEvent(event);
|
|
269
|
+
}, dependencies, activity, this.#canvas);
|
|
270
|
+
const scheduleFallbackDraw = () => {
|
|
271
|
+
const generation = this.#drawGeneration;
|
|
272
|
+
const presentedTime = this.#decoder.currentTime;
|
|
273
|
+
globalThis.queueMicrotask(() => {
|
|
274
|
+
if (generation === this.#drawGeneration && !this.#destroyed) this.#drawDecoderFrame(null, null, null, false, presentedTime);
|
|
275
|
+
});
|
|
276
|
+
};
|
|
277
|
+
this.#decoder.addEventListener("loadeddata", scheduleFallbackDraw);
|
|
278
|
+
this.#decoder.addEventListener("seeked", scheduleFallbackDraw);
|
|
279
|
+
this.#removeDecoderListeners = () => {
|
|
280
|
+
this.#decoder.removeEventListener("loadeddata", scheduleFallbackDraw);
|
|
281
|
+
this.#decoder.removeEventListener("seeked", scheduleFallbackDraw);
|
|
282
|
+
};
|
|
283
|
+
this.resize();
|
|
284
|
+
}
|
|
285
|
+
prepareConfig(config) {
|
|
286
|
+
this.#assertNotDestroyed();
|
|
287
|
+
const currentCanvas = this.#config.canvas;
|
|
288
|
+
const nextCanvas = config.canvas;
|
|
289
|
+
if (config.id !== this.#config.id || config.axis !== this.#config.axis || config.renderer !== "canvas" || config.target !== this.#config.target || config.mountTo !== this.#config.mountTo || currentCanvas === null || nextCanvas === null || config.canvas?.decoderTarget !== currentCanvas.decoderTarget) throw new FrameByFrameError("INVALID_BREAKPOINT_CONFIG", "Responsive overrides cannot change canvas identity, renderer, target, or decoder ownership.", { details: { bindingId: this.#config.id } });
|
|
290
|
+
const videoTransaction = this.#config.decoderSignature === config.decoderSignature ? void 0 : this.#video?.prepareConfig(decoderConfig(config));
|
|
291
|
+
let settled = false;
|
|
292
|
+
return {
|
|
293
|
+
commit: () => {
|
|
294
|
+
if (settled) return;
|
|
295
|
+
settled = true;
|
|
296
|
+
videoTransaction?.commit();
|
|
297
|
+
this.#config = config;
|
|
298
|
+
if (this.#context !== null) this.#canvasFailure = null;
|
|
299
|
+
this.#lastDrawSignature = null;
|
|
300
|
+
const generation = ++this.#drawGeneration;
|
|
301
|
+
this.#resolveDrawWaiters();
|
|
302
|
+
globalThis.queueMicrotask(() => {
|
|
303
|
+
if (!this.#destroyed && generation === this.#drawGeneration) this.resize();
|
|
304
|
+
});
|
|
305
|
+
},
|
|
306
|
+
cancel: () => {
|
|
307
|
+
if (settled) return;
|
|
308
|
+
settled = true;
|
|
309
|
+
videoTransaction?.cancel();
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
setActivity(activity) {
|
|
314
|
+
this.#assertNotDestroyed();
|
|
315
|
+
this.#activity = activity;
|
|
316
|
+
this.#video?.setActivity(activity);
|
|
317
|
+
if (activity === "active") this.resize();
|
|
318
|
+
}
|
|
319
|
+
setResolution(resolution) {
|
|
320
|
+
this.#assertNotDestroyed();
|
|
321
|
+
this.#video?.setResolution(resolution);
|
|
322
|
+
}
|
|
323
|
+
load() {
|
|
324
|
+
this.#assertNotDestroyed();
|
|
325
|
+
if (this.#video === null) {
|
|
326
|
+
const error = this.#canvasFailure;
|
|
327
|
+
if (error === null) return Promise.reject(canvasError("CANVAS_CONTEXT_UNAVAILABLE", "The canvas renderer is unavailable.", this.#config.id));
|
|
328
|
+
return Promise.reject(error);
|
|
329
|
+
}
|
|
330
|
+
return this.#video.load();
|
|
331
|
+
}
|
|
332
|
+
async whenReady() {
|
|
333
|
+
this.#assertNotDestroyed();
|
|
334
|
+
if (this.#video === null) {
|
|
335
|
+
const error = this.#canvasFailure;
|
|
336
|
+
if (error === null) throw canvasError("CANVAS_CONTEXT_UNAVAILABLE", "The canvas renderer is unavailable.", this.#config.id);
|
|
337
|
+
throw error;
|
|
338
|
+
}
|
|
339
|
+
for (;;) {
|
|
340
|
+
const generation = this.#drawGeneration;
|
|
341
|
+
await this.#video.whenReady();
|
|
342
|
+
if (generation !== this.#drawGeneration) continue;
|
|
343
|
+
if (this.#canvasFailure !== null) throw this.#canvasFailure;
|
|
344
|
+
const state = this.#video.getState();
|
|
345
|
+
const clip = this.#config.clips.find((candidate) => candidate.id === state.activeClipId);
|
|
346
|
+
if (clip === void 0 || clip.preload === "none" || state.loadState === "idle" || state.loadState === "unloaded") return;
|
|
347
|
+
if (this.#presentedTime !== null && this.#lastDrawSignature !== null) return;
|
|
348
|
+
await new Promise((resolve, reject) => {
|
|
349
|
+
this.#drawWaiters.add({
|
|
350
|
+
resolve,
|
|
351
|
+
reject
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
if (generation === this.#drawGeneration && this.#lastDrawSignature !== null) return;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
unload() {
|
|
358
|
+
this.#assertNotDestroyed();
|
|
359
|
+
++this.#drawGeneration;
|
|
360
|
+
this.#lastDrawSignature = null;
|
|
361
|
+
this.#presentedTime = null;
|
|
362
|
+
this.#resolveDrawWaiters();
|
|
363
|
+
if (this.#video === null) {
|
|
364
|
+
this.#fallbackState = {
|
|
365
|
+
...this.#fallbackState,
|
|
366
|
+
loadState: "unloaded",
|
|
367
|
+
error: null
|
|
368
|
+
};
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
this.#video.unload();
|
|
372
|
+
}
|
|
373
|
+
resize() {
|
|
374
|
+
this.#assertNotDestroyed();
|
|
375
|
+
const options = this.#canvasOptions();
|
|
376
|
+
const ratio = options.pixelRatio === "device" ? resolveDevicePixelRatio(this.#canvas) : options.pixelRatio;
|
|
377
|
+
const cssWidth = this.#canvas.clientWidth;
|
|
378
|
+
const cssHeight = this.#canvas.clientHeight;
|
|
379
|
+
if (!Number.isFinite(cssWidth) || !Number.isFinite(cssHeight) || cssWidth <= 0 || cssHeight <= 0) return;
|
|
380
|
+
const width = Math.max(1, Math.round(cssWidth * ratio));
|
|
381
|
+
const height = Math.max(1, Math.round(cssHeight * ratio));
|
|
382
|
+
if (this.#canvas.width !== width) this.#canvas.width = width;
|
|
383
|
+
if (this.#canvas.height !== height) this.#canvas.height = height;
|
|
384
|
+
this.#lastDrawSignature = null;
|
|
385
|
+
this.#drawDecoderFrame(null, null, null, true, this.#presentedTime ?? this.#decoder.currentTime);
|
|
386
|
+
}
|
|
387
|
+
getTarget() {
|
|
388
|
+
this.#assertNotDestroyed();
|
|
389
|
+
return this.#canvas;
|
|
390
|
+
}
|
|
391
|
+
getState() {
|
|
392
|
+
return {
|
|
393
|
+
...this.#video?.getState() ?? this.#fallbackState,
|
|
394
|
+
...this.#canvasFailure === null ? { presentedTime: this.#presentedTime } : {
|
|
395
|
+
loadState: "error",
|
|
396
|
+
presentedTime: this.#presentedTime,
|
|
397
|
+
error: this.#canvasFailure
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
destroy() {
|
|
402
|
+
if (this.#destroyed) return;
|
|
403
|
+
this.#destroyed = true;
|
|
404
|
+
++this.#drawGeneration;
|
|
405
|
+
this.#lastDrawSignature = null;
|
|
406
|
+
this.#presentedTime = null;
|
|
407
|
+
this.#canvasFailure = null;
|
|
408
|
+
this.#fallbackState = {
|
|
409
|
+
...this.#fallbackState,
|
|
410
|
+
loadState: "unloaded",
|
|
411
|
+
presentedTime: null,
|
|
412
|
+
error: null
|
|
413
|
+
};
|
|
414
|
+
this.#removeDecoderListeners();
|
|
415
|
+
this.#rejectDrawWaiters(new FrameByFrameError("CONTROLLER_DESTROYED", "The controller was destroyed while a canvas frame was pending.", { details: { bindingId: this.#config.id } }));
|
|
416
|
+
try {
|
|
417
|
+
if (this.#video === null) this.#decoderHandle.release();
|
|
418
|
+
else this.#video.destroy();
|
|
419
|
+
} finally {
|
|
420
|
+
this.#canvasHandle.release();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
#handleVideoEvent(event) {
|
|
424
|
+
if (event.type === "frame") {
|
|
425
|
+
this.#drawDecoderFrame(event.expectedDisplayTime, event.width, event.height, false, event.presentedTime);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (event.type === "loadstart") {
|
|
429
|
+
++this.#drawGeneration;
|
|
430
|
+
this.#lastDrawSignature = null;
|
|
431
|
+
this.#presentedTime = null;
|
|
432
|
+
this.#canvasFailure = null;
|
|
433
|
+
this.#resolveDrawWaiters();
|
|
434
|
+
}
|
|
435
|
+
this.#onEvent(event);
|
|
436
|
+
}
|
|
437
|
+
#drawDecoderFrame(expectedDisplayTime, observedWidth, observedHeight, force, observedTime = this.#decoder.currentTime) {
|
|
438
|
+
if (this.#destroyed || this.#activity !== "active" || this.#context === null || !Number.isFinite(observedTime)) return;
|
|
439
|
+
const clipId = (this.#video?.getState())?.activeClipId;
|
|
440
|
+
if (clipId === null || clipId === void 0) return;
|
|
441
|
+
const sourceWidth = this.#decoder.videoWidth > 0 ? this.#decoder.videoWidth : observedWidth ?? 0;
|
|
442
|
+
const sourceHeight = this.#decoder.videoHeight > 0 ? this.#decoder.videoHeight : observedHeight ?? 0;
|
|
443
|
+
const options = this.#canvasOptions();
|
|
444
|
+
const ratio = options.pixelRatio === "device" ? resolveDevicePixelRatio(this.#canvas) : options.pixelRatio;
|
|
445
|
+
const plan = calculateCanvasDrawPlan(sourceWidth, sourceHeight, this.#canvas.width, this.#canvas.height, options.fit, ratio);
|
|
446
|
+
if (plan === null) return;
|
|
447
|
+
const signature = JSON.stringify({
|
|
448
|
+
clipId,
|
|
449
|
+
observedTime,
|
|
450
|
+
sourceWidth,
|
|
451
|
+
sourceHeight,
|
|
452
|
+
width: this.#canvas.width,
|
|
453
|
+
height: this.#canvas.height,
|
|
454
|
+
fit: options.fit,
|
|
455
|
+
smoothing: options.imageSmoothingEnabled
|
|
456
|
+
});
|
|
457
|
+
if (!force && signature === this.#lastDrawSignature) return;
|
|
458
|
+
try {
|
|
459
|
+
this.#context.imageSmoothingEnabled = options.imageSmoothingEnabled;
|
|
460
|
+
this.#context.clearRect(0, 0, this.#canvas.width, this.#canvas.height);
|
|
461
|
+
this.#context.drawImage(this.#decoder, plan.sourceX, plan.sourceY, plan.sourceWidth, plan.sourceHeight, plan.destinationX, plan.destinationY, plan.destinationWidth, plan.destinationHeight);
|
|
462
|
+
} catch (cause) {
|
|
463
|
+
this.#failDraw(canvasError(isSecurityError(cause) ? "CANVAS_SECURITY_ERROR" : "CANVAS_DRAW_FAILED", isSecurityError(cause) ? "The decoded video frame could not be drawn because of canvas security restrictions." : "The decoded video frame could not be drawn to the canvas.", this.#config.id, cause));
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
this.#canvasFailure = null;
|
|
467
|
+
this.#lastDrawSignature = signature;
|
|
468
|
+
this.#presentedTime = observedTime;
|
|
469
|
+
this.#resolveDrawWaiters();
|
|
470
|
+
this.#onEvent({
|
|
471
|
+
type: "frame",
|
|
472
|
+
clipId,
|
|
473
|
+
presentedTime: observedTime,
|
|
474
|
+
expectedDisplayTime,
|
|
475
|
+
width: sourceWidth,
|
|
476
|
+
height: sourceHeight
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
#failDraw(error) {
|
|
480
|
+
const shouldEmit = this.#canvasFailure?.code !== error.code;
|
|
481
|
+
this.#canvasFailure = error;
|
|
482
|
+
this.#rejectDrawWaiters(error);
|
|
483
|
+
if (shouldEmit) this.#onEvent({
|
|
484
|
+
type: "error",
|
|
485
|
+
error
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
#canvasOptions() {
|
|
489
|
+
const options = this.#config.canvas;
|
|
490
|
+
if (options === null) throw new FrameByFrameError("INVALID_MEDIA_CONFIG", "Canvas renderer options are missing.", { details: { bindingId: this.#config.id } });
|
|
491
|
+
return options;
|
|
492
|
+
}
|
|
493
|
+
#resolveDrawWaiters() {
|
|
494
|
+
for (const waiter of this.#drawWaiters) waiter.resolve();
|
|
495
|
+
this.#drawWaiters.clear();
|
|
496
|
+
}
|
|
497
|
+
#rejectDrawWaiters(error) {
|
|
498
|
+
for (const waiter of this.#drawWaiters) waiter.reject(error);
|
|
499
|
+
this.#drawWaiters.clear();
|
|
500
|
+
}
|
|
501
|
+
#assertNotDestroyed() {
|
|
502
|
+
if (this.#destroyed) throw new FrameByFrameError("CONTROLLER_DESTROYED", "The canvas renderer has already been destroyed.", { details: { bindingId: this.#config.id } });
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
/** Creates a canvas renderer around already resolved and claimed presentation targets. */
|
|
506
|
+
const createCanvasRenderer = (config, canvasHandle, decoderHandle, onEvent, activity = "active", dependencies) => new CanvasRenderer(config, canvasHandle, decoderHandle, onEvent, activity, dependencies);
|
|
507
|
+
|
|
508
|
+
//#endregion
|
|
509
|
+
//#region src/canvas.ts
|
|
510
|
+
const canvasTargetRegistry = new CanvasTargetRegistry();
|
|
511
|
+
const createRenderer = (config, onEvent, activity) => {
|
|
512
|
+
if (config.renderer === "video") return createVideoRenderer(config, onEvent, activity);
|
|
513
|
+
const canvasHandle = resolveCanvasTarget(config, canvasTargetRegistry);
|
|
514
|
+
let decoderHandle = null;
|
|
515
|
+
try {
|
|
516
|
+
decoderHandle = resolveCanvasDecoder(config, canvasHandle.target, videoTargetRegistry);
|
|
517
|
+
return createCanvasRenderer(config, canvasHandle, decoderHandle, onEvent, activity);
|
|
518
|
+
} catch (error) {
|
|
519
|
+
decoderHandle?.release();
|
|
520
|
+
canvasHandle.release();
|
|
521
|
+
throw error;
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
/** Creates an SSR-safe controller with opt-in native-video and 2D-canvas renderers. */
|
|
525
|
+
const createFrameByFrame = (options) => createController(options, {
|
|
526
|
+
resolveSource: resolveScrollSource,
|
|
527
|
+
sourceRegistry,
|
|
528
|
+
reportAsyncError,
|
|
529
|
+
createRenderer,
|
|
530
|
+
supportedRenderers: /* @__PURE__ */ new Set(["video", "canvas"])
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
//#endregion
|
|
534
|
+
export { FrameByFrameError, createFrameByFrame, createTimeline };
|
|
535
|
+
//# sourceMappingURL=canvas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas.js","names":["#owners","#canvasHandle","#decoderHandle","#canvas","#decoder","#context","#onEvent","#drawWaiters","#video","#removeDecoderListeners","#config","#activity","#canvasFailure","#fallbackState","#destroyed","#handleVideoEvent","#drawGeneration","#drawDecoderFrame","#assertNotDestroyed","#lastDrawSignature","#resolveDrawWaiters","#presentedTime","#canvasOptions","#rejectDrawWaiters","#failDraw"],"sources":["../src/media/canvas-target.ts","../src/media/canvas-layout.ts","../src/media/canvas-renderer.ts","../src/canvas.ts"],"sourcesContent":["import { FrameByFrameError } from '../core/errors.js';\nimport {\n isMediaDocument,\n isMediaHtmlElement,\n isMediaVideoElement,\n resolveMediaReference,\n} from './video-target.js';\n\nimport type { ControllerBindingConfig } from '../core/controller-config.js';\nimport type { ResolvedVideoTarget, VideoTargetRegistry } from './video-target.js';\n\n/** A claimed visible canvas and its package ownership metadata. */\nexport interface ResolvedCanvasTarget {\n readonly target: HTMLCanvasElement;\n readonly owned: boolean;\n release(): void;\n}\n\nconst isRecord = (value: unknown): value is Readonly<Record<string, unknown>> =>\n typeof value === 'object' && value !== null;\n\nconst getElementName = (value: Readonly<Record<string, unknown>>): string => {\n const localName = value['localName'];\n\n if (typeof localName === 'string') {\n return localName.toLowerCase();\n }\n\n const tagName = value['tagName'];\n return typeof tagName === 'string' ? tagName.toLowerCase() : '';\n};\n\nconst isCanvasElement = (value: unknown): value is HTMLCanvasElement =>\n isRecord(value) &&\n value['nodeType'] === 1 &&\n getElementName(value) === 'canvas' &&\n typeof value['getContext'] === 'function' &&\n isMediaDocument(value['ownerDocument']);\n\nconst targetError = (bindingId: string, message: string, cause: unknown): never => {\n throw new FrameByFrameError('INVALID_TARGET_TYPE', message, {\n cause,\n details: { bindingId },\n });\n};\n\n/** Prevents more than one mounted binding from drawing to the same canvas. */\nexport class CanvasTargetRegistry {\n readonly #owners = new WeakMap<object, object>();\n\n acquire(target: HTMLCanvasElement, owner: object, bindingId: string): () => void {\n if (this.#owners.has(target)) {\n throw new FrameByFrameError(\n 'TARGET_CONFLICT',\n 'The canvas target is already controlled by another mounted binding.',\n { details: { bindingId } },\n );\n }\n\n this.#owners.set(target, owner);\n let active = true;\n\n return (): void => {\n if (!active) {\n return;\n }\n\n active = false;\n\n if (this.#owners.get(target) === owner) {\n this.#owners.delete(target);\n }\n };\n }\n}\n\nconst createOwnedCanvas = (mountTo: HTMLElement, bindingId: string): HTMLCanvasElement => {\n const document: unknown = mountTo.ownerDocument;\n\n if (!isMediaDocument(document)) {\n throw new FrameByFrameError(\n 'ENVIRONMENT_UNAVAILABLE',\n 'The canvas mount container does not expose an owner document.',\n { details: { bindingId } },\n );\n }\n\n const target: unknown = document.createElement('canvas');\n\n if (!isCanvasElement(target)) {\n return targetError(\n bindingId,\n 'The owner document could not create an HTMLCanvasElement.',\n target,\n );\n }\n\n mountTo.appendChild(target);\n return target;\n};\n\n/** Resolves, creates, and globally claims one binding's visible canvas. */\nexport const resolveCanvasTarget = (\n config: ControllerBindingConfig,\n registry: CanvasTargetRegistry,\n): ResolvedCanvasTarget => {\n const owner = {};\n let target: HTMLCanvasElement;\n let owned = false;\n\n if (config.target !== undefined) {\n const candidate = resolveMediaReference(config.target, config.id, 'canvas target');\n\n if (!isCanvasElement(candidate)) {\n return targetError(config.id, 'The resolved target must be an HTMLCanvasElement.', candidate);\n }\n\n target = candidate;\n } else {\n const candidate = resolveMediaReference(config.mountTo, config.id, 'canvas mount container');\n\n if (!isMediaHtmlElement(candidate)) {\n return targetError(\n config.id,\n 'The resolved mountTo value must be an HTMLElement.',\n candidate,\n );\n }\n\n target = createOwnedCanvas(candidate, config.id);\n owned = true;\n }\n\n let releaseOwnership: (() => void) | null = null;\n\n try {\n releaseOwnership = registry.acquire(target, owner, config.id);\n } catch (error) {\n if (owned) {\n target.parentNode?.removeChild(target);\n }\n\n throw error;\n }\n\n let active = true;\n\n return {\n target,\n owned,\n release: (): void => {\n if (!active) {\n return;\n }\n\n active = false;\n releaseOwnership();\n\n if (owned) {\n target.parentNode?.removeChild(target);\n }\n },\n };\n};\n\n/** Resolves or creates the video decoder used behind one visible canvas. */\nexport const resolveCanvasDecoder = (\n config: ControllerBindingConfig,\n canvas: HTMLCanvasElement,\n registry: VideoTargetRegistry,\n): ResolvedVideoTarget => {\n const canvasOptions = config.canvas;\n\n if (canvasOptions === null) {\n throw new FrameByFrameError('INVALID_MEDIA_CONFIG', 'Canvas renderer options are missing.', {\n details: { bindingId: config.id },\n });\n }\n\n const owner = {};\n const supplied = canvasOptions.decoderTarget !== undefined;\n let target: HTMLVideoElement;\n\n if (supplied) {\n const candidate = resolveMediaReference(\n canvasOptions.decoderTarget,\n config.id,\n 'canvas decoder target',\n );\n\n if (!isMediaVideoElement(candidate)) {\n return targetError(\n config.id,\n 'The resolved canvas decoderTarget must be an HTMLVideoElement.',\n candidate,\n );\n }\n\n target = candidate;\n } else {\n const document: unknown = canvas.ownerDocument;\n\n if (!isMediaDocument(document)) {\n throw new FrameByFrameError(\n 'ENVIRONMENT_UNAVAILABLE',\n 'The canvas target does not expose an owner document for its video decoder.',\n { details: { bindingId: config.id } },\n );\n }\n\n const candidate: unknown = document.createElement('video');\n\n if (!isMediaVideoElement(candidate)) {\n return targetError(\n config.id,\n 'The canvas owner document could not create an HTMLVideoElement decoder.',\n candidate,\n );\n }\n\n target = candidate;\n }\n\n const releaseOwnership = registry.acquire(target, owner, config.id);\n let active = true;\n\n return {\n target,\n owned: !supplied,\n release: (): void => {\n if (!active) {\n return;\n }\n\n active = false;\n releaseOwnership();\n\n if (!supplied) {\n target.parentNode?.removeChild(target);\n }\n },\n };\n};\n","import type { CanvasFit } from '../types.js';\n\n/** Source and destination rectangles passed to CanvasRenderingContext2D.drawImage(). */\nexport interface CanvasDrawPlan {\n readonly sourceX: number;\n readonly sourceY: number;\n readonly sourceWidth: number;\n readonly sourceHeight: number;\n readonly destinationX: number;\n readonly destinationY: number;\n readonly destinationWidth: number;\n readonly destinationHeight: number;\n}\n\nconst centered = (outer: number, inner: number): number => (outer - inner) / 2;\n\n/** Computes centered object-fit-like rectangles without reading layout or touching a canvas. */\nexport const calculateCanvasDrawPlan = (\n sourceWidth: number,\n sourceHeight: number,\n targetWidth: number,\n targetHeight: number,\n fit: CanvasFit,\n pixelRatio: number,\n): CanvasDrawPlan | null => {\n if (\n ![sourceWidth, sourceHeight, targetWidth, targetHeight, pixelRatio].every(\n (value) => Number.isFinite(value) && value > 0,\n )\n ) {\n return null;\n }\n\n if (fit === 'fill') {\n return {\n sourceX: 0,\n sourceY: 0,\n sourceWidth,\n sourceHeight,\n destinationX: 0,\n destinationY: 0,\n destinationWidth: targetWidth,\n destinationHeight: targetHeight,\n };\n }\n\n if (fit === 'none') {\n const destinationWidth = sourceWidth * pixelRatio;\n const destinationHeight = sourceHeight * pixelRatio;\n\n return {\n sourceX: 0,\n sourceY: 0,\n sourceWidth,\n sourceHeight,\n destinationX: centered(targetWidth, destinationWidth),\n destinationY: centered(targetHeight, destinationHeight),\n destinationWidth,\n destinationHeight,\n };\n }\n\n const sourceRatio = sourceWidth / sourceHeight;\n const targetRatio = targetWidth / targetHeight;\n\n if (fit === 'cover') {\n if (sourceRatio > targetRatio) {\n const croppedWidth = sourceHeight * targetRatio;\n\n return {\n sourceX: centered(sourceWidth, croppedWidth),\n sourceY: 0,\n sourceWidth: croppedWidth,\n sourceHeight,\n destinationX: 0,\n destinationY: 0,\n destinationWidth: targetWidth,\n destinationHeight: targetHeight,\n };\n }\n\n const croppedHeight = sourceWidth / targetRatio;\n\n return {\n sourceX: 0,\n sourceY: centered(sourceHeight, croppedHeight),\n sourceWidth,\n sourceHeight: croppedHeight,\n destinationX: 0,\n destinationY: 0,\n destinationWidth: targetWidth,\n destinationHeight: targetHeight,\n };\n }\n\n const scale = Math.min(targetWidth / sourceWidth, targetHeight / sourceHeight);\n const destinationWidth = sourceWidth * scale;\n const destinationHeight = sourceHeight * scale;\n\n return {\n sourceX: 0,\n sourceY: 0,\n sourceWidth,\n sourceHeight,\n destinationX: centered(targetWidth, destinationWidth),\n destinationY: centered(targetHeight, destinationHeight),\n destinationWidth,\n destinationHeight,\n };\n};\n","import { FrameByFrameError } from '../core/errors.js';\nimport { calculateCanvasDrawPlan } from './canvas-layout.js';\nimport { createNativeVideoRenderer } from './video-renderer.js';\n\nimport type {\n ControllerBindingConfig,\n ControllerCanvasOptions,\n} from '../core/controller-config.js';\nimport type { ResolvedCanvasTarget } from './canvas-target.js';\nimport type { ResolvedVideoTarget } from './video-target.js';\nimport type {\n MediaRenderer,\n VideoRenderer,\n VideoRendererActivity,\n VideoRendererConfigTransaction,\n VideoRendererDependencies,\n VideoRendererEvent,\n VideoRendererState,\n} from './video-renderer.js';\nimport type { FrameByFrameErrorCode, TimelineResolution } from '../types.js';\n\ninterface DrawWaiter {\n readonly resolve: () => void;\n readonly reject: (error: FrameByFrameError) => void;\n}\n\nconst initialState = (): VideoRendererState => ({\n loadState: 'idle',\n loadProgress: Object.freeze({}),\n activeClipId: null,\n selectedSource: null,\n duration: null,\n appliedTime: null,\n presentedTime: null,\n seeking: false,\n error: null,\n});\n\nconst decoderConfig = (config: ControllerBindingConfig): ControllerBindingConfig =>\n Object.freeze({\n ...config,\n clips: Object.freeze(\n config.clips.map((clip) =>\n clip.preload === 'metadata' ? Object.freeze({ ...clip, preload: 'auto' as const }) : clip,\n ),\n ),\n });\n\nconst canvasError = (\n code: FrameByFrameErrorCode,\n message: string,\n bindingId: string,\n cause?: unknown,\n): FrameByFrameError =>\n new FrameByFrameError(code, message, {\n cause,\n details: { bindingId },\n });\n\nconst isSecurityError = (cause: unknown): boolean =>\n typeof cause === 'object' &&\n cause !== null &&\n 'name' in cause &&\n (cause as { readonly name?: unknown }).name === 'SecurityError';\n\nconst resolveDevicePixelRatio = (target: HTMLCanvasElement): number => {\n const ownerRatio: unknown = target.ownerDocument.defaultView?.devicePixelRatio;\n const globalRatio: unknown = (globalThis as { readonly devicePixelRatio?: unknown })\n .devicePixelRatio;\n const ratio = typeof ownerRatio === 'number' ? ownerRatio : globalRatio;\n return typeof ratio === 'number' && Number.isFinite(ratio) && ratio > 0 ? ratio : 1;\n};\n\nclass CanvasRenderer implements MediaRenderer {\n #config: ControllerBindingConfig;\n readonly #canvasHandle: ResolvedCanvasTarget;\n readonly #decoderHandle: ResolvedVideoTarget;\n readonly #canvas: HTMLCanvasElement;\n readonly #decoder: HTMLVideoElement;\n readonly #context: CanvasRenderingContext2D | null;\n readonly #onEvent: (event: VideoRendererEvent) => void;\n readonly #drawWaiters = new Set<DrawWaiter>();\n readonly #video: VideoRenderer | null;\n readonly #removeDecoderListeners: () => void;\n\n #fallbackState = initialState();\n #canvasFailure: FrameByFrameError | null = null;\n #presentedTime: number | null = null;\n #lastDrawSignature: string | null = null;\n #drawGeneration = 0;\n #activity: VideoRendererActivity;\n #destroyed = false;\n\n constructor(\n config: ControllerBindingConfig,\n canvasHandle: ResolvedCanvasTarget,\n decoderHandle: ResolvedVideoTarget,\n onEvent: (event: VideoRendererEvent) => void,\n activity: VideoRendererActivity,\n dependencies: VideoRendererDependencies | undefined,\n ) {\n this.#config = config;\n this.#canvasHandle = canvasHandle;\n this.#decoderHandle = decoderHandle;\n this.#canvas = canvasHandle.target;\n this.#decoder = decoderHandle.target;\n this.#onEvent = onEvent;\n this.#activity = activity;\n\n let context: CanvasRenderingContext2D | null = null;\n\n try {\n context = this.#canvas.getContext('2d');\n } catch (cause) {\n this.#canvasFailure = canvasError(\n 'CANVAS_CONTEXT_UNAVAILABLE',\n 'The canvas target could not create a 2D rendering context.',\n config.id,\n cause,\n );\n }\n\n if (context === null && this.#canvasFailure === null) {\n this.#canvasFailure = canvasError(\n 'CANVAS_CONTEXT_UNAVAILABLE',\n 'The canvas target does not provide a 2D rendering context.',\n config.id,\n );\n }\n\n this.#context = context;\n\n if (context === null) {\n this.#video = null;\n this.#decoderHandle.release();\n this.#fallbackState = {\n ...this.#fallbackState,\n loadState: 'error',\n error: this.#canvasFailure,\n };\n this.#removeDecoderListeners = (): void => undefined;\n globalThis.queueMicrotask((): void => {\n if (!this.#destroyed && this.#canvasFailure !== null) {\n this.#onEvent({ type: 'error', error: this.#canvasFailure });\n }\n });\n return;\n }\n\n this.#video = createNativeVideoRenderer(\n decoderConfig(config),\n decoderHandle,\n (event): void => {\n this.#handleVideoEvent(event);\n },\n dependencies,\n activity,\n this.#canvas,\n );\n\n const scheduleFallbackDraw = (): void => {\n const generation = this.#drawGeneration;\n const presentedTime = this.#decoder.currentTime;\n globalThis.queueMicrotask((): void => {\n if (generation === this.#drawGeneration && !this.#destroyed) {\n this.#drawDecoderFrame(null, null, null, false, presentedTime);\n }\n });\n };\n this.#decoder.addEventListener('loadeddata', scheduleFallbackDraw);\n this.#decoder.addEventListener('seeked', scheduleFallbackDraw);\n this.#removeDecoderListeners = (): void => {\n this.#decoder.removeEventListener('loadeddata', scheduleFallbackDraw);\n this.#decoder.removeEventListener('seeked', scheduleFallbackDraw);\n };\n\n this.resize();\n }\n\n prepareConfig(config: ControllerBindingConfig): VideoRendererConfigTransaction {\n this.#assertNotDestroyed();\n const currentCanvas = this.#config.canvas;\n const nextCanvas = config.canvas;\n\n if (\n config.id !== this.#config.id ||\n config.axis !== this.#config.axis ||\n config.renderer !== 'canvas' ||\n config.target !== this.#config.target ||\n config.mountTo !== this.#config.mountTo ||\n currentCanvas === null ||\n nextCanvas === null ||\n config.canvas?.decoderTarget !== currentCanvas.decoderTarget\n ) {\n throw new FrameByFrameError(\n 'INVALID_BREAKPOINT_CONFIG',\n 'Responsive overrides cannot change canvas identity, renderer, target, or decoder ownership.',\n { details: { bindingId: this.#config.id } },\n );\n }\n\n const videoTransaction =\n this.#config.decoderSignature === config.decoderSignature\n ? undefined\n : this.#video?.prepareConfig(decoderConfig(config));\n let settled = false;\n\n return {\n commit: (): void => {\n if (settled) {\n return;\n }\n\n settled = true;\n videoTransaction?.commit();\n this.#config = config;\n if (this.#context !== null) {\n this.#canvasFailure = null;\n }\n this.#lastDrawSignature = null;\n const generation = ++this.#drawGeneration;\n this.#resolveDrawWaiters();\n globalThis.queueMicrotask((): void => {\n if (!this.#destroyed && generation === this.#drawGeneration) {\n this.resize();\n }\n });\n },\n cancel: (): void => {\n if (settled) {\n return;\n }\n\n settled = true;\n videoTransaction?.cancel();\n },\n };\n }\n\n setActivity(activity: VideoRendererActivity): void {\n this.#assertNotDestroyed();\n this.#activity = activity;\n this.#video?.setActivity(activity);\n\n if (activity === 'active') {\n this.resize();\n }\n }\n\n setResolution(resolution: TimelineResolution | null): void {\n this.#assertNotDestroyed();\n this.#video?.setResolution(resolution);\n }\n\n load(): Promise<void> {\n this.#assertNotDestroyed();\n\n if (this.#video === null) {\n const error = this.#canvasFailure;\n\n if (error === null) {\n return Promise.reject(\n canvasError(\n 'CANVAS_CONTEXT_UNAVAILABLE',\n 'The canvas renderer is unavailable.',\n this.#config.id,\n ),\n );\n }\n\n return Promise.reject(error);\n }\n\n return this.#video.load();\n }\n\n async whenReady(): Promise<void> {\n this.#assertNotDestroyed();\n\n if (this.#video === null) {\n const error = this.#canvasFailure;\n\n if (error === null) {\n throw canvasError(\n 'CANVAS_CONTEXT_UNAVAILABLE',\n 'The canvas renderer is unavailable.',\n this.#config.id,\n );\n }\n\n throw error;\n }\n\n for (;;) {\n const generation = this.#drawGeneration;\n await this.#video.whenReady();\n\n if (generation !== this.#drawGeneration) {\n continue;\n }\n\n if (this.#canvasFailure !== null) {\n throw this.#canvasFailure;\n }\n\n const state = this.#video.getState();\n const clip = this.#config.clips.find((candidate) => candidate.id === state.activeClipId);\n\n if (\n clip === undefined ||\n clip.preload === 'none' ||\n state.loadState === 'idle' ||\n state.loadState === 'unloaded'\n ) {\n return;\n }\n\n if (this.#presentedTime !== null && this.#lastDrawSignature !== null) {\n return;\n }\n\n await new Promise<void>((resolve, reject): void => {\n this.#drawWaiters.add({ resolve, reject });\n });\n\n if (generation === this.#drawGeneration && this.#lastDrawSignature !== null) {\n return;\n }\n }\n }\n\n unload(): void {\n this.#assertNotDestroyed();\n ++this.#drawGeneration;\n this.#lastDrawSignature = null;\n this.#presentedTime = null;\n this.#resolveDrawWaiters();\n\n if (this.#video === null) {\n this.#fallbackState = { ...this.#fallbackState, loadState: 'unloaded', error: null };\n return;\n }\n\n this.#video.unload();\n }\n\n resize(): void {\n this.#assertNotDestroyed();\n const options = this.#canvasOptions();\n const ratio =\n options.pixelRatio === 'device' ? resolveDevicePixelRatio(this.#canvas) : options.pixelRatio;\n const cssWidth = this.#canvas.clientWidth;\n const cssHeight = this.#canvas.clientHeight;\n\n if (\n !Number.isFinite(cssWidth) ||\n !Number.isFinite(cssHeight) ||\n cssWidth <= 0 ||\n cssHeight <= 0\n ) {\n return;\n }\n\n const width = Math.max(1, Math.round(cssWidth * ratio));\n const height = Math.max(1, Math.round(cssHeight * ratio));\n\n if (this.#canvas.width !== width) {\n this.#canvas.width = width;\n }\n\n if (this.#canvas.height !== height) {\n this.#canvas.height = height;\n }\n\n this.#lastDrawSignature = null;\n this.#drawDecoderFrame(\n null,\n null,\n null,\n true,\n this.#presentedTime ?? this.#decoder.currentTime,\n );\n }\n\n getTarget(): HTMLCanvasElement {\n this.#assertNotDestroyed();\n return this.#canvas;\n }\n\n getState(): VideoRendererState {\n const state = this.#video?.getState() ?? this.#fallbackState;\n\n return {\n ...state,\n ...(this.#canvasFailure === null\n ? { presentedTime: this.#presentedTime }\n : {\n loadState: 'error' as const,\n presentedTime: this.#presentedTime,\n error: this.#canvasFailure,\n }),\n };\n }\n\n destroy(): void {\n if (this.#destroyed) {\n return;\n }\n\n this.#destroyed = true;\n ++this.#drawGeneration;\n this.#lastDrawSignature = null;\n this.#presentedTime = null;\n this.#canvasFailure = null;\n this.#fallbackState = {\n ...this.#fallbackState,\n loadState: 'unloaded',\n presentedTime: null,\n error: null,\n };\n this.#removeDecoderListeners();\n this.#rejectDrawWaiters(\n new FrameByFrameError(\n 'CONTROLLER_DESTROYED',\n 'The controller was destroyed while a canvas frame was pending.',\n { details: { bindingId: this.#config.id } },\n ),\n );\n\n try {\n if (this.#video === null) {\n this.#decoderHandle.release();\n } else {\n this.#video.destroy();\n }\n } finally {\n this.#canvasHandle.release();\n }\n }\n\n #handleVideoEvent(event: VideoRendererEvent): void {\n if (event.type === 'frame') {\n this.#drawDecoderFrame(\n event.expectedDisplayTime,\n event.width,\n event.height,\n false,\n event.presentedTime,\n );\n return;\n }\n\n if (event.type === 'loadstart') {\n ++this.#drawGeneration;\n this.#lastDrawSignature = null;\n this.#presentedTime = null;\n this.#canvasFailure = null;\n this.#resolveDrawWaiters();\n }\n\n this.#onEvent(event);\n }\n\n #drawDecoderFrame(\n expectedDisplayTime: number | null,\n observedWidth: number | null,\n observedHeight: number | null,\n force: boolean,\n observedTime: number = this.#decoder.currentTime,\n ): void {\n if (\n this.#destroyed ||\n this.#activity !== 'active' ||\n this.#context === null ||\n !Number.isFinite(observedTime)\n ) {\n return;\n }\n\n const state = this.#video?.getState();\n const clipId = state?.activeClipId;\n\n if (clipId === null || clipId === undefined) {\n return;\n }\n\n const sourceWidth =\n this.#decoder.videoWidth > 0 ? this.#decoder.videoWidth : (observedWidth ?? 0);\n const sourceHeight =\n this.#decoder.videoHeight > 0 ? this.#decoder.videoHeight : (observedHeight ?? 0);\n const options = this.#canvasOptions();\n const ratio =\n options.pixelRatio === 'device' ? resolveDevicePixelRatio(this.#canvas) : options.pixelRatio;\n const plan = calculateCanvasDrawPlan(\n sourceWidth,\n sourceHeight,\n this.#canvas.width,\n this.#canvas.height,\n options.fit,\n ratio,\n );\n\n if (plan === null) {\n return;\n }\n\n const signature = JSON.stringify({\n clipId,\n observedTime,\n sourceWidth,\n sourceHeight,\n width: this.#canvas.width,\n height: this.#canvas.height,\n fit: options.fit,\n smoothing: options.imageSmoothingEnabled,\n });\n\n if (!force && signature === this.#lastDrawSignature) {\n return;\n }\n\n try {\n this.#context.imageSmoothingEnabled = options.imageSmoothingEnabled;\n this.#context.clearRect(0, 0, this.#canvas.width, this.#canvas.height);\n this.#context.drawImage(\n this.#decoder,\n plan.sourceX,\n plan.sourceY,\n plan.sourceWidth,\n plan.sourceHeight,\n plan.destinationX,\n plan.destinationY,\n plan.destinationWidth,\n plan.destinationHeight,\n );\n } catch (cause) {\n this.#failDraw(\n canvasError(\n isSecurityError(cause) ? 'CANVAS_SECURITY_ERROR' : 'CANVAS_DRAW_FAILED',\n isSecurityError(cause)\n ? 'The decoded video frame could not be drawn because of canvas security restrictions.'\n : 'The decoded video frame could not be drawn to the canvas.',\n this.#config.id,\n cause,\n ),\n );\n return;\n }\n\n this.#canvasFailure = null;\n this.#lastDrawSignature = signature;\n this.#presentedTime = observedTime;\n this.#resolveDrawWaiters();\n this.#onEvent({\n type: 'frame',\n clipId,\n presentedTime: observedTime,\n expectedDisplayTime,\n width: sourceWidth,\n height: sourceHeight,\n });\n }\n\n #failDraw(error: FrameByFrameError): void {\n const shouldEmit = this.#canvasFailure?.code !== error.code;\n this.#canvasFailure = error;\n this.#rejectDrawWaiters(error);\n\n if (shouldEmit) {\n this.#onEvent({ type: 'error', error });\n }\n }\n\n #canvasOptions(): ControllerCanvasOptions {\n const options = this.#config.canvas;\n\n if (options === null) {\n throw new FrameByFrameError('INVALID_MEDIA_CONFIG', 'Canvas renderer options are missing.', {\n details: { bindingId: this.#config.id },\n });\n }\n\n return options;\n }\n\n #resolveDrawWaiters(): void {\n for (const waiter of this.#drawWaiters) {\n waiter.resolve();\n }\n\n this.#drawWaiters.clear();\n }\n\n #rejectDrawWaiters(error: FrameByFrameError): void {\n for (const waiter of this.#drawWaiters) {\n waiter.reject(error);\n }\n\n this.#drawWaiters.clear();\n }\n\n #assertNotDestroyed(): void {\n if (this.#destroyed) {\n throw new FrameByFrameError(\n 'CONTROLLER_DESTROYED',\n 'The canvas renderer has already been destroyed.',\n { details: { bindingId: this.#config.id } },\n );\n }\n }\n}\n\n/** Creates a canvas renderer around already resolved and claimed presentation targets. */\nexport const createCanvasRenderer = (\n config: ControllerBindingConfig,\n canvasHandle: ResolvedCanvasTarget,\n decoderHandle: ResolvedVideoTarget,\n onEvent: (event: VideoRendererEvent) => void,\n activity: VideoRendererActivity = 'active',\n dependencies?: VideoRendererDependencies,\n): MediaRenderer =>\n new CanvasRenderer(config, canvasHandle, decoderHandle, onEvent, activity, dependencies);\n","import { createController } from './core/controller.js';\nimport { FrameByFrameError } from './core/errors.js';\nimport {\n createVideoRenderer,\n reportAsyncError,\n sourceRegistry,\n videoTargetRegistry,\n} from './core/public-controller.js';\nimport { createTimeline } from './mapping/timeline.js';\nimport {\n CanvasTargetRegistry,\n resolveCanvasDecoder,\n resolveCanvasTarget,\n} from './media/canvas-target.js';\nimport { createCanvasRenderer } from './media/canvas-renderer.js';\nimport { resolveScrollSource } from './scroll/source.js';\n\nimport type { CanvasFrameByFrameController, CanvasFrameByFrameOptions } from './types.js';\nimport type { MediaRendererFactory } from './media/video-renderer.js';\n\nconst canvasTargetRegistry = new CanvasTargetRegistry();\n\nconst createRenderer: MediaRendererFactory = (config, onEvent, activity) => {\n if (config.renderer === 'video') {\n return createVideoRenderer(config, onEvent, activity);\n }\n\n const canvasHandle = resolveCanvasTarget(config, canvasTargetRegistry);\n let decoderHandle: ReturnType<typeof resolveCanvasDecoder> | null = null;\n\n try {\n decoderHandle = resolveCanvasDecoder(config, canvasHandle.target, videoTargetRegistry);\n return createCanvasRenderer(config, canvasHandle, decoderHandle, onEvent, activity);\n } catch (error) {\n decoderHandle?.release();\n canvasHandle.release();\n throw error;\n }\n};\n\n/** Creates an SSR-safe controller with opt-in native-video and 2D-canvas renderers. */\nexport const createFrameByFrame = (\n options: CanvasFrameByFrameOptions,\n): CanvasFrameByFrameController =>\n createController(options, {\n resolveSource: resolveScrollSource,\n sourceRegistry,\n reportAsyncError,\n createRenderer,\n supportedRenderers: new Set(['video', 'canvas']),\n });\n\nexport { FrameByFrameError, createTimeline };\nexport type * from './types.js';\n"],"mappings":";;;AAkBA,MAAM,YAAY,UAChB,OAAO,UAAU,YAAY,UAAU;AAEzC,MAAM,kBAAkB,UAAqD;CAC3E,MAAM,YAAY,MAAM;CAExB,IAAI,OAAO,cAAc,UACvB,OAAO,UAAU,YAAY;CAG/B,MAAM,UAAU,MAAM;CACtB,OAAO,OAAO,YAAY,WAAW,QAAQ,YAAY,IAAI;AAC/D;AAEA,MAAM,mBAAmB,UACvB,SAAS,KAAK,KACd,MAAM,gBAAgB,KACtB,eAAe,KAAK,MAAM,YAC1B,OAAO,MAAM,kBAAkB,cAC/B,gBAAgB,MAAM,gBAAgB;AAExC,MAAM,eAAe,WAAmB,SAAiB,UAA0B;CACjF,MAAM,IAAI,kBAAkB,uBAAuB,SAAS;EAC1D;EACA,SAAS,EAAE,UAAU;CACvB,CAAC;AACH;;AAGA,IAAa,uBAAb,MAAkC;CAChC,AAASA,0BAAU,IAAI,QAAwB;CAE/C,QAAQ,QAA2B,OAAe,WAA+B;EAC/E,IAAI,KAAKA,QAAQ,IAAI,MAAM,GACzB,MAAM,IAAI,kBACR,mBACA,uEACA,EAAE,SAAS,EAAE,UAAU,EAAE,CAC3B;EAGF,KAAKA,QAAQ,IAAI,QAAQ,KAAK;EAC9B,IAAI,SAAS;EAEb,aAAmB;GACjB,IAAI,CAAC,QACH;GAGF,SAAS;GAET,IAAI,KAAKA,QAAQ,IAAI,MAAM,MAAM,OAC/B,KAAKA,QAAQ,OAAO,MAAM;EAE9B;CACF;AACF;AAEA,MAAM,qBAAqB,SAAsB,cAAyC;CACxF,MAAM,WAAoB,QAAQ;CAElC,IAAI,CAAC,gBAAgB,QAAQ,GAC3B,MAAM,IAAI,kBACR,2BACA,iEACA,EAAE,SAAS,EAAE,UAAU,EAAE,CAC3B;CAGF,MAAM,SAAkB,SAAS,cAAc,QAAQ;CAEvD,IAAI,CAAC,gBAAgB,MAAM,GACzB,OAAO,YACL,WACA,6DACA,MACF;CAGF,QAAQ,YAAY,MAAM;CAC1B,OAAO;AACT;;AAGA,MAAa,uBACX,QACA,aACyB;CACzB,MAAM,QAAQ,CAAC;CACf,IAAI;CACJ,IAAI,QAAQ;CAEZ,IAAI,OAAO,WAAW,QAAW;EAC/B,MAAM,YAAY,sBAAsB,OAAO,QAAQ,OAAO,IAAI,eAAe;EAEjF,IAAI,CAAC,gBAAgB,SAAS,GAC5B,OAAO,YAAY,OAAO,IAAI,qDAAqD,SAAS;EAG9F,SAAS;CACX,OAAO;EACL,MAAM,YAAY,sBAAsB,OAAO,SAAS,OAAO,IAAI,wBAAwB;EAE3F,IAAI,CAAC,mBAAmB,SAAS,GAC/B,OAAO,YACL,OAAO,IACP,sDACA,SACF;EAGF,SAAS,kBAAkB,WAAW,OAAO,EAAE;EAC/C,QAAQ;CACV;CAEA,IAAI,mBAAwC;CAE5C,IAAI;EACF,mBAAmB,SAAS,QAAQ,QAAQ,OAAO,OAAO,EAAE;CAC9D,SAAS,OAAO;EACd,IAAI,OACF,OAAO,YAAY,YAAY,MAAM;EAGvC,MAAM;CACR;CAEA,IAAI,SAAS;CAEb,OAAO;EACL;EACA;EACA,eAAqB;GACnB,IAAI,CAAC,QACH;GAGF,SAAS;GACT,iBAAiB;GAEjB,IAAI,OACF,OAAO,YAAY,YAAY,MAAM;EAEzC;CACF;AACF;;AAGA,MAAa,wBACX,QACA,QACA,aACwB;CACxB,MAAM,gBAAgB,OAAO;CAE7B,IAAI,kBAAkB,MACpB,MAAM,IAAI,kBAAkB,wBAAwB,wCAAwC,EAC1F,SAAS,EAAE,WAAW,OAAO,GAAG,EAClC,CAAC;CAGH,MAAM,QAAQ,CAAC;CACf,MAAM,WAAW,cAAc,kBAAkB;CACjD,IAAI;CAEJ,IAAI,UAAU;EACZ,MAAM,YAAY,sBAChB,cAAc,eACd,OAAO,IACP,uBACF;EAEA,IAAI,CAAC,oBAAoB,SAAS,GAChC,OAAO,YACL,OAAO,IACP,kEACA,SACF;EAGF,SAAS;CACX,OAAO;EACL,MAAM,WAAoB,OAAO;EAEjC,IAAI,CAAC,gBAAgB,QAAQ,GAC3B,MAAM,IAAI,kBACR,2BACA,8EACA,EAAE,SAAS,EAAE,WAAW,OAAO,GAAG,EAAE,CACtC;EAGF,MAAM,YAAqB,SAAS,cAAc,OAAO;EAEzD,IAAI,CAAC,oBAAoB,SAAS,GAChC,OAAO,YACL,OAAO,IACP,2EACA,SACF;EAGF,SAAS;CACX;CAEA,MAAM,mBAAmB,SAAS,QAAQ,QAAQ,OAAO,OAAO,EAAE;CAClE,IAAI,SAAS;CAEb,OAAO;EACL;EACA,OAAO,CAAC;EACR,eAAqB;GACnB,IAAI,CAAC,QACH;GAGF,SAAS;GACT,iBAAiB;GAEjB,IAAI,CAAC,UACH,OAAO,YAAY,YAAY,MAAM;EAEzC;CACF;AACF;;;;ACpOA,MAAM,YAAY,OAAe,WAA2B,QAAQ,SAAS;;AAG7E,MAAa,2BACX,aACA,cACA,aACA,cACA,KACA,eAC0B;CAC1B,IACE,CAAC;EAAC;EAAa;EAAc;EAAa;EAAc;CAAU,CAAC,CAAC,OACjE,UAAU,OAAO,SAAS,KAAK,KAAK,QAAQ,CAC/C,GAEA,OAAO;CAGT,IAAI,QAAQ,QACV,OAAO;EACL,SAAS;EACT,SAAS;EACT;EACA;EACA,cAAc;EACd,cAAc;EACd,kBAAkB;EAClB,mBAAmB;CACrB;CAGF,IAAI,QAAQ,QAAQ;EAClB,MAAM,mBAAmB,cAAc;EACvC,MAAM,oBAAoB,eAAe;EAEzC,OAAO;GACL,SAAS;GACT,SAAS;GACT;GACA;GACA,cAAc,SAAS,aAAa,gBAAgB;GACpD,cAAc,SAAS,cAAc,iBAAiB;GACtD;GACA;EACF;CACF;CAEA,MAAM,cAAc,cAAc;CAClC,MAAM,cAAc,cAAc;CAElC,IAAI,QAAQ,SAAS;EACnB,IAAI,cAAc,aAAa;GAC7B,MAAM,eAAe,eAAe;GAEpC,OAAO;IACL,SAAS,SAAS,aAAa,YAAY;IAC3C,SAAS;IACT,aAAa;IACb;IACA,cAAc;IACd,cAAc;IACd,kBAAkB;IAClB,mBAAmB;GACrB;EACF;EAEA,MAAM,gBAAgB,cAAc;EAEpC,OAAO;GACL,SAAS;GACT,SAAS,SAAS,cAAc,aAAa;GAC7C;GACA,cAAc;GACd,cAAc;GACd,cAAc;GACd,kBAAkB;GAClB,mBAAmB;EACrB;CACF;CAEA,MAAM,QAAQ,KAAK,IAAI,cAAc,aAAa,eAAe,YAAY;CAC7E,MAAM,mBAAmB,cAAc;CACvC,MAAM,oBAAoB,eAAe;CAEzC,OAAO;EACL,SAAS;EACT,SAAS;EACT;EACA;EACA,cAAc,SAAS,aAAa,gBAAgB;EACpD,cAAc,SAAS,cAAc,iBAAiB;EACtD;EACA;CACF;AACF;;;;ACnFA,MAAM,sBAA0C;CAC9C,WAAW;CACX,cAAc,OAAO,OAAO,CAAC,CAAC;CAC9B,cAAc;CACd,gBAAgB;CAChB,UAAU;CACV,aAAa;CACb,eAAe;CACf,SAAS;CACT,OAAO;AACT;AAEA,MAAM,iBAAiB,WACrB,OAAO,OAAO;CACZ,GAAG;CACH,OAAO,OAAO,OACZ,OAAO,MAAM,KAAK,SAChB,KAAK,YAAY,aAAa,OAAO,OAAO;EAAE,GAAG;EAAM,SAAS;CAAgB,CAAC,IAAI,IACvF,CACF;AACF,CAAC;AAEH,MAAM,eACJ,MACA,SACA,WACA,UAEA,IAAI,kBAAkB,MAAM,SAAS;CACnC;CACA,SAAS,EAAE,UAAU;AACvB,CAAC;AAEH,MAAM,mBAAmB,UACvB,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACT,MAAsC,SAAS;AAElD,MAAM,2BAA2B,WAAsC;CACrE,MAAM,aAAsB,OAAO,cAAc,aAAa;CAC9D,MAAM,cAAwB,WAC3B;CACH,MAAM,QAAQ,OAAO,eAAe,WAAW,aAAa;CAC5D,OAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,KAAK,QAAQ,IAAI,QAAQ;AACpF;AAEA,IAAM,iBAAN,MAA8C;CAC5C;CACA,AAASC;CACT,AAASC;CACT,AAASC;CACT,AAASC;CACT,AAASC;CACT,AAASC;CACT,AAASC,+BAAe,IAAI,IAAgB;CAC5C,AAASC;CACT,AAASC;CAET,iBAAiB,aAAa;CAC9B,iBAA2C;CAC3C,iBAAgC;CAChC,qBAAoC;CACpC,kBAAkB;CAClB;CACA,aAAa;CAEb,YACE,QACA,cACA,eACA,SACA,UACA,cACA;EACA,KAAKC,UAAU;EACf,KAAKT,gBAAgB;EACrB,KAAKC,iBAAiB;EACtB,KAAKC,UAAU,aAAa;EAC5B,KAAKC,WAAW,cAAc;EAC9B,KAAKE,WAAW;EAChB,KAAKK,YAAY;EAEjB,IAAI,UAA2C;EAE/C,IAAI;GACF,UAAU,KAAKR,QAAQ,WAAW,IAAI;EACxC,SAAS,OAAO;GACd,KAAKS,iBAAiB,YACpB,8BACA,8DACA,OAAO,IACP,KACF;EACF;EAEA,IAAI,YAAY,QAAQ,KAAKA,mBAAmB,MAC9C,KAAKA,iBAAiB,YACpB,8BACA,8DACA,OAAO,EACT;EAGF,KAAKP,WAAW;EAEhB,IAAI,YAAY,MAAM;GACpB,KAAKG,SAAS;GACd,KAAKN,eAAe,QAAQ;GAC5B,KAAKW,iBAAiB;IACpB,GAAG,KAAKA;IACR,WAAW;IACX,OAAO,KAAKD;GACd;GACA,KAAKH,gCAAsC;GAC3C,WAAW,qBAA2B;IACpC,IAAI,CAAC,KAAKK,cAAc,KAAKF,mBAAmB,MAC9C,KAAKN,SAAS;KAAE,MAAM;KAAS,OAAO,KAAKM;IAAe,CAAC;GAE/D,CAAC;GACD;EACF;EAEA,KAAKJ,SAAS,0BACZ,cAAc,MAAM,GACpB,gBACC,UAAgB;GACf,KAAKO,kBAAkB,KAAK;EAC9B,GACA,cACA,UACA,KAAKZ,OACP;EAEA,MAAM,6BAAmC;GACvC,MAAM,aAAa,KAAKa;GACxB,MAAM,gBAAgB,KAAKZ,SAAS;GACpC,WAAW,qBAA2B;IACpC,IAAI,eAAe,KAAKY,mBAAmB,CAAC,KAAKF,YAC/C,KAAKG,kBAAkB,MAAM,MAAM,MAAM,OAAO,aAAa;GAEjE,CAAC;EACH;EACA,KAAKb,SAAS,iBAAiB,cAAc,oBAAoB;EACjE,KAAKA,SAAS,iBAAiB,UAAU,oBAAoB;EAC7D,KAAKK,gCAAsC;GACzC,KAAKL,SAAS,oBAAoB,cAAc,oBAAoB;GACpE,KAAKA,SAAS,oBAAoB,UAAU,oBAAoB;EAClE;EAEA,KAAK,OAAO;CACd;CAEA,cAAc,QAAiE;EAC7E,KAAKc,oBAAoB;EACzB,MAAM,gBAAgB,KAAKR,QAAQ;EACnC,MAAM,aAAa,OAAO;EAE1B,IACE,OAAO,OAAO,KAAKA,QAAQ,MAC3B,OAAO,SAAS,KAAKA,QAAQ,QAC7B,OAAO,aAAa,YACpB,OAAO,WAAW,KAAKA,QAAQ,UAC/B,OAAO,YAAY,KAAKA,QAAQ,WAChC,kBAAkB,QAClB,eAAe,QACf,OAAO,QAAQ,kBAAkB,cAAc,eAE/C,MAAM,IAAI,kBACR,6BACA,+FACA,EAAE,SAAS,EAAE,WAAW,KAAKA,QAAQ,GAAG,EAAE,CAC5C;EAGF,MAAM,mBACJ,KAAKA,QAAQ,qBAAqB,OAAO,mBACrC,SACA,KAAKF,QAAQ,cAAc,cAAc,MAAM,CAAC;EACtD,IAAI,UAAU;EAEd,OAAO;GACL,cAAoB;IAClB,IAAI,SACF;IAGF,UAAU;IACV,kBAAkB,OAAO;IACzB,KAAKE,UAAU;IACf,IAAI,KAAKL,aAAa,MACpB,KAAKO,iBAAiB;IAExB,KAAKO,qBAAqB;IAC1B,MAAM,aAAa,EAAE,KAAKH;IAC1B,KAAKI,oBAAoB;IACzB,WAAW,qBAA2B;KACpC,IAAI,CAAC,KAAKN,cAAc,eAAe,KAAKE,iBAC1C,KAAK,OAAO;IAEhB,CAAC;GACH;GACA,cAAoB;IAClB,IAAI,SACF;IAGF,UAAU;IACV,kBAAkB,OAAO;GAC3B;EACF;CACF;CAEA,YAAY,UAAuC;EACjD,KAAKE,oBAAoB;EACzB,KAAKP,YAAY;EACjB,KAAKH,QAAQ,YAAY,QAAQ;EAEjC,IAAI,aAAa,UACf,KAAK,OAAO;CAEhB;CAEA,cAAc,YAA6C;EACzD,KAAKU,oBAAoB;EACzB,KAAKV,QAAQ,cAAc,UAAU;CACvC;CAEA,OAAsB;EACpB,KAAKU,oBAAoB;EAEzB,IAAI,KAAKV,WAAW,MAAM;GACxB,MAAM,QAAQ,KAAKI;GAEnB,IAAI,UAAU,MACZ,OAAO,QAAQ,OACb,YACE,8BACA,uCACA,KAAKF,QAAQ,EACf,CACF;GAGF,OAAO,QAAQ,OAAO,KAAK;EAC7B;EAEA,OAAO,KAAKF,OAAO,KAAK;CAC1B;CAEA,MAAM,YAA2B;EAC/B,KAAKU,oBAAoB;EAEzB,IAAI,KAAKV,WAAW,MAAM;GACxB,MAAM,QAAQ,KAAKI;GAEnB,IAAI,UAAU,MACZ,MAAM,YACJ,8BACA,uCACA,KAAKF,QAAQ,EACf;GAGF,MAAM;EACR;EAEA,SAAS;GACP,MAAM,aAAa,KAAKM;GACxB,MAAM,KAAKR,OAAO,UAAU;GAE5B,IAAI,eAAe,KAAKQ,iBACtB;GAGF,IAAI,KAAKJ,mBAAmB,MAC1B,MAAM,KAAKA;GAGb,MAAM,QAAQ,KAAKJ,OAAO,SAAS;GACnC,MAAM,OAAO,KAAKE,QAAQ,MAAM,MAAM,cAAc,UAAU,OAAO,MAAM,YAAY;GAEvF,IACE,SAAS,UACT,KAAK,YAAY,UACjB,MAAM,cAAc,UACpB,MAAM,cAAc,YAEpB;GAGF,IAAI,KAAKW,mBAAmB,QAAQ,KAAKF,uBAAuB,MAC9D;GAGF,MAAM,IAAI,SAAe,SAAS,WAAiB;IACjD,KAAKZ,aAAa,IAAI;KAAE;KAAS;IAAO,CAAC;GAC3C,CAAC;GAED,IAAI,eAAe,KAAKS,mBAAmB,KAAKG,uBAAuB,MACrE;EAEJ;CACF;CAEA,SAAe;EACb,KAAKD,oBAAoB;EACzB,EAAE,KAAKF;EACP,KAAKG,qBAAqB;EAC1B,KAAKE,iBAAiB;EACtB,KAAKD,oBAAoB;EAEzB,IAAI,KAAKZ,WAAW,MAAM;GACxB,KAAKK,iBAAiB;IAAE,GAAG,KAAKA;IAAgB,WAAW;IAAY,OAAO;GAAK;GACnF;EACF;EAEA,KAAKL,OAAO,OAAO;CACrB;CAEA,SAAe;EACb,KAAKU,oBAAoB;EACzB,MAAM,UAAU,KAAKI,eAAe;EACpC,MAAM,QACJ,QAAQ,eAAe,WAAW,wBAAwB,KAAKnB,OAAO,IAAI,QAAQ;EACpF,MAAM,WAAW,KAAKA,QAAQ;EAC9B,MAAM,YAAY,KAAKA,QAAQ;EAE/B,IACE,CAAC,OAAO,SAAS,QAAQ,KACzB,CAAC,OAAO,SAAS,SAAS,KAC1B,YAAY,KACZ,aAAa,GAEb;EAGF,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,WAAW,KAAK,CAAC;EACtD,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,MAAM,YAAY,KAAK,CAAC;EAExD,IAAI,KAAKA,QAAQ,UAAU,OACzB,KAAKA,QAAQ,QAAQ;EAGvB,IAAI,KAAKA,QAAQ,WAAW,QAC1B,KAAKA,QAAQ,SAAS;EAGxB,KAAKgB,qBAAqB;EAC1B,KAAKF,kBACH,MACA,MACA,MACA,MACA,KAAKI,kBAAkB,KAAKjB,SAAS,WACvC;CACF;CAEA,YAA+B;EAC7B,KAAKc,oBAAoB;EACzB,OAAO,KAAKf;CACd;CAEA,WAA+B;EAG7B,OAAO;GACL,GAHY,KAAKK,QAAQ,SAAS,KAAK,KAAKK;GAI5C,GAAI,KAAKD,mBAAmB,OACxB,EAAE,eAAe,KAAKS,eAAe,IACrC;IACE,WAAW;IACX,eAAe,KAAKA;IACpB,OAAO,KAAKT;GACd;EACN;CACF;CAEA,UAAgB;EACd,IAAI,KAAKE,YACP;EAGF,KAAKA,aAAa;EAClB,EAAE,KAAKE;EACP,KAAKG,qBAAqB;EAC1B,KAAKE,iBAAiB;EACtB,KAAKT,iBAAiB;EACtB,KAAKC,iBAAiB;GACpB,GAAG,KAAKA;GACR,WAAW;GACX,eAAe;GACf,OAAO;EACT;EACA,KAAKJ,wBAAwB;EAC7B,KAAKc,mBACH,IAAI,kBACF,wBACA,kEACA,EAAE,SAAS,EAAE,WAAW,KAAKb,QAAQ,GAAG,EAAE,CAC5C,CACF;EAEA,IAAI;GACF,IAAI,KAAKF,WAAW,MAClB,KAAKN,eAAe,QAAQ;QAE5B,KAAKM,OAAO,QAAQ;EAExB,UAAU;GACR,KAAKP,cAAc,QAAQ;EAC7B;CACF;CAEA,kBAAkB,OAAiC;EACjD,IAAI,MAAM,SAAS,SAAS;GAC1B,KAAKgB,kBACH,MAAM,qBACN,MAAM,OACN,MAAM,QACN,OACA,MAAM,aACR;GACA;EACF;EAEA,IAAI,MAAM,SAAS,aAAa;GAC9B,EAAE,KAAKD;GACP,KAAKG,qBAAqB;GAC1B,KAAKE,iBAAiB;GACtB,KAAKT,iBAAiB;GACtB,KAAKQ,oBAAoB;EAC3B;EAEA,KAAKd,SAAS,KAAK;CACrB;CAEA,kBACE,qBACA,eACA,gBACA,OACA,eAAuB,KAAKF,SAAS,aAC/B;EACN,IACE,KAAKU,cACL,KAAKH,cAAc,YACnB,KAAKN,aAAa,QAClB,CAAC,OAAO,SAAS,YAAY,GAE7B;EAIF,MAAM,UADQ,KAAKG,QAAQ,SAAS,EAChB,EAAE;EAEtB,IAAI,WAAW,QAAQ,WAAW,QAChC;EAGF,MAAM,cACJ,KAAKJ,SAAS,aAAa,IAAI,KAAKA,SAAS,aAAc,iBAAiB;EAC9E,MAAM,eACJ,KAAKA,SAAS,cAAc,IAAI,KAAKA,SAAS,cAAe,kBAAkB;EACjF,MAAM,UAAU,KAAKkB,eAAe;EACpC,MAAM,QACJ,QAAQ,eAAe,WAAW,wBAAwB,KAAKnB,OAAO,IAAI,QAAQ;EACpF,MAAM,OAAO,wBACX,aACA,cACA,KAAKA,QAAQ,OACb,KAAKA,QAAQ,QACb,QAAQ,KACR,KACF;EAEA,IAAI,SAAS,MACX;EAGF,MAAM,YAAY,KAAK,UAAU;GAC/B;GACA;GACA;GACA;GACA,OAAO,KAAKA,QAAQ;GACpB,QAAQ,KAAKA,QAAQ;GACrB,KAAK,QAAQ;GACb,WAAW,QAAQ;EACrB,CAAC;EAED,IAAI,CAAC,SAAS,cAAc,KAAKgB,oBAC/B;EAGF,IAAI;GACF,KAAKd,SAAS,wBAAwB,QAAQ;GAC9C,KAAKA,SAAS,UAAU,GAAG,GAAG,KAAKF,QAAQ,OAAO,KAAKA,QAAQ,MAAM;GACrE,KAAKE,SAAS,UACZ,KAAKD,UACL,KAAK,SACL,KAAK,SACL,KAAK,aACL,KAAK,cACL,KAAK,cACL,KAAK,cACL,KAAK,kBACL,KAAK,iBACP;EACF,SAAS,OAAO;GACd,KAAKoB,UACH,YACE,gBAAgB,KAAK,IAAI,0BAA0B,sBACnD,gBAAgB,KAAK,IACjB,wFACA,6DACJ,KAAKd,QAAQ,IACb,KACF,CACF;GACA;EACF;EAEA,KAAKE,iBAAiB;EACtB,KAAKO,qBAAqB;EAC1B,KAAKE,iBAAiB;EACtB,KAAKD,oBAAoB;EACzB,KAAKd,SAAS;GACZ,MAAM;GACN;GACA,eAAe;GACf;GACA,OAAO;GACP,QAAQ;EACV,CAAC;CACH;CAEA,UAAU,OAAgC;EACxC,MAAM,aAAa,KAAKM,gBAAgB,SAAS,MAAM;EACvD,KAAKA,iBAAiB;EACtB,KAAKW,mBAAmB,KAAK;EAE7B,IAAI,YACF,KAAKjB,SAAS;GAAE,MAAM;GAAS;EAAM,CAAC;CAE1C;CAEA,iBAA0C;EACxC,MAAM,UAAU,KAAKI,QAAQ;EAE7B,IAAI,YAAY,MACd,MAAM,IAAI,kBAAkB,wBAAwB,wCAAwC,EAC1F,SAAS,EAAE,WAAW,KAAKA,QAAQ,GAAG,EACxC,CAAC;EAGH,OAAO;CACT;CAEA,sBAA4B;EAC1B,KAAK,MAAM,UAAU,KAAKH,cACxB,OAAO,QAAQ;EAGjB,KAAKA,aAAa,MAAM;CAC1B;CAEA,mBAAmB,OAAgC;EACjD,KAAK,MAAM,UAAU,KAAKA,cACxB,OAAO,OAAO,KAAK;EAGrB,KAAKA,aAAa,MAAM;CAC1B;CAEA,sBAA4B;EAC1B,IAAI,KAAKO,YACP,MAAM,IAAI,kBACR,wBACA,mDACA,EAAE,SAAS,EAAE,WAAW,KAAKJ,QAAQ,GAAG,EAAE,CAC5C;CAEJ;AACF;;AAGA,MAAa,wBACX,QACA,cACA,eACA,SACA,WAAkC,UAClC,iBAEA,IAAI,eAAe,QAAQ,cAAc,eAAe,SAAS,UAAU,YAAY;;;;ACzlBzF,MAAM,uBAAuB,IAAI,qBAAqB;AAEtD,MAAM,kBAAwC,QAAQ,SAAS,aAAa;CAC1E,IAAI,OAAO,aAAa,SACtB,OAAO,oBAAoB,QAAQ,SAAS,QAAQ;CAGtD,MAAM,eAAe,oBAAoB,QAAQ,oBAAoB;CACrE,IAAI,gBAAgE;CAEpE,IAAI;EACF,gBAAgB,qBAAqB,QAAQ,aAAa,QAAQ,mBAAmB;EACrF,OAAO,qBAAqB,QAAQ,cAAc,eAAe,SAAS,QAAQ;CACpF,SAAS,OAAO;EACd,eAAe,QAAQ;EACvB,aAAa,QAAQ;EACrB,MAAM;CACR;AACF;;AAGA,MAAa,sBACX,YAEA,iBAAiB,SAAS;CACxB,eAAe;CACf;CACA;CACA;CACA,oCAAoB,IAAI,IAAI,CAAC,SAAS,QAAQ,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { B as FrameByFrameOptions, j as FrameByFrameController } from "./types-BYfMRnsj.js";
|
|
2
|
+
//#region src/core/public-controller.d.ts
|
|
3
|
+
/** Creates an SSR-safe controller; browser capabilities are resolved by mount(). */
|
|
4
|
+
declare const createFrameByFrame: (options: FrameByFrameOptions) => FrameByFrameController;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { createFrameByFrame as t };
|
|
7
|
+
//# sourceMappingURL=index-DhdyeigP.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-DhdyeigP.d.ts","names":[],"sources":["../src/core/public-controller.ts"],"mappings":";;;cA6Ba,qBAAsB,SAAS,wBAAsB"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { $ as Timeline, A as FrameByFrameBreakpointOverride, B as FrameByFrameOptions, D as FrameByFrameBindingState, E as FrameByFrameBindingOverride, F as FrameByFrameErrorOptions, G as FrameByFrameUpdateReason, H as FrameByFrameState, I as FrameByFrameEventMap, J as ReducedMotionBehavior, K as FrameConfig, L as FrameByFrameFrameEvent, M as FrameByFrameErrorCode, N as FrameByFrameErrorDetails, O as FrameByFrameBreakpointChangeEvent, P as FrameByFrameErrorInfo, Q as ScrollUnit, R as FrameByFrameLoadProgressEvent, S as FrameByFrameAxisState, T as FrameByFrameBindingEvent, U as FrameByFrameStatus, V as FrameByFrameSeekRequestEvent, W as FrameByFrameUpdateEvent, X as ScrollSource, Y as RendererType, Z as ScrollSourceReference, _ as EasingName, at as VideoLoadProgress, b as FrameByFrameAxisConfig, ct as VideoLoadingTrigger, dt as VideoSeekConfig, et as TimelineOptions, ft as VideoSourceConfig, g as EasingFunction, h as Easing, it as VideoClipConfig, j as FrameByFrameController, k as FrameByFrameBreakpointConfig, lt as VideoPreload, nt as TimelineResolution, ot as VideoLoadState, q as MediaCrossOrigin, rt as TimelineSegment, st as VideoLoadingConfig, t as AxisName, tt as TimelinePhase, ut as VideoRendererConfig, v as ElementReference, w as FrameByFrameBindingConfig, x as FrameByFrameAxisOverride, y as FrameByFrameAxesConfig, z as FrameByFrameLoadedMetadataEvent } from "./types-BYfMRnsj.js";
|
|
2
|
+
import { n as FrameByFrameError, t as createTimeline } from "./timeline-DDuRBPRJ.js";
|
|
3
|
+
import { t as createFrameByFrame } from "./index-DhdyeigP.js";
|
|
4
|
+
export { type AxisName, type Easing, type EasingFunction, type EasingName, type ElementReference, type FrameByFrameAxesConfig, type FrameByFrameAxisConfig, type FrameByFrameAxisOverride, type FrameByFrameAxisState, type FrameByFrameBindingConfig, type FrameByFrameBindingEvent, type FrameByFrameBindingOverride, type FrameByFrameBindingState, type FrameByFrameBreakpointChangeEvent, type FrameByFrameBreakpointConfig, type FrameByFrameBreakpointOverride, type FrameByFrameController, FrameByFrameError, type FrameByFrameErrorCode, type FrameByFrameErrorDetails, type FrameByFrameErrorInfo, type FrameByFrameErrorOptions, type FrameByFrameEventMap, type FrameByFrameFrameEvent, type FrameByFrameLoadProgressEvent, type FrameByFrameLoadedMetadataEvent, type FrameByFrameOptions, type FrameByFrameSeekRequestEvent, type FrameByFrameState, type FrameByFrameStatus, type FrameByFrameUpdateEvent, type FrameByFrameUpdateReason, type FrameConfig, type MediaCrossOrigin, type ReducedMotionBehavior, type RendererType, type ScrollSource, type ScrollSourceReference, type ScrollUnit, type Timeline, type TimelineOptions, type TimelinePhase, type TimelineResolution, type TimelineSegment, type VideoClipConfig, type VideoLoadProgress, type VideoLoadState, type VideoLoadingConfig, type VideoLoadingTrigger, type VideoPreload, type VideoRendererConfig, type VideoSeekConfig, type VideoSourceConfig, createFrameByFrame, createTimeline };
|
package/dist/index.js
ADDED