@vosjs/studio-core 0.15.1 → 0.17.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 +74 -1
- package/dist/index.js +391 -68
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -98,6 +98,8 @@ function clipLength(clip) {
|
|
|
98
98
|
return clip.loop ? Math.max(span, clip.loopLen ?? span) : span;
|
|
99
99
|
}
|
|
100
100
|
var MOTION_EASE = "power2.inOut";
|
|
101
|
+
var LAYER_CARD_SHADOW = 0.32;
|
|
102
|
+
var MEDIA_KEY_PREFIX = "media:";
|
|
101
103
|
var OVERLAY_SCALE_MAX = 8;
|
|
102
104
|
var OVERLAY_MEDIA_DEFAULT_WIDTH = 0.35;
|
|
103
105
|
var OVERLAY_MEDIA_DEFAULT_RADIUS = 12;
|
|
@@ -582,6 +584,15 @@ function cardFields(frame) {
|
|
|
582
584
|
}
|
|
583
585
|
return out;
|
|
584
586
|
}
|
|
587
|
+
var mediaRef = (id) => MEDIA_KEY_PREFIX + id;
|
|
588
|
+
function mediaRefId(key) {
|
|
589
|
+
if (typeof key !== "string" || !key.startsWith(MEDIA_KEY_PREFIX)) return null;
|
|
590
|
+
return key.slice(MEDIA_KEY_PREFIX.length);
|
|
591
|
+
}
|
|
592
|
+
function layerMedia(doc, key) {
|
|
593
|
+
const id = mediaRefId(key);
|
|
594
|
+
return id === null ? null : mediaSource(doc, id);
|
|
595
|
+
}
|
|
585
596
|
function nextMediaId(doc) {
|
|
586
597
|
const taken = new Set((doc.media ?? []).map((m) => m.id));
|
|
587
598
|
let n = 1;
|
|
@@ -590,6 +601,50 @@ function nextMediaId(doc) {
|
|
|
590
601
|
}
|
|
591
602
|
|
|
592
603
|
// src/lower/motion.ts
|
|
604
|
+
function ownFreezes(doc) {
|
|
605
|
+
return docFreezes(doc).filter((f) => !f.from);
|
|
606
|
+
}
|
|
607
|
+
var STILL_DEFAULT = 0.5;
|
|
608
|
+
var HERO_BEAT = 0.25;
|
|
609
|
+
var OPENING_WINDOW = 3;
|
|
610
|
+
var OPENING_ENTER_SECONDS = 0.6;
|
|
611
|
+
var r3 = (v) => Math.round(v * 1e3) / 1e3;
|
|
612
|
+
function docStillTime(doc) {
|
|
613
|
+
const segs = docSegmentsOf(doc);
|
|
614
|
+
const rated = withFreezes(
|
|
615
|
+
splitBySpeed(segs, doc.speed ?? []),
|
|
616
|
+
docFreezes(doc)
|
|
617
|
+
);
|
|
618
|
+
const footage = totalDuration(rated);
|
|
619
|
+
const end = outputEnd(doc, footage);
|
|
620
|
+
const clampOut = (v) => Math.max(0, Math.min(end, v));
|
|
621
|
+
if (typeof doc.still === "number" && Number.isFinite(doc.still))
|
|
622
|
+
return r3(clampOut(doc.still));
|
|
623
|
+
if (!doc.endCard) {
|
|
624
|
+
const own = ownFreezes(doc);
|
|
625
|
+
if (own.length) {
|
|
626
|
+
const marks = freezeOutputExtents(
|
|
627
|
+
splitBySpeed(segs, doc.speed ?? []),
|
|
628
|
+
own
|
|
629
|
+
);
|
|
630
|
+
let rest = null;
|
|
631
|
+
for (const m of marks.values())
|
|
632
|
+
if (rest === null || m.t > rest) rest = m.t;
|
|
633
|
+
if (rest !== null && rest >= 0) return r3(clampOut(rest));
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (!(footage > 0)) return r3(clampOut(STILL_DEFAULT));
|
|
637
|
+
let hero = entranceSeconds(cardEnter(doc.frame));
|
|
638
|
+
for (const clip of doc.overlays ?? []) {
|
|
639
|
+
if (clip.start > OPENING_WINDOW) continue;
|
|
640
|
+
const step = enterOf(clip.anim);
|
|
641
|
+
const seconds = !step || step.kind === "none" ? 0 : Math.max(0, step.seconds ?? OPENING_ENTER_SECONDS);
|
|
642
|
+
hero = Math.max(hero, clip.start + seconds);
|
|
643
|
+
}
|
|
644
|
+
hero = Math.max(STILL_DEFAULT, hero + HERO_BEAT);
|
|
645
|
+
hero = Math.min(hero, Math.max(0, footage - 1 / 30));
|
|
646
|
+
return r3(clampOut(hero));
|
|
647
|
+
}
|
|
593
648
|
function docRestTime(doc) {
|
|
594
649
|
if (doc.endCard) return null;
|
|
595
650
|
const freezes = docFreezes(doc);
|
|
@@ -2654,6 +2709,8 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
2654
2709
|
// over its whole life, so it must repaint every visible frame \u2014 the
|
|
2655
2710
|
// signature alone would freeze it between edits (the olVisSig trap).
|
|
2656
2711
|
if (ol0.track) olAnim = true
|
|
2712
|
+
// A layer CARD is painted on its own plane every frame it is visible.
|
|
2713
|
+
if (ol0.card) olAnim = true
|
|
2657
2714
|
// Video overlays advance every frame; images redraw until decoded.
|
|
2658
2715
|
if (ol0.kind === 'video') olAnim = true
|
|
2659
2716
|
else if (ol0.kind === 'image') {
|
|
@@ -2663,6 +2720,76 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
2663
2720
|
}
|
|
2664
2721
|
var ovSig = W + 'x' + H + '|' + olVisSig
|
|
2665
2722
|
var ovDirty = ov.sig !== ovSig || ov.active || olAnim
|
|
2723
|
+
// The layer cards visible this frame, with their pose, for the card
|
|
2724
|
+
// planes below (the overlay loop collects them instead of painting).
|
|
2725
|
+
var lcList = []
|
|
2726
|
+
// Acquire a media overlay's element through the shared cache and sync it
|
|
2727
|
+
// to CLIP-LOCAL time (pure f(t); muted always \u2014 soundtracks belong to
|
|
2728
|
+
// doc.audio). Null until it has a frame to draw. One seam for the flat
|
|
2729
|
+
// picture and the layer card.
|
|
2730
|
+
function olAcquire(ol, olT) {
|
|
2731
|
+
var olEl = ns.videoCache ? ns.videoCache.get(ol.key) : null
|
|
2732
|
+
if (!olEl && ns.videoCache) {
|
|
2733
|
+
if (ol.kind === 'image') {
|
|
2734
|
+
olEl = new Image()
|
|
2735
|
+
olEl.crossOrigin = 'anonymous'
|
|
2736
|
+
olEl.src = ol.key
|
|
2737
|
+
} else {
|
|
2738
|
+
olEl = document.createElement('video')
|
|
2739
|
+
olEl.crossOrigin = 'anonymous'
|
|
2740
|
+
olEl.muted = true
|
|
2741
|
+
olEl.playsInline = true
|
|
2742
|
+
olEl.preload = 'auto'
|
|
2743
|
+
olEl.src = ol.key
|
|
2744
|
+
olEl.load()
|
|
2745
|
+
}
|
|
2746
|
+
ns.videoCache.set(ol.key, olEl)
|
|
2747
|
+
}
|
|
2748
|
+
if (!olEl) return null
|
|
2749
|
+
var olIsImg = ol.kind === 'image'
|
|
2750
|
+
if (!olIsImg && olEl.play) {
|
|
2751
|
+
var olDur = olEl.duration || 0
|
|
2752
|
+
var olMT = olT
|
|
2753
|
+
if (ol.loop && olDur > 0) olMT = olT % olDur
|
|
2754
|
+
else if (olDur > 0) olMT = Math.min(olT, olDur - 0.001)
|
|
2755
|
+
try {
|
|
2756
|
+
if (playing) {
|
|
2757
|
+
if (olEl.playbackRate !== 1) olEl.playbackRate = 1
|
|
2758
|
+
var olDrift = Math.abs(olEl.currentTime - olMT)
|
|
2759
|
+
if (olDur > 0 && !olEl.seeking && olDrift > 0.3 && (!ol.loop || olDur - olDrift > 0.3)) olEl.currentTime = olMT
|
|
2760
|
+
if (!ol.loop && olDur > 0 && olT >= olDur) { if (!olEl.paused) olEl.pause() }
|
|
2761
|
+
else if (olEl.paused) { var olP = olEl.play(); if (olP && olP.catch) olP.catch(function () {}) }
|
|
2762
|
+
} else {
|
|
2763
|
+
if (!olEl.paused) olEl.pause()
|
|
2764
|
+
var olTarget = Math.min(olMT, olEl.duration || olMT)
|
|
2765
|
+
// Coalesce scrub seeks (the backgroundMedia pattern): re-assigning
|
|
2766
|
+
// currentTime aborts the in-flight seek, so a per-frame scrub keeps
|
|
2767
|
+
// a remote source seeking forever. Defer until 'seeked' lands.
|
|
2768
|
+
if (olEl.readyState >= 1 && !olEl.seeking && Math.abs(olEl.currentTime - olTarget) > 0.02) {
|
|
2769
|
+
if (ns.pendingDecodes) {
|
|
2770
|
+
var olDp = new Promise(function (resolve) {
|
|
2771
|
+
var olDone = function () { olEl.removeEventListener('seeked', olDone); resolve() }
|
|
2772
|
+
olEl.addEventListener('seeked', olDone)
|
|
2773
|
+
setTimeout(olDone, 250)
|
|
2774
|
+
})
|
|
2775
|
+
ns.pendingDecodes.add(olDp)
|
|
2776
|
+
olDp.finally(function () { ns.pendingDecodes.delete(olDp) })
|
|
2777
|
+
}
|
|
2778
|
+
olEl.currentTime = olTarget
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
} catch (e) {}
|
|
2782
|
+
}
|
|
2783
|
+
// Video readiness is STICKY through seeks (the cam-bubble pattern):
|
|
2784
|
+
// readyState dips below HAVE_CURRENT_DATA while a scrub seek is in
|
|
2785
|
+
// flight, and this layer repaints every frame a video clip is visible \u2014
|
|
2786
|
+
// gating each frame on it would blink the clip out for the whole drag.
|
|
2787
|
+
// After the first decoded frame, keep drawing: Chrome paints the
|
|
2788
|
+
// element's retained frame mid-seek.
|
|
2789
|
+
if (!olIsImg && olEl.readyState >= 2) olEl.__vosHasFrame = true
|
|
2790
|
+
var olReady = olIsImg ? !!(olEl.complete && olEl.naturalWidth) : !!(olEl.readyState >= 2 || olEl.__vosHasFrame)
|
|
2791
|
+
return olReady ? olEl : null
|
|
2792
|
+
}
|
|
2666
2793
|
if (ovDirty) {
|
|
2667
2794
|
ovC.clearRect(0, 0, W, H)
|
|
2668
2795
|
// --- text overlays (compositor v2): OUTPUT-anchored clips, styles resolved
|
|
@@ -2720,71 +2847,19 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
2720
2847
|
if (olPose) olA *= Math.max(0, Math.min(1, olPose[4]))
|
|
2721
2848
|
if (olA <= 0.004) continue
|
|
2722
2849
|
if (ol.kind === 'image' || ol.kind === 'video') {
|
|
2850
|
+
// A layer CARD is painted by the card painter on its own plane, below:
|
|
2851
|
+
// collect it with its pose and skip the flat picture.
|
|
2852
|
+
if (ol.card) {
|
|
2853
|
+
lcList.push({ ol: ol, t: olT, a: olA * (ol.opacity == null ? 1 : ol.opacity), x: olMX, y: olMY, s: olMS, r: olMR, yof: olYof })
|
|
2854
|
+
continue
|
|
2855
|
+
}
|
|
2723
2856
|
// Media overlay: lazy-acquire through the shared cache (SET_DATA-added
|
|
2724
2857
|
// clips load without a LOAD \u2014 the backgroundMedia pattern), sync video
|
|
2725
2858
|
// to CLIP-LOCAL time (pure f(t)), draw a rounded media card centered on
|
|
2726
2859
|
// the fraction anchor. Muted always \u2014 soundtracks belong to doc.audio.
|
|
2727
|
-
var olEl =
|
|
2728
|
-
if (!olEl && ns.videoCache) {
|
|
2729
|
-
if (ol.kind === 'image') {
|
|
2730
|
-
olEl = new Image()
|
|
2731
|
-
olEl.crossOrigin = 'anonymous'
|
|
2732
|
-
olEl.src = ol.key
|
|
2733
|
-
} else {
|
|
2734
|
-
olEl = document.createElement('video')
|
|
2735
|
-
olEl.crossOrigin = 'anonymous'
|
|
2736
|
-
olEl.muted = true
|
|
2737
|
-
olEl.playsInline = true
|
|
2738
|
-
olEl.preload = 'auto'
|
|
2739
|
-
olEl.src = ol.key
|
|
2740
|
-
olEl.load()
|
|
2741
|
-
}
|
|
2742
|
-
ns.videoCache.set(ol.key, olEl)
|
|
2743
|
-
}
|
|
2860
|
+
var olEl = olAcquire(ol, olT)
|
|
2744
2861
|
if (!olEl) continue
|
|
2745
2862
|
var olIsImg = ol.kind === 'image'
|
|
2746
|
-
if (!olIsImg && olEl.play) {
|
|
2747
|
-
var olDur = olEl.duration || 0
|
|
2748
|
-
var olMT = olT
|
|
2749
|
-
if (ol.loop && olDur > 0) olMT = olT % olDur
|
|
2750
|
-
else if (olDur > 0) olMT = Math.min(olT, olDur - 0.001)
|
|
2751
|
-
try {
|
|
2752
|
-
if (playing) {
|
|
2753
|
-
if (olEl.playbackRate !== 1) olEl.playbackRate = 1
|
|
2754
|
-
var olDrift = Math.abs(olEl.currentTime - olMT)
|
|
2755
|
-
if (olDur > 0 && !olEl.seeking && olDrift > 0.3 && (!ol.loop || olDur - olDrift > 0.3)) olEl.currentTime = olMT
|
|
2756
|
-
if (!ol.loop && olDur > 0 && olT >= olDur) { if (!olEl.paused) olEl.pause() }
|
|
2757
|
-
else if (olEl.paused) { var olP = olEl.play(); if (olP && olP.catch) olP.catch(function () {}) }
|
|
2758
|
-
} else {
|
|
2759
|
-
if (!olEl.paused) olEl.pause()
|
|
2760
|
-
var olTarget = Math.min(olMT, olEl.duration || olMT)
|
|
2761
|
-
// Coalesce scrub seeks (the backgroundMedia pattern): re-assigning
|
|
2762
|
-
// currentTime aborts the in-flight seek, so a per-frame scrub keeps
|
|
2763
|
-
// a remote source seeking forever. Defer until 'seeked' lands.
|
|
2764
|
-
if (olEl.readyState >= 1 && !olEl.seeking && Math.abs(olEl.currentTime - olTarget) > 0.02) {
|
|
2765
|
-
if (ns.pendingDecodes) {
|
|
2766
|
-
var olDp = new Promise(function (resolve) {
|
|
2767
|
-
var olDone = function () { olEl.removeEventListener('seeked', olDone); resolve() }
|
|
2768
|
-
olEl.addEventListener('seeked', olDone)
|
|
2769
|
-
setTimeout(olDone, 250)
|
|
2770
|
-
})
|
|
2771
|
-
ns.pendingDecodes.add(olDp)
|
|
2772
|
-
olDp.finally(function () { ns.pendingDecodes.delete(olDp) })
|
|
2773
|
-
}
|
|
2774
|
-
olEl.currentTime = olTarget
|
|
2775
|
-
}
|
|
2776
|
-
}
|
|
2777
|
-
} catch (e) {}
|
|
2778
|
-
}
|
|
2779
|
-
// Video readiness is STICKY through seeks (the cam-bubble pattern):
|
|
2780
|
-
// readyState dips below HAVE_CURRENT_DATA while a scrub seek is in
|
|
2781
|
-
// flight, and this layer repaints every frame a video clip is visible \u2014
|
|
2782
|
-
// gating each frame on it would blink the clip out for the whole drag.
|
|
2783
|
-
// After the first decoded frame, keep drawing: Chrome paints the
|
|
2784
|
-
// element's retained frame mid-seek.
|
|
2785
|
-
if (!olIsImg && olEl.readyState >= 2) olEl.__vosHasFrame = true
|
|
2786
|
-
var olReady = olIsImg ? !!(olEl.complete && olEl.naturalWidth) : !!(olEl.readyState >= 2 || olEl.__vosHasFrame)
|
|
2787
|
-
if (!olReady) continue
|
|
2788
2863
|
var olNW = (olIsImg ? olEl.naturalWidth : olEl.videoWidth) || 16
|
|
2789
2864
|
var olNH = (olIsImg ? olEl.naturalHeight : olEl.videoHeight) || 9
|
|
2790
2865
|
var olDW = ol.w * W * olMS
|
|
@@ -3296,6 +3371,194 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
3296
3371
|
}
|
|
3297
3372
|
obM.scale.setScalar(obPS * obRefH * (obE.norm || 1))
|
|
3298
3373
|
}
|
|
3374
|
+
// --- layer cards (many cards): a media overlay that names a frame is
|
|
3375
|
+
// drawn by the card painter on its OWN plane in the prop group
|
|
3376
|
+
// (renderOrder 1.25: above the primary card, below the props): the
|
|
3377
|
+
// layered shadow (the primary's table and strengths), the media clipped
|
|
3378
|
+
// to its corners, the browser bar after it, the border, and for a
|
|
3379
|
+
// document media its click rings and cursor dot inside the card, at
|
|
3380
|
+
// clip-local time. Chrome scales WITH the card (a smaller window has
|
|
3381
|
+
// smaller chrome), the way the primary's scales with cf. The plane is
|
|
3382
|
+
// sized to the canvas at the card depth, placed at the clip's anchor,
|
|
3383
|
+
// leaned by the card's own rx/ry (the tilt convention) and turned by the
|
|
3384
|
+
// clip's rotation. Locals lc*.
|
|
3385
|
+
var lcC = obC.cards || (obC.cards = new Map())
|
|
3386
|
+
var lcSeen = {}
|
|
3387
|
+
var lcK = obRefH / H
|
|
3388
|
+
for (var lci = 0; lci < lcList.length; lci++) {
|
|
3389
|
+
var lcI = lcList[lci], lcO = lcI.ol, lcCard = lcO.card
|
|
3390
|
+
var lcEl = olAcquire(lcO, lcI.t)
|
|
3391
|
+
if (!lcEl) continue
|
|
3392
|
+
lcSeen[lcO.id] = true
|
|
3393
|
+
var lcE = lcC.get(lcO.id)
|
|
3394
|
+
if (!lcE) {
|
|
3395
|
+
var lcCv = document.createElement('canvas')
|
|
3396
|
+
var lcTx = new THREE3.CanvasTexture(lcCv)
|
|
3397
|
+
if (THREE3.SRGBColorSpace) lcTx.colorSpace = THREE3.SRGBColorSpace
|
|
3398
|
+
if (THREE3.LinearFilter) { lcTx.minFilter = THREE3.LinearFilter; lcTx.magFilter = THREE3.LinearFilter }
|
|
3399
|
+
lcTx.generateMipmaps = false
|
|
3400
|
+
var lcM = new THREE3.Mesh(new THREE3.PlaneGeometry(1, 1), new THREE3.MeshBasicMaterial({ map: lcTx, transparent: true, depthTest: false, depthWrite: false }))
|
|
3401
|
+
lcM.renderOrder = 1.25
|
|
3402
|
+
lcM.frustumCulled = false
|
|
3403
|
+
obC.group.add(lcM)
|
|
3404
|
+
lcE = { mesh: lcM, canvas: lcCv, c2d: lcCv.getContext('2d'), texture: lcTx }
|
|
3405
|
+
lcC.set(lcO.id, lcE)
|
|
3406
|
+
}
|
|
3407
|
+
var lcIsImg = lcO.kind === 'image'
|
|
3408
|
+
var lcCrop = lcO.crop || null
|
|
3409
|
+
var lcNW = lcCrop ? lcCrop.w : ((lcIsImg ? lcEl.naturalWidth : lcEl.videoWidth) || 16)
|
|
3410
|
+
var lcNH = lcCrop ? lcCrop.h : ((lcIsImg ? lcEl.naturalHeight : lcEl.videoHeight) || 9)
|
|
3411
|
+
var lcW = Math.max(2, lcO.w * W * lcI.s)
|
|
3412
|
+
var lcS = s * (lcW / W)
|
|
3413
|
+
var lcBar = lcCard.bar || {}
|
|
3414
|
+
var lcBarH = lcBar.kind && lcBar.kind !== 'none' ? (lcBar.height || 44) * lcS : 0
|
|
3415
|
+
var lcMH = lcW * (lcNH / lcNW)
|
|
3416
|
+
var lcH = lcMH + lcBarH
|
|
3417
|
+
var lcRad = Math.min((lcO.radius || 0) * lcS, lcH / 2)
|
|
3418
|
+
var lcMg = Math.ceil(170 * lcS)
|
|
3419
|
+
var lcCW = Math.ceil(lcW + 2 * lcMg), lcCH = Math.ceil(lcH + 2 * lcMg)
|
|
3420
|
+
var lcCv2 = lcE.canvas, lcX = lcE.c2d
|
|
3421
|
+
if (lcCv2.width !== lcCW || lcCv2.height !== lcCH) { lcCv2.width = lcCW; lcCv2.height = lcCH }
|
|
3422
|
+
lcX.clearRect(0, 0, lcCW, lcCH)
|
|
3423
|
+
var lcX0 = lcMg, lcY0 = lcMg
|
|
3424
|
+
var lcSh = lcCard.shadow == null ? ${LAYER_CARD_SHADOW} : lcCard.shadow
|
|
3425
|
+
var lcRgb = '0,0,0'
|
|
3426
|
+
var lcHex = lcCard.shadowColor
|
|
3427
|
+
if (typeof lcHex === 'string' && /^#[0-9a-fA-F]{6}$/.test(lcHex)) lcRgb = parseInt(lcHex.slice(1, 3), 16) + ',' + parseInt(lcHex.slice(3, 5), 16) + ',' + parseInt(lcHex.slice(5, 7), 16)
|
|
3428
|
+
// the shadows are cast by a body drawn off the canvas and brought back
|
|
3429
|
+
// by shadowOffsetX (the primary's seam rule)
|
|
3430
|
+
var lcD = lcCW * 2 + lcCH * 2
|
|
3431
|
+
if (lcSh > 0) {
|
|
3432
|
+
var lcL = [[2, 1, 0.1], [12, 4, 0.1], [40, 16, 0.14], [120, 44, 0.3]]
|
|
3433
|
+
for (var lcj = 0; lcj < lcL.length; lcj++) {
|
|
3434
|
+
lcX.save()
|
|
3435
|
+
lcX.shadowColor = 'rgba(' + lcRgb + ',' + +(lcSh * lcL[lcj][2]).toFixed(3) + ')'
|
|
3436
|
+
lcX.shadowBlur = lcL[lcj][0] * lcS; lcX.shadowOffsetX = lcD; lcX.shadowOffsetY = lcL[lcj][1] * lcS
|
|
3437
|
+
lcX.fillStyle = '#000'
|
|
3438
|
+
rr(lcX0 - lcD, lcY0, lcW, lcH, lcRad, lcX); lcX.fill()
|
|
3439
|
+
lcX.restore()
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
if (lcCard.shadowContact > 0) {
|
|
3443
|
+
lcX.save()
|
|
3444
|
+
lcX.shadowColor = 'rgba(' + lcRgb + ',' + lcCard.shadowContact + ')'
|
|
3445
|
+
lcX.shadowBlur = 10 * lcS; lcX.shadowOffsetX = lcD; lcX.shadowOffsetY = 3 * lcS
|
|
3446
|
+
lcX.fillStyle = '#000'
|
|
3447
|
+
rr(lcX0 - lcD, lcY0, lcW, lcH, lcRad, lcX); lcX.fill()
|
|
3448
|
+
lcX.restore()
|
|
3449
|
+
}
|
|
3450
|
+
lcX.save()
|
|
3451
|
+
rr(lcX0, lcY0, lcW, lcH, lcRad, lcX); lcX.clip()
|
|
3452
|
+
var lcMY = lcY0 + lcBarH
|
|
3453
|
+
try {
|
|
3454
|
+
if (lcCrop) lcX.drawImage(lcEl, lcCrop.x, lcCrop.y, lcCrop.w, lcCrop.h, lcX0, lcMY, lcW, lcMH)
|
|
3455
|
+
else lcX.drawImage(lcEl, lcX0, lcMY, lcW, lcMH)
|
|
3456
|
+
} catch (e) {}
|
|
3457
|
+
if (lcBarH > 0) {
|
|
3458
|
+
var lcDark = lcBar.kind.indexOf('dark') >= 0, lcMin = lcBar.kind === 'minimal'
|
|
3459
|
+
var lcThm = (lcMin && lcBar.theme) || null
|
|
3460
|
+
lcX.fillStyle = lcMin ? (lcThm ? lcThm.bar : '#141417') : lcDark ? '#2a2a2e' : '#e9e9eb'
|
|
3461
|
+
lcX.fillRect(lcX0, lcY0, lcW, lcBarH)
|
|
3462
|
+
lcX.fillStyle = lcDark || (lcMin && !(lcThm && lcThm.light)) ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.08)'
|
|
3463
|
+
lcX.fillRect(lcX0, lcY0 + lcBarH - lcS, lcW, lcS)
|
|
3464
|
+
var lcMid = lcY0 + lcBarH / 2
|
|
3465
|
+
if (lcBar.showControls !== false && !lcMin) {
|
|
3466
|
+
if (lcBar.kind.indexOf('mac') === 0) {
|
|
3467
|
+
var lcLights = ['#ff5f57', '#febc2e', '#28c840']
|
|
3468
|
+
for (var lcl = 0; lcl < 3; lcl++) { lcX.fillStyle = lcLights[lcl]; lcX.beginPath(); lcX.arc(lcX0 + (20 + lcl * 20) * lcS, lcMid, 6 * lcS, 0, Math.PI * 2); lcX.fill() }
|
|
3469
|
+
} else {
|
|
3470
|
+
lcX.strokeStyle = lcDark ? 'rgba(255,255,255,0.75)' : 'rgba(0,0,0,0.6)'
|
|
3471
|
+
lcX.lineWidth = 1.5 * lcS
|
|
3472
|
+
var lcG = 4.5 * lcS, lcGx = lcX0 + lcW - 22 * lcS
|
|
3473
|
+
lcX.beginPath(); lcX.moveTo(lcGx - lcG, lcMid - lcG); lcX.lineTo(lcGx + lcG, lcMid + lcG); lcX.moveTo(lcGx + lcG, lcMid - lcG); lcX.lineTo(lcGx - lcG, lcMid + lcG); lcX.stroke()
|
|
3474
|
+
lcX.strokeRect(lcGx - 28 * lcS - lcG, lcMid - lcG, lcG * 2, lcG * 2)
|
|
3475
|
+
lcX.beginPath(); lcX.moveTo(lcGx - 56 * lcS - lcG, lcMid); lcX.lineTo(lcGx - 56 * lcS + lcG, lcMid); lcX.stroke()
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
if (lcBar.showUrl !== false && lcBar.url) {
|
|
3479
|
+
var lcPW = Math.min(lcW * 0.5, Math.max(200 * lcS, lcW * 0.34)), lcPH = lcBarH - 16 * lcS
|
|
3480
|
+
var lcPx = lcX0 + (lcW - lcPW) / 2, lcPy = lcY0 + 8 * lcS
|
|
3481
|
+
lcX.fillStyle = lcMin ? (lcThm ? lcThm.pill : '#26262b') : lcDark ? '#1d1d20' : '#ffffff'
|
|
3482
|
+
rr(lcPx, lcPy, lcPW, lcPH, lcPH / 2, lcX); lcX.fill()
|
|
3483
|
+
lcX.fillStyle = lcMin ? (lcThm ? lcThm.text : '#9a9aa1') : lcDark ? '#a1a1a6' : '#5f5f64'
|
|
3484
|
+
lcX.font = 13 * lcS + 'px -apple-system, system-ui, sans-serif'
|
|
3485
|
+
lcX.textAlign = 'center'; lcX.textBaseline = 'middle'
|
|
3486
|
+
var lcLabel = String(lcBar.url), lcMaxW = lcPW - 28 * lcS
|
|
3487
|
+
if (lcX.measureText(lcLabel).width > lcMaxW) {
|
|
3488
|
+
while (lcLabel.length > 1 && lcX.measureText(lcLabel + '\u2026').width > lcMaxW) lcLabel = lcLabel.slice(0, -1)
|
|
3489
|
+
lcLabel += '\u2026'
|
|
3490
|
+
}
|
|
3491
|
+
lcX.fillText(lcLabel, lcPx + lcPW / 2, lcPy + lcPH / 2)
|
|
3492
|
+
lcX.textAlign = 'start'; lcX.textBaseline = 'alphabetic'
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
// the media's facts: click rings and the cursor dot, video-anchored,
|
|
3496
|
+
// at clip-local time
|
|
3497
|
+
var lcCu = lcO.cur
|
|
3498
|
+
if (lcCu && lcCu.pts && lcCu.pts.length) {
|
|
3499
|
+
var lcSp = lcCu.space || { w: lcNW, h: lcNH }
|
|
3500
|
+
var lcSize = (lcCu.size || 24) * lcS * 0.5
|
|
3501
|
+
var lcT = lcI.t
|
|
3502
|
+
var lcCks = lcCu.clicks || []
|
|
3503
|
+
for (var lcc = 0; lcc < lcCks.length; lcc++) {
|
|
3504
|
+
var lcCk = lcCks[lcc]
|
|
3505
|
+
var lcAge = lcT - lcCk.t
|
|
3506
|
+
if (lcAge < -0.08 || lcAge > 0.6) continue
|
|
3507
|
+
var lcU = Math.max(0, Math.min(1, (lcAge + 0.08) / 0.68))
|
|
3508
|
+
var lcFade = 1 - Math.pow(1 - lcU, 3)
|
|
3509
|
+
lcX.save()
|
|
3510
|
+
lcX.strokeStyle = 'rgba(255,255,255,' + (0.9 * (1 - lcU)) + ')'
|
|
3511
|
+
lcX.lineWidth = Math.max(1, 3 * lcS * (1 - lcU * 0.6))
|
|
3512
|
+
lcX.beginPath(); lcX.arc(lcX0 + (lcCk.x / (lcSp.w || lcNW)) * lcW, lcMY + (lcCk.y / (lcSp.h || lcNH)) * lcMH, lcSize * 0.8 + lcFade * 44 * lcS, 0, Math.PI * 2); lcX.stroke()
|
|
3513
|
+
lcX.restore()
|
|
3514
|
+
}
|
|
3515
|
+
var lcPts = lcCu.pts, lcPX = lcPts[0].x, lcPY = lcPts[0].y
|
|
3516
|
+
for (var lcp = 0; lcp < lcPts.length; lcp++) { if (lcPts[lcp].t <= lcT) { lcPX = lcPts[lcp].x; lcPY = lcPts[lcp].y } }
|
|
3517
|
+
lcX.save()
|
|
3518
|
+
lcX.fillStyle = 'rgba(255,255,255,0.95)'; lcX.strokeStyle = 'rgba(0,0,0,0.4)'; lcX.lineWidth = 2 * lcS
|
|
3519
|
+
lcX.beginPath(); lcX.arc(lcX0 + (lcPX / (lcSp.w || lcNW)) * lcW, lcMY + (lcPY / (lcSp.h || lcNH)) * lcMH, lcSize, 0, Math.PI * 2); lcX.fill(); lcX.stroke()
|
|
3520
|
+
lcX.restore()
|
|
3521
|
+
}
|
|
3522
|
+
lcX.restore()
|
|
3523
|
+
if (lcO.border && lcO.border.width > 0) {
|
|
3524
|
+
var lcBW = lcO.border.width * lcS
|
|
3525
|
+
lcX.save(); lcX.strokeStyle = lcO.border.color || '#fff'; lcX.lineWidth = lcBW
|
|
3526
|
+
rr(lcX0 - lcBW / 2, lcY0 - lcBW / 2, lcW + lcBW, lcH + lcBW, lcRad > 0 ? lcRad + lcBW / 2 : 0, lcX); lcX.stroke()
|
|
3527
|
+
lcX.restore()
|
|
3528
|
+
}
|
|
3529
|
+
lcE.texture.needsUpdate = true
|
|
3530
|
+
var lcM2 = lcE.mesh
|
|
3531
|
+
lcM2.visible = true
|
|
3532
|
+
if (lcM2.material) lcM2.material.opacity = lcI.a
|
|
3533
|
+
var lcLean = lcCard.lean || [0, 0]
|
|
3534
|
+
var lcRx = (lcLean[0] || 0) * Math.PI / 180, lcRy = (lcLean[1] || 0) * Math.PI / 180, lcRz = -(lcI.r || 0) * Math.PI / 180
|
|
3535
|
+
var lcPlW = lcCW * lcK, lcPlH = lcCH * lcK
|
|
3536
|
+
var lcWX = (lcI.x - 0.5) * obRefH * obAspect
|
|
3537
|
+
var lcWY = -(lcI.y - 0.5) * obRefH - lcI.yof * lcK
|
|
3538
|
+
if (obOrtho) {
|
|
3539
|
+
lcM2.position.set((obOCX + (lcI.x - 0.5) * obOW) / obAX, obOCY - (lcI.y - 0.5) * obOH, -obOD)
|
|
3540
|
+
obB.e.set(lcRx, lcRy, lcRz); lcM2.quaternion.setFromEuler(obB.e)
|
|
3541
|
+
} else if (obB) {
|
|
3542
|
+
lcM2.position.copy(obCam.position).addScaledVector(obB.f, Math.abs(${CARD_Z}) - 0.01).addScaledVector(obB.r, lcWX).addScaledVector(obB.u, lcWY)
|
|
3543
|
+
obB.e.set(lcRx, lcRy, lcRz); lcM2.quaternion.copy(obCam.quaternion).multiply(obB.q.setFromEuler(obB.e))
|
|
3544
|
+
} else {
|
|
3545
|
+
lcM2.position.set(lcWX, lcWY, ${CARD_Z} + 0.01)
|
|
3546
|
+
lcM2.rotation.set(lcRx, lcRy, lcRz)
|
|
3547
|
+
}
|
|
3548
|
+
lcM2.scale.set(lcPlW, lcPlH, 1)
|
|
3549
|
+
}
|
|
3550
|
+
// A card off screen rests; one gone from the data is disposed.
|
|
3551
|
+
lcC.forEach(function (lcE2, lcId) {
|
|
3552
|
+
if (lcSeen[lcId]) return
|
|
3553
|
+
var lcStill = false
|
|
3554
|
+
for (var lcq = 0; lcq < ols.length; lcq++) if (ols[lcq].id === lcId && ols[lcq].card) lcStill = true
|
|
3555
|
+
if (lcStill) { lcE2.mesh.visible = false; return }
|
|
3556
|
+
obC.group.remove(lcE2.mesh)
|
|
3557
|
+
if (lcE2.mesh.geometry && lcE2.mesh.geometry.dispose) lcE2.mesh.geometry.dispose()
|
|
3558
|
+
if (lcE2.mesh.material && lcE2.mesh.material.dispose) lcE2.mesh.material.dispose()
|
|
3559
|
+
if (lcE2.texture && lcE2.texture.dispose) lcE2.texture.dispose()
|
|
3560
|
+
lcC.delete(lcId)
|
|
3561
|
+
})
|
|
3299
3562
|
// Dispose props no longer in the data (live removal).
|
|
3300
3563
|
obC.pool.forEach(function (obE2, obId) {
|
|
3301
3564
|
if (!obSeen[obId]) {
|
|
@@ -5074,6 +5337,7 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
5074
5337
|
}
|
|
5075
5338
|
}`;
|
|
5076
5339
|
function studioLayerData(layers, duration) {
|
|
5340
|
+
const rec = "source" in layers ? layers : null;
|
|
5077
5341
|
return {
|
|
5078
5342
|
// Music/SFX clips with their gain envelopes baked (shared truth for the
|
|
5079
5343
|
// preview scheduler and the export's offline mix). `duckEnv` (the mic-derived
|
|
@@ -5161,9 +5425,60 @@ function studioLayerData(layers, duration) {
|
|
|
5161
5425
|
})()
|
|
5162
5426
|
};
|
|
5163
5427
|
if (o.kind !== "text") {
|
|
5428
|
+
const lm = rec ? layerMedia(rec, o.key) : null;
|
|
5429
|
+
const lmImage = lm ? lm.sourceKind === "image" : o.kind === "image";
|
|
5430
|
+
const lmCard = o.frame ? {
|
|
5431
|
+
bar: {
|
|
5432
|
+
kind: o.frame.browserBar?.kind ?? "none",
|
|
5433
|
+
url: o.frame.browserBar?.url ?? (lm ? pageDisplayUrl(lm.meta.pageUrl) : ""),
|
|
5434
|
+
showUrl: o.frame.browserBar?.showUrl ?? true,
|
|
5435
|
+
showControls: o.frame.browserBar?.showControls ?? true,
|
|
5436
|
+
height: o.frame.browserBar?.height ?? 44,
|
|
5437
|
+
...o.frame.browserBar?.theme ? { theme: o.frame.browserBar.theme } : {}
|
|
5438
|
+
},
|
|
5439
|
+
lean: [
|
|
5440
|
+
round6(o.frame.lean?.rx ?? 0),
|
|
5441
|
+
round6(o.frame.lean?.ry ?? 0)
|
|
5442
|
+
],
|
|
5443
|
+
shadow: o.frame.shadow ?? LAYER_CARD_SHADOW,
|
|
5444
|
+
shadowContact: o.frame.shadowContact ?? 0,
|
|
5445
|
+
...o.frame.shadowColor ? { shadowColor: o.frame.shadowColor } : {}
|
|
5446
|
+
} : null;
|
|
5447
|
+
const lmFacts = lm && o.frame && o.frame.cursor !== false && lm.cursor.length ? (() => {
|
|
5448
|
+
const lfx = rec.cursor.clickFx;
|
|
5449
|
+
const sm = smoothCursor(lm.cursor, {
|
|
5450
|
+
factor: rec.cursor.smoothing,
|
|
5451
|
+
clickSnap: lfx.style !== "none" || lfx.press
|
|
5452
|
+
});
|
|
5453
|
+
const clicks = [];
|
|
5454
|
+
for (const ev of lm.cursor) {
|
|
5455
|
+
if (ev.type === "down")
|
|
5456
|
+
clicks.push({
|
|
5457
|
+
t: round6(ev.t / 1e3),
|
|
5458
|
+
x: round6(ev.x),
|
|
5459
|
+
y: round6(ev.y)
|
|
5460
|
+
});
|
|
5461
|
+
}
|
|
5462
|
+
return {
|
|
5463
|
+
cur: {
|
|
5464
|
+
pts: sm.map((p) => ({
|
|
5465
|
+
t: round6(p.t),
|
|
5466
|
+
x: round6(p.x),
|
|
5467
|
+
y: round6(p.y)
|
|
5468
|
+
})),
|
|
5469
|
+
space: { w: lm.meta.width, h: lm.meta.height },
|
|
5470
|
+
size: rec.cursor.size,
|
|
5471
|
+
clicks
|
|
5472
|
+
}
|
|
5473
|
+
};
|
|
5474
|
+
})() : {};
|
|
5164
5475
|
return {
|
|
5165
5476
|
...base,
|
|
5166
|
-
|
|
5477
|
+
...lm ? { kind: lmImage ? "image" : "video" } : {},
|
|
5478
|
+
key: lm ? lm.videoKey : o.key,
|
|
5479
|
+
...lm && lm.crop ? { crop: lm.crop } : {},
|
|
5480
|
+
...lmCard ? { card: lmCard } : {},
|
|
5481
|
+
...lmFacts,
|
|
5167
5482
|
w: round6(o.width ?? OVERLAY_MEDIA_DEFAULT_WIDTH),
|
|
5168
5483
|
radius: o.radius ?? OVERLAY_MEDIA_DEFAULT_RADIUS,
|
|
5169
5484
|
opacity: o.opacity ?? 1,
|
|
@@ -6043,15 +6358,15 @@ function copyLayout(from, to, opts = {}) {
|
|
|
6043
6358
|
const freeze = trailingFreeze(from);
|
|
6044
6359
|
const last = doc.segments.at(-1);
|
|
6045
6360
|
if (freeze && last) {
|
|
6046
|
-
const
|
|
6047
|
-
|
|
6048
|
-
);
|
|
6361
|
+
const atEnd = (f) => Math.abs(f.at - last.out) <= 1e-9 && sameMedia(f.media, last.media);
|
|
6362
|
+
const own2 = (doc.freeze ?? []).filter((f) => !atEnd(f));
|
|
6363
|
+
const kept = (doc.freeze ?? []).find((f) => atEnd(f) && !f.from);
|
|
6049
6364
|
const taken = new Set(own2.map((f) => f.id));
|
|
6050
6365
|
let n = 0;
|
|
6051
6366
|
while (taken.has(`f${n}`)) n++;
|
|
6052
6367
|
doc.freeze = [
|
|
6053
6368
|
...own2,
|
|
6054
|
-
{
|
|
6369
|
+
kept ? { ...kept, seconds: Math.max(kept.seconds, freeze.seconds) } : {
|
|
6055
6370
|
id: `f${n}`,
|
|
6056
6371
|
at: last.out,
|
|
6057
6372
|
seconds: freeze.seconds,
|
|
@@ -6100,15 +6415,15 @@ function applyTemplate(template, take, opts) {
|
|
|
6100
6415
|
const tail = trailingFreeze(template);
|
|
6101
6416
|
const lastSeg = doc.segments.at(-1);
|
|
6102
6417
|
if (tail && opts.at === "end" && lastSeg) {
|
|
6103
|
-
const
|
|
6104
|
-
|
|
6105
|
-
);
|
|
6418
|
+
const atEnd = (f) => Math.abs(f.at - lastSeg.out) <= 1e-9 && sameMedia(f.media, lastSeg.media);
|
|
6419
|
+
const kept = (doc.freeze ?? []).find((f) => atEnd(f) && !f.from);
|
|
6420
|
+
const own2 = (doc.freeze ?? []).filter((f) => !atEnd(f));
|
|
6106
6421
|
const taken = new Set(own2.map((f) => f.id));
|
|
6107
6422
|
let n = 0;
|
|
6108
6423
|
while (taken.has(`f${n}`)) n++;
|
|
6109
6424
|
doc.freeze = [
|
|
6110
6425
|
...own2,
|
|
6111
|
-
{
|
|
6426
|
+
kept ? { ...kept, seconds: Math.max(kept.seconds, tail.seconds) } : {
|
|
6112
6427
|
id: `f${n}`,
|
|
6113
6428
|
at: lastSeg.out,
|
|
6114
6429
|
seconds: tail.seconds,
|
|
@@ -8478,11 +8793,13 @@ export {
|
|
|
8478
8793
|
FREEZE_SECONDS_MIN,
|
|
8479
8794
|
HOUSE_GRADIENT,
|
|
8480
8795
|
IDLE_KINDS,
|
|
8796
|
+
LAYER_CARD_SHADOW,
|
|
8481
8797
|
LAYOUT_FRAME_FIELDS,
|
|
8482
8798
|
LOOK_KINDS,
|
|
8483
8799
|
MEDIA_ENTER_KINDS,
|
|
8484
8800
|
MEDIA_EXIT_KINDS,
|
|
8485
8801
|
MEDIA_FRAME_KEYS,
|
|
8802
|
+
MEDIA_KEY_PREFIX,
|
|
8486
8803
|
MINIMAL_BAR_THEMES,
|
|
8487
8804
|
MOTION_DELTA,
|
|
8488
8805
|
MOTION_EASE,
|
|
@@ -8509,6 +8826,7 @@ export {
|
|
|
8509
8826
|
SPEED_RATE_MIN,
|
|
8510
8827
|
SPEED_SPAN_MIN,
|
|
8511
8828
|
STAGE_CLIP_PREFIX,
|
|
8829
|
+
STILL_DEFAULT,
|
|
8512
8830
|
STUDIO_ENTRY_ID,
|
|
8513
8831
|
STYLE_FIELDS,
|
|
8514
8832
|
TEXT3D_DEPTH_DEFAULT,
|
|
@@ -8591,6 +8909,7 @@ export {
|
|
|
8591
8909
|
docMediaList,
|
|
8592
8910
|
docOutputDuration,
|
|
8593
8911
|
docRestTime,
|
|
8912
|
+
docStillTime,
|
|
8594
8913
|
docToCropSpace,
|
|
8595
8914
|
docToFullSpace,
|
|
8596
8915
|
dropTemplate,
|
|
@@ -8627,6 +8946,7 @@ export {
|
|
|
8627
8946
|
isStageClip,
|
|
8628
8947
|
isUnitStep,
|
|
8629
8948
|
lastLayerEnd,
|
|
8949
|
+
layerMedia,
|
|
8630
8950
|
layoutOf,
|
|
8631
8951
|
levelForFocusFraction,
|
|
8632
8952
|
lookFromBrand,
|
|
@@ -8638,6 +8958,8 @@ export {
|
|
|
8638
8958
|
mediaDuration,
|
|
8639
8959
|
mediaFrame,
|
|
8640
8960
|
mediaKey,
|
|
8961
|
+
mediaRef,
|
|
8962
|
+
mediaRefId,
|
|
8641
8963
|
mediaSource,
|
|
8642
8964
|
micLane,
|
|
8643
8965
|
migrateHostedDoc,
|
|
@@ -8664,6 +8986,7 @@ export {
|
|
|
8664
8986
|
overlayMotionPoseAt,
|
|
8665
8987
|
overlayRect,
|
|
8666
8988
|
overlaysLane,
|
|
8989
|
+
ownFreezes,
|
|
8667
8990
|
pageDisplayUrl,
|
|
8668
8991
|
parsePoseId,
|
|
8669
8992
|
pickStyle,
|