@it-compiles/anima 0.1.0 → 0.1.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.
@@ -0,0 +1,3424 @@
1
+ function isExpr(v) {
2
+ return typeof v === "function";
3
+ }
4
+ function isGroup(obj) {
5
+ return "isGroup" in obj && obj.isGroup === true;
6
+ }
7
+ const Screen = {
8
+ width: 1280,
9
+ height: 720,
10
+ center: () => [640, 360],
11
+ centerX: () => 640,
12
+ centerY: () => 360,
13
+ topLeft: () => [0, 0],
14
+ topRight: () => [1280, 0],
15
+ bottomLeft: () => [0, 720],
16
+ bottomRight: () => [1280, 720]
17
+ };
18
+ const vec = {
19
+ add: ([x1, y1], [x2, y2]) => [x1 + x2, y1 + y2],
20
+ sub: ([x1, y1], [x2, y2]) => [x1 - x2, y1 - y2],
21
+ lerp: ([x1, y1], [x2, y2], u) => [x1 + (x2 - x1) * u, y1 + (y2 - y1) * u]
22
+ };
23
+ const Vec = (x, y) => [x, y];
24
+ const lerp = (a, b, u) => a + (b - a) * u;
25
+ const angle = (deg) => {
26
+ return deg * (Math.PI / 180);
27
+ };
28
+ function applySR([x, y], scale, rotate, scaleX = 1, scaleY = 1) {
29
+ const sx = x * scale * scaleX;
30
+ const sy = y * scale * scaleY;
31
+ const c = Math.cos(rotate);
32
+ const s = Math.sin(rotate);
33
+ return [
34
+ sx * c - sy * s,
35
+ sx * s + sy * c
36
+ ];
37
+ }
38
+ class BBoxHandleImpl {
39
+ paddingValues = { top: 0, right: 0, bottom: 0, left: 0 };
40
+ // Store a function that computes the rect lazily
41
+ computeRect;
42
+ constructor(computeRect) {
43
+ this.computeRect = computeRect;
44
+ }
45
+ pad(p) {
46
+ const parentCompute = this.computeRect;
47
+ const parentPadding = this.paddingValues;
48
+ let newPadding;
49
+ if (typeof p === "number") {
50
+ newPadding = {
51
+ top: parentPadding.top + p,
52
+ right: parentPadding.right + p,
53
+ bottom: parentPadding.bottom + p,
54
+ left: parentPadding.left + p
55
+ };
56
+ } else {
57
+ newPadding = {
58
+ top: parentPadding.top + (p.top ?? 0),
59
+ right: parentPadding.right + (p.right ?? 0),
60
+ bottom: parentPadding.bottom + (p.bottom ?? 0),
61
+ left: parentPadding.left + (p.left ?? 0)
62
+ };
63
+ }
64
+ const result = new BBoxHandleImpl(parentCompute);
65
+ result.paddingValues = newPadding;
66
+ return result;
67
+ }
68
+ getRect() {
69
+ const baseRect = this.computeRect();
70
+ return {
71
+ x: baseRect.x - this.paddingValues.left,
72
+ y: baseRect.y - this.paddingValues.top,
73
+ w: baseRect.w + this.paddingValues.left + this.paddingValues.right,
74
+ h: baseRect.h + this.paddingValues.top + this.paddingValues.bottom
75
+ };
76
+ }
77
+ size() {
78
+ return (_ctx) => {
79
+ const r = this.getRect();
80
+ return [r.w, r.h];
81
+ };
82
+ }
83
+ width() {
84
+ return (_ctx) => {
85
+ return this.getRect().w;
86
+ };
87
+ }
88
+ height() {
89
+ return (_ctx) => {
90
+ return this.getRect().h;
91
+ };
92
+ }
93
+ whSize() {
94
+ return {
95
+ width: this.width(),
96
+ height: this.height()
97
+ };
98
+ }
99
+ center() {
100
+ return (_ctx) => {
101
+ const r = this.getRect();
102
+ return [r.x + r.w / 2, r.y + r.h / 2];
103
+ };
104
+ }
105
+ anchor(a) {
106
+ return (_ctx) => {
107
+ const r = this.getRect();
108
+ let x = r.x, y = r.y;
109
+ if (a === "top" || a === "center" || a === "bottom" || a === "baseline") {
110
+ x = r.x + r.w / 2;
111
+ } else if (a === "topRight" || a === "right" || a === "bottomRight") {
112
+ x = r.x + r.w;
113
+ }
114
+ if (a === "left" || a === "center" || a === "right") {
115
+ y = r.y + r.h / 2;
116
+ } else if (a === "bottomLeft" || a === "bottom" || a === "bottomRight" || a === "baselineLeft" || a === "baseline") {
117
+ y = r.y + r.h;
118
+ }
119
+ return [x, y];
120
+ };
121
+ }
122
+ toRect() {
123
+ return (_ctx) => {
124
+ return this.getRect();
125
+ };
126
+ }
127
+ }
128
+ class AnimCtxImpl {
129
+ base = /* @__PURE__ */ new Map();
130
+ channels = /* @__PURE__ */ new Map();
131
+ pins = /* @__PURE__ */ new Map();
132
+ scene = null;
133
+ debugGroups = [];
134
+ /** Resolve a Vec2 expression to a concrete Vec2 */
135
+ resolveVec2(expr) {
136
+ if (isExpr(expr)) {
137
+ const ctx = this.createResolvedCtx();
138
+ return expr(ctx);
139
+ }
140
+ return expr;
141
+ }
142
+ /** Resolve a number expression to a concrete number */
143
+ resolveNum(expr) {
144
+ if (isExpr(expr)) {
145
+ const ctx = this.createResolvedCtx();
146
+ return expr(ctx);
147
+ }
148
+ return expr;
149
+ }
150
+ /** Create a ResolvedCtx based on current state */
151
+ createResolvedCtx() {
152
+ if (!this.scene) throw new Error("Scene not set");
153
+ return this.solve(this.scene);
154
+ }
155
+ reset(scene) {
156
+ this.base.clear();
157
+ this.channels.clear();
158
+ this.pins.clear();
159
+ this.debugGroups = [];
160
+ this.scene = scene;
161
+ for (const obj of scene.objects()) {
162
+ this.base.set(obj, scene.getInitialTransform(obj));
163
+ }
164
+ }
165
+ getDebugGroups() {
166
+ return this.debugGroups;
167
+ }
168
+ getDebugPins() {
169
+ return this.pins;
170
+ }
171
+ /** Get the current world position of a specific anchor on an object */
172
+ getCurrentAnchorPosition(obj, anchor) {
173
+ if (!this.scene) throw new Error("Scene not set");
174
+ const existingPin = this.pins.get(obj);
175
+ if (existingPin && existingPin.anchor === anchor) {
176
+ return existingPin.world;
177
+ }
178
+ const base = this.base.get(obj);
179
+ const ch = this.channels.get(obj);
180
+ const scale = ch?.scale ?? base.scale;
181
+ const scaleX = (ch?.scaleX ?? 1) * scale;
182
+ const scaleY = (ch?.scaleY ?? 1) * scale;
183
+ const rotate = ch?.rotate ?? base.rotate;
184
+ let originPos;
185
+ if (existingPin) {
186
+ const pinnedLocal = this.scene.resolveAnchor(obj, existingPin.anchor);
187
+ const cos2 = Math.cos(rotate);
188
+ const sin2 = Math.sin(rotate);
189
+ const transformedX2 = pinnedLocal[0] * scaleX * cos2 - pinnedLocal[1] * scaleY * sin2;
190
+ const transformedY2 = pinnedLocal[0] * scaleX * sin2 + pinnedLocal[1] * scaleY * cos2;
191
+ originPos = [existingPin.world[0] - transformedX2, existingPin.world[1] - transformedY2];
192
+ } else {
193
+ originPos = base.translate;
194
+ }
195
+ const local = this.scene.resolveAnchor(obj, anchor);
196
+ const cos = Math.cos(rotate);
197
+ const sin = Math.sin(rotate);
198
+ const transformedX = local[0] * scaleX * cos - local[1] * scaleY * sin;
199
+ const transformedY = local[0] * scaleX * sin + local[1] * scaleY * cos;
200
+ return [originPos[0] + transformedX, originPos[1] + transformedY];
201
+ }
202
+ getGroupBounds(group) {
203
+ return this.computeGroupWorldBounds(group);
204
+ }
205
+ /** Get the origin position of an object in world space, accounting for any existing pin */
206
+ getOriginPosition(obj) {
207
+ if (!this.scene) throw new Error("Scene not set");
208
+ const pin = this.pins.get(obj);
209
+ if (!pin) {
210
+ return this.base.get(obj).translate;
211
+ }
212
+ const base = this.base.get(obj);
213
+ const ch = this.channels.get(obj);
214
+ const scale = ch?.scale ?? base.scale;
215
+ const scaleX = (ch?.scaleX ?? 1) * scale;
216
+ const scaleY = (ch?.scaleY ?? 1) * scale;
217
+ const rotate = ch?.rotate ?? base.rotate;
218
+ const localAnchor = this.scene.resolveAnchor(obj, pin.anchor);
219
+ const cos = Math.cos(rotate);
220
+ const sin = Math.sin(rotate);
221
+ const transformedX = localAnchor[0] * scaleX * cos - localAnchor[1] * scaleY * sin;
222
+ const transformedY = localAnchor[0] * scaleX * sin + localAnchor[1] * scaleY * cos;
223
+ return [pin.world[0] - transformedX, pin.world[1] - transformedY];
224
+ }
225
+ resolveGroupAnchor(group, anchor) {
226
+ const bounds = this.getGroupBounds(group);
227
+ const anchorMap = {
228
+ origin: [bounds.x, bounds.y],
229
+ // For groups, origin is same as topLeft
230
+ topLeft: [bounds.x, bounds.y],
231
+ top: [bounds.x + bounds.w / 2, bounds.y],
232
+ topRight: [bounds.x + bounds.w, bounds.y],
233
+ left: [bounds.x, bounds.y + bounds.h / 2],
234
+ center: [bounds.x + bounds.w / 2, bounds.y + bounds.h / 2],
235
+ right: [bounds.x + bounds.w, bounds.y + bounds.h / 2],
236
+ bottomLeft: [bounds.x, bounds.y + bounds.h],
237
+ bottom: [bounds.x + bounds.w / 2, bounds.y + bounds.h],
238
+ bottomRight: [bounds.x + bounds.w, bounds.y + bounds.h],
239
+ baselineLeft: [bounds.x, bounds.y + bounds.h],
240
+ // baseline defaults to bottom
241
+ baseline: [bounds.x + bounds.w / 2, bounds.y + bounds.h]
242
+ };
243
+ return anchorMap[anchor];
244
+ }
245
+ getGroupCenter(group) {
246
+ const bounds = this.getGroupBounds(group);
247
+ return [bounds.x + bounds.w / 2, bounds.y + bounds.h / 2];
248
+ }
249
+ scale(obj) {
250
+ if (isGroup(obj)) {
251
+ return {
252
+ to: (vExpr, u) => {
253
+ const v = this.resolveNum(vExpr);
254
+ const groupCenter = this.getGroupCenter(obj);
255
+ const scaleFactor = lerp(1, v, u);
256
+ for (const member of obj.members) {
257
+ const base = this.base.get(member);
258
+ const ch = this.channels.get(member) ?? {};
259
+ const currentScale = ch.scale ?? base.scale;
260
+ ch.scale = currentScale * scaleFactor;
261
+ const originPos = this.getOriginPosition(member);
262
+ const offsetFromCenter = vec.sub(originPos, groupCenter);
263
+ const scaledOffset = [offsetFromCenter[0] * scaleFactor, offsetFromCenter[1] * scaleFactor];
264
+ const newOriginPos = vec.add(groupCenter, scaledOffset);
265
+ this.pins.set(member, {
266
+ anchor: "origin",
267
+ world: newOriginPos
268
+ });
269
+ this.channels.set(member, ch);
270
+ }
271
+ },
272
+ by: (deltaExpr, u) => {
273
+ const delta = this.resolveNum(deltaExpr);
274
+ const groupCenter = this.getGroupCenter(obj);
275
+ const scaleFactor = lerp(1, 1 + delta, u);
276
+ for (const member of obj.members) {
277
+ const base = this.base.get(member);
278
+ const ch = this.channels.get(member) ?? {};
279
+ const currentScale = ch.scale ?? base.scale;
280
+ ch.scale = currentScale * scaleFactor;
281
+ const originPos = this.getOriginPosition(member);
282
+ const offsetFromCenter = vec.sub(originPos, groupCenter);
283
+ const scaledOffset = [offsetFromCenter[0] * scaleFactor, offsetFromCenter[1] * scaleFactor];
284
+ const newOriginPos = vec.add(groupCenter, scaledOffset);
285
+ this.pins.set(member, {
286
+ anchor: "origin",
287
+ world: newOriginPos
288
+ });
289
+ this.channels.set(member, ch);
290
+ }
291
+ }
292
+ };
293
+ }
294
+ return {
295
+ to: (vExpr, u) => {
296
+ const v = this.resolveNum(vExpr);
297
+ const ch = this.channels.get(obj) ?? {};
298
+ const current = ch.scale ?? this.base.get(obj).scale;
299
+ ch.scale = lerp(current, v, u);
300
+ this.channels.set(obj, ch);
301
+ },
302
+ by: (deltaExpr, u) => {
303
+ const delta = this.resolveNum(deltaExpr);
304
+ const ch = this.channels.get(obj) ?? {};
305
+ const current = ch.scale ?? this.base.get(obj).scale;
306
+ ch.scale = current * lerp(1, 1 + delta, u);
307
+ this.channels.set(obj, ch);
308
+ }
309
+ };
310
+ }
311
+ scaleX(obj) {
312
+ return {
313
+ to: (vExpr, u) => {
314
+ const v = this.resolveNum(vExpr);
315
+ const ch = this.channels.get(obj) ?? {};
316
+ const current = ch.scaleX ?? 1;
317
+ ch.scaleX = lerp(current, v, u);
318
+ this.channels.set(obj, ch);
319
+ },
320
+ by: (deltaExpr, u) => {
321
+ const delta = this.resolveNum(deltaExpr);
322
+ const ch = this.channels.get(obj) ?? {};
323
+ const current = ch.scaleX ?? 1;
324
+ ch.scaleX = current * lerp(1, 1 + delta, u);
325
+ this.channels.set(obj, ch);
326
+ }
327
+ };
328
+ }
329
+ scaleY(obj) {
330
+ return {
331
+ to: (vExpr, u) => {
332
+ const v = this.resolveNum(vExpr);
333
+ const ch = this.channels.get(obj) ?? {};
334
+ const current = ch.scaleY ?? 1;
335
+ ch.scaleY = lerp(current, v, u);
336
+ this.channels.set(obj, ch);
337
+ },
338
+ by: (deltaExpr, u) => {
339
+ const delta = this.resolveNum(deltaExpr);
340
+ const ch = this.channels.get(obj) ?? {};
341
+ const current = ch.scaleY ?? 1;
342
+ ch.scaleY = current * lerp(1, 1 + delta, u);
343
+ this.channels.set(obj, ch);
344
+ }
345
+ };
346
+ }
347
+ width(obj) {
348
+ if (!this.scene) throw new Error("Scene not set");
349
+ const bounds = this.scene.getBounds(obj);
350
+ const originalWidth = bounds.w;
351
+ return {
352
+ to: (targetPxExpr, u) => {
353
+ const targetPx = this.resolveNum(targetPxExpr);
354
+ const targetScaleX = targetPx / originalWidth;
355
+ const ch = this.channels.get(obj) ?? {};
356
+ const current = ch.scaleX ?? 1;
357
+ ch.scaleX = lerp(current, targetScaleX, u);
358
+ this.channels.set(obj, ch);
359
+ },
360
+ by: (deltaPxExpr, u) => {
361
+ const deltaPx = this.resolveNum(deltaPxExpr);
362
+ const ch = this.channels.get(obj) ?? {};
363
+ const currentScaleX = ch.scaleX ?? 1;
364
+ const currentWidth = originalWidth * currentScaleX;
365
+ const targetWidth = currentWidth + deltaPx;
366
+ const targetScaleX = targetWidth / originalWidth;
367
+ ch.scaleX = lerp(currentScaleX, targetScaleX, u);
368
+ this.channels.set(obj, ch);
369
+ }
370
+ };
371
+ }
372
+ height(obj) {
373
+ if (!this.scene) throw new Error("Scene not set");
374
+ const bounds = this.scene.getBounds(obj);
375
+ const originalHeight = bounds.h;
376
+ return {
377
+ to: (targetPxExpr, u) => {
378
+ const targetPx = this.resolveNum(targetPxExpr);
379
+ const targetScaleY = targetPx / originalHeight;
380
+ const ch = this.channels.get(obj) ?? {};
381
+ const current = ch.scaleY ?? 1;
382
+ ch.scaleY = lerp(current, targetScaleY, u);
383
+ this.channels.set(obj, ch);
384
+ },
385
+ by: (deltaPxExpr, u) => {
386
+ const deltaPx = this.resolveNum(deltaPxExpr);
387
+ const ch = this.channels.get(obj) ?? {};
388
+ const currentScaleY = ch.scaleY ?? 1;
389
+ const currentHeight = originalHeight * currentScaleY;
390
+ const targetHeight = currentHeight + deltaPx;
391
+ const targetScaleY = targetHeight / originalHeight;
392
+ ch.scaleY = lerp(currentScaleY, targetScaleY, u);
393
+ this.channels.set(obj, ch);
394
+ }
395
+ };
396
+ }
397
+ resize(obj) {
398
+ if (!this.scene) throw new Error("Scene not set");
399
+ const bounds = this.scene.getBounds(obj);
400
+ const originalWidth = bounds.w;
401
+ const originalHeight = bounds.h;
402
+ const resolveTarget = (target) => {
403
+ if (typeof target === "number") return target;
404
+ if (typeof target === "function") return this.resolveNum(target);
405
+ if ("fit" in target) return { fit: this.resolveNum(target.fit) };
406
+ if ("width" in target && "height" in target) {
407
+ return { width: this.resolveNum(target.width), height: this.resolveNum(target.height) };
408
+ }
409
+ if ("width" in target) return { width: this.resolveNum(target.width) };
410
+ if ("height" in target) return { height: this.resolveNum(target.height) };
411
+ return target;
412
+ };
413
+ const parseTarget = (target) => {
414
+ if (typeof target === "number") {
415
+ return { scale: target / originalWidth };
416
+ }
417
+ if ("fit" in target) {
418
+ const scaleW = target.fit / originalWidth;
419
+ const scaleH = target.fit / originalHeight;
420
+ return { scale: Math.min(scaleW, scaleH) };
421
+ }
422
+ if ("width" in target && "height" in target) {
423
+ return {
424
+ scaleX: target.width / originalWidth,
425
+ scaleY: target.height / originalHeight
426
+ };
427
+ }
428
+ if ("width" in target) {
429
+ return { scale: target.width / originalWidth };
430
+ }
431
+ if ("height" in target) {
432
+ return { scale: target.height / originalHeight };
433
+ }
434
+ return {};
435
+ };
436
+ return {
437
+ to: (targetExpr, u) => {
438
+ const target = resolveTarget(targetExpr);
439
+ const { scaleX, scaleY, scale } = parseTarget(target);
440
+ const ch = this.channels.get(obj) ?? {};
441
+ if (scale !== void 0) {
442
+ const currentScale = ch.scale ?? this.base.get(obj).scale;
443
+ ch.scale = lerp(currentScale, scale, u);
444
+ } else {
445
+ if (scaleX !== void 0) {
446
+ const current = ch.scaleX ?? 1;
447
+ ch.scaleX = lerp(current, scaleX, u);
448
+ }
449
+ if (scaleY !== void 0) {
450
+ const current = ch.scaleY ?? 1;
451
+ ch.scaleY = lerp(current, scaleY, u);
452
+ }
453
+ }
454
+ this.channels.set(obj, ch);
455
+ },
456
+ by: (deltaExpr, u) => {
457
+ const delta = resolveTarget(deltaExpr);
458
+ const ch = this.channels.get(obj) ?? {};
459
+ if (typeof delta === "number") {
460
+ const currentScale = ch.scale ?? this.base.get(obj).scale;
461
+ const currentWidth = originalWidth * currentScale;
462
+ const targetWidth = currentWidth + delta;
463
+ const targetScale = targetWidth / originalWidth;
464
+ ch.scale = lerp(currentScale, targetScale, u);
465
+ } else if ("fit" in delta) {
466
+ const currentScale = ch.scale ?? this.base.get(obj).scale;
467
+ const currentFit = Math.max(originalWidth, originalHeight) * currentScale;
468
+ const targetFit = currentFit + delta.fit;
469
+ const scaleW = targetFit / originalWidth;
470
+ const scaleH = targetFit / originalHeight;
471
+ const targetScale = Math.min(scaleW, scaleH);
472
+ ch.scale = lerp(currentScale, targetScale, u);
473
+ } else if ("width" in delta && "height" in delta) {
474
+ const currentScaleX = ch.scaleX ?? 1;
475
+ const currentScaleY = ch.scaleY ?? 1;
476
+ const currentWidth = originalWidth * currentScaleX;
477
+ const currentHeight = originalHeight * currentScaleY;
478
+ const targetScaleX = (currentWidth + delta.width) / originalWidth;
479
+ const targetScaleY = (currentHeight + delta.height) / originalHeight;
480
+ ch.scaleX = lerp(currentScaleX, targetScaleX, u);
481
+ ch.scaleY = lerp(currentScaleY, targetScaleY, u);
482
+ } else if ("width" in delta) {
483
+ const currentScale = ch.scale ?? this.base.get(obj).scale;
484
+ const currentWidth = originalWidth * currentScale;
485
+ const targetScale = (currentWidth + delta.width) / originalWidth;
486
+ ch.scale = lerp(currentScale, targetScale, u);
487
+ } else if ("height" in delta) {
488
+ const currentScale = ch.scale ?? this.base.get(obj).scale;
489
+ const currentHeight = originalHeight * currentScale;
490
+ const targetScale = (currentHeight + delta.height) / originalHeight;
491
+ ch.scale = lerp(currentScale, targetScale, u);
492
+ }
493
+ this.channels.set(obj, ch);
494
+ }
495
+ };
496
+ }
497
+ fit(obj) {
498
+ return {
499
+ to: (bbox, u) => {
500
+ this.resize(obj).to(bbox.whSize(), u);
501
+ this.pin(obj, "center").to(bbox.center(), u);
502
+ },
503
+ toBBox: (target, u, opts) => {
504
+ let bbox = this.bbox(target);
505
+ if (opts?.pad !== void 0) {
506
+ bbox = bbox.pad(opts.pad);
507
+ }
508
+ this.resize(obj).to(bbox.whSize(), u);
509
+ this.pin(obj, "center").to(bbox.center(), u);
510
+ }
511
+ };
512
+ }
513
+ computeWorldBounds(obj) {
514
+ if (!this.scene) throw new Error("Scene not set");
515
+ const bounds = this.scene.getBounds(obj);
516
+ const ch = this.channels.get(obj);
517
+ const base = this.base.get(obj);
518
+ const pin = this.pins.get(obj);
519
+ let translate;
520
+ if (pin) {
521
+ const local = this.scene.resolveAnchor(obj, pin.anchor);
522
+ const scale2 = ch?.scale ?? base.scale;
523
+ const scaleX2 = (ch?.scaleX ?? 1) * scale2;
524
+ const scaleY2 = (ch?.scaleY ?? 1) * scale2;
525
+ const rotate = ch?.rotate ?? base.rotate;
526
+ const cos = Math.cos(rotate);
527
+ const sin = Math.sin(rotate);
528
+ const transformedX = local[0] * scaleX2 * cos - local[1] * scaleY2 * sin;
529
+ const transformedY = local[0] * scaleX2 * sin + local[1] * scaleY2 * cos;
530
+ translate = [pin.world[0] - transformedX, pin.world[1] - transformedY];
531
+ } else {
532
+ translate = base.translate;
533
+ }
534
+ const scale = ch?.scale ?? base.scale;
535
+ const scaleX = (ch?.scaleX ?? 1) * scale;
536
+ const scaleY = (ch?.scaleY ?? 1) * scale;
537
+ return {
538
+ x: translate[0] + bounds.x * scaleX,
539
+ y: translate[1] + bounds.y * scaleY,
540
+ w: bounds.w * scaleX,
541
+ h: bounds.h * scaleY
542
+ };
543
+ }
544
+ computeGroupWorldBounds(group) {
545
+ let minX = Infinity, minY = Infinity;
546
+ let maxX = -Infinity, maxY = -Infinity;
547
+ for (const member of group.members) {
548
+ const b = this.computeWorldBounds(member);
549
+ minX = Math.min(minX, b.x);
550
+ minY = Math.min(minY, b.y);
551
+ maxX = Math.max(maxX, b.x + b.w);
552
+ maxY = Math.max(maxY, b.y + b.h);
553
+ }
554
+ if (!isFinite(minX)) {
555
+ return { x: 0, y: 0, w: 0, h: 0 };
556
+ }
557
+ return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
558
+ }
559
+ bbox(obj) {
560
+ if (!this.scene) throw new Error("Scene not set");
561
+ const computeRect = () => {
562
+ return isGroup(obj) ? this.computeGroupWorldBounds(obj) : this.computeWorldBounds(obj);
563
+ };
564
+ return new BBoxHandleImpl(computeRect);
565
+ }
566
+ rotate(obj) {
567
+ if (isGroup(obj)) {
568
+ return {
569
+ to: (radExpr, u) => {
570
+ const rad = this.resolveNum(radExpr);
571
+ const groupCenter = this.getGroupCenter(obj);
572
+ for (const member of obj.members) {
573
+ const base = this.base.get(member);
574
+ const ch = this.channels.get(member) ?? {};
575
+ const currentRot = ch.rotate ?? base.rotate;
576
+ ch.rotate = lerp(currentRot, rad, u);
577
+ const originPos = this.getOriginPosition(member);
578
+ const offsetFromCenter = vec.sub(originPos, groupCenter);
579
+ const angle2 = lerp(0, rad, u);
580
+ const cos = Math.cos(angle2);
581
+ const sin = Math.sin(angle2);
582
+ const rotatedOffset = [
583
+ offsetFromCenter[0] * cos - offsetFromCenter[1] * sin,
584
+ offsetFromCenter[0] * sin + offsetFromCenter[1] * cos
585
+ ];
586
+ const newOriginPos = vec.add(groupCenter, rotatedOffset);
587
+ this.pins.set(member, {
588
+ anchor: "origin",
589
+ world: newOriginPos
590
+ });
591
+ this.channels.set(member, ch);
592
+ }
593
+ },
594
+ by: (deltaExpr, u) => {
595
+ const delta = this.resolveNum(deltaExpr);
596
+ const groupCenter = this.getGroupCenter(obj);
597
+ for (const member of obj.members) {
598
+ const base = this.base.get(member);
599
+ const ch = this.channels.get(member) ?? {};
600
+ const currentRot = ch.rotate ?? base.rotate;
601
+ ch.rotate = currentRot + delta * u;
602
+ const originPos = this.getOriginPosition(member);
603
+ const offsetFromCenter = vec.sub(originPos, groupCenter);
604
+ const angle2 = delta * u;
605
+ const cos = Math.cos(angle2);
606
+ const sin = Math.sin(angle2);
607
+ const rotatedOffset = [
608
+ offsetFromCenter[0] * cos - offsetFromCenter[1] * sin,
609
+ offsetFromCenter[0] * sin + offsetFromCenter[1] * cos
610
+ ];
611
+ const newOriginPos = vec.add(groupCenter, rotatedOffset);
612
+ this.pins.set(member, {
613
+ anchor: "origin",
614
+ world: newOriginPos
615
+ });
616
+ this.channels.set(member, ch);
617
+ }
618
+ }
619
+ };
620
+ }
621
+ return {
622
+ to: (radExpr, u) => {
623
+ const rad = this.resolveNum(radExpr);
624
+ const ch = this.channels.get(obj) ?? {};
625
+ const current = ch.rotate ?? this.base.get(obj).rotate;
626
+ ch.rotate = lerp(current, rad, u);
627
+ this.channels.set(obj, ch);
628
+ },
629
+ by: (deltaExpr, u) => {
630
+ const delta = this.resolveNum(deltaExpr);
631
+ const ch = this.channels.get(obj) ?? {};
632
+ const current = ch.rotate ?? this.base.get(obj).rotate;
633
+ ch.rotate = current + delta * u;
634
+ this.channels.set(obj, ch);
635
+ }
636
+ };
637
+ }
638
+ pin(obj, anchor) {
639
+ if (isGroup(obj)) {
640
+ return {
641
+ to: (posExpr, u) => {
642
+ const pos = this.resolveVec2(posExpr);
643
+ const currentAnchor = this.resolveGroupAnchor(obj, anchor);
644
+ const targetAnchor = vec.lerp(currentAnchor, pos, u);
645
+ const delta = vec.sub(targetAnchor, currentAnchor);
646
+ for (const member of obj.members) {
647
+ const existingPin = this.pins.get(member);
648
+ const memberAnchor = existingPin?.anchor ?? "origin";
649
+ const currentAnchorPos = this.getCurrentAnchorPosition(member, memberAnchor);
650
+ const newPos = vec.add(currentAnchorPos, delta);
651
+ this.pins.set(member, {
652
+ anchor: memberAnchor,
653
+ world: newPos
654
+ });
655
+ }
656
+ },
657
+ by: (deltaExpr, u) => {
658
+ const delta = this.resolveVec2(deltaExpr);
659
+ const interpolatedDelta = vec.lerp([0, 0], delta, u);
660
+ for (const member of obj.members) {
661
+ const existingPin = this.pins.get(member);
662
+ const memberAnchor = existingPin?.anchor ?? "origin";
663
+ const currentAnchorPos = this.getCurrentAnchorPosition(member, memberAnchor);
664
+ const newPos = vec.add(currentAnchorPos, interpolatedDelta);
665
+ this.pins.set(member, {
666
+ anchor: memberAnchor,
667
+ world: newPos
668
+ });
669
+ }
670
+ }
671
+ };
672
+ }
673
+ return {
674
+ to: (posExpr, u) => {
675
+ const pos = this.resolveVec2(posExpr);
676
+ const current = this.getCurrentAnchorPosition(obj, anchor);
677
+ const world = vec.lerp(current, pos, u);
678
+ this.pins.set(obj, {
679
+ anchor,
680
+ world
681
+ });
682
+ },
683
+ by: (deltaExpr, u) => {
684
+ const delta = this.resolveVec2(deltaExpr);
685
+ const current = this.getCurrentAnchorPosition(obj, anchor);
686
+ const world = vec.add(current, vec.lerp([0, 0], delta, u));
687
+ this.pins.set(obj, {
688
+ anchor,
689
+ world
690
+ });
691
+ }
692
+ };
693
+ }
694
+ opacity(obj) {
695
+ if (isGroup(obj)) {
696
+ return {
697
+ to: (vExpr, u) => {
698
+ const v = this.resolveNum(vExpr);
699
+ for (const member of obj.members) {
700
+ const ch = this.channels.get(member) ?? {};
701
+ const current = ch.opacity ?? 1;
702
+ ch.opacity = lerp(current, v, u);
703
+ this.channels.set(member, ch);
704
+ }
705
+ },
706
+ by: (deltaExpr, u) => {
707
+ const delta = this.resolveNum(deltaExpr);
708
+ for (const member of obj.members) {
709
+ const ch = this.channels.get(member) ?? {};
710
+ const current = ch.opacity ?? 1;
711
+ ch.opacity = current * lerp(1, 1 + delta, u);
712
+ this.channels.set(member, ch);
713
+ }
714
+ }
715
+ };
716
+ }
717
+ return {
718
+ to: (vExpr, u) => {
719
+ const v = this.resolveNum(vExpr);
720
+ const ch = this.channels.get(obj) ?? {};
721
+ const current = ch.opacity ?? 1;
722
+ ch.opacity = lerp(current, v, u);
723
+ this.channels.set(obj, ch);
724
+ },
725
+ by: (deltaExpr, u) => {
726
+ const delta = this.resolveNum(deltaExpr);
727
+ const ch = this.channels.get(obj) ?? {};
728
+ const current = ch.opacity ?? 1;
729
+ ch.opacity = current * lerp(1, 1 + delta, u);
730
+ this.channels.set(obj, ch);
731
+ }
732
+ };
733
+ }
734
+ group(...objects) {
735
+ const groupObj = {
736
+ id: /* @__PURE__ */ Symbol("group"),
737
+ members: objects,
738
+ isGroup: true
739
+ };
740
+ const self = this;
741
+ const builder = {
742
+ ...groupObj,
743
+ pin: (anchor) => {
744
+ const methods = self.pin(groupObj, anchor);
745
+ return {
746
+ to: (pos, u) => {
747
+ methods.to(pos, u);
748
+ self.trackGroupDebug(groupObj, u, { anchor, target: pos });
749
+ },
750
+ by: (delta, u) => {
751
+ methods.by(delta, u);
752
+ self.trackGroupDebug(groupObj, u);
753
+ }
754
+ };
755
+ },
756
+ scale: () => {
757
+ const methods = self.scale(groupObj);
758
+ return {
759
+ to: (v, u) => {
760
+ methods.to(v, u);
761
+ self.trackGroupDebug(groupObj, u);
762
+ },
763
+ by: (delta, u) => {
764
+ methods.by(delta, u);
765
+ self.trackGroupDebug(groupObj, u);
766
+ }
767
+ };
768
+ },
769
+ rotate: () => {
770
+ const methods = self.rotate(groupObj);
771
+ return {
772
+ to: (v, u) => {
773
+ methods.to(v, u);
774
+ self.trackGroupDebug(groupObj, u);
775
+ },
776
+ by: (delta, u) => {
777
+ methods.by(delta, u);
778
+ self.trackGroupDebug(groupObj, u);
779
+ }
780
+ };
781
+ },
782
+ opacity: () => {
783
+ const methods = self.opacity(groupObj);
784
+ return {
785
+ to: (v, u) => {
786
+ methods.to(v, u);
787
+ self.trackGroupDebug(groupObj, u);
788
+ },
789
+ by: (delta, u) => {
790
+ methods.by(delta, u);
791
+ self.trackGroupDebug(groupObj, u);
792
+ }
793
+ };
794
+ }
795
+ };
796
+ return builder;
797
+ }
798
+ trackGroupDebug(groupObj, u, pin) {
799
+ if (u < 1) {
800
+ let entry = this.debugGroups.find(
801
+ (g) => g.kind === "group" && g.members === groupObj.members
802
+ );
803
+ if (!entry) {
804
+ entry = {
805
+ members: groupObj.members,
806
+ kind: "group"
807
+ };
808
+ this.debugGroups.push(entry);
809
+ }
810
+ if (pin) {
811
+ entry.pin = pin;
812
+ }
813
+ return entry;
814
+ }
815
+ return null;
816
+ }
817
+ /** Flatten objects for final result (extracts all SceneObjects from groups) */
818
+ flattenToSceneObjects(objects) {
819
+ const result = [];
820
+ for (const obj of objects) {
821
+ if (isGroup(obj)) {
822
+ result.push(...obj.members);
823
+ } else {
824
+ result.push(obj);
825
+ }
826
+ }
827
+ return result;
828
+ }
829
+ /** Get bounds for an ObjRef (single object or group) */
830
+ getObjRefBounds(obj) {
831
+ if (isGroup(obj)) {
832
+ return this.computeGroupWorldBounds(obj);
833
+ }
834
+ return this.computeWorldBounds(obj);
835
+ }
836
+ /** Get the top-left position of an ObjRef (bounds position, not pin position) */
837
+ getObjRefPosition(obj) {
838
+ const bounds = this.getObjRefBounds(obj);
839
+ return [bounds.x, bounds.y];
840
+ }
841
+ /** Move an ObjRef by a delta (moves all members of a group together, preserving pin anchors) */
842
+ moveObjRef(obj, fromPos, toPos, u) {
843
+ const delta = vec.sub(toPos, fromPos);
844
+ const interpolatedDelta = vec.lerp([0, 0], delta, u);
845
+ if (isGroup(obj)) {
846
+ for (const member of obj.members) {
847
+ const existingPin = this.pins.get(member);
848
+ const anchor = existingPin?.anchor ?? "origin";
849
+ const currentAnchorPos = this.getCurrentAnchorPosition(member, anchor);
850
+ const newPos = vec.add(currentAnchorPos, interpolatedDelta);
851
+ this.pins.set(member, {
852
+ anchor,
853
+ world: newPos
854
+ });
855
+ }
856
+ } else {
857
+ const existingPin = this.pins.get(obj);
858
+ const anchor = existingPin?.anchor ?? "origin";
859
+ const currentAnchorPos = this.getCurrentAnchorPosition(obj, anchor);
860
+ const newPos = vec.add(currentAnchorPos, interpolatedDelta);
861
+ this.pins.set(obj, {
862
+ anchor,
863
+ world: newPos
864
+ });
865
+ }
866
+ }
867
+ alignOnCrossAxis(start, size, maxSize, align) {
868
+ if (align === "center") {
869
+ return start + (maxSize - size) / 2;
870
+ }
871
+ if (align === "topLeft" || align === "left" || align === "top") {
872
+ return start;
873
+ }
874
+ if (align === "bottomRight" || align === "right" || align === "bottom") {
875
+ return start + maxSize - size;
876
+ }
877
+ if (align === "baselineLeft" || align === "baseline") {
878
+ return start + maxSize - size;
879
+ }
880
+ return start;
881
+ }
882
+ calculateLayoutPositions(objects, axis, align, gap) {
883
+ if (!this.scene) throw new Error("Scene not set");
884
+ const boundsList = objects.map((obj) => this.getObjRefBounds(obj));
885
+ const startX = Math.min(...boundsList.map((b) => b.x));
886
+ const startY = Math.min(...boundsList.map((b) => b.y));
887
+ const maxH = Math.max(...boundsList.map((b) => b.h));
888
+ const maxW = Math.max(...boundsList.map((b) => b.w));
889
+ const targets = [];
890
+ let cursor = axis === "x" ? startX : startY;
891
+ for (let i = 0; i < objects.length; i++) {
892
+ const b = boundsList[i];
893
+ let x, y;
894
+ if (axis === "x") {
895
+ x = cursor;
896
+ y = this.alignOnCrossAxis(startY, b.h, maxH, align);
897
+ cursor += b.w + gap;
898
+ } else {
899
+ x = this.alignOnCrossAxis(startX, b.w, maxW, align);
900
+ y = cursor;
901
+ cursor += b.h + gap;
902
+ }
903
+ targets.push([x, y]);
904
+ }
905
+ return targets;
906
+ }
907
+ layout(...args) {
908
+ let opts = {};
909
+ let objects;
910
+ const last2 = args[args.length - 1];
911
+ if (last2 && typeof last2 === "object" && !("id" in last2)) {
912
+ opts = last2;
913
+ objects = args.slice(0, -1);
914
+ } else {
915
+ objects = args;
916
+ }
917
+ const axis = opts.axis ?? "x";
918
+ const align = opts.align ?? "center";
919
+ const gap = opts.gap ?? 0;
920
+ return {
921
+ to: (u) => {
922
+ const targets = this.calculateLayoutPositions(objects, axis, align, gap);
923
+ for (let i = 0; i < objects.length; i++) {
924
+ const obj = objects[i];
925
+ const currentPos = this.getObjRefPosition(obj);
926
+ const targetPos = targets[i];
927
+ this.moveObjRef(obj, currentPos, targetPos, u);
928
+ }
929
+ const allMembers = this.flattenToSceneObjects(objects);
930
+ const groupObj = {
931
+ id: /* @__PURE__ */ Symbol("layout-group"),
932
+ members: allMembers,
933
+ isGroup: true
934
+ };
935
+ let debugEntry = null;
936
+ if (u < 1) {
937
+ debugEntry = {
938
+ members: allMembers,
939
+ kind: "layout"
940
+ };
941
+ this.debugGroups.push(debugEntry);
942
+ }
943
+ const self = this;
944
+ const builder = {
945
+ ...groupObj,
946
+ pin: (anchor) => {
947
+ const methods = self.pin(groupObj, anchor);
948
+ return {
949
+ to: (pos, pinU) => {
950
+ methods.to(pos, pinU);
951
+ if (debugEntry) {
952
+ debugEntry.pin = { anchor, target: pos };
953
+ }
954
+ },
955
+ by: (delta, pinU) => {
956
+ methods.by(delta, pinU);
957
+ }
958
+ };
959
+ },
960
+ scale: () => self.scale(groupObj),
961
+ rotate: () => self.rotate(groupObj),
962
+ opacity: () => self.opacity(groupObj)
963
+ };
964
+ return builder;
965
+ }
966
+ };
967
+ }
968
+ inline(...args) {
969
+ const { objects, gap } = this.parseLayoutArgs(args);
970
+ return this.layout(...objects, { axis: "x", align: "baselineLeft", gap });
971
+ }
972
+ stack(...args) {
973
+ const { objects, gap } = this.parseLayoutArgs(args);
974
+ return this.layout(...objects, { axis: "y", align: "center", gap });
975
+ }
976
+ paragraph(...args) {
977
+ const { objects, gap } = this.parseLayoutArgs(args);
978
+ return this.layout(...objects, { axis: "y", align: "left", gap });
979
+ }
980
+ /** Parse layout args - last arg may be a gap number */
981
+ parseLayoutArgs(args) {
982
+ const last2 = args[args.length - 1];
983
+ if (typeof last2 === "number") {
984
+ return { objects: args.slice(0, -1), gap: last2 };
985
+ }
986
+ return { objects: args, gap: 0 };
987
+ }
988
+ /** Get anchor offset within bounds */
989
+ getAnchorOffsetFromRect(bounds, anchor) {
990
+ let ox = 0, oy = 0;
991
+ if (anchor === "top" || anchor === "center" || anchor === "bottom" || anchor === "baseline") {
992
+ ox = bounds.w / 2;
993
+ } else if (anchor === "topRight" || anchor === "right" || anchor === "bottomRight") {
994
+ ox = bounds.w;
995
+ }
996
+ if (anchor === "left" || anchor === "center" || anchor === "right") {
997
+ oy = bounds.h / 2;
998
+ } else if (anchor === "bottomLeft" || anchor === "bottom" || anchor === "bottomRight" || anchor === "baselineLeft" || anchor === "baseline") {
999
+ oy = bounds.h;
1000
+ }
1001
+ return [ox, oy];
1002
+ }
1003
+ /** Get anchor position in world space for an ObjRef */
1004
+ getObjRefAnchorPosition(obj, anchor) {
1005
+ const bounds = this.getObjRefBounds(obj);
1006
+ const offset = this.getAnchorOffsetFromRect(bounds, anchor);
1007
+ return [bounds.x + offset[0], bounds.y + offset[1]];
1008
+ }
1009
+ align(...args) {
1010
+ let opts = {};
1011
+ let objects;
1012
+ const last2 = args[args.length - 1];
1013
+ if (last2 && typeof last2 === "object" && !("id" in last2)) {
1014
+ opts = last2;
1015
+ objects = args.slice(0, -1);
1016
+ } else {
1017
+ objects = args;
1018
+ }
1019
+ const anchor = opts.anchor ?? "center";
1020
+ return {
1021
+ to: (u) => {
1022
+ if (!this.scene || objects.length === 0) {
1023
+ const allMembers2 = this.flattenToSceneObjects(objects);
1024
+ return this.group(...allMembers2);
1025
+ }
1026
+ const targetAnchorPos = this.getObjRefAnchorPosition(objects[0], anchor);
1027
+ for (let i = 1; i < objects.length; i++) {
1028
+ const obj = objects[i];
1029
+ const bounds = this.getObjRefBounds(obj);
1030
+ const anchorOffset = this.getAnchorOffsetFromRect(bounds, anchor);
1031
+ const targetPos = [
1032
+ targetAnchorPos[0] - anchorOffset[0],
1033
+ targetAnchorPos[1] - anchorOffset[1]
1034
+ ];
1035
+ const currentPos = this.getObjRefPosition(obj);
1036
+ this.moveObjRef(obj, currentPos, targetPos, u);
1037
+ }
1038
+ const allMembers = this.flattenToSceneObjects(objects);
1039
+ const groupObj = {
1040
+ id: /* @__PURE__ */ Symbol("align-group"),
1041
+ members: allMembers,
1042
+ isGroup: true
1043
+ };
1044
+ let debugEntry = null;
1045
+ if (u < 1) {
1046
+ debugEntry = {
1047
+ members: allMembers,
1048
+ kind: "layout"
1049
+ };
1050
+ this.debugGroups.push(debugEntry);
1051
+ }
1052
+ const self = this;
1053
+ return {
1054
+ ...groupObj,
1055
+ pin: (pinAnchor) => {
1056
+ const methods = self.pin(groupObj, pinAnchor);
1057
+ return {
1058
+ to: (pos, pinU) => {
1059
+ methods.to(pos, pinU);
1060
+ if (debugEntry) {
1061
+ debugEntry.pin = { anchor: pinAnchor, target: pos };
1062
+ }
1063
+ },
1064
+ by: (delta, pinU) => {
1065
+ methods.by(delta, pinU);
1066
+ }
1067
+ };
1068
+ },
1069
+ scale: () => self.scale(groupObj),
1070
+ rotate: () => self.rotate(groupObj),
1071
+ opacity: () => self.opacity(groupObj)
1072
+ };
1073
+ }
1074
+ };
1075
+ }
1076
+ center(...objects) {
1077
+ return this.align(...objects, { anchor: "center" });
1078
+ }
1079
+ solve(scene) {
1080
+ const resolved = /* @__PURE__ */ new Map();
1081
+ const opacity = /* @__PURE__ */ new Map();
1082
+ for (const obj of scene.objects()) {
1083
+ const base = scene.getInitialTransform(obj);
1084
+ const ch = this.channels.get(obj);
1085
+ const scale = ch?.scale ?? base.scale;
1086
+ const scaleX = ch?.scaleX;
1087
+ const scaleY = ch?.scaleY;
1088
+ const rotate = ch?.rotate ?? base.rotate;
1089
+ let translate = base.translate ?? [0, 0];
1090
+ const pin = this.pins.get(obj);
1091
+ if (pin) {
1092
+ const local = scene.resolveAnchor(obj, pin.anchor);
1093
+ const transformed = applySR(local, scale, rotate, scaleX ?? 1, scaleY ?? 1);
1094
+ translate = vec.sub(pin.world, transformed);
1095
+ }
1096
+ resolved.set(obj, {
1097
+ translate,
1098
+ rotate: rotate ?? 0,
1099
+ scale: scale ?? 1,
1100
+ scaleX,
1101
+ scaleY
1102
+ });
1103
+ opacity.set(obj, ch?.opacity ?? 1);
1104
+ }
1105
+ return {
1106
+ getTransform: (o) => resolved.get(o),
1107
+ getOpacity: (o) => opacity.get(o) ?? 1
1108
+ };
1109
+ }
1110
+ }
1111
+ function getAnchorOffset(anchor, bounds) {
1112
+ const { x, y, w, h } = bounds;
1113
+ const left = x;
1114
+ const right = x + w;
1115
+ const top = y;
1116
+ const bottom = y + h;
1117
+ const centerX = x + w / 2;
1118
+ const centerY = y + h / 2;
1119
+ switch (anchor) {
1120
+ case "origin":
1121
+ return [0, 0];
1122
+ case "topLeft":
1123
+ return [left, top];
1124
+ case "top":
1125
+ return [centerX, top];
1126
+ case "topRight":
1127
+ return [right, top];
1128
+ case "left":
1129
+ return [left, centerY];
1130
+ case "center":
1131
+ return [centerX, centerY];
1132
+ case "right":
1133
+ return [right, centerY];
1134
+ case "bottomLeft":
1135
+ return [left, bottom];
1136
+ case "bottom":
1137
+ return [centerX, bottom];
1138
+ case "bottomRight":
1139
+ return [right, bottom];
1140
+ case "baselineLeft":
1141
+ return [left, bottom];
1142
+ case "baseline":
1143
+ return [centerX, bottom];
1144
+ default:
1145
+ return [0, 0];
1146
+ }
1147
+ }
1148
+ function renderDebugOverlay(g, scene, solved, debugGroups, pins, dpr) {
1149
+ g.save();
1150
+ g.scale(dpr, dpr);
1151
+ const debugInfos = [];
1152
+ for (const obj of scene.objects()) {
1153
+ const transform = solved.getTransform(obj);
1154
+ if (!transform || !transform.translate) continue;
1155
+ const bounds = scene.getBounds(obj);
1156
+ const opacity = solved.getOpacity(obj);
1157
+ debugInfos.push({ obj, transform, bounds, opacity });
1158
+ }
1159
+ for (const groupInfo of debugGroups) {
1160
+ const { members, kind, pin } = groupInfo;
1161
+ let minX = Infinity, minY = Infinity;
1162
+ let maxX = -Infinity, maxY = -Infinity;
1163
+ for (const member of members) {
1164
+ const transform = solved.getTransform(member);
1165
+ if (!transform || !transform.translate) continue;
1166
+ const memberBounds = scene.getBounds(member);
1167
+ const x = transform.translate[0] + memberBounds.x;
1168
+ const y = transform.translate[1] + memberBounds.y;
1169
+ minX = Math.min(minX, x);
1170
+ minY = Math.min(minY, y);
1171
+ maxX = Math.max(maxX, x + memberBounds.w * transform.scale);
1172
+ maxY = Math.max(maxY, y + memberBounds.h * transform.scale);
1173
+ }
1174
+ if (!isFinite(minX)) continue;
1175
+ const bounds = { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
1176
+ const color = kind === "group" ? "rgba(255, 200, 0" : "rgba(255, 0, 255";
1177
+ g.strokeStyle = color + ", 0.7)";
1178
+ g.lineWidth = 2;
1179
+ g.setLineDash([6, 4]);
1180
+ g.strokeRect(bounds.x, bounds.y, bounds.w, bounds.h);
1181
+ g.font = "10px monospace";
1182
+ g.fillStyle = color + ", 0.9)";
1183
+ g.fillText(kind, bounds.x + 2, bounds.y - 3);
1184
+ if (pin) {
1185
+ let anchorX = bounds.x, anchorY = bounds.y;
1186
+ const anchor = pin.anchor;
1187
+ if (anchor === "top" || anchor === "center" || anchor === "bottom" || anchor === "baseline") {
1188
+ anchorX = bounds.x + bounds.w / 2;
1189
+ } else if (anchor === "topRight" || anchor === "right" || anchor === "bottomRight") {
1190
+ anchorX = bounds.x + bounds.w;
1191
+ }
1192
+ if (anchor === "left" || anchor === "center" || anchor === "right") {
1193
+ anchorY = bounds.y + bounds.h / 2;
1194
+ } else if (anchor === "bottomLeft" || anchor === "bottom" || anchor === "bottomRight" || anchor === "baselineLeft" || anchor === "baseline") {
1195
+ anchorY = bounds.y + bounds.h;
1196
+ }
1197
+ g.fillStyle = color + ", 0.9)";
1198
+ g.beginPath();
1199
+ g.moveTo(anchorX, anchorY - 5);
1200
+ g.lineTo(anchorX + 5, anchorY);
1201
+ g.lineTo(anchorX, anchorY + 5);
1202
+ g.lineTo(anchorX - 5, anchorY);
1203
+ g.closePath();
1204
+ g.fill();
1205
+ const [tx, ty] = pin.target;
1206
+ g.strokeStyle = color + ", 0.9)";
1207
+ g.lineWidth = 2;
1208
+ g.setLineDash([]);
1209
+ g.beginPath();
1210
+ g.moveTo(tx - 6, ty - 6);
1211
+ g.lineTo(tx + 6, ty + 6);
1212
+ g.moveTo(tx + 6, ty - 6);
1213
+ g.lineTo(tx - 6, ty + 6);
1214
+ g.stroke();
1215
+ g.strokeStyle = color + ", 0.4)";
1216
+ g.lineWidth = 1;
1217
+ g.setLineDash([3, 3]);
1218
+ g.beginPath();
1219
+ g.moveTo(anchorX, anchorY);
1220
+ g.lineTo(tx, ty);
1221
+ g.stroke();
1222
+ g.font = "9px monospace";
1223
+ g.fillStyle = color + ", 0.9)";
1224
+ g.fillText(anchor, anchorX + 7, anchorY - 2);
1225
+ }
1226
+ }
1227
+ for (const { transform, bounds } of debugInfos) {
1228
+ g.save();
1229
+ g.translate(transform.translate[0], transform.translate[1]);
1230
+ g.rotate(transform.rotate);
1231
+ const sx = (transform.scaleX ?? 1) * transform.scale;
1232
+ const sy = (transform.scaleY ?? 1) * transform.scale;
1233
+ g.scale(sx, sy);
1234
+ const avgScale = (sx + sy) / 2;
1235
+ g.strokeStyle = "rgba(0, 200, 255, 0.8)";
1236
+ g.lineWidth = 1 / avgScale;
1237
+ g.setLineDash([4 / avgScale, 4 / avgScale]);
1238
+ g.strokeRect(bounds.x, bounds.y, bounds.w, bounds.h);
1239
+ g.restore();
1240
+ }
1241
+ for (const { obj, transform, bounds } of debugInfos) {
1242
+ const x = transform.translate[0];
1243
+ const y = transform.translate[1];
1244
+ const sx = (transform.scaleX ?? 1) * transform.scale;
1245
+ const sy = (transform.scaleY ?? 1) * transform.scale;
1246
+ const cos = Math.cos(transform.rotate);
1247
+ const sin = Math.sin(transform.rotate);
1248
+ g.fillStyle = "rgba(255, 100, 100, 0.9)";
1249
+ g.beginPath();
1250
+ g.arc(x, y, 4, 0, Math.PI * 2);
1251
+ g.fill();
1252
+ const centerOffX = (bounds.x + bounds.w / 2) * sx;
1253
+ const centerOffY = (bounds.y + bounds.h / 2) * sy;
1254
+ const centerX = x + centerOffX * cos - centerOffY * sin;
1255
+ const centerY = y + centerOffX * sin + centerOffY * cos;
1256
+ g.fillStyle = "rgba(100, 255, 100, 0.9)";
1257
+ g.beginPath();
1258
+ g.arc(centerX, centerY, 3, 0, Math.PI * 2);
1259
+ g.fill();
1260
+ g.strokeStyle = "rgba(255, 100, 100, 0.6)";
1261
+ g.lineWidth = 1;
1262
+ g.setLineDash([]);
1263
+ g.beginPath();
1264
+ g.moveTo(x - 8, y);
1265
+ g.lineTo(x + 8, y);
1266
+ g.moveTo(x, y - 8);
1267
+ g.lineTo(x, y + 8);
1268
+ g.stroke();
1269
+ const baseline = scene.getBaseline(obj);
1270
+ if (baseline !== null) {
1271
+ g.save();
1272
+ g.translate(x, y);
1273
+ g.rotate(transform.rotate);
1274
+ g.scale(transform.scale, transform.scale);
1275
+ g.strokeStyle = "rgba(255, 165, 0, 0.8)";
1276
+ g.lineWidth = 1 / transform.scale;
1277
+ g.setLineDash([2 / transform.scale, 2 / transform.scale]);
1278
+ g.beginPath();
1279
+ g.moveTo(bounds.x - 5, baseline);
1280
+ g.lineTo(bounds.x + bounds.w + 5, baseline);
1281
+ g.stroke();
1282
+ g.restore();
1283
+ }
1284
+ const pin = pins.get(obj);
1285
+ if (pin) {
1286
+ const [anchorOffX, anchorOffY] = getAnchorOffset(pin.anchor, bounds);
1287
+ const scaledOffX = anchorOffX * sx;
1288
+ const scaledOffY = anchorOffY * sy;
1289
+ const anchorX = x + scaledOffX * cos - scaledOffY * sin;
1290
+ const anchorY = y + scaledOffX * sin + scaledOffY * cos;
1291
+ const [tx, ty] = pin.world;
1292
+ g.fillStyle = "rgba(0, 200, 255, 0.9)";
1293
+ g.beginPath();
1294
+ g.moveTo(anchorX, anchorY - 4);
1295
+ g.lineTo(anchorX + 4, anchorY);
1296
+ g.lineTo(anchorX, anchorY + 4);
1297
+ g.lineTo(anchorX - 4, anchorY);
1298
+ g.closePath();
1299
+ g.fill();
1300
+ g.strokeStyle = "rgba(0, 200, 255, 0.9)";
1301
+ g.lineWidth = 2;
1302
+ g.setLineDash([]);
1303
+ g.beginPath();
1304
+ g.moveTo(tx - 5, ty - 5);
1305
+ g.lineTo(tx + 5, ty + 5);
1306
+ g.moveTo(tx + 5, ty - 5);
1307
+ g.lineTo(tx - 5, ty + 5);
1308
+ g.stroke();
1309
+ g.strokeStyle = "rgba(0, 200, 255, 0.4)";
1310
+ g.lineWidth = 1;
1311
+ g.setLineDash([3, 3]);
1312
+ g.beginPath();
1313
+ g.moveTo(anchorX, anchorY);
1314
+ g.lineTo(tx, ty);
1315
+ g.stroke();
1316
+ g.font = "9px monospace";
1317
+ g.fillStyle = "rgba(0, 200, 255, 0.9)";
1318
+ g.fillText(pin.anchor, anchorX + 6, anchorY - 4);
1319
+ }
1320
+ }
1321
+ g.restore();
1322
+ }
1323
+ function makePlayer(canvas, opts) {
1324
+ const ctx = new AnimCtxImpl();
1325
+ const { scene, timeline } = opts;
1326
+ const duration = timeline.duration;
1327
+ let isPlaying = false;
1328
+ let currentTime = 0;
1329
+ let startTime = 0;
1330
+ let animationFrameId = null;
1331
+ let onTimeUpdate = null;
1332
+ let onError = null;
1333
+ let hasError = false;
1334
+ let debugMode = false;
1335
+ function render(timeMs, throwOnError = false) {
1336
+ try {
1337
+ ctx.reset(scene);
1338
+ timeline.evaluate(timeMs, ctx);
1339
+ const solved = ctx.solve(scene);
1340
+ scene.render(solved, canvas);
1341
+ if (debugMode) {
1342
+ const g = canvas.getContext("2d");
1343
+ const dpr = window.devicePixelRatio || 1;
1344
+ renderDebugOverlay(g, scene, solved, ctx.getDebugGroups(), ctx.getDebugPins(), dpr);
1345
+ }
1346
+ return true;
1347
+ } catch (e) {
1348
+ hasError = true;
1349
+ isPlaying = false;
1350
+ if (animationFrameId !== null) {
1351
+ cancelAnimationFrame(animationFrameId);
1352
+ animationFrameId = null;
1353
+ }
1354
+ const error = e instanceof Error ? e : new Error(String(e));
1355
+ if (throwOnError || !onError) {
1356
+ throw error;
1357
+ }
1358
+ onError(error);
1359
+ return false;
1360
+ }
1361
+ }
1362
+ function frame(now) {
1363
+ if (!isPlaying || hasError) return;
1364
+ currentTime = now - startTime;
1365
+ if (currentTime >= duration) {
1366
+ currentTime = duration;
1367
+ isPlaying = false;
1368
+ render(currentTime);
1369
+ onTimeUpdate?.(currentTime);
1370
+ return;
1371
+ }
1372
+ if (render(currentTime)) {
1373
+ onTimeUpdate?.(currentTime);
1374
+ animationFrameId = requestAnimationFrame(frame);
1375
+ }
1376
+ }
1377
+ render(0, true);
1378
+ return {
1379
+ get isPlaying() {
1380
+ return isPlaying;
1381
+ },
1382
+ get currentTime() {
1383
+ return currentTime;
1384
+ },
1385
+ get duration() {
1386
+ return duration;
1387
+ },
1388
+ get onTimeUpdate() {
1389
+ return onTimeUpdate;
1390
+ },
1391
+ set onTimeUpdate(callback) {
1392
+ onTimeUpdate = callback;
1393
+ },
1394
+ get onError() {
1395
+ return onError;
1396
+ },
1397
+ set onError(callback) {
1398
+ onError = callback;
1399
+ },
1400
+ get debug() {
1401
+ return debugMode;
1402
+ },
1403
+ set debug(value) {
1404
+ debugMode = value;
1405
+ if (!isPlaying) {
1406
+ render(currentTime);
1407
+ }
1408
+ },
1409
+ play() {
1410
+ if (isPlaying || hasError) return;
1411
+ if (currentTime >= duration) {
1412
+ currentTime = 0;
1413
+ }
1414
+ isPlaying = true;
1415
+ startTime = performance.now() - currentTime;
1416
+ animationFrameId = requestAnimationFrame(frame);
1417
+ },
1418
+ pause() {
1419
+ isPlaying = false;
1420
+ if (animationFrameId !== null) {
1421
+ cancelAnimationFrame(animationFrameId);
1422
+ animationFrameId = null;
1423
+ }
1424
+ },
1425
+ stop() {
1426
+ this.pause();
1427
+ currentTime = 0;
1428
+ render(0);
1429
+ onTimeUpdate?.(0);
1430
+ },
1431
+ seek(timeMs) {
1432
+ if (hasError) return;
1433
+ currentTime = Math.max(0, Math.min(duration, timeMs));
1434
+ startTime = performance.now() - currentTime;
1435
+ render(currentTime);
1436
+ onTimeUpdate?.(currentTime);
1437
+ },
1438
+ renderAt(timeMs) {
1439
+ if (hasError) return;
1440
+ render(Math.max(0, Math.min(duration, timeMs)));
1441
+ },
1442
+ dispose() {
1443
+ this.pause();
1444
+ }
1445
+ };
1446
+ }
1447
+ var __accessCheck = (obj, member, msg) => {
1448
+ if (!member.has(obj))
1449
+ throw TypeError("Cannot " + msg);
1450
+ };
1451
+ var __privateGet = (obj, member, getter) => {
1452
+ __accessCheck(obj, member, "read from private field");
1453
+ return getter ? getter.call(obj) : member.get(obj);
1454
+ };
1455
+ var __privateAdd = (obj, member, value) => {
1456
+ if (member.has(obj))
1457
+ throw TypeError("Cannot add the same private member more than once");
1458
+ member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
1459
+ };
1460
+ var __privateSet = (obj, member, value, setter) => {
1461
+ __accessCheck(obj, member, "write to private field");
1462
+ member.set(obj, value);
1463
+ return value;
1464
+ };
1465
+ var __privateWrapper = (obj, member, setter, getter) => ({
1466
+ set _(value) {
1467
+ __privateSet(obj, member, value);
1468
+ },
1469
+ get _() {
1470
+ return __privateGet(obj, member, getter);
1471
+ }
1472
+ });
1473
+ var __privateMethod = (obj, member, method) => {
1474
+ __accessCheck(obj, member, "access private method");
1475
+ return method;
1476
+ };
1477
+ var bytes = new Uint8Array(8);
1478
+ var view = new DataView(bytes.buffer);
1479
+ var u8 = (value) => {
1480
+ return [(value % 256 + 256) % 256];
1481
+ };
1482
+ var u16 = (value) => {
1483
+ view.setUint16(0, value, false);
1484
+ return [bytes[0], bytes[1]];
1485
+ };
1486
+ var i16 = (value) => {
1487
+ view.setInt16(0, value, false);
1488
+ return [bytes[0], bytes[1]];
1489
+ };
1490
+ var u24 = (value) => {
1491
+ view.setUint32(0, value, false);
1492
+ return [bytes[1], bytes[2], bytes[3]];
1493
+ };
1494
+ var u32 = (value) => {
1495
+ view.setUint32(0, value, false);
1496
+ return [bytes[0], bytes[1], bytes[2], bytes[3]];
1497
+ };
1498
+ var i32 = (value) => {
1499
+ view.setInt32(0, value, false);
1500
+ return [bytes[0], bytes[1], bytes[2], bytes[3]];
1501
+ };
1502
+ var u64 = (value) => {
1503
+ view.setUint32(0, Math.floor(value / 2 ** 32), false);
1504
+ view.setUint32(4, value, false);
1505
+ return [bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]];
1506
+ };
1507
+ var fixed_8_8 = (value) => {
1508
+ view.setInt16(0, 2 ** 8 * value, false);
1509
+ return [bytes[0], bytes[1]];
1510
+ };
1511
+ var fixed_16_16 = (value) => {
1512
+ view.setInt32(0, 2 ** 16 * value, false);
1513
+ return [bytes[0], bytes[1], bytes[2], bytes[3]];
1514
+ };
1515
+ var fixed_2_30 = (value) => {
1516
+ view.setInt32(0, 2 ** 30 * value, false);
1517
+ return [bytes[0], bytes[1], bytes[2], bytes[3]];
1518
+ };
1519
+ var ascii = (text, nullTerminated = false) => {
1520
+ let bytes2 = Array(text.length).fill(null).map((_, i) => text.charCodeAt(i));
1521
+ if (nullTerminated)
1522
+ bytes2.push(0);
1523
+ return bytes2;
1524
+ };
1525
+ var last = (arr) => {
1526
+ return arr && arr[arr.length - 1];
1527
+ };
1528
+ var lastPresentedSample = (samples) => {
1529
+ let result = void 0;
1530
+ for (let sample of samples) {
1531
+ if (!result || sample.presentationTimestamp > result.presentationTimestamp) {
1532
+ result = sample;
1533
+ }
1534
+ }
1535
+ return result;
1536
+ };
1537
+ var intoTimescale = (timeInSeconds, timescale, round = true) => {
1538
+ let value = timeInSeconds * timescale;
1539
+ return round ? Math.round(value) : value;
1540
+ };
1541
+ var rotationMatrix = (rotationInDegrees) => {
1542
+ let theta = rotationInDegrees * (Math.PI / 180);
1543
+ let cosTheta = Math.cos(theta);
1544
+ let sinTheta = Math.sin(theta);
1545
+ return [
1546
+ cosTheta,
1547
+ sinTheta,
1548
+ 0,
1549
+ -sinTheta,
1550
+ cosTheta,
1551
+ 0,
1552
+ 0,
1553
+ 0,
1554
+ 1
1555
+ ];
1556
+ };
1557
+ var IDENTITY_MATRIX = rotationMatrix(0);
1558
+ var matrixToBytes = (matrix) => {
1559
+ return [
1560
+ fixed_16_16(matrix[0]),
1561
+ fixed_16_16(matrix[1]),
1562
+ fixed_2_30(matrix[2]),
1563
+ fixed_16_16(matrix[3]),
1564
+ fixed_16_16(matrix[4]),
1565
+ fixed_2_30(matrix[5]),
1566
+ fixed_16_16(matrix[6]),
1567
+ fixed_16_16(matrix[7]),
1568
+ fixed_2_30(matrix[8])
1569
+ ];
1570
+ };
1571
+ var deepClone = (x) => {
1572
+ if (!x)
1573
+ return x;
1574
+ if (typeof x !== "object")
1575
+ return x;
1576
+ if (Array.isArray(x))
1577
+ return x.map(deepClone);
1578
+ return Object.fromEntries(Object.entries(x).map(([key, value]) => [key, deepClone(value)]));
1579
+ };
1580
+ var isU32 = (value) => {
1581
+ return value >= 0 && value < 2 ** 32;
1582
+ };
1583
+ var box = (type, contents, children) => ({
1584
+ type,
1585
+ contents: contents && new Uint8Array(contents.flat(10)),
1586
+ children
1587
+ });
1588
+ var fullBox = (type, version, flags, contents, children) => box(
1589
+ type,
1590
+ [u8(version), u24(flags), contents ?? []],
1591
+ children
1592
+ );
1593
+ var ftyp = (details) => {
1594
+ let minorVersion = 512;
1595
+ if (details.fragmented)
1596
+ return box("ftyp", [
1597
+ ascii("iso5"),
1598
+ // Major brand
1599
+ u32(minorVersion),
1600
+ // Minor version
1601
+ // Compatible brands
1602
+ ascii("iso5"),
1603
+ ascii("iso6"),
1604
+ ascii("mp41")
1605
+ ]);
1606
+ return box("ftyp", [
1607
+ ascii("isom"),
1608
+ // Major brand
1609
+ u32(minorVersion),
1610
+ // Minor version
1611
+ // Compatible brands
1612
+ ascii("isom"),
1613
+ details.holdsAvc ? ascii("avc1") : [],
1614
+ ascii("mp41")
1615
+ ]);
1616
+ };
1617
+ var mdat = (reserveLargeSize) => ({ type: "mdat", largeSize: reserveLargeSize });
1618
+ var free = (size) => ({ type: "free", size });
1619
+ var moov = (tracks, creationTime, fragmented = false) => box("moov", null, [
1620
+ mvhd(creationTime, tracks),
1621
+ ...tracks.map((x) => trak(x, creationTime)),
1622
+ fragmented ? mvex(tracks) : null
1623
+ ]);
1624
+ var mvhd = (creationTime, tracks) => {
1625
+ let duration = intoTimescale(Math.max(
1626
+ 0,
1627
+ ...tracks.filter((x) => x.samples.length > 0).map((x) => {
1628
+ const lastSample = lastPresentedSample(x.samples);
1629
+ return lastSample.presentationTimestamp + lastSample.duration;
1630
+ })
1631
+ ), GLOBAL_TIMESCALE);
1632
+ let nextTrackId = Math.max(...tracks.map((x) => x.id)) + 1;
1633
+ let needsU64 = !isU32(creationTime) || !isU32(duration);
1634
+ let u32OrU64 = needsU64 ? u64 : u32;
1635
+ return fullBox("mvhd", +needsU64, 0, [
1636
+ u32OrU64(creationTime),
1637
+ // Creation time
1638
+ u32OrU64(creationTime),
1639
+ // Modification time
1640
+ u32(GLOBAL_TIMESCALE),
1641
+ // Timescale
1642
+ u32OrU64(duration),
1643
+ // Duration
1644
+ fixed_16_16(1),
1645
+ // Preferred rate
1646
+ fixed_8_8(1),
1647
+ // Preferred volume
1648
+ Array(10).fill(0),
1649
+ // Reserved
1650
+ matrixToBytes(IDENTITY_MATRIX),
1651
+ // Matrix
1652
+ Array(24).fill(0),
1653
+ // Pre-defined
1654
+ u32(nextTrackId)
1655
+ // Next track ID
1656
+ ]);
1657
+ };
1658
+ var trak = (track, creationTime) => box("trak", null, [
1659
+ tkhd(track, creationTime),
1660
+ mdia(track, creationTime)
1661
+ ]);
1662
+ var tkhd = (track, creationTime) => {
1663
+ let lastSample = lastPresentedSample(track.samples);
1664
+ let durationInGlobalTimescale = intoTimescale(
1665
+ lastSample ? lastSample.presentationTimestamp + lastSample.duration : 0,
1666
+ GLOBAL_TIMESCALE
1667
+ );
1668
+ let needsU64 = !isU32(creationTime) || !isU32(durationInGlobalTimescale);
1669
+ let u32OrU64 = needsU64 ? u64 : u32;
1670
+ let matrix;
1671
+ if (track.info.type === "video") {
1672
+ matrix = typeof track.info.rotation === "number" ? rotationMatrix(track.info.rotation) : track.info.rotation;
1673
+ } else {
1674
+ matrix = IDENTITY_MATRIX;
1675
+ }
1676
+ return fullBox("tkhd", +needsU64, 3, [
1677
+ u32OrU64(creationTime),
1678
+ // Creation time
1679
+ u32OrU64(creationTime),
1680
+ // Modification time
1681
+ u32(track.id),
1682
+ // Track ID
1683
+ u32(0),
1684
+ // Reserved
1685
+ u32OrU64(durationInGlobalTimescale),
1686
+ // Duration
1687
+ Array(8).fill(0),
1688
+ // Reserved
1689
+ u16(0),
1690
+ // Layer
1691
+ u16(0),
1692
+ // Alternate group
1693
+ fixed_8_8(track.info.type === "audio" ? 1 : 0),
1694
+ // Volume
1695
+ u16(0),
1696
+ // Reserved
1697
+ matrixToBytes(matrix),
1698
+ // Matrix
1699
+ fixed_16_16(track.info.type === "video" ? track.info.width : 0),
1700
+ // Track width
1701
+ fixed_16_16(track.info.type === "video" ? track.info.height : 0)
1702
+ // Track height
1703
+ ]);
1704
+ };
1705
+ var mdia = (track, creationTime) => box("mdia", null, [
1706
+ mdhd(track, creationTime),
1707
+ hdlr(track.info.type === "video" ? "vide" : "soun"),
1708
+ minf(track)
1709
+ ]);
1710
+ var mdhd = (track, creationTime) => {
1711
+ let lastSample = lastPresentedSample(track.samples);
1712
+ let localDuration = intoTimescale(
1713
+ lastSample ? lastSample.presentationTimestamp + lastSample.duration : 0,
1714
+ track.timescale
1715
+ );
1716
+ let needsU64 = !isU32(creationTime) || !isU32(localDuration);
1717
+ let u32OrU64 = needsU64 ? u64 : u32;
1718
+ return fullBox("mdhd", +needsU64, 0, [
1719
+ u32OrU64(creationTime),
1720
+ // Creation time
1721
+ u32OrU64(creationTime),
1722
+ // Modification time
1723
+ u32(track.timescale),
1724
+ // Timescale
1725
+ u32OrU64(localDuration),
1726
+ // Duration
1727
+ u16(21956),
1728
+ // Language ("und", undetermined)
1729
+ u16(0)
1730
+ // Quality
1731
+ ]);
1732
+ };
1733
+ var hdlr = (componentSubtype) => fullBox("hdlr", 0, 0, [
1734
+ ascii("mhlr"),
1735
+ // Component type
1736
+ ascii(componentSubtype),
1737
+ // Component subtype
1738
+ u32(0),
1739
+ // Component manufacturer
1740
+ u32(0),
1741
+ // Component flags
1742
+ u32(0),
1743
+ // Component flags mask
1744
+ ascii("mp4-muxer-hdlr", true)
1745
+ // Component name
1746
+ ]);
1747
+ var minf = (track) => box("minf", null, [
1748
+ track.info.type === "video" ? vmhd() : smhd(),
1749
+ dinf(),
1750
+ stbl(track)
1751
+ ]);
1752
+ var vmhd = () => fullBox("vmhd", 0, 1, [
1753
+ u16(0),
1754
+ // Graphics mode
1755
+ u16(0),
1756
+ // Opcolor R
1757
+ u16(0),
1758
+ // Opcolor G
1759
+ u16(0)
1760
+ // Opcolor B
1761
+ ]);
1762
+ var smhd = () => fullBox("smhd", 0, 0, [
1763
+ u16(0),
1764
+ // Balance
1765
+ u16(0)
1766
+ // Reserved
1767
+ ]);
1768
+ var dinf = () => box("dinf", null, [
1769
+ dref()
1770
+ ]);
1771
+ var dref = () => fullBox("dref", 0, 0, [
1772
+ u32(1)
1773
+ // Entry count
1774
+ ], [
1775
+ url()
1776
+ ]);
1777
+ var url = () => fullBox("url ", 0, 1);
1778
+ var stbl = (track) => {
1779
+ const needsCtts = track.compositionTimeOffsetTable.length > 1 || track.compositionTimeOffsetTable.some((x) => x.sampleCompositionTimeOffset !== 0);
1780
+ return box("stbl", null, [
1781
+ stsd(track),
1782
+ stts(track),
1783
+ stss(track),
1784
+ stsc(track),
1785
+ stsz(track),
1786
+ stco(track),
1787
+ needsCtts ? ctts(track) : null
1788
+ ]);
1789
+ };
1790
+ var stsd = (track) => fullBox("stsd", 0, 0, [
1791
+ u32(1)
1792
+ // Entry count
1793
+ ], [
1794
+ track.info.type === "video" ? videoSampleDescription(
1795
+ VIDEO_CODEC_TO_BOX_NAME[track.info.codec],
1796
+ track
1797
+ ) : soundSampleDescription(
1798
+ AUDIO_CODEC_TO_BOX_NAME[track.info.codec],
1799
+ track
1800
+ )
1801
+ ]);
1802
+ var videoSampleDescription = (compressionType, track) => box(compressionType, [
1803
+ Array(6).fill(0),
1804
+ // Reserved
1805
+ u16(1),
1806
+ // Data reference index
1807
+ u16(0),
1808
+ // Pre-defined
1809
+ u16(0),
1810
+ // Reserved
1811
+ Array(12).fill(0),
1812
+ // Pre-defined
1813
+ u16(track.info.width),
1814
+ // Width
1815
+ u16(track.info.height),
1816
+ // Height
1817
+ u32(4718592),
1818
+ // Horizontal resolution
1819
+ u32(4718592),
1820
+ // Vertical resolution
1821
+ u32(0),
1822
+ // Reserved
1823
+ u16(1),
1824
+ // Frame count
1825
+ Array(32).fill(0),
1826
+ // Compressor name
1827
+ u16(24),
1828
+ // Depth
1829
+ i16(65535)
1830
+ // Pre-defined
1831
+ ], [
1832
+ VIDEO_CODEC_TO_CONFIGURATION_BOX[track.info.codec](track),
1833
+ track.info.decoderConfig.colorSpace ? colr(track) : null
1834
+ ]);
1835
+ var COLOR_PRIMARIES_MAP = {
1836
+ "bt709": 1,
1837
+ // ITU-R BT.709
1838
+ "bt470bg": 5,
1839
+ // ITU-R BT.470BG
1840
+ "smpte170m": 6
1841
+ // ITU-R BT.601 525 - SMPTE 170M
1842
+ };
1843
+ var TRANSFER_CHARACTERISTICS_MAP = {
1844
+ "bt709": 1,
1845
+ // ITU-R BT.709
1846
+ "smpte170m": 6,
1847
+ // SMPTE 170M
1848
+ "iec61966-2-1": 13
1849
+ // IEC 61966-2-1
1850
+ };
1851
+ var MATRIX_COEFFICIENTS_MAP = {
1852
+ "rgb": 0,
1853
+ // Identity
1854
+ "bt709": 1,
1855
+ // ITU-R BT.709
1856
+ "bt470bg": 5,
1857
+ // ITU-R BT.470BG
1858
+ "smpte170m": 6
1859
+ // SMPTE 170M
1860
+ };
1861
+ var colr = (track) => box("colr", [
1862
+ ascii("nclx"),
1863
+ // Colour type
1864
+ u16(COLOR_PRIMARIES_MAP[track.info.decoderConfig.colorSpace.primaries]),
1865
+ // Colour primaries
1866
+ u16(TRANSFER_CHARACTERISTICS_MAP[track.info.decoderConfig.colorSpace.transfer]),
1867
+ // Transfer characteristics
1868
+ u16(MATRIX_COEFFICIENTS_MAP[track.info.decoderConfig.colorSpace.matrix]),
1869
+ // Matrix coefficients
1870
+ u8((track.info.decoderConfig.colorSpace.fullRange ? 1 : 0) << 7)
1871
+ // Full range flag
1872
+ ]);
1873
+ var avcC = (track) => track.info.decoderConfig && box("avcC", [
1874
+ // For AVC, description is an AVCDecoderConfigurationRecord, so nothing else to do here
1875
+ ...new Uint8Array(track.info.decoderConfig.description)
1876
+ ]);
1877
+ var hvcC = (track) => track.info.decoderConfig && box("hvcC", [
1878
+ // For HEVC, description is a HEVCDecoderConfigurationRecord, so nothing else to do here
1879
+ ...new Uint8Array(track.info.decoderConfig.description)
1880
+ ]);
1881
+ var vpcC = (track) => {
1882
+ if (!track.info.decoderConfig) {
1883
+ return null;
1884
+ }
1885
+ let decoderConfig = track.info.decoderConfig;
1886
+ if (!decoderConfig.colorSpace) {
1887
+ throw new Error(`'colorSpace' is required in the decoder config for VP9.`);
1888
+ }
1889
+ let parts = decoderConfig.codec.split(".");
1890
+ let profile = Number(parts[1]);
1891
+ let level = Number(parts[2]);
1892
+ let bitDepth = Number(parts[3]);
1893
+ let chromaSubsampling = 0;
1894
+ let thirdByte = (bitDepth << 4) + (chromaSubsampling << 1) + Number(decoderConfig.colorSpace.fullRange);
1895
+ let colourPrimaries = 2;
1896
+ let transferCharacteristics = 2;
1897
+ let matrixCoefficients = 2;
1898
+ return fullBox("vpcC", 1, 0, [
1899
+ u8(profile),
1900
+ // Profile
1901
+ u8(level),
1902
+ // Level
1903
+ u8(thirdByte),
1904
+ // Bit depth, chroma subsampling, full range
1905
+ u8(colourPrimaries),
1906
+ // Colour primaries
1907
+ u8(transferCharacteristics),
1908
+ // Transfer characteristics
1909
+ u8(matrixCoefficients),
1910
+ // Matrix coefficients
1911
+ u16(0)
1912
+ // Codec initialization data size
1913
+ ]);
1914
+ };
1915
+ var av1C = () => {
1916
+ let marker = 1;
1917
+ let version = 1;
1918
+ let firstByte = (marker << 7) + version;
1919
+ return box("av1C", [
1920
+ firstByte,
1921
+ 0,
1922
+ 0,
1923
+ 0
1924
+ ]);
1925
+ };
1926
+ var soundSampleDescription = (compressionType, track) => box(compressionType, [
1927
+ Array(6).fill(0),
1928
+ // Reserved
1929
+ u16(1),
1930
+ // Data reference index
1931
+ u16(0),
1932
+ // Version
1933
+ u16(0),
1934
+ // Revision level
1935
+ u32(0),
1936
+ // Vendor
1937
+ u16(track.info.numberOfChannels),
1938
+ // Number of channels
1939
+ u16(16),
1940
+ // Sample size (bits)
1941
+ u16(0),
1942
+ // Compression ID
1943
+ u16(0),
1944
+ // Packet size
1945
+ fixed_16_16(track.info.sampleRate)
1946
+ // Sample rate
1947
+ ], [
1948
+ AUDIO_CODEC_TO_CONFIGURATION_BOX[track.info.codec](track)
1949
+ ]);
1950
+ var esds = (track) => {
1951
+ let description = new Uint8Array(track.info.decoderConfig.description);
1952
+ return fullBox("esds", 0, 0, [
1953
+ // https://stackoverflow.com/a/54803118
1954
+ u32(58753152),
1955
+ // TAG(3) = Object Descriptor ([2])
1956
+ u8(32 + description.byteLength),
1957
+ // length of this OD (which includes the next 2 tags)
1958
+ u16(1),
1959
+ // ES_ID = 1
1960
+ u8(0),
1961
+ // flags etc = 0
1962
+ u32(75530368),
1963
+ // TAG(4) = ES Descriptor ([2]) embedded in above OD
1964
+ u8(18 + description.byteLength),
1965
+ // length of this ESD
1966
+ u8(64),
1967
+ // MPEG-4 Audio
1968
+ u8(21),
1969
+ // stream type(6bits)=5 audio, flags(2bits)=1
1970
+ u24(0),
1971
+ // 24bit buffer size
1972
+ u32(130071),
1973
+ // max bitrate
1974
+ u32(130071),
1975
+ // avg bitrate
1976
+ u32(92307584),
1977
+ // TAG(5) = ASC ([2],[3]) embedded in above OD
1978
+ u8(description.byteLength),
1979
+ // length
1980
+ ...description,
1981
+ u32(109084800),
1982
+ // TAG(6)
1983
+ u8(1),
1984
+ // length
1985
+ u8(2)
1986
+ // data
1987
+ ]);
1988
+ };
1989
+ var dOps = (track) => {
1990
+ let preskip = 3840;
1991
+ let gain = 0;
1992
+ const description = track.info.decoderConfig?.description;
1993
+ if (description) {
1994
+ if (description.byteLength < 18) {
1995
+ throw new TypeError("Invalid decoder description provided for Opus; must be at least 18 bytes long.");
1996
+ }
1997
+ const view2 = ArrayBuffer.isView(description) ? new DataView(description.buffer, description.byteOffset, description.byteLength) : new DataView(description);
1998
+ preskip = view2.getUint16(10, true);
1999
+ gain = view2.getInt16(14, true);
2000
+ }
2001
+ return box("dOps", [
2002
+ u8(0),
2003
+ // Version
2004
+ u8(track.info.numberOfChannels),
2005
+ // OutputChannelCount
2006
+ u16(preskip),
2007
+ u32(track.info.sampleRate),
2008
+ // InputSampleRate
2009
+ fixed_8_8(gain),
2010
+ // OutputGain
2011
+ u8(0)
2012
+ // ChannelMappingFamily
2013
+ ]);
2014
+ };
2015
+ var stts = (track) => {
2016
+ return fullBox("stts", 0, 0, [
2017
+ u32(track.timeToSampleTable.length),
2018
+ // Number of entries
2019
+ track.timeToSampleTable.map((x) => [
2020
+ // Time-to-sample table
2021
+ u32(x.sampleCount),
2022
+ // Sample count
2023
+ u32(x.sampleDelta)
2024
+ // Sample duration
2025
+ ])
2026
+ ]);
2027
+ };
2028
+ var stss = (track) => {
2029
+ if (track.samples.every((x) => x.type === "key"))
2030
+ return null;
2031
+ let keySamples = [...track.samples.entries()].filter(([, sample]) => sample.type === "key");
2032
+ return fullBox("stss", 0, 0, [
2033
+ u32(keySamples.length),
2034
+ // Number of entries
2035
+ keySamples.map(([index]) => u32(index + 1))
2036
+ // Sync sample table
2037
+ ]);
2038
+ };
2039
+ var stsc = (track) => {
2040
+ return fullBox("stsc", 0, 0, [
2041
+ u32(track.compactlyCodedChunkTable.length),
2042
+ // Number of entries
2043
+ track.compactlyCodedChunkTable.map((x) => [
2044
+ // Sample-to-chunk table
2045
+ u32(x.firstChunk),
2046
+ // First chunk
2047
+ u32(x.samplesPerChunk),
2048
+ // Samples per chunk
2049
+ u32(1)
2050
+ // Sample description index
2051
+ ])
2052
+ ]);
2053
+ };
2054
+ var stsz = (track) => fullBox("stsz", 0, 0, [
2055
+ u32(0),
2056
+ // Sample size (0 means non-constant size)
2057
+ u32(track.samples.length),
2058
+ // Number of entries
2059
+ track.samples.map((x) => u32(x.size))
2060
+ // Sample size table
2061
+ ]);
2062
+ var stco = (track) => {
2063
+ if (track.finalizedChunks.length > 0 && last(track.finalizedChunks).offset >= 2 ** 32) {
2064
+ return fullBox("co64", 0, 0, [
2065
+ u32(track.finalizedChunks.length),
2066
+ // Number of entries
2067
+ track.finalizedChunks.map((x) => u64(x.offset))
2068
+ // Chunk offset table
2069
+ ]);
2070
+ }
2071
+ return fullBox("stco", 0, 0, [
2072
+ u32(track.finalizedChunks.length),
2073
+ // Number of entries
2074
+ track.finalizedChunks.map((x) => u32(x.offset))
2075
+ // Chunk offset table
2076
+ ]);
2077
+ };
2078
+ var ctts = (track) => {
2079
+ return fullBox("ctts", 0, 0, [
2080
+ u32(track.compositionTimeOffsetTable.length),
2081
+ // Number of entries
2082
+ track.compositionTimeOffsetTable.map((x) => [
2083
+ // Time-to-sample table
2084
+ u32(x.sampleCount),
2085
+ // Sample count
2086
+ u32(x.sampleCompositionTimeOffset)
2087
+ // Sample offset
2088
+ ])
2089
+ ]);
2090
+ };
2091
+ var mvex = (tracks) => {
2092
+ return box("mvex", null, tracks.map(trex));
2093
+ };
2094
+ var trex = (track) => {
2095
+ return fullBox("trex", 0, 0, [
2096
+ u32(track.id),
2097
+ // Track ID
2098
+ u32(1),
2099
+ // Default sample description index
2100
+ u32(0),
2101
+ // Default sample duration
2102
+ u32(0),
2103
+ // Default sample size
2104
+ u32(0)
2105
+ // Default sample flags
2106
+ ]);
2107
+ };
2108
+ var moof = (sequenceNumber, tracks) => {
2109
+ return box("moof", null, [
2110
+ mfhd(sequenceNumber),
2111
+ ...tracks.map(traf)
2112
+ ]);
2113
+ };
2114
+ var mfhd = (sequenceNumber) => {
2115
+ return fullBox("mfhd", 0, 0, [
2116
+ u32(sequenceNumber)
2117
+ // Sequence number
2118
+ ]);
2119
+ };
2120
+ var fragmentSampleFlags = (sample) => {
2121
+ let byte1 = 0;
2122
+ let byte2 = 0;
2123
+ let byte3 = 0;
2124
+ let byte4 = 0;
2125
+ let sampleIsDifferenceSample = sample.type === "delta";
2126
+ byte2 |= +sampleIsDifferenceSample;
2127
+ if (sampleIsDifferenceSample) {
2128
+ byte1 |= 1;
2129
+ } else {
2130
+ byte1 |= 2;
2131
+ }
2132
+ return byte1 << 24 | byte2 << 16 | byte3 << 8 | byte4;
2133
+ };
2134
+ var traf = (track) => {
2135
+ return box("traf", null, [
2136
+ tfhd(track),
2137
+ tfdt(track),
2138
+ trun(track)
2139
+ ]);
2140
+ };
2141
+ var tfhd = (track) => {
2142
+ let tfFlags = 0;
2143
+ tfFlags |= 8;
2144
+ tfFlags |= 16;
2145
+ tfFlags |= 32;
2146
+ tfFlags |= 131072;
2147
+ let referenceSample = track.currentChunk.samples[1] ?? track.currentChunk.samples[0];
2148
+ let referenceSampleInfo = {
2149
+ duration: referenceSample.timescaleUnitsToNextSample,
2150
+ size: referenceSample.size,
2151
+ flags: fragmentSampleFlags(referenceSample)
2152
+ };
2153
+ return fullBox("tfhd", 0, tfFlags, [
2154
+ u32(track.id),
2155
+ // Track ID
2156
+ u32(referenceSampleInfo.duration),
2157
+ // Default sample duration
2158
+ u32(referenceSampleInfo.size),
2159
+ // Default sample size
2160
+ u32(referenceSampleInfo.flags)
2161
+ // Default sample flags
2162
+ ]);
2163
+ };
2164
+ var tfdt = (track) => {
2165
+ return fullBox("tfdt", 1, 0, [
2166
+ u64(intoTimescale(track.currentChunk.startTimestamp, track.timescale))
2167
+ // Base Media Decode Time
2168
+ ]);
2169
+ };
2170
+ var trun = (track) => {
2171
+ let allSampleDurations = track.currentChunk.samples.map((x) => x.timescaleUnitsToNextSample);
2172
+ let allSampleSizes = track.currentChunk.samples.map((x) => x.size);
2173
+ let allSampleFlags = track.currentChunk.samples.map(fragmentSampleFlags);
2174
+ let allSampleCompositionTimeOffsets = track.currentChunk.samples.map((x) => intoTimescale(x.presentationTimestamp - x.decodeTimestamp, track.timescale));
2175
+ let uniqueSampleDurations = new Set(allSampleDurations);
2176
+ let uniqueSampleSizes = new Set(allSampleSizes);
2177
+ let uniqueSampleFlags = new Set(allSampleFlags);
2178
+ let uniqueSampleCompositionTimeOffsets = new Set(allSampleCompositionTimeOffsets);
2179
+ let firstSampleFlagsPresent = uniqueSampleFlags.size === 2 && allSampleFlags[0] !== allSampleFlags[1];
2180
+ let sampleDurationPresent = uniqueSampleDurations.size > 1;
2181
+ let sampleSizePresent = uniqueSampleSizes.size > 1;
2182
+ let sampleFlagsPresent = !firstSampleFlagsPresent && uniqueSampleFlags.size > 1;
2183
+ let sampleCompositionTimeOffsetsPresent = uniqueSampleCompositionTimeOffsets.size > 1 || [...uniqueSampleCompositionTimeOffsets].some((x) => x !== 0);
2184
+ let flags = 0;
2185
+ flags |= 1;
2186
+ flags |= 4 * +firstSampleFlagsPresent;
2187
+ flags |= 256 * +sampleDurationPresent;
2188
+ flags |= 512 * +sampleSizePresent;
2189
+ flags |= 1024 * +sampleFlagsPresent;
2190
+ flags |= 2048 * +sampleCompositionTimeOffsetsPresent;
2191
+ return fullBox("trun", 1, flags, [
2192
+ u32(track.currentChunk.samples.length),
2193
+ // Sample count
2194
+ u32(track.currentChunk.offset - track.currentChunk.moofOffset || 0),
2195
+ // Data offset
2196
+ firstSampleFlagsPresent ? u32(allSampleFlags[0]) : [],
2197
+ track.currentChunk.samples.map((_, i) => [
2198
+ sampleDurationPresent ? u32(allSampleDurations[i]) : [],
2199
+ // Sample duration
2200
+ sampleSizePresent ? u32(allSampleSizes[i]) : [],
2201
+ // Sample size
2202
+ sampleFlagsPresent ? u32(allSampleFlags[i]) : [],
2203
+ // Sample flags
2204
+ // Sample composition time offsets
2205
+ sampleCompositionTimeOffsetsPresent ? i32(allSampleCompositionTimeOffsets[i]) : []
2206
+ ])
2207
+ ]);
2208
+ };
2209
+ var mfra = (tracks) => {
2210
+ return box("mfra", null, [
2211
+ ...tracks.map(tfra),
2212
+ mfro()
2213
+ ]);
2214
+ };
2215
+ var tfra = (track, trackIndex) => {
2216
+ let version = 1;
2217
+ return fullBox("tfra", version, 0, [
2218
+ u32(track.id),
2219
+ // Track ID
2220
+ u32(63),
2221
+ // This specifies that traf number, trun number and sample number are 32-bit ints
2222
+ u32(track.finalizedChunks.length),
2223
+ // Number of entries
2224
+ track.finalizedChunks.map((chunk) => [
2225
+ u64(intoTimescale(chunk.startTimestamp, track.timescale)),
2226
+ // Time
2227
+ u64(chunk.moofOffset),
2228
+ // moof offset
2229
+ u32(trackIndex + 1),
2230
+ // traf number
2231
+ u32(1),
2232
+ // trun number
2233
+ u32(1)
2234
+ // Sample number
2235
+ ])
2236
+ ]);
2237
+ };
2238
+ var mfro = () => {
2239
+ return fullBox("mfro", 0, 0, [
2240
+ // This value needs to be overwritten manually from the outside, where the actual size of the enclosing mfra box
2241
+ // is known
2242
+ u32(0)
2243
+ // Size
2244
+ ]);
2245
+ };
2246
+ var VIDEO_CODEC_TO_BOX_NAME = {
2247
+ "avc": "avc1",
2248
+ "hevc": "hvc1",
2249
+ "vp9": "vp09",
2250
+ "av1": "av01"
2251
+ };
2252
+ var VIDEO_CODEC_TO_CONFIGURATION_BOX = {
2253
+ "avc": avcC,
2254
+ "hevc": hvcC,
2255
+ "vp9": vpcC,
2256
+ "av1": av1C
2257
+ };
2258
+ var AUDIO_CODEC_TO_BOX_NAME = {
2259
+ "aac": "mp4a",
2260
+ "opus": "Opus"
2261
+ };
2262
+ var AUDIO_CODEC_TO_CONFIGURATION_BOX = {
2263
+ "aac": esds,
2264
+ "opus": dOps
2265
+ };
2266
+ var Target = class {
2267
+ };
2268
+ var ArrayBufferTarget = class extends Target {
2269
+ constructor() {
2270
+ super(...arguments);
2271
+ this.buffer = null;
2272
+ }
2273
+ };
2274
+ var StreamTarget = class extends Target {
2275
+ constructor(options) {
2276
+ super();
2277
+ this.options = options;
2278
+ if (typeof options !== "object") {
2279
+ throw new TypeError("StreamTarget requires an options object to be passed to its constructor.");
2280
+ }
2281
+ if (options.onData) {
2282
+ if (typeof options.onData !== "function") {
2283
+ throw new TypeError("options.onData, when provided, must be a function.");
2284
+ }
2285
+ if (options.onData.length < 2) {
2286
+ throw new TypeError(
2287
+ "options.onData, when provided, must be a function that takes in at least two arguments (data and position). Ignoring the position argument, which specifies the byte offset at which the data is to be written, can lead to broken outputs."
2288
+ );
2289
+ }
2290
+ }
2291
+ if (options.chunked !== void 0 && typeof options.chunked !== "boolean") {
2292
+ throw new TypeError("options.chunked, when provided, must be a boolean.");
2293
+ }
2294
+ if (options.chunkSize !== void 0 && (!Number.isInteger(options.chunkSize) || options.chunkSize < 1024)) {
2295
+ throw new TypeError("options.chunkSize, when provided, must be an integer and not smaller than 1024.");
2296
+ }
2297
+ }
2298
+ };
2299
+ var FileSystemWritableFileStreamTarget = class extends Target {
2300
+ constructor(stream, options) {
2301
+ super();
2302
+ this.stream = stream;
2303
+ this.options = options;
2304
+ if (!(stream instanceof FileSystemWritableFileStream)) {
2305
+ throw new TypeError("FileSystemWritableFileStreamTarget requires a FileSystemWritableFileStream instance.");
2306
+ }
2307
+ if (options !== void 0 && typeof options !== "object") {
2308
+ throw new TypeError("FileSystemWritableFileStreamTarget's options, when provided, must be an object.");
2309
+ }
2310
+ if (options) {
2311
+ if (options.chunkSize !== void 0 && (!Number.isInteger(options.chunkSize) || options.chunkSize <= 0)) {
2312
+ throw new TypeError("options.chunkSize, when provided, must be a positive integer");
2313
+ }
2314
+ }
2315
+ }
2316
+ };
2317
+ var _helper, _helperView;
2318
+ var Writer = class {
2319
+ constructor() {
2320
+ this.pos = 0;
2321
+ __privateAdd(this, _helper, new Uint8Array(8));
2322
+ __privateAdd(this, _helperView, new DataView(__privateGet(this, _helper).buffer));
2323
+ this.offsets = /* @__PURE__ */ new WeakMap();
2324
+ }
2325
+ /** Sets the current position for future writes to a new one. */
2326
+ seek(newPos) {
2327
+ this.pos = newPos;
2328
+ }
2329
+ writeU32(value) {
2330
+ __privateGet(this, _helperView).setUint32(0, value, false);
2331
+ this.write(__privateGet(this, _helper).subarray(0, 4));
2332
+ }
2333
+ writeU64(value) {
2334
+ __privateGet(this, _helperView).setUint32(0, Math.floor(value / 2 ** 32), false);
2335
+ __privateGet(this, _helperView).setUint32(4, value, false);
2336
+ this.write(__privateGet(this, _helper).subarray(0, 8));
2337
+ }
2338
+ writeAscii(text) {
2339
+ for (let i = 0; i < text.length; i++) {
2340
+ __privateGet(this, _helperView).setUint8(i % 8, text.charCodeAt(i));
2341
+ if (i % 8 === 7)
2342
+ this.write(__privateGet(this, _helper));
2343
+ }
2344
+ if (text.length % 8 !== 0) {
2345
+ this.write(__privateGet(this, _helper).subarray(0, text.length % 8));
2346
+ }
2347
+ }
2348
+ writeBox(box2) {
2349
+ this.offsets.set(box2, this.pos);
2350
+ if (box2.contents && !box2.children) {
2351
+ this.writeBoxHeader(box2, box2.size ?? box2.contents.byteLength + 8);
2352
+ this.write(box2.contents);
2353
+ } else {
2354
+ let startPos = this.pos;
2355
+ this.writeBoxHeader(box2, 0);
2356
+ if (box2.contents)
2357
+ this.write(box2.contents);
2358
+ if (box2.children) {
2359
+ for (let child of box2.children)
2360
+ if (child)
2361
+ this.writeBox(child);
2362
+ }
2363
+ let endPos = this.pos;
2364
+ let size = box2.size ?? endPos - startPos;
2365
+ this.seek(startPos);
2366
+ this.writeBoxHeader(box2, size);
2367
+ this.seek(endPos);
2368
+ }
2369
+ }
2370
+ writeBoxHeader(box2, size) {
2371
+ this.writeU32(box2.largeSize ? 1 : size);
2372
+ this.writeAscii(box2.type);
2373
+ if (box2.largeSize)
2374
+ this.writeU64(size);
2375
+ }
2376
+ measureBoxHeader(box2) {
2377
+ return 8 + (box2.largeSize ? 8 : 0);
2378
+ }
2379
+ patchBox(box2) {
2380
+ let endPos = this.pos;
2381
+ this.seek(this.offsets.get(box2));
2382
+ this.writeBox(box2);
2383
+ this.seek(endPos);
2384
+ }
2385
+ measureBox(box2) {
2386
+ if (box2.contents && !box2.children) {
2387
+ let headerSize = this.measureBoxHeader(box2);
2388
+ return headerSize + box2.contents.byteLength;
2389
+ } else {
2390
+ let result = this.measureBoxHeader(box2);
2391
+ if (box2.contents)
2392
+ result += box2.contents.byteLength;
2393
+ if (box2.children) {
2394
+ for (let child of box2.children)
2395
+ if (child)
2396
+ result += this.measureBox(child);
2397
+ }
2398
+ return result;
2399
+ }
2400
+ }
2401
+ };
2402
+ _helper = /* @__PURE__ */ new WeakMap();
2403
+ _helperView = /* @__PURE__ */ new WeakMap();
2404
+ var _target, _buffer, _bytes, _maxPos, _ensureSize, ensureSize_fn;
2405
+ var ArrayBufferTargetWriter = class extends Writer {
2406
+ constructor(target) {
2407
+ super();
2408
+ __privateAdd(this, _ensureSize);
2409
+ __privateAdd(this, _target, void 0);
2410
+ __privateAdd(this, _buffer, new ArrayBuffer(2 ** 16));
2411
+ __privateAdd(this, _bytes, new Uint8Array(__privateGet(this, _buffer)));
2412
+ __privateAdd(this, _maxPos, 0);
2413
+ __privateSet(this, _target, target);
2414
+ }
2415
+ write(data) {
2416
+ __privateMethod(this, _ensureSize, ensureSize_fn).call(this, this.pos + data.byteLength);
2417
+ __privateGet(this, _bytes).set(data, this.pos);
2418
+ this.pos += data.byteLength;
2419
+ __privateSet(this, _maxPos, Math.max(__privateGet(this, _maxPos), this.pos));
2420
+ }
2421
+ finalize() {
2422
+ __privateMethod(this, _ensureSize, ensureSize_fn).call(this, this.pos);
2423
+ __privateGet(this, _target).buffer = __privateGet(this, _buffer).slice(0, Math.max(__privateGet(this, _maxPos), this.pos));
2424
+ }
2425
+ };
2426
+ _target = /* @__PURE__ */ new WeakMap();
2427
+ _buffer = /* @__PURE__ */ new WeakMap();
2428
+ _bytes = /* @__PURE__ */ new WeakMap();
2429
+ _maxPos = /* @__PURE__ */ new WeakMap();
2430
+ _ensureSize = /* @__PURE__ */ new WeakSet();
2431
+ ensureSize_fn = function(size) {
2432
+ let newLength = __privateGet(this, _buffer).byteLength;
2433
+ while (newLength < size)
2434
+ newLength *= 2;
2435
+ if (newLength === __privateGet(this, _buffer).byteLength)
2436
+ return;
2437
+ let newBuffer = new ArrayBuffer(newLength);
2438
+ let newBytes = new Uint8Array(newBuffer);
2439
+ newBytes.set(__privateGet(this, _bytes), 0);
2440
+ __privateSet(this, _buffer, newBuffer);
2441
+ __privateSet(this, _bytes, newBytes);
2442
+ };
2443
+ var DEFAULT_CHUNK_SIZE = 2 ** 24;
2444
+ var MAX_CHUNKS_AT_ONCE = 2;
2445
+ var _target2, _sections, _chunked, _chunkSize, _chunks, _writeDataIntoChunks, writeDataIntoChunks_fn, _insertSectionIntoChunk, insertSectionIntoChunk_fn, _createChunk, createChunk_fn, _flushChunks, flushChunks_fn;
2446
+ var StreamTargetWriter = class extends Writer {
2447
+ constructor(target) {
2448
+ super();
2449
+ __privateAdd(this, _writeDataIntoChunks);
2450
+ __privateAdd(this, _insertSectionIntoChunk);
2451
+ __privateAdd(this, _createChunk);
2452
+ __privateAdd(this, _flushChunks);
2453
+ __privateAdd(this, _target2, void 0);
2454
+ __privateAdd(this, _sections, []);
2455
+ __privateAdd(this, _chunked, void 0);
2456
+ __privateAdd(this, _chunkSize, void 0);
2457
+ __privateAdd(this, _chunks, []);
2458
+ __privateSet(this, _target2, target);
2459
+ __privateSet(this, _chunked, target.options?.chunked ?? false);
2460
+ __privateSet(this, _chunkSize, target.options?.chunkSize ?? DEFAULT_CHUNK_SIZE);
2461
+ }
2462
+ write(data) {
2463
+ __privateGet(this, _sections).push({
2464
+ data: data.slice(),
2465
+ start: this.pos
2466
+ });
2467
+ this.pos += data.byteLength;
2468
+ }
2469
+ flush() {
2470
+ if (__privateGet(this, _sections).length === 0)
2471
+ return;
2472
+ let chunks = [];
2473
+ let sorted = [...__privateGet(this, _sections)].sort((a, b) => a.start - b.start);
2474
+ chunks.push({
2475
+ start: sorted[0].start,
2476
+ size: sorted[0].data.byteLength
2477
+ });
2478
+ for (let i = 1; i < sorted.length; i++) {
2479
+ let lastChunk = chunks[chunks.length - 1];
2480
+ let section = sorted[i];
2481
+ if (section.start <= lastChunk.start + lastChunk.size) {
2482
+ lastChunk.size = Math.max(lastChunk.size, section.start + section.data.byteLength - lastChunk.start);
2483
+ } else {
2484
+ chunks.push({
2485
+ start: section.start,
2486
+ size: section.data.byteLength
2487
+ });
2488
+ }
2489
+ }
2490
+ for (let chunk of chunks) {
2491
+ chunk.data = new Uint8Array(chunk.size);
2492
+ for (let section of __privateGet(this, _sections)) {
2493
+ if (chunk.start <= section.start && section.start < chunk.start + chunk.size) {
2494
+ chunk.data.set(section.data, section.start - chunk.start);
2495
+ }
2496
+ }
2497
+ if (__privateGet(this, _chunked)) {
2498
+ __privateMethod(this, _writeDataIntoChunks, writeDataIntoChunks_fn).call(this, chunk.data, chunk.start);
2499
+ __privateMethod(this, _flushChunks, flushChunks_fn).call(this);
2500
+ } else {
2501
+ __privateGet(this, _target2).options.onData?.(chunk.data, chunk.start);
2502
+ }
2503
+ }
2504
+ __privateGet(this, _sections).length = 0;
2505
+ }
2506
+ finalize() {
2507
+ if (__privateGet(this, _chunked)) {
2508
+ __privateMethod(this, _flushChunks, flushChunks_fn).call(this, true);
2509
+ }
2510
+ }
2511
+ };
2512
+ _target2 = /* @__PURE__ */ new WeakMap();
2513
+ _sections = /* @__PURE__ */ new WeakMap();
2514
+ _chunked = /* @__PURE__ */ new WeakMap();
2515
+ _chunkSize = /* @__PURE__ */ new WeakMap();
2516
+ _chunks = /* @__PURE__ */ new WeakMap();
2517
+ _writeDataIntoChunks = /* @__PURE__ */ new WeakSet();
2518
+ writeDataIntoChunks_fn = function(data, position) {
2519
+ let chunkIndex = __privateGet(this, _chunks).findIndex((x) => x.start <= position && position < x.start + __privateGet(this, _chunkSize));
2520
+ if (chunkIndex === -1)
2521
+ chunkIndex = __privateMethod(this, _createChunk, createChunk_fn).call(this, position);
2522
+ let chunk = __privateGet(this, _chunks)[chunkIndex];
2523
+ let relativePosition = position - chunk.start;
2524
+ let toWrite = data.subarray(0, Math.min(__privateGet(this, _chunkSize) - relativePosition, data.byteLength));
2525
+ chunk.data.set(toWrite, relativePosition);
2526
+ let section = {
2527
+ start: relativePosition,
2528
+ end: relativePosition + toWrite.byteLength
2529
+ };
2530
+ __privateMethod(this, _insertSectionIntoChunk, insertSectionIntoChunk_fn).call(this, chunk, section);
2531
+ if (chunk.written[0].start === 0 && chunk.written[0].end === __privateGet(this, _chunkSize)) {
2532
+ chunk.shouldFlush = true;
2533
+ }
2534
+ if (__privateGet(this, _chunks).length > MAX_CHUNKS_AT_ONCE) {
2535
+ for (let i = 0; i < __privateGet(this, _chunks).length - 1; i++) {
2536
+ __privateGet(this, _chunks)[i].shouldFlush = true;
2537
+ }
2538
+ __privateMethod(this, _flushChunks, flushChunks_fn).call(this);
2539
+ }
2540
+ if (toWrite.byteLength < data.byteLength) {
2541
+ __privateMethod(this, _writeDataIntoChunks, writeDataIntoChunks_fn).call(this, data.subarray(toWrite.byteLength), position + toWrite.byteLength);
2542
+ }
2543
+ };
2544
+ _insertSectionIntoChunk = /* @__PURE__ */ new WeakSet();
2545
+ insertSectionIntoChunk_fn = function(chunk, section) {
2546
+ let low = 0;
2547
+ let high = chunk.written.length - 1;
2548
+ let index = -1;
2549
+ while (low <= high) {
2550
+ let mid = Math.floor(low + (high - low + 1) / 2);
2551
+ if (chunk.written[mid].start <= section.start) {
2552
+ low = mid + 1;
2553
+ index = mid;
2554
+ } else {
2555
+ high = mid - 1;
2556
+ }
2557
+ }
2558
+ chunk.written.splice(index + 1, 0, section);
2559
+ if (index === -1 || chunk.written[index].end < section.start)
2560
+ index++;
2561
+ while (index < chunk.written.length - 1 && chunk.written[index].end >= chunk.written[index + 1].start) {
2562
+ chunk.written[index].end = Math.max(chunk.written[index].end, chunk.written[index + 1].end);
2563
+ chunk.written.splice(index + 1, 1);
2564
+ }
2565
+ };
2566
+ _createChunk = /* @__PURE__ */ new WeakSet();
2567
+ createChunk_fn = function(includesPosition) {
2568
+ let start = Math.floor(includesPosition / __privateGet(this, _chunkSize)) * __privateGet(this, _chunkSize);
2569
+ let chunk = {
2570
+ start,
2571
+ data: new Uint8Array(__privateGet(this, _chunkSize)),
2572
+ written: [],
2573
+ shouldFlush: false
2574
+ };
2575
+ __privateGet(this, _chunks).push(chunk);
2576
+ __privateGet(this, _chunks).sort((a, b) => a.start - b.start);
2577
+ return __privateGet(this, _chunks).indexOf(chunk);
2578
+ };
2579
+ _flushChunks = /* @__PURE__ */ new WeakSet();
2580
+ flushChunks_fn = function(force = false) {
2581
+ for (let i = 0; i < __privateGet(this, _chunks).length; i++) {
2582
+ let chunk = __privateGet(this, _chunks)[i];
2583
+ if (!chunk.shouldFlush && !force)
2584
+ continue;
2585
+ for (let section of chunk.written) {
2586
+ __privateGet(this, _target2).options.onData?.(
2587
+ chunk.data.subarray(section.start, section.end),
2588
+ chunk.start + section.start
2589
+ );
2590
+ }
2591
+ __privateGet(this, _chunks).splice(i--, 1);
2592
+ }
2593
+ };
2594
+ var FileSystemWritableFileStreamTargetWriter = class extends StreamTargetWriter {
2595
+ constructor(target) {
2596
+ super(new StreamTarget({
2597
+ onData: (data, position) => target.stream.write({
2598
+ type: "write",
2599
+ data,
2600
+ position
2601
+ }),
2602
+ chunked: true,
2603
+ chunkSize: target.options?.chunkSize
2604
+ }));
2605
+ }
2606
+ };
2607
+ var GLOBAL_TIMESCALE = 1e3;
2608
+ var SUPPORTED_VIDEO_CODECS = ["avc", "hevc", "vp9", "av1"];
2609
+ var SUPPORTED_AUDIO_CODECS = ["aac", "opus"];
2610
+ var TIMESTAMP_OFFSET = 2082844800;
2611
+ var FIRST_TIMESTAMP_BEHAVIORS = ["strict", "offset", "cross-track-offset"];
2612
+ var _options, _writer, _ftypSize, _mdat, _videoTrack, _audioTrack, _creationTime, _finalizedChunks, _nextFragmentNumber, _videoSampleQueue, _audioSampleQueue, _finalized, _validateOptions, validateOptions_fn, _writeHeader, writeHeader_fn, _computeMoovSizeUpperBound, computeMoovSizeUpperBound_fn, _prepareTracks, prepareTracks_fn, _generateMpeg4AudioSpecificConfig, generateMpeg4AudioSpecificConfig_fn, _createSampleForTrack, createSampleForTrack_fn, _addSampleToTrack, addSampleToTrack_fn, _validateTimestamp, validateTimestamp_fn, _finalizeCurrentChunk, finalizeCurrentChunk_fn, _finalizeFragment, finalizeFragment_fn, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn, _ensureNotFinalized, ensureNotFinalized_fn;
2613
+ var Muxer = class {
2614
+ constructor(options) {
2615
+ __privateAdd(this, _validateOptions);
2616
+ __privateAdd(this, _writeHeader);
2617
+ __privateAdd(this, _computeMoovSizeUpperBound);
2618
+ __privateAdd(this, _prepareTracks);
2619
+ __privateAdd(this, _generateMpeg4AudioSpecificConfig);
2620
+ __privateAdd(this, _createSampleForTrack);
2621
+ __privateAdd(this, _addSampleToTrack);
2622
+ __privateAdd(this, _validateTimestamp);
2623
+ __privateAdd(this, _finalizeCurrentChunk);
2624
+ __privateAdd(this, _finalizeFragment);
2625
+ __privateAdd(this, _maybeFlushStreamingTargetWriter);
2626
+ __privateAdd(this, _ensureNotFinalized);
2627
+ __privateAdd(this, _options, void 0);
2628
+ __privateAdd(this, _writer, void 0);
2629
+ __privateAdd(this, _ftypSize, void 0);
2630
+ __privateAdd(this, _mdat, void 0);
2631
+ __privateAdd(this, _videoTrack, null);
2632
+ __privateAdd(this, _audioTrack, null);
2633
+ __privateAdd(this, _creationTime, Math.floor(Date.now() / 1e3) + TIMESTAMP_OFFSET);
2634
+ __privateAdd(this, _finalizedChunks, []);
2635
+ __privateAdd(this, _nextFragmentNumber, 1);
2636
+ __privateAdd(this, _videoSampleQueue, []);
2637
+ __privateAdd(this, _audioSampleQueue, []);
2638
+ __privateAdd(this, _finalized, false);
2639
+ __privateMethod(this, _validateOptions, validateOptions_fn).call(this, options);
2640
+ options.video = deepClone(options.video);
2641
+ options.audio = deepClone(options.audio);
2642
+ options.fastStart = deepClone(options.fastStart);
2643
+ this.target = options.target;
2644
+ __privateSet(this, _options, {
2645
+ firstTimestampBehavior: "strict",
2646
+ ...options
2647
+ });
2648
+ if (options.target instanceof ArrayBufferTarget) {
2649
+ __privateSet(this, _writer, new ArrayBufferTargetWriter(options.target));
2650
+ } else if (options.target instanceof StreamTarget) {
2651
+ __privateSet(this, _writer, new StreamTargetWriter(options.target));
2652
+ } else if (options.target instanceof FileSystemWritableFileStreamTarget) {
2653
+ __privateSet(this, _writer, new FileSystemWritableFileStreamTargetWriter(options.target));
2654
+ } else {
2655
+ throw new Error(`Invalid target: ${options.target}`);
2656
+ }
2657
+ __privateMethod(this, _prepareTracks, prepareTracks_fn).call(this);
2658
+ __privateMethod(this, _writeHeader, writeHeader_fn).call(this);
2659
+ }
2660
+ addVideoChunk(sample, meta, timestamp, compositionTimeOffset) {
2661
+ if (!(sample instanceof EncodedVideoChunk)) {
2662
+ throw new TypeError("addVideoChunk's first argument (sample) must be of type EncodedVideoChunk.");
2663
+ }
2664
+ if (meta && typeof meta !== "object") {
2665
+ throw new TypeError("addVideoChunk's second argument (meta), when provided, must be an object.");
2666
+ }
2667
+ if (timestamp !== void 0 && (!Number.isFinite(timestamp) || timestamp < 0)) {
2668
+ throw new TypeError(
2669
+ "addVideoChunk's third argument (timestamp), when provided, must be a non-negative real number."
2670
+ );
2671
+ }
2672
+ if (compositionTimeOffset !== void 0 && !Number.isFinite(compositionTimeOffset)) {
2673
+ throw new TypeError(
2674
+ "addVideoChunk's fourth argument (compositionTimeOffset), when provided, must be a real number."
2675
+ );
2676
+ }
2677
+ let data = new Uint8Array(sample.byteLength);
2678
+ sample.copyTo(data);
2679
+ this.addVideoChunkRaw(
2680
+ data,
2681
+ sample.type,
2682
+ timestamp ?? sample.timestamp,
2683
+ sample.duration,
2684
+ meta,
2685
+ compositionTimeOffset
2686
+ );
2687
+ }
2688
+ addVideoChunkRaw(data, type, timestamp, duration, meta, compositionTimeOffset) {
2689
+ if (!(data instanceof Uint8Array)) {
2690
+ throw new TypeError("addVideoChunkRaw's first argument (data) must be an instance of Uint8Array.");
2691
+ }
2692
+ if (type !== "key" && type !== "delta") {
2693
+ throw new TypeError("addVideoChunkRaw's second argument (type) must be either 'key' or 'delta'.");
2694
+ }
2695
+ if (!Number.isFinite(timestamp) || timestamp < 0) {
2696
+ throw new TypeError("addVideoChunkRaw's third argument (timestamp) must be a non-negative real number.");
2697
+ }
2698
+ if (!Number.isFinite(duration) || duration < 0) {
2699
+ throw new TypeError("addVideoChunkRaw's fourth argument (duration) must be a non-negative real number.");
2700
+ }
2701
+ if (meta && typeof meta !== "object") {
2702
+ throw new TypeError("addVideoChunkRaw's fifth argument (meta), when provided, must be an object.");
2703
+ }
2704
+ if (compositionTimeOffset !== void 0 && !Number.isFinite(compositionTimeOffset)) {
2705
+ throw new TypeError(
2706
+ "addVideoChunkRaw's sixth argument (compositionTimeOffset), when provided, must be a real number."
2707
+ );
2708
+ }
2709
+ __privateMethod(this, _ensureNotFinalized, ensureNotFinalized_fn).call(this);
2710
+ if (!__privateGet(this, _options).video)
2711
+ throw new Error("No video track declared.");
2712
+ if (typeof __privateGet(this, _options).fastStart === "object" && __privateGet(this, _videoTrack).samples.length === __privateGet(this, _options).fastStart.expectedVideoChunks) {
2713
+ throw new Error(`Cannot add more video chunks than specified in 'fastStart' (${__privateGet(this, _options).fastStart.expectedVideoChunks}).`);
2714
+ }
2715
+ let videoSample = __privateMethod(this, _createSampleForTrack, createSampleForTrack_fn).call(this, __privateGet(this, _videoTrack), data, type, timestamp, duration, meta, compositionTimeOffset);
2716
+ if (__privateGet(this, _options).fastStart === "fragmented" && __privateGet(this, _audioTrack)) {
2717
+ while (__privateGet(this, _audioSampleQueue).length > 0 && __privateGet(this, _audioSampleQueue)[0].decodeTimestamp <= videoSample.decodeTimestamp) {
2718
+ let audioSample = __privateGet(this, _audioSampleQueue).shift();
2719
+ __privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _audioTrack), audioSample);
2720
+ }
2721
+ if (videoSample.decodeTimestamp <= __privateGet(this, _audioTrack).lastDecodeTimestamp) {
2722
+ __privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _videoTrack), videoSample);
2723
+ } else {
2724
+ __privateGet(this, _videoSampleQueue).push(videoSample);
2725
+ }
2726
+ } else {
2727
+ __privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _videoTrack), videoSample);
2728
+ }
2729
+ }
2730
+ addAudioChunk(sample, meta, timestamp) {
2731
+ if (!(sample instanceof EncodedAudioChunk)) {
2732
+ throw new TypeError("addAudioChunk's first argument (sample) must be of type EncodedAudioChunk.");
2733
+ }
2734
+ if (meta && typeof meta !== "object") {
2735
+ throw new TypeError("addAudioChunk's second argument (meta), when provided, must be an object.");
2736
+ }
2737
+ if (timestamp !== void 0 && (!Number.isFinite(timestamp) || timestamp < 0)) {
2738
+ throw new TypeError(
2739
+ "addAudioChunk's third argument (timestamp), when provided, must be a non-negative real number."
2740
+ );
2741
+ }
2742
+ let data = new Uint8Array(sample.byteLength);
2743
+ sample.copyTo(data);
2744
+ this.addAudioChunkRaw(data, sample.type, timestamp ?? sample.timestamp, sample.duration, meta);
2745
+ }
2746
+ addAudioChunkRaw(data, type, timestamp, duration, meta) {
2747
+ if (!(data instanceof Uint8Array)) {
2748
+ throw new TypeError("addAudioChunkRaw's first argument (data) must be an instance of Uint8Array.");
2749
+ }
2750
+ if (type !== "key" && type !== "delta") {
2751
+ throw new TypeError("addAudioChunkRaw's second argument (type) must be either 'key' or 'delta'.");
2752
+ }
2753
+ if (!Number.isFinite(timestamp) || timestamp < 0) {
2754
+ throw new TypeError("addAudioChunkRaw's third argument (timestamp) must be a non-negative real number.");
2755
+ }
2756
+ if (!Number.isFinite(duration) || duration < 0) {
2757
+ throw new TypeError("addAudioChunkRaw's fourth argument (duration) must be a non-negative real number.");
2758
+ }
2759
+ if (meta && typeof meta !== "object") {
2760
+ throw new TypeError("addAudioChunkRaw's fifth argument (meta), when provided, must be an object.");
2761
+ }
2762
+ __privateMethod(this, _ensureNotFinalized, ensureNotFinalized_fn).call(this);
2763
+ if (!__privateGet(this, _options).audio)
2764
+ throw new Error("No audio track declared.");
2765
+ if (typeof __privateGet(this, _options).fastStart === "object" && __privateGet(this, _audioTrack).samples.length === __privateGet(this, _options).fastStart.expectedAudioChunks) {
2766
+ throw new Error(`Cannot add more audio chunks than specified in 'fastStart' (${__privateGet(this, _options).fastStart.expectedAudioChunks}).`);
2767
+ }
2768
+ let audioSample = __privateMethod(this, _createSampleForTrack, createSampleForTrack_fn).call(this, __privateGet(this, _audioTrack), data, type, timestamp, duration, meta);
2769
+ if (__privateGet(this, _options).fastStart === "fragmented" && __privateGet(this, _videoTrack)) {
2770
+ while (__privateGet(this, _videoSampleQueue).length > 0 && __privateGet(this, _videoSampleQueue)[0].decodeTimestamp <= audioSample.decodeTimestamp) {
2771
+ let videoSample = __privateGet(this, _videoSampleQueue).shift();
2772
+ __privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _videoTrack), videoSample);
2773
+ }
2774
+ if (audioSample.decodeTimestamp <= __privateGet(this, _videoTrack).lastDecodeTimestamp) {
2775
+ __privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _audioTrack), audioSample);
2776
+ } else {
2777
+ __privateGet(this, _audioSampleQueue).push(audioSample);
2778
+ }
2779
+ } else {
2780
+ __privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _audioTrack), audioSample);
2781
+ }
2782
+ }
2783
+ /** Finalizes the file, making it ready for use. Must be called after all video and audio chunks have been added. */
2784
+ finalize() {
2785
+ if (__privateGet(this, _finalized)) {
2786
+ throw new Error("Cannot finalize a muxer more than once.");
2787
+ }
2788
+ if (__privateGet(this, _options).fastStart === "fragmented") {
2789
+ for (let videoSample of __privateGet(this, _videoSampleQueue))
2790
+ __privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _videoTrack), videoSample);
2791
+ for (let audioSample of __privateGet(this, _audioSampleQueue))
2792
+ __privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _audioTrack), audioSample);
2793
+ __privateMethod(this, _finalizeFragment, finalizeFragment_fn).call(this, false);
2794
+ } else {
2795
+ if (__privateGet(this, _videoTrack))
2796
+ __privateMethod(this, _finalizeCurrentChunk, finalizeCurrentChunk_fn).call(this, __privateGet(this, _videoTrack));
2797
+ if (__privateGet(this, _audioTrack))
2798
+ __privateMethod(this, _finalizeCurrentChunk, finalizeCurrentChunk_fn).call(this, __privateGet(this, _audioTrack));
2799
+ }
2800
+ let tracks = [__privateGet(this, _videoTrack), __privateGet(this, _audioTrack)].filter(Boolean);
2801
+ if (__privateGet(this, _options).fastStart === "in-memory") {
2802
+ let mdatSize;
2803
+ for (let i = 0; i < 2; i++) {
2804
+ let movieBox2 = moov(tracks, __privateGet(this, _creationTime));
2805
+ let movieBoxSize = __privateGet(this, _writer).measureBox(movieBox2);
2806
+ mdatSize = __privateGet(this, _writer).measureBox(__privateGet(this, _mdat));
2807
+ let currentChunkPos = __privateGet(this, _writer).pos + movieBoxSize + mdatSize;
2808
+ for (let chunk of __privateGet(this, _finalizedChunks)) {
2809
+ chunk.offset = currentChunkPos;
2810
+ for (let { data } of chunk.samples) {
2811
+ currentChunkPos += data.byteLength;
2812
+ mdatSize += data.byteLength;
2813
+ }
2814
+ }
2815
+ if (currentChunkPos < 2 ** 32)
2816
+ break;
2817
+ if (mdatSize >= 2 ** 32)
2818
+ __privateGet(this, _mdat).largeSize = true;
2819
+ }
2820
+ let movieBox = moov(tracks, __privateGet(this, _creationTime));
2821
+ __privateGet(this, _writer).writeBox(movieBox);
2822
+ __privateGet(this, _mdat).size = mdatSize;
2823
+ __privateGet(this, _writer).writeBox(__privateGet(this, _mdat));
2824
+ for (let chunk of __privateGet(this, _finalizedChunks)) {
2825
+ for (let sample of chunk.samples) {
2826
+ __privateGet(this, _writer).write(sample.data);
2827
+ sample.data = null;
2828
+ }
2829
+ }
2830
+ } else if (__privateGet(this, _options).fastStart === "fragmented") {
2831
+ let startPos = __privateGet(this, _writer).pos;
2832
+ let mfraBox = mfra(tracks);
2833
+ __privateGet(this, _writer).writeBox(mfraBox);
2834
+ let mfraBoxSize = __privateGet(this, _writer).pos - startPos;
2835
+ __privateGet(this, _writer).seek(__privateGet(this, _writer).pos - 4);
2836
+ __privateGet(this, _writer).writeU32(mfraBoxSize);
2837
+ } else {
2838
+ let mdatPos = __privateGet(this, _writer).offsets.get(__privateGet(this, _mdat));
2839
+ let mdatSize = __privateGet(this, _writer).pos - mdatPos;
2840
+ __privateGet(this, _mdat).size = mdatSize;
2841
+ __privateGet(this, _mdat).largeSize = mdatSize >= 2 ** 32;
2842
+ __privateGet(this, _writer).patchBox(__privateGet(this, _mdat));
2843
+ let movieBox = moov(tracks, __privateGet(this, _creationTime));
2844
+ if (typeof __privateGet(this, _options).fastStart === "object") {
2845
+ __privateGet(this, _writer).seek(__privateGet(this, _ftypSize));
2846
+ __privateGet(this, _writer).writeBox(movieBox);
2847
+ let remainingBytes = mdatPos - __privateGet(this, _writer).pos;
2848
+ __privateGet(this, _writer).writeBox(free(remainingBytes));
2849
+ } else {
2850
+ __privateGet(this, _writer).writeBox(movieBox);
2851
+ }
2852
+ }
2853
+ __privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this);
2854
+ __privateGet(this, _writer).finalize();
2855
+ __privateSet(this, _finalized, true);
2856
+ }
2857
+ };
2858
+ _options = /* @__PURE__ */ new WeakMap();
2859
+ _writer = /* @__PURE__ */ new WeakMap();
2860
+ _ftypSize = /* @__PURE__ */ new WeakMap();
2861
+ _mdat = /* @__PURE__ */ new WeakMap();
2862
+ _videoTrack = /* @__PURE__ */ new WeakMap();
2863
+ _audioTrack = /* @__PURE__ */ new WeakMap();
2864
+ _creationTime = /* @__PURE__ */ new WeakMap();
2865
+ _finalizedChunks = /* @__PURE__ */ new WeakMap();
2866
+ _nextFragmentNumber = /* @__PURE__ */ new WeakMap();
2867
+ _videoSampleQueue = /* @__PURE__ */ new WeakMap();
2868
+ _audioSampleQueue = /* @__PURE__ */ new WeakMap();
2869
+ _finalized = /* @__PURE__ */ new WeakMap();
2870
+ _validateOptions = /* @__PURE__ */ new WeakSet();
2871
+ validateOptions_fn = function(options) {
2872
+ if (typeof options !== "object") {
2873
+ throw new TypeError("The muxer requires an options object to be passed to its constructor.");
2874
+ }
2875
+ if (!(options.target instanceof Target)) {
2876
+ throw new TypeError("The target must be provided and an instance of Target.");
2877
+ }
2878
+ if (options.video) {
2879
+ if (!SUPPORTED_VIDEO_CODECS.includes(options.video.codec)) {
2880
+ throw new TypeError(`Unsupported video codec: ${options.video.codec}`);
2881
+ }
2882
+ if (!Number.isInteger(options.video.width) || options.video.width <= 0) {
2883
+ throw new TypeError(`Invalid video width: ${options.video.width}. Must be a positive integer.`);
2884
+ }
2885
+ if (!Number.isInteger(options.video.height) || options.video.height <= 0) {
2886
+ throw new TypeError(`Invalid video height: ${options.video.height}. Must be a positive integer.`);
2887
+ }
2888
+ const videoRotation = options.video.rotation;
2889
+ if (typeof videoRotation === "number" && ![0, 90, 180, 270].includes(videoRotation)) {
2890
+ throw new TypeError(`Invalid video rotation: ${videoRotation}. Has to be 0, 90, 180 or 270.`);
2891
+ } else if (Array.isArray(videoRotation) && (videoRotation.length !== 9 || videoRotation.some((value) => typeof value !== "number"))) {
2892
+ throw new TypeError(`Invalid video transformation matrix: ${videoRotation.join()}`);
2893
+ }
2894
+ if (options.video.frameRate !== void 0 && (!Number.isInteger(options.video.frameRate) || options.video.frameRate <= 0)) {
2895
+ throw new TypeError(
2896
+ `Invalid video frame rate: ${options.video.frameRate}. Must be a positive integer.`
2897
+ );
2898
+ }
2899
+ }
2900
+ if (options.audio) {
2901
+ if (!SUPPORTED_AUDIO_CODECS.includes(options.audio.codec)) {
2902
+ throw new TypeError(`Unsupported audio codec: ${options.audio.codec}`);
2903
+ }
2904
+ if (!Number.isInteger(options.audio.numberOfChannels) || options.audio.numberOfChannels <= 0) {
2905
+ throw new TypeError(
2906
+ `Invalid number of audio channels: ${options.audio.numberOfChannels}. Must be a positive integer.`
2907
+ );
2908
+ }
2909
+ if (!Number.isInteger(options.audio.sampleRate) || options.audio.sampleRate <= 0) {
2910
+ throw new TypeError(
2911
+ `Invalid audio sample rate: ${options.audio.sampleRate}. Must be a positive integer.`
2912
+ );
2913
+ }
2914
+ }
2915
+ if (options.firstTimestampBehavior && !FIRST_TIMESTAMP_BEHAVIORS.includes(options.firstTimestampBehavior)) {
2916
+ throw new TypeError(`Invalid first timestamp behavior: ${options.firstTimestampBehavior}`);
2917
+ }
2918
+ if (typeof options.fastStart === "object") {
2919
+ if (options.video) {
2920
+ if (options.fastStart.expectedVideoChunks === void 0) {
2921
+ throw new TypeError(`'fastStart' is an object but is missing property 'expectedVideoChunks'.`);
2922
+ } else if (!Number.isInteger(options.fastStart.expectedVideoChunks) || options.fastStart.expectedVideoChunks < 0) {
2923
+ throw new TypeError(`'expectedVideoChunks' must be a non-negative integer.`);
2924
+ }
2925
+ }
2926
+ if (options.audio) {
2927
+ if (options.fastStart.expectedAudioChunks === void 0) {
2928
+ throw new TypeError(`'fastStart' is an object but is missing property 'expectedAudioChunks'.`);
2929
+ } else if (!Number.isInteger(options.fastStart.expectedAudioChunks) || options.fastStart.expectedAudioChunks < 0) {
2930
+ throw new TypeError(`'expectedAudioChunks' must be a non-negative integer.`);
2931
+ }
2932
+ }
2933
+ } else if (![false, "in-memory", "fragmented"].includes(options.fastStart)) {
2934
+ throw new TypeError(`'fastStart' option must be false, 'in-memory', 'fragmented' or an object.`);
2935
+ }
2936
+ if (options.minFragmentDuration !== void 0 && (!Number.isFinite(options.minFragmentDuration) || options.minFragmentDuration < 0)) {
2937
+ throw new TypeError(`'minFragmentDuration' must be a non-negative number.`);
2938
+ }
2939
+ };
2940
+ _writeHeader = /* @__PURE__ */ new WeakSet();
2941
+ writeHeader_fn = function() {
2942
+ __privateGet(this, _writer).writeBox(ftyp({
2943
+ holdsAvc: __privateGet(this, _options).video?.codec === "avc",
2944
+ fragmented: __privateGet(this, _options).fastStart === "fragmented"
2945
+ }));
2946
+ __privateSet(this, _ftypSize, __privateGet(this, _writer).pos);
2947
+ if (__privateGet(this, _options).fastStart === "in-memory") {
2948
+ __privateSet(this, _mdat, mdat(false));
2949
+ } else if (__privateGet(this, _options).fastStart === "fragmented") ;
2950
+ else {
2951
+ if (typeof __privateGet(this, _options).fastStart === "object") {
2952
+ let moovSizeUpperBound = __privateMethod(this, _computeMoovSizeUpperBound, computeMoovSizeUpperBound_fn).call(this);
2953
+ __privateGet(this, _writer).seek(__privateGet(this, _writer).pos + moovSizeUpperBound);
2954
+ }
2955
+ __privateSet(this, _mdat, mdat(true));
2956
+ __privateGet(this, _writer).writeBox(__privateGet(this, _mdat));
2957
+ }
2958
+ __privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this);
2959
+ };
2960
+ _computeMoovSizeUpperBound = /* @__PURE__ */ new WeakSet();
2961
+ computeMoovSizeUpperBound_fn = function() {
2962
+ if (typeof __privateGet(this, _options).fastStart !== "object")
2963
+ return;
2964
+ let upperBound = 0;
2965
+ let sampleCounts = [
2966
+ __privateGet(this, _options).fastStart.expectedVideoChunks,
2967
+ __privateGet(this, _options).fastStart.expectedAudioChunks
2968
+ ];
2969
+ for (let n of sampleCounts) {
2970
+ if (!n)
2971
+ continue;
2972
+ upperBound += (4 + 4) * Math.ceil(2 / 3 * n);
2973
+ upperBound += 4 * n;
2974
+ upperBound += (4 + 4 + 4) * Math.ceil(2 / 3 * n);
2975
+ upperBound += 4 * n;
2976
+ upperBound += 8 * n;
2977
+ }
2978
+ upperBound += 4096;
2979
+ return upperBound;
2980
+ };
2981
+ _prepareTracks = /* @__PURE__ */ new WeakSet();
2982
+ prepareTracks_fn = function() {
2983
+ if (__privateGet(this, _options).video) {
2984
+ __privateSet(this, _videoTrack, {
2985
+ id: 1,
2986
+ info: {
2987
+ type: "video",
2988
+ codec: __privateGet(this, _options).video.codec,
2989
+ width: __privateGet(this, _options).video.width,
2990
+ height: __privateGet(this, _options).video.height,
2991
+ rotation: __privateGet(this, _options).video.rotation ?? 0,
2992
+ decoderConfig: null
2993
+ },
2994
+ // The fallback contains many common frame rates as factors
2995
+ timescale: __privateGet(this, _options).video.frameRate ?? 57600,
2996
+ samples: [],
2997
+ finalizedChunks: [],
2998
+ currentChunk: null,
2999
+ firstDecodeTimestamp: void 0,
3000
+ lastDecodeTimestamp: -1,
3001
+ timeToSampleTable: [],
3002
+ compositionTimeOffsetTable: [],
3003
+ lastTimescaleUnits: null,
3004
+ lastSample: null,
3005
+ compactlyCodedChunkTable: []
3006
+ });
3007
+ }
3008
+ if (__privateGet(this, _options).audio) {
3009
+ __privateSet(this, _audioTrack, {
3010
+ id: __privateGet(this, _options).video ? 2 : 1,
3011
+ info: {
3012
+ type: "audio",
3013
+ codec: __privateGet(this, _options).audio.codec,
3014
+ numberOfChannels: __privateGet(this, _options).audio.numberOfChannels,
3015
+ sampleRate: __privateGet(this, _options).audio.sampleRate,
3016
+ decoderConfig: null
3017
+ },
3018
+ timescale: __privateGet(this, _options).audio.sampleRate,
3019
+ samples: [],
3020
+ finalizedChunks: [],
3021
+ currentChunk: null,
3022
+ firstDecodeTimestamp: void 0,
3023
+ lastDecodeTimestamp: -1,
3024
+ timeToSampleTable: [],
3025
+ compositionTimeOffsetTable: [],
3026
+ lastTimescaleUnits: null,
3027
+ lastSample: null,
3028
+ compactlyCodedChunkTable: []
3029
+ });
3030
+ if (__privateGet(this, _options).audio.codec === "aac") {
3031
+ let guessedCodecPrivate = __privateMethod(this, _generateMpeg4AudioSpecificConfig, generateMpeg4AudioSpecificConfig_fn).call(
3032
+ this,
3033
+ 2,
3034
+ // Object type for AAC-LC, since it's the most common
3035
+ __privateGet(this, _options).audio.sampleRate,
3036
+ __privateGet(this, _options).audio.numberOfChannels
3037
+ );
3038
+ __privateGet(this, _audioTrack).info.decoderConfig = {
3039
+ codec: __privateGet(this, _options).audio.codec,
3040
+ description: guessedCodecPrivate,
3041
+ numberOfChannels: __privateGet(this, _options).audio.numberOfChannels,
3042
+ sampleRate: __privateGet(this, _options).audio.sampleRate
3043
+ };
3044
+ }
3045
+ }
3046
+ };
3047
+ _generateMpeg4AudioSpecificConfig = /* @__PURE__ */ new WeakSet();
3048
+ generateMpeg4AudioSpecificConfig_fn = function(objectType, sampleRate, numberOfChannels) {
3049
+ let frequencyIndices = [96e3, 88200, 64e3, 48e3, 44100, 32e3, 24e3, 22050, 16e3, 12e3, 11025, 8e3, 7350];
3050
+ let frequencyIndex = frequencyIndices.indexOf(sampleRate);
3051
+ let channelConfig = numberOfChannels;
3052
+ let configBits = "";
3053
+ configBits += objectType.toString(2).padStart(5, "0");
3054
+ configBits += frequencyIndex.toString(2).padStart(4, "0");
3055
+ if (frequencyIndex === 15)
3056
+ configBits += sampleRate.toString(2).padStart(24, "0");
3057
+ configBits += channelConfig.toString(2).padStart(4, "0");
3058
+ let paddingLength = Math.ceil(configBits.length / 8) * 8;
3059
+ configBits = configBits.padEnd(paddingLength, "0");
3060
+ let configBytes = new Uint8Array(configBits.length / 8);
3061
+ for (let i = 0; i < configBits.length; i += 8) {
3062
+ configBytes[i / 8] = parseInt(configBits.slice(i, i + 8), 2);
3063
+ }
3064
+ return configBytes;
3065
+ };
3066
+ _createSampleForTrack = /* @__PURE__ */ new WeakSet();
3067
+ createSampleForTrack_fn = function(track, data, type, timestamp, duration, meta, compositionTimeOffset) {
3068
+ let presentationTimestampInSeconds = timestamp / 1e6;
3069
+ let decodeTimestampInSeconds = (timestamp - (compositionTimeOffset ?? 0)) / 1e6;
3070
+ let durationInSeconds = duration / 1e6;
3071
+ let adjusted = __privateMethod(this, _validateTimestamp, validateTimestamp_fn).call(this, presentationTimestampInSeconds, decodeTimestampInSeconds, track);
3072
+ presentationTimestampInSeconds = adjusted.presentationTimestamp;
3073
+ decodeTimestampInSeconds = adjusted.decodeTimestamp;
3074
+ if (meta?.decoderConfig) {
3075
+ if (track.info.decoderConfig === null) {
3076
+ track.info.decoderConfig = meta.decoderConfig;
3077
+ } else {
3078
+ Object.assign(track.info.decoderConfig, meta.decoderConfig);
3079
+ }
3080
+ }
3081
+ let sample = {
3082
+ presentationTimestamp: presentationTimestampInSeconds,
3083
+ decodeTimestamp: decodeTimestampInSeconds,
3084
+ duration: durationInSeconds,
3085
+ data,
3086
+ size: data.byteLength,
3087
+ type,
3088
+ // Will be refined once the next sample comes in
3089
+ timescaleUnitsToNextSample: intoTimescale(durationInSeconds, track.timescale)
3090
+ };
3091
+ return sample;
3092
+ };
3093
+ _addSampleToTrack = /* @__PURE__ */ new WeakSet();
3094
+ addSampleToTrack_fn = function(track, sample) {
3095
+ if (__privateGet(this, _options).fastStart !== "fragmented") {
3096
+ track.samples.push(sample);
3097
+ }
3098
+ const sampleCompositionTimeOffset = intoTimescale(sample.presentationTimestamp - sample.decodeTimestamp, track.timescale);
3099
+ if (track.lastTimescaleUnits !== null) {
3100
+ let timescaleUnits = intoTimescale(sample.decodeTimestamp, track.timescale, false);
3101
+ let delta = Math.round(timescaleUnits - track.lastTimescaleUnits);
3102
+ track.lastTimescaleUnits += delta;
3103
+ track.lastSample.timescaleUnitsToNextSample = delta;
3104
+ if (__privateGet(this, _options).fastStart !== "fragmented") {
3105
+ let lastTableEntry = last(track.timeToSampleTable);
3106
+ if (lastTableEntry.sampleCount === 1) {
3107
+ lastTableEntry.sampleDelta = delta;
3108
+ lastTableEntry.sampleCount++;
3109
+ } else if (lastTableEntry.sampleDelta === delta) {
3110
+ lastTableEntry.sampleCount++;
3111
+ } else {
3112
+ lastTableEntry.sampleCount--;
3113
+ track.timeToSampleTable.push({
3114
+ sampleCount: 2,
3115
+ sampleDelta: delta
3116
+ });
3117
+ }
3118
+ const lastCompositionTimeOffsetTableEntry = last(track.compositionTimeOffsetTable);
3119
+ if (lastCompositionTimeOffsetTableEntry.sampleCompositionTimeOffset === sampleCompositionTimeOffset) {
3120
+ lastCompositionTimeOffsetTableEntry.sampleCount++;
3121
+ } else {
3122
+ track.compositionTimeOffsetTable.push({
3123
+ sampleCount: 1,
3124
+ sampleCompositionTimeOffset
3125
+ });
3126
+ }
3127
+ }
3128
+ } else {
3129
+ track.lastTimescaleUnits = 0;
3130
+ if (__privateGet(this, _options).fastStart !== "fragmented") {
3131
+ track.timeToSampleTable.push({
3132
+ sampleCount: 1,
3133
+ sampleDelta: intoTimescale(sample.duration, track.timescale)
3134
+ });
3135
+ track.compositionTimeOffsetTable.push({
3136
+ sampleCount: 1,
3137
+ sampleCompositionTimeOffset
3138
+ });
3139
+ }
3140
+ }
3141
+ track.lastSample = sample;
3142
+ let beginNewChunk = false;
3143
+ if (!track.currentChunk) {
3144
+ beginNewChunk = true;
3145
+ } else {
3146
+ let currentChunkDuration = sample.presentationTimestamp - track.currentChunk.startTimestamp;
3147
+ if (__privateGet(this, _options).fastStart === "fragmented") {
3148
+ let mostImportantTrack = __privateGet(this, _videoTrack) ?? __privateGet(this, _audioTrack);
3149
+ const chunkDuration = __privateGet(this, _options).minFragmentDuration ?? 1;
3150
+ if (track === mostImportantTrack && sample.type === "key" && currentChunkDuration >= chunkDuration) {
3151
+ beginNewChunk = true;
3152
+ __privateMethod(this, _finalizeFragment, finalizeFragment_fn).call(this);
3153
+ }
3154
+ } else {
3155
+ beginNewChunk = currentChunkDuration >= 0.5;
3156
+ }
3157
+ }
3158
+ if (beginNewChunk) {
3159
+ if (track.currentChunk) {
3160
+ __privateMethod(this, _finalizeCurrentChunk, finalizeCurrentChunk_fn).call(this, track);
3161
+ }
3162
+ track.currentChunk = {
3163
+ startTimestamp: sample.presentationTimestamp,
3164
+ samples: []
3165
+ };
3166
+ }
3167
+ track.currentChunk.samples.push(sample);
3168
+ };
3169
+ _validateTimestamp = /* @__PURE__ */ new WeakSet();
3170
+ validateTimestamp_fn = function(presentationTimestamp, decodeTimestamp, track) {
3171
+ const strictTimestampBehavior = __privateGet(this, _options).firstTimestampBehavior === "strict";
3172
+ const noLastDecodeTimestamp = track.lastDecodeTimestamp === -1;
3173
+ const timestampNonZero = decodeTimestamp !== 0;
3174
+ if (strictTimestampBehavior && noLastDecodeTimestamp && timestampNonZero) {
3175
+ throw new Error(
3176
+ `The first chunk for your media track must have a timestamp of 0 (received DTS=${decodeTimestamp}).Non-zero first timestamps are often caused by directly piping frames or audio data from a MediaStreamTrack into the encoder. Their timestamps are typically relative to the age of thedocument, which is probably what you want.
3177
+
3178
+ If you want to offset all timestamps of a track such that the first one is zero, set firstTimestampBehavior: 'offset' in the options.
3179
+ `
3180
+ );
3181
+ } else if (__privateGet(this, _options).firstTimestampBehavior === "offset" || __privateGet(this, _options).firstTimestampBehavior === "cross-track-offset") {
3182
+ if (track.firstDecodeTimestamp === void 0) {
3183
+ track.firstDecodeTimestamp = decodeTimestamp;
3184
+ }
3185
+ let baseDecodeTimestamp;
3186
+ if (__privateGet(this, _options).firstTimestampBehavior === "offset") {
3187
+ baseDecodeTimestamp = track.firstDecodeTimestamp;
3188
+ } else {
3189
+ baseDecodeTimestamp = Math.min(
3190
+ __privateGet(this, _videoTrack)?.firstDecodeTimestamp ?? Infinity,
3191
+ __privateGet(this, _audioTrack)?.firstDecodeTimestamp ?? Infinity
3192
+ );
3193
+ }
3194
+ decodeTimestamp -= baseDecodeTimestamp;
3195
+ presentationTimestamp -= baseDecodeTimestamp;
3196
+ }
3197
+ if (decodeTimestamp < track.lastDecodeTimestamp) {
3198
+ throw new Error(
3199
+ `Timestamps must be monotonically increasing (DTS went from ${track.lastDecodeTimestamp * 1e6} to ${decodeTimestamp * 1e6}).`
3200
+ );
3201
+ }
3202
+ track.lastDecodeTimestamp = decodeTimestamp;
3203
+ return { presentationTimestamp, decodeTimestamp };
3204
+ };
3205
+ _finalizeCurrentChunk = /* @__PURE__ */ new WeakSet();
3206
+ finalizeCurrentChunk_fn = function(track) {
3207
+ if (__privateGet(this, _options).fastStart === "fragmented") {
3208
+ throw new Error("Can't finalize individual chunks if 'fastStart' is set to 'fragmented'.");
3209
+ }
3210
+ if (!track.currentChunk)
3211
+ return;
3212
+ track.finalizedChunks.push(track.currentChunk);
3213
+ __privateGet(this, _finalizedChunks).push(track.currentChunk);
3214
+ if (track.compactlyCodedChunkTable.length === 0 || last(track.compactlyCodedChunkTable).samplesPerChunk !== track.currentChunk.samples.length) {
3215
+ track.compactlyCodedChunkTable.push({
3216
+ firstChunk: track.finalizedChunks.length,
3217
+ // 1-indexed
3218
+ samplesPerChunk: track.currentChunk.samples.length
3219
+ });
3220
+ }
3221
+ if (__privateGet(this, _options).fastStart === "in-memory") {
3222
+ track.currentChunk.offset = 0;
3223
+ return;
3224
+ }
3225
+ track.currentChunk.offset = __privateGet(this, _writer).pos;
3226
+ for (let sample of track.currentChunk.samples) {
3227
+ __privateGet(this, _writer).write(sample.data);
3228
+ sample.data = null;
3229
+ }
3230
+ __privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this);
3231
+ };
3232
+ _finalizeFragment = /* @__PURE__ */ new WeakSet();
3233
+ finalizeFragment_fn = function(flushStreamingWriter = true) {
3234
+ if (__privateGet(this, _options).fastStart !== "fragmented") {
3235
+ throw new Error("Can't finalize a fragment unless 'fastStart' is set to 'fragmented'.");
3236
+ }
3237
+ let tracks = [__privateGet(this, _videoTrack), __privateGet(this, _audioTrack)].filter((track) => track && track.currentChunk);
3238
+ if (tracks.length === 0)
3239
+ return;
3240
+ let fragmentNumber = __privateWrapper(this, _nextFragmentNumber)._++;
3241
+ if (fragmentNumber === 1) {
3242
+ let movieBox = moov(tracks, __privateGet(this, _creationTime), true);
3243
+ __privateGet(this, _writer).writeBox(movieBox);
3244
+ }
3245
+ let moofOffset = __privateGet(this, _writer).pos;
3246
+ let moofBox = moof(fragmentNumber, tracks);
3247
+ __privateGet(this, _writer).writeBox(moofBox);
3248
+ {
3249
+ let mdatBox = mdat(false);
3250
+ let totalTrackSampleSize = 0;
3251
+ for (let track of tracks) {
3252
+ for (let sample of track.currentChunk.samples) {
3253
+ totalTrackSampleSize += sample.size;
3254
+ }
3255
+ }
3256
+ let mdatSize = __privateGet(this, _writer).measureBox(mdatBox) + totalTrackSampleSize;
3257
+ if (mdatSize >= 2 ** 32) {
3258
+ mdatBox.largeSize = true;
3259
+ mdatSize = __privateGet(this, _writer).measureBox(mdatBox) + totalTrackSampleSize;
3260
+ }
3261
+ mdatBox.size = mdatSize;
3262
+ __privateGet(this, _writer).writeBox(mdatBox);
3263
+ }
3264
+ for (let track of tracks) {
3265
+ track.currentChunk.offset = __privateGet(this, _writer).pos;
3266
+ track.currentChunk.moofOffset = moofOffset;
3267
+ for (let sample of track.currentChunk.samples) {
3268
+ __privateGet(this, _writer).write(sample.data);
3269
+ sample.data = null;
3270
+ }
3271
+ }
3272
+ let endPos = __privateGet(this, _writer).pos;
3273
+ __privateGet(this, _writer).seek(__privateGet(this, _writer).offsets.get(moofBox));
3274
+ let newMoofBox = moof(fragmentNumber, tracks);
3275
+ __privateGet(this, _writer).writeBox(newMoofBox);
3276
+ __privateGet(this, _writer).seek(endPos);
3277
+ for (let track of tracks) {
3278
+ track.finalizedChunks.push(track.currentChunk);
3279
+ __privateGet(this, _finalizedChunks).push(track.currentChunk);
3280
+ track.currentChunk = null;
3281
+ }
3282
+ if (flushStreamingWriter) {
3283
+ __privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this);
3284
+ }
3285
+ };
3286
+ _maybeFlushStreamingTargetWriter = /* @__PURE__ */ new WeakSet();
3287
+ maybeFlushStreamingTargetWriter_fn = function() {
3288
+ if (__privateGet(this, _writer) instanceof StreamTargetWriter) {
3289
+ __privateGet(this, _writer).flush();
3290
+ }
3291
+ };
3292
+ _ensureNotFinalized = /* @__PURE__ */ new WeakSet();
3293
+ ensureNotFinalized_fn = function() {
3294
+ if (__privateGet(this, _finalized)) {
3295
+ throw new Error("Cannot add new video or audio chunks after the file has been finalized.");
3296
+ }
3297
+ };
3298
+ const CANVAS_WIDTH = 1280;
3299
+ const CANVAS_HEIGHT = 720;
3300
+ function isWebCodecsSupported() {
3301
+ return typeof VideoEncoder !== "undefined" && typeof VideoFrame !== "undefined";
3302
+ }
3303
+ function createVideoRenderer(options) {
3304
+ const { scene, timeline, fps, bitrate, scale, onProgress } = options;
3305
+ let cancelled = false;
3306
+ let encoder = null;
3307
+ let muxer = null;
3308
+ let encoderError = null;
3309
+ return {
3310
+ async start() {
3311
+ if (!isWebCodecsSupported()) {
3312
+ throw new Error("WebCodecs is not supported in this browser. Please use Chrome, Edge, or Opera.");
3313
+ }
3314
+ const duration = timeline.duration;
3315
+ const totalFrames = Math.ceil(duration / 1e3 * fps);
3316
+ const frameDurationUs = 1e6 / fps;
3317
+ const videoWidth = CANVAS_WIDTH * scale;
3318
+ const videoHeight = CANVAS_HEIGHT * scale;
3319
+ const canvas = document.createElement("canvas");
3320
+ canvas.width = videoWidth;
3321
+ canvas.height = videoHeight;
3322
+ const ctx = canvas.getContext("2d");
3323
+ const animCtx = new AnimCtxImpl();
3324
+ muxer = new Muxer({
3325
+ target: new ArrayBufferTarget(),
3326
+ video: {
3327
+ codec: "avc",
3328
+ width: videoWidth,
3329
+ height: videoHeight
3330
+ },
3331
+ fastStart: "in-memory"
3332
+ });
3333
+ encoder = new VideoEncoder({
3334
+ output: (chunk, meta) => {
3335
+ if (cancelled || !muxer) return;
3336
+ muxer.addVideoChunk(chunk, meta);
3337
+ },
3338
+ error: (e) => {
3339
+ console.error("VideoEncoder error:", e);
3340
+ encoderError = e;
3341
+ }
3342
+ });
3343
+ encoder.configure({
3344
+ codec: "avc1.640032",
3345
+ // H.264 High Profile Level 5.1 (supports up to 4K)
3346
+ width: videoWidth,
3347
+ height: videoHeight,
3348
+ bitrate,
3349
+ framerate: fps
3350
+ });
3351
+ for (let frame = 0; frame < totalFrames && !cancelled && !encoderError; frame++) {
3352
+ const timeMs = frame / fps * 1e3;
3353
+ animCtx.reset(scene);
3354
+ timeline.evaluate(timeMs, animCtx);
3355
+ const solved = animCtx.solve(scene);
3356
+ ctx.fillStyle = "#ffffff";
3357
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
3358
+ scene.render(solved, canvas, scale);
3359
+ const videoFrame = new VideoFrame(canvas, {
3360
+ timestamp: frame * frameDurationUs
3361
+ });
3362
+ const keyFrame = frame % fps === 0;
3363
+ encoder.encode(videoFrame, { keyFrame });
3364
+ videoFrame.close();
3365
+ onProgress((frame + 1) / totalFrames, frame + 1, totalFrames);
3366
+ if (frame % 5 === 0) {
3367
+ await new Promise((resolve) => setTimeout(resolve, 0));
3368
+ }
3369
+ }
3370
+ if (encoderError) {
3371
+ throw encoderError;
3372
+ }
3373
+ if (cancelled) {
3374
+ encoder.close();
3375
+ throw new Error("Rendering cancelled");
3376
+ }
3377
+ await encoder.flush();
3378
+ encoder.close();
3379
+ encoder = null;
3380
+ if (cancelled || !muxer) {
3381
+ throw new Error("Rendering cancelled");
3382
+ }
3383
+ muxer.finalize();
3384
+ const buffer = muxer.target.buffer;
3385
+ muxer = null;
3386
+ const blob = new Blob([buffer], { type: "video/mp4" });
3387
+ return {
3388
+ blob,
3389
+ duration,
3390
+ frames: totalFrames
3391
+ };
3392
+ },
3393
+ cancel() {
3394
+ cancelled = true;
3395
+ if (encoder && encoder.state !== "closed") {
3396
+ encoder.close();
3397
+ }
3398
+ }
3399
+ };
3400
+ }
3401
+ function downloadBlob(blob, filename) {
3402
+ const url2 = URL.createObjectURL(blob);
3403
+ const a = document.createElement("a");
3404
+ a.href = url2;
3405
+ a.download = filename;
3406
+ document.body.appendChild(a);
3407
+ a.click();
3408
+ document.body.removeChild(a);
3409
+ URL.revokeObjectURL(url2);
3410
+ }
3411
+ export {
3412
+ Screen as S,
3413
+ Vec as V,
3414
+ isExpr as a,
3415
+ angle as b,
3416
+ createVideoRenderer as c,
3417
+ isWebCodecsSupported as d,
3418
+ downloadBlob as e,
3419
+ isGroup as i,
3420
+ lerp as l,
3421
+ makePlayer as m,
3422
+ vec as v
3423
+ };
3424
+ //# sourceMappingURL=video-renderer-Buv1c43x.js.map