@vosjs/studio-core 0.13.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +115 -5
- package/dist/index.js +332 -95
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -209,6 +209,20 @@ function exportSizeFor(ratio, resolution) {
|
|
|
209
209
|
const safe = ratio > 0 && Number.isFinite(ratio) ? ratio : 16 / 9;
|
|
210
210
|
return safe >= 1 ? { width: even(short * safe), height: even(short) } : { width: even(short), height: even(short / safe) };
|
|
211
211
|
}
|
|
212
|
+
var MEDIA_FRAME_KEYS = [
|
|
213
|
+
"browserBar",
|
|
214
|
+
"radius",
|
|
215
|
+
"shadow",
|
|
216
|
+
"shadowContact",
|
|
217
|
+
"shadowColor",
|
|
218
|
+
"inset",
|
|
219
|
+
"border",
|
|
220
|
+
"borderWidth",
|
|
221
|
+
"borderColor",
|
|
222
|
+
"fit",
|
|
223
|
+
"focus",
|
|
224
|
+
"focusFollow"
|
|
225
|
+
];
|
|
212
226
|
|
|
213
227
|
// src/capture.ts
|
|
214
228
|
var CHROME_TOP_MIN = 20;
|
|
@@ -511,6 +525,71 @@ function lastLayerEnd(doc) {
|
|
|
511
525
|
|
|
512
526
|
// src/lower/motion.ts
|
|
513
527
|
import { splitBySpeed, totalDuration } from "@vosjs/timeline";
|
|
528
|
+
|
|
529
|
+
// src/media.ts
|
|
530
|
+
var PRIMARY_MEDIA = "";
|
|
531
|
+
var mediaKey = (id) => id ?? PRIMARY_MEDIA;
|
|
532
|
+
var sameMedia = (a, b) => mediaKey(a) === mediaKey(b);
|
|
533
|
+
function docMediaList(doc) {
|
|
534
|
+
return [
|
|
535
|
+
{ id: PRIMARY_MEDIA, source: doc.source },
|
|
536
|
+
...(doc.media ?? []).map((m) => ({ id: m.id, source: m }))
|
|
537
|
+
];
|
|
538
|
+
}
|
|
539
|
+
function mediaSource(doc, id) {
|
|
540
|
+
const key = mediaKey(id);
|
|
541
|
+
if (key === PRIMARY_MEDIA) return doc.source;
|
|
542
|
+
return (doc.media ?? []).find((m) => m.id === key) ?? null;
|
|
543
|
+
}
|
|
544
|
+
function mediaDuration(source) {
|
|
545
|
+
return (source.meta.durationMs || 0) / 1e3;
|
|
546
|
+
}
|
|
547
|
+
function mediaAtOutput(rated, t) {
|
|
548
|
+
let acc = 0;
|
|
549
|
+
let last = null;
|
|
550
|
+
for (const p of rated) {
|
|
551
|
+
const len = Math.max(0, p.out - p.in) / (p.rate && p.rate > 0 ? p.rate : 1);
|
|
552
|
+
if (t < acc + len) return mediaKey(p.media);
|
|
553
|
+
acc += len;
|
|
554
|
+
last = p;
|
|
555
|
+
}
|
|
556
|
+
return last ? mediaKey(last.media) : PRIMARY_MEDIA;
|
|
557
|
+
}
|
|
558
|
+
function mediaFrame(doc, id) {
|
|
559
|
+
const key = mediaKey(id);
|
|
560
|
+
if (key === PRIMARY_MEDIA) return doc.frame;
|
|
561
|
+
const m = (doc.media ?? []).find((x) => x.id === key);
|
|
562
|
+
if (!m) return doc.frame;
|
|
563
|
+
const over = m.frame ?? {};
|
|
564
|
+
const out = { ...doc.frame };
|
|
565
|
+
for (const k of MEDIA_FRAME_KEYS) {
|
|
566
|
+
if (k === "browserBar") continue;
|
|
567
|
+
const v = over[k];
|
|
568
|
+
if (v !== void 0) out[k] = v;
|
|
569
|
+
}
|
|
570
|
+
const bar = { ...doc.frame.browserBar };
|
|
571
|
+
const page = pageDisplayUrl(m.meta.pageUrl);
|
|
572
|
+
if (page) bar.url = page;
|
|
573
|
+
if (!m.meta.pageUrl) bar.kind = "none";
|
|
574
|
+
out.browserBar = { ...bar, ...over.browserBar ?? {} };
|
|
575
|
+
return out;
|
|
576
|
+
}
|
|
577
|
+
function cardFields(frame) {
|
|
578
|
+
const out = {};
|
|
579
|
+
for (const k of MEDIA_FRAME_KEYS) {
|
|
580
|
+
const v = frame[k];
|
|
581
|
+
if (v !== void 0) out[k] = v;
|
|
582
|
+
}
|
|
583
|
+
return out;
|
|
584
|
+
}
|
|
585
|
+
function nextMediaId(doc) {
|
|
586
|
+
const taken = new Set((doc.media ?? []).map((m) => m.id));
|
|
587
|
+
let n = 1;
|
|
588
|
+
while (taken.has(`m${n}`)) n++;
|
|
589
|
+
return `m${n}`;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// src/lower/motion.ts
|
|
514
593
|
function docRestTime(doc) {
|
|
515
594
|
if (doc.endCard) return null;
|
|
516
595
|
const freezes = docFreezes(doc);
|
|
@@ -575,17 +654,19 @@ function exitSeconds(e) {
|
|
|
575
654
|
}
|
|
576
655
|
function placeFreezes(rated, freezes) {
|
|
577
656
|
const marks = /* @__PURE__ */ new Map();
|
|
578
|
-
const
|
|
657
|
+
const placed = /* @__PURE__ */ new Set();
|
|
579
658
|
const pieces = [];
|
|
580
659
|
let acc = 0;
|
|
581
|
-
const freezePiece = (f) => {
|
|
660
|
+
const freezePiece = (f, media) => {
|
|
582
661
|
const at = Math.max(HOLD_SOURCE_SPAN, f.at);
|
|
583
662
|
marks.set(f.id, { t: acc, duration: f.seconds });
|
|
584
663
|
pieces.push({
|
|
585
664
|
in: at - HOLD_SOURCE_SPAN,
|
|
586
665
|
out: at,
|
|
587
|
-
rate: HOLD_SOURCE_SPAN / f.seconds
|
|
666
|
+
rate: HOLD_SOURCE_SPAN / f.seconds,
|
|
667
|
+
...media !== void 0 ? { media } : {}
|
|
588
668
|
});
|
|
669
|
+
placed.add(f.id);
|
|
589
670
|
acc += f.seconds;
|
|
590
671
|
};
|
|
591
672
|
const push2 = (p) => {
|
|
@@ -595,32 +676,23 @@ function placeFreezes(rated, freezes) {
|
|
|
595
676
|
for (let i = 0; i < rated.length; i++) {
|
|
596
677
|
let piece = rated[i];
|
|
597
678
|
const next = rated[i + 1];
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
continue;
|
|
604
|
-
}
|
|
679
|
+
const continues = !!next && sameMedia(next.media, piece.media) && Math.abs(next.in - piece.out) < 1e-9 && next.out > piece.out;
|
|
680
|
+
const mine = freezes.filter(
|
|
681
|
+
(f) => f.seconds > 0 && !placed.has(f.id) && sameMedia(f.media, piece.media) && f.at >= piece.in - 1e-9 && (continues ? f.at < piece.out - 1e-9 : f.at <= piece.out + 1e-9)
|
|
682
|
+
).sort((a, b) => a.at - b.at);
|
|
683
|
+
for (const f of mine) {
|
|
605
684
|
if (f.at <= piece.in + 1e-9) {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
continue;
|
|
609
|
-
}
|
|
610
|
-
if (f.at < piece.out - 1e-9) {
|
|
685
|
+
freezePiece(f, piece.media);
|
|
686
|
+
} else if (f.at < piece.out - 1e-9) {
|
|
611
687
|
push2({ ...piece, out: f.at });
|
|
612
|
-
|
|
613
|
-
freezePiece(f);
|
|
688
|
+
freezePiece(f, piece.media);
|
|
614
689
|
piece = { ...piece, in: f.at };
|
|
615
|
-
|
|
690
|
+
} else {
|
|
691
|
+
const media = piece.media;
|
|
692
|
+
push2(piece);
|
|
693
|
+
piece = null;
|
|
694
|
+
freezePiece(f, media);
|
|
616
695
|
}
|
|
617
|
-
const continues = next && Math.abs(next.in - piece.out) < 1e-9 && next.out > piece.out;
|
|
618
|
-
if (continues) break;
|
|
619
|
-
push2(piece);
|
|
620
|
-
piece = null;
|
|
621
|
-
pending.shift();
|
|
622
|
-
freezePiece(f);
|
|
623
|
-
break;
|
|
624
696
|
}
|
|
625
697
|
if (piece) push2(piece);
|
|
626
698
|
}
|
|
@@ -1570,7 +1642,7 @@ import {
|
|
|
1570
1642
|
sample,
|
|
1571
1643
|
segmentRate as segmentRate2,
|
|
1572
1644
|
sortKeyframes as sortKeyframes2,
|
|
1573
|
-
sourceToTimeline
|
|
1645
|
+
sourceToTimeline,
|
|
1574
1646
|
splitBySpeed as splitBySpeed2,
|
|
1575
1647
|
totalDuration as totalDuration2
|
|
1576
1648
|
} from "@vosjs/timeline";
|
|
@@ -1640,18 +1712,20 @@ function computeCardLayout(frame, video, W, H, zoomFocus) {
|
|
|
1640
1712
|
cardH: dh + barH
|
|
1641
1713
|
};
|
|
1642
1714
|
}
|
|
1643
|
-
function docCardLayout(doc) {
|
|
1715
|
+
function docCardLayout(doc, media) {
|
|
1644
1716
|
const meta = doc.source.meta;
|
|
1645
1717
|
const H = 1080;
|
|
1646
1718
|
const W = Math.max(
|
|
1647
1719
|
2,
|
|
1648
1720
|
Math.round(H * aspectRatioValue(doc.frame.aspectRatio, meta))
|
|
1649
1721
|
);
|
|
1722
|
+
const src = mediaSource(doc, media) ?? doc.source;
|
|
1723
|
+
const sm = src.meta;
|
|
1650
1724
|
return computeCardLayout(
|
|
1651
|
-
doc
|
|
1725
|
+
mediaFrame(doc, media),
|
|
1652
1726
|
{
|
|
1653
|
-
width:
|
|
1654
|
-
height:
|
|
1727
|
+
width: sm.captureWidth ?? sm.width,
|
|
1728
|
+
height: sm.captureHeight ?? sm.height
|
|
1655
1729
|
},
|
|
1656
1730
|
W,
|
|
1657
1731
|
H
|
|
@@ -3242,7 +3316,7 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
3242
3316
|
}`;
|
|
3243
3317
|
|
|
3244
3318
|
// src/lower/extractClicks.ts
|
|
3245
|
-
import { segmentRate
|
|
3319
|
+
import { segmentRate } from "@vosjs/timeline";
|
|
3246
3320
|
var CLICK_FX_PRE = 0.06;
|
|
3247
3321
|
var CLICK_RIPPLE_DUR = 0.45;
|
|
3248
3322
|
var CLICK_PULSE_DUR = 0.35;
|
|
@@ -3251,14 +3325,27 @@ var CLICK_SYNTH_RELEASE = 0.12;
|
|
|
3251
3325
|
var CLICK_PAIR_MAX = 10;
|
|
3252
3326
|
var CLICK_RECT_MAX_FRAC = 0.35;
|
|
3253
3327
|
var RECT_CONTAIN_MARGIN = 8;
|
|
3254
|
-
function
|
|
3328
|
+
function outputAt(segments, sourceT, media) {
|
|
3329
|
+
if (!segments.length) return sourceT;
|
|
3330
|
+
let acc = 0;
|
|
3331
|
+
for (const s of segments) {
|
|
3332
|
+
const rate = segmentRate(s);
|
|
3333
|
+
if (sameMedia(s.media, media) && sourceT >= s.in && sourceT <= s.out)
|
|
3334
|
+
return acc + (sourceT - s.in) / rate;
|
|
3335
|
+
acc += Math.max(0, s.out - s.in) / rate;
|
|
3336
|
+
}
|
|
3337
|
+
return null;
|
|
3338
|
+
}
|
|
3339
|
+
function keptOutputEnd(segments, sIn, sOut, media) {
|
|
3255
3340
|
let acc = 0;
|
|
3256
3341
|
let end = null;
|
|
3257
3342
|
for (const p of segments) {
|
|
3258
3343
|
const rate = segmentRate(p);
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3344
|
+
if (sameMedia(p.media, media)) {
|
|
3345
|
+
const ovIn = Math.max(sIn, p.in);
|
|
3346
|
+
const ovOut = Math.min(sOut, p.out);
|
|
3347
|
+
if (ovOut > ovIn) end = acc + (ovOut - p.in) / rate;
|
|
3348
|
+
}
|
|
3262
3349
|
acc += Math.max(0, p.out - p.in) / rate;
|
|
3263
3350
|
}
|
|
3264
3351
|
return end;
|
|
@@ -3290,10 +3377,10 @@ function extractClicks(track, segments, opts) {
|
|
|
3290
3377
|
break;
|
|
3291
3378
|
}
|
|
3292
3379
|
}
|
|
3293
|
-
const ot =
|
|
3380
|
+
const ot = outputAt(segments, st, opts.media);
|
|
3294
3381
|
if (ot === null) continue;
|
|
3295
3382
|
const upSrc = st + Math.max(pressLen, 0.02);
|
|
3296
|
-
const up = keptOutputEnd(segments, st, upSrc) ?? ot + CLICK_SYNTH_RELEASE;
|
|
3383
|
+
const up = keptOutputEnd(segments, st, upSrc, opts.media) ?? ot + CLICK_SYNTH_RELEASE;
|
|
3297
3384
|
const click = {
|
|
3298
3385
|
ot: round5(ot),
|
|
3299
3386
|
up: round5(Math.max(up, ot + 0.02)),
|
|
@@ -3346,19 +3433,32 @@ var PROGRAM_DURATION = 1;
|
|
|
3346
3433
|
function ratedSegments(doc) {
|
|
3347
3434
|
const segs = isRecordingDoc(doc) ? doc.segments.length ? doc.segments : [{ in: 0, out: doc.source.meta.durationMs / 1e3 }] : [{ in: 0, out: programDuration(doc) }];
|
|
3348
3435
|
const freezes = isRecordingDoc(doc) ? docFreezes(doc) : [];
|
|
3349
|
-
|
|
3436
|
+
const speeds = doc.speed ?? [];
|
|
3437
|
+
const pieces = segs.flatMap(
|
|
3438
|
+
(seg) => splitBySpeed2(
|
|
3439
|
+
[seg],
|
|
3440
|
+
speeds.filter(
|
|
3441
|
+
(sp) => sameMedia(sp.media, seg.media)
|
|
3442
|
+
)
|
|
3443
|
+
)
|
|
3444
|
+
);
|
|
3445
|
+
return withFreezes(pieces, freezes);
|
|
3350
3446
|
}
|
|
3351
3447
|
function durationSec(doc, rated) {
|
|
3352
3448
|
const trimmed = totalDuration2(rated);
|
|
3353
3449
|
return trimmed > 0 ? trimmed : doc.source.meta.durationMs / 1e3;
|
|
3354
3450
|
}
|
|
3355
|
-
function spanOutputExtent(segments, sIn, sOut) {
|
|
3451
|
+
function spanOutputExtent(segments, sIn, sOut, media) {
|
|
3356
3452
|
let acc = 0;
|
|
3357
3453
|
let start = null;
|
|
3358
3454
|
let end = null;
|
|
3359
3455
|
for (const p of segments) {
|
|
3360
3456
|
const rate = segmentRate2(p);
|
|
3361
3457
|
const len = Math.max(0, p.out - p.in) / rate;
|
|
3458
|
+
if (!sameMedia(p.media, media)) {
|
|
3459
|
+
acc += len;
|
|
3460
|
+
continue;
|
|
3461
|
+
}
|
|
3362
3462
|
const ovIn = Math.max(sIn, p.in);
|
|
3363
3463
|
const ovOut = Math.min(sOut, p.out);
|
|
3364
3464
|
if (ovOut > ovIn) {
|
|
@@ -3389,7 +3489,7 @@ function trackEmitter() {
|
|
|
3389
3489
|
function zoomTrackFromDoc(zoom, segments, style = ZOOM_STYLES[DEFAULT_ZOOM_STYLE]) {
|
|
3390
3490
|
const panEase = style.panEase;
|
|
3391
3491
|
const mapped = [...zoom].sort((a, b) => a.in - b.in).flatMap((z) => {
|
|
3392
|
-
const ext = spanOutputExtent(segments, z.in, z.out);
|
|
3492
|
+
const ext = spanOutputExtent(segments, z.in, z.out, z.media);
|
|
3393
3493
|
return ext ? [{ z, tIn: ext.start, tOut: ext.end }] : [];
|
|
3394
3494
|
});
|
|
3395
3495
|
const { keyframes, push: push2 } = trackEmitter();
|
|
@@ -3408,7 +3508,7 @@ function zoomTrackFromDoc(zoom, segments, style = ZOOM_STYLES[DEFAULT_ZOOM_STYLE
|
|
|
3408
3508
|
push2(start + style.rampIn * m, entry, spanEase(z.ease, style.ease));
|
|
3409
3509
|
}
|
|
3410
3510
|
for (const e of z.followEvents ?? []) {
|
|
3411
|
-
const eOut =
|
|
3511
|
+
const eOut = sourceToTimeline(segments, e.t);
|
|
3412
3512
|
if (eOut === null || eOut <= tIn || eOut >= tOut) continue;
|
|
3413
3513
|
const next2 = [cur[0], e.cx, e.cy];
|
|
3414
3514
|
push2(eOut, cur, "none");
|
|
@@ -3451,7 +3551,7 @@ function tiltTrackFromDoc(tilt, segments, motion = {}) {
|
|
|
3451
3551
|
const panEase = TILT_PAN_EASE;
|
|
3452
3552
|
const rest = [0, 0];
|
|
3453
3553
|
const mapped = [...tilt].sort((a, b) => a.in - b.in).flatMap((z) => {
|
|
3454
|
-
const ext = spanOutputExtent(segments, z.in, z.out);
|
|
3554
|
+
const ext = spanOutputExtent(segments, z.in, z.out, z.media);
|
|
3455
3555
|
return ext ? [{ z, tIn: ext.start, tOut: ext.end }] : [];
|
|
3456
3556
|
});
|
|
3457
3557
|
const { keyframes, push: push2 } = trackEmitter();
|
|
@@ -3494,7 +3594,7 @@ function camTrackFromDoc(cam, spans, segments, W, H = 1080) {
|
|
|
3494
3594
|
];
|
|
3495
3595
|
const panEase = CAM_PAN_EASE;
|
|
3496
3596
|
const mapped = [...spans].sort((a, b) => a.in - b.in).flatMap((z) => {
|
|
3497
|
-
const ext = spanOutputExtent(segments, z.in, z.out);
|
|
3597
|
+
const ext = spanOutputExtent(segments, z.in, z.out, z.media);
|
|
3498
3598
|
return ext ? [{ z, tIn: ext.start, tOut: ext.end }] : [];
|
|
3499
3599
|
});
|
|
3500
3600
|
const { keyframes, push: push2 } = trackEmitter();
|
|
@@ -3870,6 +3970,26 @@ var SETUP = `async (ctx) => {
|
|
|
3870
3970
|
// the same sync as the screen video (source-anchored, so element time == the
|
|
3871
3971
|
// video's). Unmuted \u2014 syncVid's autoplay net re-mutes on policy rejection.
|
|
3872
3972
|
const mic = ctx.data.micSrc ? await load(ctx.data.micSrc, false) : null
|
|
3973
|
+
// The take's OTHER media (concat): one element each, decoded the way the
|
|
3974
|
+
// primary is; ON_FRAME drives the one under the playhead and pauses the
|
|
3975
|
+
// rest. Absent = one media, byte-identical.
|
|
3976
|
+
const mediaEls = {}
|
|
3977
|
+
for (const m of ctx.data.media || []) {
|
|
3978
|
+
try {
|
|
3979
|
+
const el = m.isImage ? await loadImage(m.src) : await load(m.src, !m.hasAudio)
|
|
3980
|
+
if (!m.isImage && ctx.data.videoDecodeMode === 'webcodecs') {
|
|
3981
|
+
try {
|
|
3982
|
+
const wp = await makeWcProvider(m.src)
|
|
3983
|
+
if (wp && (!el.videoWidth || (wp.width === el.videoWidth && wp.height === el.videoHeight))) el.__voilaWc = wp
|
|
3984
|
+
} catch (e) {
|
|
3985
|
+
console.warn('[voila] webcodecs provider unavailable for a media, seeks stay html5', e)
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
mediaEls[m.id] = el
|
|
3989
|
+
} catch (e) {
|
|
3990
|
+
console.warn('[voila] a media failed to load', m.id, e)
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3873
3993
|
// Background media (frame.backgroundMedia): warm-load so the first paint is
|
|
3874
3994
|
// complete. FAIL-OPEN \u2014 a bad key degrades to the CSS fill underneath, never
|
|
3875
3995
|
// a dead LOAD (unlike the recording, which is the comp's reason to exist).
|
|
@@ -3898,9 +4018,27 @@ var SETUP = `async (ctx) => {
|
|
|
3898
4018
|
const d = ctx.data
|
|
3899
4019
|
const TL = globalThis.__vosTimeline
|
|
3900
4020
|
const srcT = TL.mapTime(d.segments || [], t)
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
4021
|
+
// The media under the playhead (concat): step its element; the
|
|
4022
|
+
// primary's sidecars belong to the primary's moments only.
|
|
4023
|
+
let am = ''
|
|
4024
|
+
{
|
|
4025
|
+
let acc = 0
|
|
4026
|
+
const sg = d.segments || []
|
|
4027
|
+
for (let i = 0; i < sg.length; i++) {
|
|
4028
|
+
const s0 = sg[i]
|
|
4029
|
+
const ln = Math.max(0, s0.out - s0.in) / (s0.rate || 1)
|
|
4030
|
+
if (t < acc + ln || i === sg.length - 1) { am = s0.media || ''; break }
|
|
4031
|
+
acc += ln
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
const amEl = am ? mediaEls[am] : null
|
|
4035
|
+
if (amEl) {
|
|
4036
|
+
if (amEl.play) stepPaused(amEl, srcT)
|
|
4037
|
+
} else {
|
|
4038
|
+
if (video.play) stepPaused(video, srcT)
|
|
4039
|
+
if (cam) stepPaused(cam, srcT)
|
|
4040
|
+
if (mic) stepPaused(mic, srcT)
|
|
4041
|
+
}
|
|
3904
4042
|
const bg2 = d.frame && d.frame.backgroundMedia
|
|
3905
4043
|
if (bg2 && bg2.key && bg2.kind !== 'image') {
|
|
3906
4044
|
const bgEl2 = cache.get(bg2.key)
|
|
@@ -3910,7 +4048,7 @@ var SETUP = `async (ctx) => {
|
|
|
3910
4048
|
}
|
|
3911
4049
|
}
|
|
3912
4050
|
})
|
|
3913
|
-
return { video: video, cam: cam, mic: mic }
|
|
4051
|
+
return { video: video, cam: cam, mic: mic, media: mediaEls }
|
|
3914
4052
|
}`;
|
|
3915
4053
|
var CREATE_CONTENT = `(ctx, setupData) => {
|
|
3916
4054
|
const THREE = ctx.THREE
|
|
@@ -3961,6 +4099,7 @@ var CREATE_CONTENT = `(ctx, setupData) => {
|
|
|
3961
4099
|
// { c2d, canvas, texture }) drive the card and let bg/overlay fall back.
|
|
3962
4100
|
canvas: card.canvas, c2d: card.c2d, texture: card.texture,
|
|
3963
4101
|
video: setupData.video, cam: setupData.cam, mic: setupData.mic,
|
|
4102
|
+
media: setupData.media,
|
|
3964
4103
|
},
|
|
3965
4104
|
}
|
|
3966
4105
|
}`;
|
|
@@ -4071,6 +4210,29 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
4071
4210
|
// Output-timeline seconds (engine-fed master clock) \u2192 source seconds on screen.
|
|
4072
4211
|
var t = ctx.time || 0
|
|
4073
4212
|
var srcT = TL.mapTime(d.segments || [], t)
|
|
4213
|
+
// The media under the playhead (concat): its element, crop, cursor and
|
|
4214
|
+
// space stand in for the primary's below, and every other media's
|
|
4215
|
+
// element rests. An absent media list is the primary alone.
|
|
4216
|
+
var amId = ''
|
|
4217
|
+
{ var amAcc = 0, amSg = d.segments || []
|
|
4218
|
+
for (var amI = 0; amI < amSg.length; amI++) {
|
|
4219
|
+
var amS = amSg[amI], amLn = Math.max(0, amS.out - amS.in) / (amS.rate || 1)
|
|
4220
|
+
if (t < amAcc + amLn || amI === amSg.length - 1) { amId = amS.media || ''; break }
|
|
4221
|
+
amAcc += amLn
|
|
4222
|
+
} }
|
|
4223
|
+
var actM = null
|
|
4224
|
+
if (amId && d.media) for (var amJ = 0; amJ < d.media.length; amJ++) if (d.media[amJ].id === amId) actM = d.media[amJ]
|
|
4225
|
+
if (actM && r.media && r.media[amId]) video = r.media[amId]
|
|
4226
|
+
else amId = ''
|
|
4227
|
+
// The card under the playhead: another media's own card fields over the
|
|
4228
|
+
// take's frame (resolved at lowering), the take's frame for the primary.
|
|
4229
|
+
var cfr = amId && actM && actM.frame ? actM.frame : frame
|
|
4230
|
+
if (r.media) for (var amK in r.media) { var amE = r.media[amK]; if (amK !== amId && amE && amE.pause && amE.paused === false) amE.pause() }
|
|
4231
|
+
if (amId) {
|
|
4232
|
+
if (r.video && r.video.pause && r.video.paused === false) r.video.pause()
|
|
4233
|
+
if (r.cam && r.cam.pause && r.cam.paused === false) r.cam.pause()
|
|
4234
|
+
if (r.mic && r.mic.pause && r.mic.paused === false) r.mic.pause()
|
|
4235
|
+
}
|
|
4074
4236
|
var s = H / 1080 // scale design-px controls to comp px
|
|
4075
4237
|
|
|
4076
4238
|
// Play natively while playing (smooth); seek precisely otherwise (paused, scrubbing,
|
|
@@ -4152,8 +4314,8 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
4152
4314
|
} catch (e) {}
|
|
4153
4315
|
}
|
|
4154
4316
|
if (video.play) syncVid(video) // stills (HTMLImageElement) have nothing to sync
|
|
4155
|
-
if (r.cam) syncVid(r.cam)
|
|
4156
|
-
if (r.mic) syncVid(r.mic)
|
|
4317
|
+
if (r.cam && !amId) syncVid(r.cam)
|
|
4318
|
+
if (r.mic && !amId) syncVid(r.mic)
|
|
4157
4319
|
// Gain routing (live via SET_DATA). With a mic sidecar (AT split) the
|
|
4158
4320
|
// recording <video> carries SYSTEM audio \u2014 its volume is the system fader \u2014
|
|
4159
4321
|
// and the sidecar element is the voice (micGain). Legacy takes have one
|
|
@@ -4367,19 +4529,19 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
4367
4529
|
// strip is part of the card: it takes barH from the available height and the video
|
|
4368
4530
|
// sits below it \u2014 bar + video share the rounded clip and zoom together.
|
|
4369
4531
|
var pad = (frame.padding || 0) * s
|
|
4370
|
-
// Per-side placement (
|
|
4532
|
+
// Per-side placement (cfr.inset: fractions of the frame, a negative side
|
|
4371
4533
|
// bleeds the card past the edge) overrides the symmetric padding on the
|
|
4372
4534
|
// sides it names; absent sides keep pad, so the old math falls out.
|
|
4373
|
-
var ipIns =
|
|
4535
|
+
var ipIns = cfr.inset || {}
|
|
4374
4536
|
var ipL = ipIns.left == null ? pad : ipIns.left * W
|
|
4375
4537
|
var ipR = ipIns.right == null ? pad : ipIns.right * W
|
|
4376
4538
|
var ipT = ipIns.top == null ? pad : ipIns.top * H
|
|
4377
4539
|
var ipB = ipIns.bottom == null ? pad : ipIns.bottom * H
|
|
4378
|
-
var bar =
|
|
4540
|
+
var bar = cfr.browserBar || {}
|
|
4379
4541
|
// Window takes carry a viewport crop (drawImage source rect, capture px) that
|
|
4380
4542
|
// removes the real browser chrome \u2014 the card's source dims are then the CROP
|
|
4381
4543
|
// dims (meta/cursor were rewritten into crop space at doc build).
|
|
4382
|
-
var crp = d.crop || null
|
|
4544
|
+
var crp = actM ? (actM.crop || null) : (d.crop || null)
|
|
4383
4545
|
var vw = crp ? crp.w : (video.videoWidth || video.naturalWidth || 16)
|
|
4384
4546
|
var vh = crp ? crp.h : (video.videoHeight || video.naturalHeight || 9)
|
|
4385
4547
|
// Card-chrome scale: everything that belongs to the CARD (browser bar +
|
|
@@ -4393,11 +4555,11 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
4393
4555
|
// Cover fit: the CARD rect and the VIDEO rect separate. Under
|
|
4394
4556
|
// contain (default, byte-identical for every existing doc) the card IS the
|
|
4395
4557
|
// fitted video; under cover the card is the padded area itself and the
|
|
4396
|
-
// footage cover-fills it, cropped around
|
|
4558
|
+
// footage cover-fills it, cropped around cfr.focus (normalized video
|
|
4397
4559
|
// fractions, the zoom cx/cy convention; clamped so no gap ever shows).
|
|
4398
4560
|
// Chrome under cover scales by s alone (cf = 1): the card is as wide as
|
|
4399
4561
|
// the frame allows, which is the case cf existed to protect against.
|
|
4400
|
-
var fitCover =
|
|
4562
|
+
var fitCover = cfr.fit === 'cover'
|
|
4401
4563
|
var cf = fitCover ? 1 : Math.min(1, (W / H) / (vw / vh))
|
|
4402
4564
|
var s2 = s * cf
|
|
4403
4565
|
// current zoom \u2014 a standard keyframe track in OUTPUT time (hold + arrival pairs
|
|
@@ -4416,11 +4578,11 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
4416
4578
|
sc = Math.max(availW / vw, availH / vh)
|
|
4417
4579
|
dw = vw * sc; dh = vh * sc
|
|
4418
4580
|
cardX = ipL; cardY = ipT; cardW = availW; cardH = availH + barH
|
|
4419
|
-
var fcv =
|
|
4581
|
+
var fcv = cfr.focus || {}
|
|
4420
4582
|
var fcx = fcv.cx == null ? 0.5 : Math.max(0, Math.min(1, fcv.cx))
|
|
4421
4583
|
var fcy = fcv.cy == null ? 0.5 : Math.max(0, Math.min(1, fcv.cy))
|
|
4422
4584
|
// focusFollow: the crop keeps the camera's focus in frame (a 9:16 cut).
|
|
4423
|
-
if (
|
|
4585
|
+
if (cfr.focusFollow === 'camera' && zt && zt.keyframes && zt.keyframes.length) { fcx = zx; fcy = zy }
|
|
4424
4586
|
var vTop = ipT + barH
|
|
4425
4587
|
dx = Math.min(cardX, Math.max(cardX + availW - dw, cardX + availW / 2 - fcx * dw))
|
|
4426
4588
|
dy = Math.min(vTop, Math.max(vTop + availH - dh, vTop + availH / 2 - fcy * dh))
|
|
@@ -4432,14 +4594,14 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
4432
4594
|
dx = ipL + (availW - dw) / 2; dy = ipT + barH + (availH - dh) / 2
|
|
4433
4595
|
cardX = dx; cardY = dy - barH; cardW = dw; cardH = dh + barH
|
|
4434
4596
|
}
|
|
4435
|
-
var radius = (
|
|
4436
|
-
var shadow =
|
|
4597
|
+
var radius = (cfr.radius || 0) * s2
|
|
4598
|
+
var shadow = cfr.shadow || 0
|
|
4437
4599
|
// The shadow's colour (a #rrggbb; the strengths are its alpha) and the
|
|
4438
4600
|
// optional tight CONTACT layer that makes a light card sit on a light
|
|
4439
4601
|
// ground.
|
|
4440
|
-
var shC =
|
|
4602
|
+
var shC = cfr.shadowContact || 0
|
|
4441
4603
|
var shRgb = '0,0,0'
|
|
4442
|
-
var shHex =
|
|
4604
|
+
var shHex = cfr.shadowColor
|
|
4443
4605
|
if (typeof shHex === 'string' && /^#[0-9a-fA-F]{6}$/.test(shHex)) {
|
|
4444
4606
|
shRgb = parseInt(shHex.slice(1, 3), 16) + ',' + parseInt(shHex.slice(3, 5), 16) + ',' + parseInt(shHex.slice(5, 7), 16)
|
|
4445
4607
|
}
|
|
@@ -4579,15 +4741,15 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
4579
4741
|
// expanded by half the width so the stroke's inner edge lands on the card's
|
|
4580
4742
|
// own edge and never covers footage (inset, a 24px width ate 24px of the
|
|
4581
4743
|
// recording). Outer corner radius grows with the width, the CSS-border rule;
|
|
4582
|
-
// a square card stays square.
|
|
4744
|
+
// a square card stays square. cfr.border is the ALPHA (0 = off) and rides
|
|
4583
4745
|
// globalAlpha, so borderColor takes any CSS colour notation without this
|
|
4584
4746
|
// having to parse one. Card chrome, so it scales by s2.
|
|
4585
|
-
if (
|
|
4586
|
-
var bdW = (
|
|
4747
|
+
if (cfr.border) {
|
|
4748
|
+
var bdW = (cfr.borderWidth > 0 ? cfr.borderWidth : ${FRAME_BORDER_WIDTH_DEFAULT}) * s2
|
|
4587
4749
|
var bdH = bdW / 2
|
|
4588
4750
|
c.save()
|
|
4589
|
-
c.globalAlpha = Math.min(1,
|
|
4590
|
-
c.strokeStyle =
|
|
4751
|
+
c.globalAlpha = Math.min(1, cfr.border)
|
|
4752
|
+
c.strokeStyle = cfr.borderColor || '${FRAME_BORDER_COLOR_DEFAULT}'
|
|
4591
4753
|
c.lineWidth = bdW
|
|
4592
4754
|
rr(cardX - bdH, cardY - bdH, cardW + bdW, cardH + bdW, radius > 0 ? radius + bdH : 0)
|
|
4593
4755
|
c.stroke()
|
|
@@ -4598,7 +4760,7 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
4598
4760
|
// card. Contain never needs it (the video rect IS the card).
|
|
4599
4761
|
if (fitCover) { c.save(); rr(cardX, cardY, cardW, cardH, radius); c.clip() }
|
|
4600
4762
|
// cursor coordinate space + drawn radius (shared by click effects + the dot)
|
|
4601
|
-
var space = d.cursorSpace || { w: vw, h: vh }
|
|
4763
|
+
var space = (actM ? actM.cursorSpace : d.cursorSpace) || { w: vw, h: vh }
|
|
4602
4764
|
var curSize = ((d.cursorStyle && d.cursorStyle.size) || 24) * s2 * 0.5
|
|
4603
4765
|
|
|
4604
4766
|
// click effects \u2014 pure f(t): d.clicks are
|
|
@@ -4630,8 +4792,9 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
4630
4792
|
// click's source moment, or an effect near a cut would keep painting
|
|
4631
4793
|
// over the NEXT segment's unrelated footage
|
|
4632
4794
|
if (Math.abs(srcT - ck.st) > 2) continue
|
|
4633
|
-
var
|
|
4634
|
-
var
|
|
4795
|
+
var ckSp = ck.sp || space
|
|
4796
|
+
var ckAx = dx + (ck.x / (ckSp.w || vw)) * dw
|
|
4797
|
+
var ckAy = dy + (ck.y / (ckSp.h || vh)) * dh
|
|
4635
4798
|
// press dip: smoothstep down around the real mousedown (80 ms lead \u2014
|
|
4636
4799
|
// anticipation is what makes effects feel synced), hold through the real
|
|
4637
4800
|
// down\u2192up span (drags dip long), easeOutBack rebound with a slight
|
|
@@ -4744,7 +4907,7 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
4744
4907
|
// The dot is the only thing cursorStyle.visible hides \u2014 the track still drives
|
|
4745
4908
|
// cursor-follow zoom, and click effects draw above on their own switch.
|
|
4746
4909
|
// Undefined reads as visible so pre-toggle docs are unchanged.
|
|
4747
|
-
var cur = d.cursor || []
|
|
4910
|
+
var cur = (actM ? actM.cursor : d.cursor) || []
|
|
4748
4911
|
if (cur.length && !(d.cursorStyle && d.cursorStyle.visible === false)) {
|
|
4749
4912
|
var px = cur[0].x, py = cur[0].y
|
|
4750
4913
|
for (var j = 0; j < cur.length; j++) { if (cur[j].t <= srcT) { px = cur[j].x; py = cur[j].y } }
|
|
@@ -4753,7 +4916,7 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
4753
4916
|
// Idle fade: a sparse SOURCE-time opacity curve baked by cursorIdleFade.
|
|
4754
4917
|
// Linear between keys \u2014 opacity needs no easing, and the ramps are already
|
|
4755
4918
|
// shaped by where the keys sit. Absent/empty = the cursor never dwells.
|
|
4756
|
-
var cuA = 1, cuK = d.cursorFade
|
|
4919
|
+
var cuA = 1, cuK = actM ? actM.cursorFade : d.cursorFade
|
|
4757
4920
|
if (cuK && cuK.length) {
|
|
4758
4921
|
if (srcT <= cuK[0].t) cuA = cuK[0].a
|
|
4759
4922
|
else if (srcT >= cuK[cuK.length - 1].t) cuA = cuK[cuK.length - 1].a
|
|
@@ -4786,7 +4949,7 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
4786
4949
|
// runtime; the card c2d under stubs). Redraw + re-upload only while the
|
|
4787
4950
|
// bubble is active (a video \u2192 every frame), on resize, or once when it turns
|
|
4788
4951
|
// off (to clear) \u2014 so a cam-less take never uploads the overlay after frame 1.
|
|
4789
|
-
var camV = r.cam, camS = d.cam || {}
|
|
4952
|
+
var camV = amId ? null : r.cam, camS = d.cam || {}
|
|
4790
4953
|
var camOn = !camS.window || (srcT >= camS.window.in && srcT <= camS.window.out)
|
|
4791
4954
|
// Readiness is STICKY: readyState drops to HAVE_METADATA while a seek is in
|
|
4792
4955
|
// flight, so gating each frame on it makes the bubble vanish on every scrub
|
|
@@ -5128,7 +5291,8 @@ function lowerToComposition(input) {
|
|
|
5128
5291
|
segments: rated.map((seg) => ({
|
|
5129
5292
|
in: round6(seg.in),
|
|
5130
5293
|
out: round6(seg.out),
|
|
5131
|
-
...seg.rate !== void 0 && seg.rate !== 1 ? { rate: round6(seg.rate) } : {}
|
|
5294
|
+
...seg.rate !== void 0 && seg.rate !== 1 ? { rate: round6(seg.rate) } : {},
|
|
5295
|
+
...seg.media ? { media: seg.media } : {}
|
|
5132
5296
|
})),
|
|
5133
5297
|
frame: doc.frame,
|
|
5134
5298
|
micGain: doc.micGain ?? 1,
|
|
@@ -5139,6 +5303,38 @@ function lowerToComposition(input) {
|
|
|
5139
5303
|
})),
|
|
5140
5304
|
cursorStyle: doc.cursor,
|
|
5141
5305
|
cursorSpace: { w: doc.source.meta.width, h: doc.source.meta.height },
|
|
5306
|
+
// The other media (concat): each with its element's source, its crop
|
|
5307
|
+
// and its own cursor in its own capture space; absent = one media.
|
|
5308
|
+
...doc.media && doc.media.length ? {
|
|
5309
|
+
media: doc.media.map((m) => {
|
|
5310
|
+
const sm = smoothCursor(m.cursor, {
|
|
5311
|
+
factor: doc.cursor.smoothing,
|
|
5312
|
+
clickSnap: fx.style !== "none" || fx.press
|
|
5313
|
+
});
|
|
5314
|
+
const fade = doc.cursor.hideWhenIdle !== false && doc.cursor.visible !== false ? cursorIdleFade(m.cursor, {
|
|
5315
|
+
space: { w: m.meta.width, h: m.meta.height },
|
|
5316
|
+
sourceDuration: (m.meta.durationMs || 0) / 1e3
|
|
5317
|
+
}) : [];
|
|
5318
|
+
return {
|
|
5319
|
+
id: m.id,
|
|
5320
|
+
src: m.videoKey,
|
|
5321
|
+
isImage: m.sourceKind === "image",
|
|
5322
|
+
crop: m.crop ?? null,
|
|
5323
|
+
hasAudio: !!m.meta.hasAudio,
|
|
5324
|
+
cursor: sm.map((p) => ({
|
|
5325
|
+
t: round6(p.t),
|
|
5326
|
+
x: round6(p.x),
|
|
5327
|
+
y: round6(p.y)
|
|
5328
|
+
})),
|
|
5329
|
+
cursorSpace: { w: m.meta.width, h: m.meta.height },
|
|
5330
|
+
// The card this media wears (its own placement, bar, corner,
|
|
5331
|
+
// shadow, border, fit and focus over the take's), resolved
|
|
5332
|
+
// here so ON_FRAME swaps one object and reads no document.
|
|
5333
|
+
frame: cardFields(mediaFrame(doc, m.id)),
|
|
5334
|
+
...fade.length ? { cursorFade: fade } : {}
|
|
5335
|
+
};
|
|
5336
|
+
})
|
|
5337
|
+
} : {},
|
|
5142
5338
|
...cursorFade.length ? { cursorFade } : {},
|
|
5143
5339
|
// Click effects: OUTPUT-anchored click records + resolved styling (named
|
|
5144
5340
|
// intensities/colors become numbers HERE — ON_FRAME reads no registry).
|
|
@@ -5207,10 +5403,24 @@ function clickFxData(doc, rated) {
|
|
|
5207
5403
|
const meta = doc.source.meta;
|
|
5208
5404
|
const level = CLICK_FX_INTENSITY[fx.intensity];
|
|
5209
5405
|
return {
|
|
5210
|
-
clicks: on ?
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5406
|
+
clicks: on ? [
|
|
5407
|
+
...extractClicks(doc.source.cursor, rated, {
|
|
5408
|
+
rects: fx.style === "highlight",
|
|
5409
|
+
space: { w: meta.width, h: meta.height }
|
|
5410
|
+
}),
|
|
5411
|
+
// The other media's clicks map through their own pieces and carry
|
|
5412
|
+
// their own capture space, so the ring lands where the press was.
|
|
5413
|
+
...(doc.media ?? []).flatMap(
|
|
5414
|
+
(m) => extractClicks(m.cursor, rated, {
|
|
5415
|
+
rects: fx.style === "highlight",
|
|
5416
|
+
space: { w: m.meta.width, h: m.meta.height },
|
|
5417
|
+
media: m.id
|
|
5418
|
+
}).map((ck) => ({
|
|
5419
|
+
...ck,
|
|
5420
|
+
sp: { w: m.meta.width, h: m.meta.height }
|
|
5421
|
+
}))
|
|
5422
|
+
)
|
|
5423
|
+
].sort((a, b) => a.ot - b.ot) : [],
|
|
5214
5424
|
clickFx: {
|
|
5215
5425
|
style: fx.style,
|
|
5216
5426
|
press: fx.press,
|
|
@@ -6835,7 +7045,7 @@ import {
|
|
|
6835
7045
|
mapTime as mapTime2,
|
|
6836
7046
|
removeSegment,
|
|
6837
7047
|
segmentRate as segmentRate3,
|
|
6838
|
-
sourceToTimeline as
|
|
7048
|
+
sourceToTimeline as sourceToTimeline2,
|
|
6839
7049
|
splitBySpeed as splitBySpeed3,
|
|
6840
7050
|
totalDuration as totalDuration6,
|
|
6841
7051
|
trimSegment
|
|
@@ -6920,7 +7130,7 @@ var videoLane = {
|
|
|
6920
7130
|
freezes.filter((f) => f.at < seg.out - 1e-9),
|
|
6921
7131
|
index === 0 || !contiguous(segments, index)
|
|
6922
7132
|
);
|
|
6923
|
-
const anchorOut =
|
|
7133
|
+
const anchorOut = sourceToTimeline2(fullMap, Math.min(edgeSrc, sourceDuration)) ?? edgeSrc;
|
|
6924
7134
|
const sourceT = mapTime2(fullMap, anchorOut + (g.t - edgeOutNow));
|
|
6925
7135
|
const next = trimSegment(
|
|
6926
7136
|
segments,
|
|
@@ -7050,10 +7260,16 @@ var freezeLane = {
|
|
|
7050
7260
|
const srcT = round11(mapTime2(rated, Math.max(0, g.t)));
|
|
7051
7261
|
if (!kept(srcT) || !clear(srcT)) return null;
|
|
7052
7262
|
const id = nextFreezeId(doc);
|
|
7263
|
+
const mediaHere = mediaAtOutput(rated, Math.max(0, g.t));
|
|
7053
7264
|
return (d) => {
|
|
7054
7265
|
d.freeze = [
|
|
7055
7266
|
...d.freeze ?? [],
|
|
7056
|
-
{
|
|
7267
|
+
{
|
|
7268
|
+
id,
|
|
7269
|
+
at: srcT,
|
|
7270
|
+
seconds: FREEZE_DEFAULT_SECONDS,
|
|
7271
|
+
...mediaHere ? { media: mediaHere } : {}
|
|
7272
|
+
}
|
|
7057
7273
|
].sort((a, b) => a.at - b.at);
|
|
7058
7274
|
};
|
|
7059
7275
|
}
|
|
@@ -7138,7 +7354,7 @@ var zoomLane = {
|
|
|
7138
7354
|
items(doc) {
|
|
7139
7355
|
const segments = ratedSegments(doc);
|
|
7140
7356
|
return doc.zoom.flatMap((z) => {
|
|
7141
|
-
const ext = spanOutputExtent(segments, z.in, z.out);
|
|
7357
|
+
const ext = spanOutputExtent(segments, z.in, z.out, z.media);
|
|
7142
7358
|
return ext === null ? [] : [
|
|
7143
7359
|
{
|
|
7144
7360
|
id: z.id,
|
|
@@ -7156,10 +7372,13 @@ var zoomLane = {
|
|
|
7156
7372
|
const sourceDuration = anchorSourceDuration(doc);
|
|
7157
7373
|
if (g.type === "create") {
|
|
7158
7374
|
const srcT = mapTime2(rated, Math.max(0, g.t));
|
|
7159
|
-
|
|
7160
|
-
const
|
|
7161
|
-
const
|
|
7162
|
-
|
|
7375
|
+
const mediaHere = mediaAtOutput(rated, Math.max(0, g.t));
|
|
7376
|
+
const here = spans.filter((z) => (z.media ?? "") === mediaHere);
|
|
7377
|
+
const mediaLen = mediaDuration(mediaSource(doc, mediaHere) ?? doc.source);
|
|
7378
|
+
if (here.some((z) => srcT >= z.in && srcT < z.out)) return null;
|
|
7379
|
+
const next = here.filter((z) => z.in > srcT).sort((a, b) => a.in - b.in).at(0);
|
|
7380
|
+
const limit = Math.min(mediaLen, next ? next.in : mediaLen);
|
|
7381
|
+
const len = Math.min(spanDefaultLen(mediaLen), limit - srcT);
|
|
7163
7382
|
if (len < ZOOM_SPAN_MIN * rateAt(rated, srcT)) return null;
|
|
7164
7383
|
const id = nextZoomId(doc);
|
|
7165
7384
|
const { cx, cy } = cursorFocusAt(doc, srcT);
|
|
@@ -7173,7 +7392,8 @@ var zoomLane = {
|
|
|
7173
7392
|
level: DEFAULT_ZOOM_LEVEL,
|
|
7174
7393
|
cx,
|
|
7175
7394
|
cy,
|
|
7176
|
-
source: "manual"
|
|
7395
|
+
source: "manual",
|
|
7396
|
+
...mediaHere ? { media: mediaHere } : {}
|
|
7177
7397
|
}
|
|
7178
7398
|
].sort((a, b) => a.in - b.in);
|
|
7179
7399
|
};
|
|
@@ -7263,7 +7483,7 @@ var tiltLane = {
|
|
|
7263
7483
|
items(doc) {
|
|
7264
7484
|
const segments = ratedSegments(doc);
|
|
7265
7485
|
return (doc.tilt ?? []).flatMap((z) => {
|
|
7266
|
-
const ext = spanOutputExtent(segments, z.in, z.out);
|
|
7486
|
+
const ext = spanOutputExtent(segments, z.in, z.out, z.media);
|
|
7267
7487
|
return ext === null ? [] : [
|
|
7268
7488
|
{
|
|
7269
7489
|
id: z.id,
|
|
@@ -7281,10 +7501,13 @@ var tiltLane = {
|
|
|
7281
7501
|
const sourceDuration = anchorSourceDuration(doc);
|
|
7282
7502
|
if (g.type === "create") {
|
|
7283
7503
|
const srcT = mapTime2(rated, Math.max(0, g.t));
|
|
7284
|
-
|
|
7285
|
-
const
|
|
7286
|
-
const
|
|
7287
|
-
|
|
7504
|
+
const mediaHere = mediaAtOutput(rated, Math.max(0, g.t));
|
|
7505
|
+
const here = spans.filter((z) => (z.media ?? "") === mediaHere);
|
|
7506
|
+
const mediaLen = mediaDuration(mediaSource(doc, mediaHere) ?? doc.source);
|
|
7507
|
+
if (here.some((z) => srcT >= z.in && srcT < z.out)) return null;
|
|
7508
|
+
const next = here.filter((z) => z.in > srcT).sort((a, b) => a.in - b.in).at(0);
|
|
7509
|
+
const limit = Math.min(mediaLen, next ? next.in : mediaLen);
|
|
7510
|
+
const len = Math.min(spanDefaultLen(mediaLen), limit - srcT);
|
|
7288
7511
|
if (len < TILT_SPAN_MIN * rateAt(rated, srcT)) return null;
|
|
7289
7512
|
const id = nextTiltId(doc);
|
|
7290
7513
|
return (d) => {
|
|
@@ -7296,7 +7519,8 @@ var tiltLane = {
|
|
|
7296
7519
|
out: round11(srcT + len),
|
|
7297
7520
|
rx: DEFAULT_TILT_POSE.rx,
|
|
7298
7521
|
ry: DEFAULT_TILT_POSE.ry,
|
|
7299
|
-
source: "manual"
|
|
7522
|
+
source: "manual",
|
|
7523
|
+
...mediaHere ? { media: mediaHere } : {}
|
|
7300
7524
|
}
|
|
7301
7525
|
].sort((a, b) => a.in - b.in);
|
|
7302
7526
|
};
|
|
@@ -7376,7 +7600,7 @@ var camMoveLane = {
|
|
|
7376
7600
|
if (!doc.source.camKey) return [];
|
|
7377
7601
|
const segments = ratedSegments(doc);
|
|
7378
7602
|
return (doc.camMotion ?? []).flatMap((z) => {
|
|
7379
|
-
const ext = spanOutputExtent(segments, z.in, z.out);
|
|
7603
|
+
const ext = spanOutputExtent(segments, z.in, z.out, z.media);
|
|
7380
7604
|
return ext === null ? [] : [
|
|
7381
7605
|
{
|
|
7382
7606
|
id: z.id,
|
|
@@ -7642,6 +7866,7 @@ var speedLane = {
|
|
|
7642
7866
|
const len = Math.min(spanDefaultLen(sourceDuration), limit - srcT);
|
|
7643
7867
|
if (len < SPEED_SPAN_MIN * DEFAULT_SPEED_RATE) return null;
|
|
7644
7868
|
const id = nextSpeedId(doc);
|
|
7869
|
+
const mediaHere = mediaAtOutput(rated, Math.max(0, g.t));
|
|
7645
7870
|
return (d) => {
|
|
7646
7871
|
d.speed = [
|
|
7647
7872
|
...d.speed ?? [],
|
|
@@ -7650,7 +7875,8 @@ var speedLane = {
|
|
|
7650
7875
|
in: round11(srcT),
|
|
7651
7876
|
out: round11(srcT + len),
|
|
7652
7877
|
rate: DEFAULT_SPEED_RATE,
|
|
7653
|
-
source: "manual"
|
|
7878
|
+
source: "manual",
|
|
7879
|
+
...mediaHere ? { media: mediaHere } : {}
|
|
7654
7880
|
}
|
|
7655
7881
|
].sort((a, b) => a.in - b.in);
|
|
7656
7882
|
};
|
|
@@ -7698,7 +7924,7 @@ var speedLane = {
|
|
|
7698
7924
|
out: sp.out
|
|
7699
7925
|
};
|
|
7700
7926
|
} else {
|
|
7701
|
-
const startOut =
|
|
7927
|
+
const startOut = sourceToTimeline2(base, sp.in) ?? sp.in;
|
|
7702
7928
|
const sourceT = sp.in + sp.rate * Math.max(0, g.t - startOut);
|
|
7703
7929
|
next = {
|
|
7704
7930
|
in: sp.in,
|
|
@@ -8245,6 +8471,7 @@ export {
|
|
|
8245
8471
|
LOOK_KINDS,
|
|
8246
8472
|
MEDIA_ENTER_KINDS,
|
|
8247
8473
|
MEDIA_EXIT_KINDS,
|
|
8474
|
+
MEDIA_FRAME_KEYS,
|
|
8248
8475
|
MINIMAL_BAR_THEMES,
|
|
8249
8476
|
MOTION_DELTA,
|
|
8250
8477
|
MOTION_EASE,
|
|
@@ -8261,6 +8488,7 @@ export {
|
|
|
8261
8488
|
OVERLAY_Z,
|
|
8262
8489
|
PLATE_GROUND,
|
|
8263
8490
|
PLAYBACK_ACTIVITY,
|
|
8491
|
+
PRIMARY_MEDIA,
|
|
8264
8492
|
PROGRAM_RETIME,
|
|
8265
8493
|
REJECT_OVERLAP,
|
|
8266
8494
|
REST_TILT_ID,
|
|
@@ -8323,6 +8551,7 @@ export {
|
|
|
8323
8551
|
camTrackFromDoc,
|
|
8324
8552
|
cardEnter,
|
|
8325
8553
|
cardExit,
|
|
8554
|
+
cardFields,
|
|
8326
8555
|
cardInset,
|
|
8327
8556
|
cardPointToScreen,
|
|
8328
8557
|
cardPoseTrack,
|
|
@@ -8348,6 +8577,7 @@ export {
|
|
|
8348
8577
|
destinationsForChannel,
|
|
8349
8578
|
docCardLayout,
|
|
8350
8579
|
docFreezes,
|
|
8580
|
+
docMediaList,
|
|
8351
8581
|
docOutputDuration,
|
|
8352
8582
|
docRestTime,
|
|
8353
8583
|
docToCropSpace,
|
|
@@ -8393,12 +8623,18 @@ export {
|
|
|
8393
8623
|
lowerProgramDoc,
|
|
8394
8624
|
lowerStudioDoc,
|
|
8395
8625
|
lowerToComposition,
|
|
8626
|
+
mediaAtOutput,
|
|
8627
|
+
mediaDuration,
|
|
8628
|
+
mediaFrame,
|
|
8629
|
+
mediaKey,
|
|
8630
|
+
mediaSource,
|
|
8396
8631
|
micLane,
|
|
8397
8632
|
migrateHostedDoc,
|
|
8398
8633
|
migrateMotion,
|
|
8399
8634
|
momentsFromDoc,
|
|
8400
8635
|
motionTrack,
|
|
8401
8636
|
musicBedClip,
|
|
8637
|
+
nextMediaId,
|
|
8402
8638
|
normalizeCaptureSpace,
|
|
8403
8639
|
objectMotionBase,
|
|
8404
8640
|
objectMotionPoseAt,
|
|
@@ -8441,6 +8677,7 @@ export {
|
|
|
8441
8677
|
resolveText3dAsset,
|
|
8442
8678
|
resolveZoomStyle,
|
|
8443
8679
|
restLeanOf,
|
|
8680
|
+
sameMedia,
|
|
8444
8681
|
sceneChanges,
|
|
8445
8682
|
scrollRuns,
|
|
8446
8683
|
setSpeedInRange,
|