@vosjs/studio-core 0.7.1 → 0.9.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/README.md +1 -1
- package/dist/index.js +161 -68
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ The shared layers lower as one engine `stack` entry, `STUDIO_ENTRY_ID` (`'vosso.
|
|
|
36
36
|
- **Seconds everywhere.** Zoom, speed, tilt and cam-motion spans and the cursor samples are anchored in source time, so they follow footage through trims. Overlays, audio clips and click effects are anchored in output time, so a title keeps its perceived length through a speed change. `ratedSegments`, `effectiveSegments`, `docOutputDuration` and `outputRangeToSource` are the remaps.
|
|
37
37
|
- **Normalized coordinates.** A zoom's `cx`/`cy` and an overlay's `transform.x`/`y` are fractions of the frame in `[0, 1]`, never pixels, so a document survives an aspect-ratio switch. `focusBounds` and `clampFocus` keep a focus inside the card at a given `level`.
|
|
38
38
|
- **Camera styles.** `ZOOM_STYLES` names eight camera personalities (`glide`, the default, then `focus`, `cinema`, `snappy`, `cut`, `keynote`, `drift`, `none`); a style parametrizes both the planner and the camera motion, and `doc.zoomParams` layers per-document overrides.
|
|
39
|
-
- **The stage.** `CARD_FOV`, `CARD_Z`, `OVERLAY_Z`, `BACKGROUND_Z` and `planeSizeAtDepth` define the frustum-filling planes the program draws (background, the tiltable card, the overlay); `computeCardLayout` and `docCardLayout` are the host-side mirror of the program's card math, pinned to it by tests.
|
|
39
|
+
- **The stage.** `CARD_FOV`, `CARD_Z`, `OVERLAY_Z`, `BACKGROUND_Z` and `planeSizeAtDepth` define the frustum-filling planes the program draws (background, the tiltable card, the overlay); `cardVisibleExtent` and `cardOverscanFor` are how far a posed card is seen past the frame, which is what the card layer's canvas and plane grow by (so a lean over zoomed footage never shows the texture's edge); `computeCardLayout` and `docCardLayout` are the host-side mirror of the program's card math, pinned to it by tests.
|
|
40
40
|
- **Backdrops.** `withBackdrop(frame, backdrop)` and `backdropMedia` put a looping video or a still behind the card, output-anchored modulo its duration. The package carries the mechanism and `BASE_FRAME_STYLE`; which loop a host opens on is the host's choice.
|
|
41
41
|
|
|
42
42
|
## Seeing a recording: the digest
|
package/dist/index.js
CHANGED
|
@@ -1266,7 +1266,8 @@ var CARD_FOV = 30;
|
|
|
1266
1266
|
var OVERLAY_Z = -2;
|
|
1267
1267
|
var CARD_Z = -4;
|
|
1268
1268
|
var BACKGROUND_Z = -6;
|
|
1269
|
-
var
|
|
1269
|
+
var CARD_OVERSCAN_STEP = 0.05;
|
|
1270
|
+
var CARD_OVERSCAN_MAX = 4;
|
|
1270
1271
|
var CAMERA_NEAR = 0.1;
|
|
1271
1272
|
var CAMERA_FAR = 100;
|
|
1272
1273
|
function planeSizeAtDepth(distance, fovDeg, aspect) {
|
|
@@ -3299,6 +3300,54 @@ function objectMotionPoseAt(o, t, clipDur) {
|
|
|
3299
3300
|
if (!track.keyframes.length) return null;
|
|
3300
3301
|
return [...sample(track, t, lerpArray)];
|
|
3301
3302
|
}
|
|
3303
|
+
var FRAME_STEP_SRC = `
|
|
3304
|
+
function stepPaused(vid, srcT) {
|
|
3305
|
+
if (!vid.paused) vid.pause()
|
|
3306
|
+
// A WebCodecs provider (capture pages) services the frame by PTS at
|
|
3307
|
+
// decode speed \u2014 no element seek, no 250ms settle cap. The element stays
|
|
3308
|
+
// paused as the dimension source and fallback.
|
|
3309
|
+
var wcp = vid.__voilaWc
|
|
3310
|
+
if (wcp) {
|
|
3311
|
+
var wcT = Math.min(srcT, wcp.duration || srcT)
|
|
3312
|
+
if (wcp.req !== wcT) {
|
|
3313
|
+
var wdp = wcp.seek(wcT)
|
|
3314
|
+
if (ns.pendingDecodes) {
|
|
3315
|
+
ns.pendingDecodes.add(wdp)
|
|
3316
|
+
wdp.finally(function () { ns.pendingDecodes.delete(wdp) })
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
return
|
|
3320
|
+
}
|
|
3321
|
+
var target = Math.min(srcT, vid.duration || srcT)
|
|
3322
|
+
if (vid.readyState >= 1 && Math.abs(vid.currentTime - target) > 0.02) {
|
|
3323
|
+
if (ns.pendingDecodes) {
|
|
3324
|
+
var dp = new Promise(function (resolve) {
|
|
3325
|
+
var done = function () { vid.removeEventListener('seeked', done); resolve() }
|
|
3326
|
+
vid.addEventListener('seeked', done)
|
|
3327
|
+
setTimeout(done, 250) // fallback so a missed 'seeked' can't hang the export
|
|
3328
|
+
})
|
|
3329
|
+
ns.pendingDecodes.add(dp)
|
|
3330
|
+
dp.finally(function () { ns.pendingDecodes.delete(dp) })
|
|
3331
|
+
}
|
|
3332
|
+
vid.currentTime = target
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
function stepBg(bgEl, bgT) {
|
|
3336
|
+
if (!bgEl.paused) bgEl.pause()
|
|
3337
|
+
var bgTarget = Math.min(bgT, bgEl.duration || bgT)
|
|
3338
|
+
if (bgEl.readyState >= 1 && !bgEl.seeking && Math.abs(bgEl.currentTime - bgTarget) > 0.02) {
|
|
3339
|
+
if (ns.pendingDecodes) {
|
|
3340
|
+
var bgDp = new Promise(function (resolve) {
|
|
3341
|
+
var bgDone = function () { bgEl.removeEventListener('seeked', bgDone); resolve() }
|
|
3342
|
+
bgEl.addEventListener('seeked', bgDone)
|
|
3343
|
+
setTimeout(bgDone, 250) // fallback so a missed 'seeked' can't hang the export
|
|
3344
|
+
})
|
|
3345
|
+
ns.pendingDecodes.add(bgDp)
|
|
3346
|
+
bgDp.finally(function () { ns.pendingDecodes.delete(bgDp) })
|
|
3347
|
+
}
|
|
3348
|
+
bgEl.currentTime = bgTarget
|
|
3349
|
+
}
|
|
3350
|
+
}`;
|
|
3302
3351
|
var SETUP = `async (ctx) => {
|
|
3303
3352
|
;${timelineRuntimeCode2}
|
|
3304
3353
|
const ns = (window.__vos__ = window.__vos__ || {})
|
|
@@ -3311,6 +3360,7 @@ var SETUP = `async (ctx) => {
|
|
|
3311
3360
|
if (!ns.waitForVideosReady) ns.waitForVideosReady = async () => {
|
|
3312
3361
|
if (ns.pendingDecodes.size) await Promise.all([...ns.pendingDecodes])
|
|
3313
3362
|
}
|
|
3363
|
+
${FRAME_STEP_SRC}
|
|
3314
3364
|
const cache = ns.videoCache || (ns.videoCache = new Map())
|
|
3315
3365
|
// Server capture pages opt into BLOB-backed elements (data.videoFetchMode,
|
|
3316
3366
|
// merged in by the render queue \u2014 never stored in a doc or config): a
|
|
@@ -3504,6 +3554,36 @@ var SETUP = `async (ctx) => {
|
|
|
3504
3554
|
else (await load(bgm.key, true)).loop = true
|
|
3505
3555
|
} catch (e) { console.warn('[voila] background media failed to load', e) }
|
|
3506
3556
|
}
|
|
3557
|
+
// Frame prep: a capture harness calls every ns.framePrep hook right after
|
|
3558
|
+
// seeking the timeline and before the first paint, then awaits
|
|
3559
|
+
// waitForVideosReady. The footage for the frame is requested HERE, so the
|
|
3560
|
+
// paint that follows draws the right frame the first time. Without the
|
|
3561
|
+
// hook the request could only be issued from inside ON_FRAME (the only
|
|
3562
|
+
// place that knew the source time), so every take frame painted twice:
|
|
3563
|
+
// once with the previous footage, then again after the decode. The steps
|
|
3564
|
+
// are the SAME functions ON_FRAME's syncVid runs (one source, inlined in
|
|
3565
|
+
// both scopes), so the paint finds its target already met and registers
|
|
3566
|
+
// nothing. Keyed by program so a warm LOAD re-registers instead of
|
|
3567
|
+
// chaining; playback never calls it; a harness without it keeps the
|
|
3568
|
+
// two-phase loop and stays correct.
|
|
3569
|
+
ns.framePrep = ns.framePrep || new Map()
|
|
3570
|
+
ns.framePrep.set('voila.card', (t) => {
|
|
3571
|
+
if (ns.isPaused === false) return
|
|
3572
|
+
const d = ctx.data
|
|
3573
|
+
const TL = globalThis.__vosTimeline
|
|
3574
|
+
const srcT = TL.mapTime(d.segments || [], t)
|
|
3575
|
+
if (video.play) stepPaused(video, srcT)
|
|
3576
|
+
if (cam) stepPaused(cam, srcT)
|
|
3577
|
+
if (mic) stepPaused(mic, srcT)
|
|
3578
|
+
const bg2 = d.frame && d.frame.backgroundMedia
|
|
3579
|
+
if (bg2 && bg2.key && bg2.kind !== 'image') {
|
|
3580
|
+
const bgEl2 = cache.get(bg2.key)
|
|
3581
|
+
if (bgEl2 && bgEl2.play) {
|
|
3582
|
+
const bgDur2 = bg2.duration || bgEl2.duration || 0
|
|
3583
|
+
stepBg(bgEl2, bgDur2 > 0 ? t % bgDur2 : 0)
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
})
|
|
3507
3587
|
return { video: video, cam: cam, mic: mic }
|
|
3508
3588
|
}`;
|
|
3509
3589
|
var CREATE_CONTENT = `(ctx, setupData) => {
|
|
@@ -3582,30 +3662,81 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
3582
3662
|
// the GPU texture allocated at the original dims and re-uploads the new canvas
|
|
3583
3663
|
// against stale dims \u2192 stretch/duplicate; dispose() forces a full realloc) and
|
|
3584
3664
|
// rebuilding each frustum-filling plane at the new aspect + syncing the camera.
|
|
3585
|
-
// Card OVERSCAN
|
|
3586
|
-
//
|
|
3587
|
-
//
|
|
3588
|
-
//
|
|
3589
|
-
//
|
|
3590
|
-
// card layer
|
|
3665
|
+
// Card OVERSCAN: the card's pose (the tilt track, the card-pose track)
|
|
3666
|
+
// lets the frame see MORE plane than the frame is wide on a receding
|
|
3667
|
+
// side, and the card's content (zoomed footage, a bled card, the shadow)
|
|
3668
|
+
// runs past the frame, so a plane that ends at the frame edge shows its
|
|
3669
|
+
// texture's edge, and the backdrop through it, as soon as it leans. The
|
|
3670
|
+
// card layer's canvas and plane therefore grow about the frame's centre
|
|
3671
|
+
// by the extent the two tracks can reach at the LIVE aspect: each frame
|
|
3672
|
+
// corner's viewing ray meets the posed plane and reads back in plane
|
|
3673
|
+
// units; the extremes of both tracks (largest lean per axis, smallest
|
|
3674
|
+
// scale, largest rise, every sign) bound every interpolated frame. This
|
|
3675
|
+
// is the twin of stage.ts cardVisibleExtent/cardOverscanFor (pinned by
|
|
3676
|
+
// stage.test.ts + cardTilt.test.ts; change them together). A card that
|
|
3677
|
+
// never moves, and the flat stub (no card layer), stay at 1: byte- and
|
|
3678
|
+
// pixel-identical. The factor is quantised up in steps so a scrubbed
|
|
3679
|
+
// pose grows the canvas a few times, not per tick.
|
|
3591
3680
|
var d = ctx.data || {}
|
|
3592
|
-
var
|
|
3593
|
-
|
|
3594
|
-
|
|
3681
|
+
var okx = 1, oky = 1
|
|
3682
|
+
if (r.card) {
|
|
3683
|
+
var okT = d.tiltTrack, okP = d.cardPoseTrack, okKf, oki, okv
|
|
3684
|
+
var okRx = 0, okRy = 0, okSc = 1, okDy = 0
|
|
3685
|
+
okKf = okT && okT.keyframes || []
|
|
3686
|
+
for (oki = 0; oki < okKf.length; oki++) { okv = okKf[oki].value || []; okRx = Math.max(okRx, Math.abs(okv[0] || 0)); okRy = Math.max(okRy, Math.abs(okv[1] || 0)) }
|
|
3687
|
+
okKf = okP && okP.keyframes || []
|
|
3688
|
+
for (oki = 0; oki < okKf.length; oki++) { okv = okKf[oki].value || []; okSc = Math.min(okSc, okv[0] == null ? 1 : okv[0]); okDy = Math.max(okDy, Math.abs(okv[1] || 0)) }
|
|
3689
|
+
if (okRx || okRy || okSc < 1 || okDy) {
|
|
3690
|
+
var okD = ${Math.abs(CARD_Z)}, okMax = ${CARD_OVERSCAN_MAX}, okStep = ${CARD_OVERSCAN_STEP}
|
|
3691
|
+
var okHh = okD * Math.tan(${CARD_FOV} * Math.PI / 180 / 2), okHw = okHh * W / H
|
|
3692
|
+
// [max |x|, max |y|] of the frame corners on the posed plane, in
|
|
3693
|
+
// half-plane units (R = Rx \xB7 Ry, three.js Euler XYZ with rz = 0).
|
|
3694
|
+
var okE = function (rxd, ryd, sc, dy) {
|
|
3695
|
+
var rx = rxd * Math.PI / 180, ry = ryd * Math.PI / 180
|
|
3696
|
+
var cx = Math.cos(rx), sx = Math.sin(rx), cy = Math.cos(ry), sy = Math.sin(ry)
|
|
3697
|
+
var scl = Math.max(0.05, sc), py = dy * 2 * okHh
|
|
3698
|
+
var n0 = sy, n1 = -sx * cy, n2 = cx * cy
|
|
3699
|
+
var nPos = n1 * py - n2 * okD
|
|
3700
|
+
var ex = 0, ey = 0
|
|
3701
|
+
for (var kx = -1; kx <= 1; kx += 2) for (var ky = -1; ky <= 1; ky += 2) {
|
|
3702
|
+
var d0 = kx * okHw, d1 = ky * okHh, d2 = -okD
|
|
3703
|
+
var nd = n0 * d0 + n1 * d1 + n2 * d2
|
|
3704
|
+
var lam = nd === 0 ? -1 : nPos / nd
|
|
3705
|
+
if (!(lam > 0)) { ex = okMax; ey = okMax; continue }
|
|
3706
|
+
var q0 = lam * d0, q1 = lam * d1 - py, q2 = lam * d2 + okD
|
|
3707
|
+
ex = Math.max(ex, Math.abs((cy * q0 + sx * sy * q1 - cx * sy * q2) / scl / okHw))
|
|
3708
|
+
ey = Math.max(ey, Math.abs((cx * q1 + sx * q2) / scl / okHh))
|
|
3709
|
+
}
|
|
3710
|
+
return [Math.min(okMax, ex), Math.min(okMax, ey)]
|
|
3711
|
+
}
|
|
3712
|
+
for (var okA = -1; okA <= 1; okA += 2) for (var okB = -1; okB <= 1; okB += 2) for (var okC = -1; okC <= 1; okC += 2) {
|
|
3713
|
+
var okR = okE(okA * okRx, okB * okRy, okSc, okC * okDy)
|
|
3714
|
+
okx = Math.max(okx, okR[0]); oky = Math.max(oky, okR[1])
|
|
3715
|
+
}
|
|
3716
|
+
var okQ = function (k) { if (!(k > 1)) return 1; var q = Math.ceil(Math.round((k + okStep / 10) * 1000) / (okStep * 1000)) * okStep; return Math.min(okMax, Math.round(q * 1000) / 1000) }
|
|
3717
|
+
okx = okQ(okx); oky = okQ(oky)
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
// The canvas grows by an EVEN number of pixels on each axis so the frame
|
|
3721
|
+
// region sits on whole pixels inside it (a half-pixel base translation
|
|
3722
|
+
// would resample every draw).
|
|
3723
|
+
var cW = W + 2 * Math.round(W * (okx - 1) / 2), cH = H + 2 * Math.round(H * (oky - 1) / 2)
|
|
3595
3724
|
if (cv.width !== cW || cv.height !== cH) {
|
|
3596
3725
|
var THREE = ctx.THREE
|
|
3597
3726
|
var aspect = W / H
|
|
3598
3727
|
var cm = ctx.camera
|
|
3599
3728
|
if (cm && cm.isPerspectiveCamera) { cm.aspect = aspect; cm.updateProjectionMatrix() }
|
|
3600
|
-
|
|
3729
|
+
// Each layer's canvas size and its plane's per-axis growth; the card's
|
|
3730
|
+
// plane grows by exactly its canvas's ratio, so a texel stays a pixel.
|
|
3731
|
+
var lyr = [[bg, ${BACKGROUND_Z}, W, H], [card, ${CARD_Z}, cW, cH], [ov, ${OVERLAY_Z}, W, H]]
|
|
3601
3732
|
for (var Li = 0; Li < lyr.length; Li++) {
|
|
3602
|
-
var Ly = lyr[Li][0], Lz = lyr[Li][1],
|
|
3603
|
-
if (Ly.canvas) { Ly.canvas.width =
|
|
3733
|
+
var Ly = lyr[Li][0], Lz = lyr[Li][1], Lw = lyr[Li][2], Lhp = lyr[Li][3]
|
|
3734
|
+
if (Ly.canvas) { Ly.canvas.width = Lw; Ly.canvas.height = Lhp }
|
|
3604
3735
|
if (Ly.texture && Ly.texture.dispose) Ly.texture.dispose()
|
|
3605
3736
|
if (Ly.mesh && THREE) {
|
|
3606
3737
|
if (Ly.mesh.geometry && Ly.mesh.geometry.dispose) Ly.mesh.geometry.dispose()
|
|
3607
|
-
var Lh = 2 * Math.abs(Lz) * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)
|
|
3608
|
-
Ly.mesh.geometry = new THREE.PlaneGeometry(Lh * aspect, Lh)
|
|
3738
|
+
var Lh = 2 * Math.abs(Lz) * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)
|
|
3739
|
+
Ly.mesh.geometry = new THREE.PlaneGeometry(Lh * aspect * (Lw / W), Lh * (Lhp / H))
|
|
3609
3740
|
}
|
|
3610
3741
|
}
|
|
3611
3742
|
}
|
|
@@ -3631,6 +3762,7 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
3631
3762
|
// correction covers cut-boundary jumps), else step to the exact frame (registering a
|
|
3632
3763
|
// decode promise so the deterministic export awaits it). Shared by the screen video
|
|
3633
3764
|
// and the webcam so both stay frame-accurate and in sync.
|
|
3765
|
+
${FRAME_STEP_SRC}
|
|
3634
3766
|
function syncVid(vid) {
|
|
3635
3767
|
try {
|
|
3636
3768
|
if (playing) {
|
|
@@ -3676,35 +3808,11 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
3676
3808
|
vid.__voilaAutoMuted = false
|
|
3677
3809
|
}
|
|
3678
3810
|
} else {
|
|
3679
|
-
|
|
3680
|
-
//
|
|
3681
|
-
//
|
|
3682
|
-
//
|
|
3683
|
-
|
|
3684
|
-
if (wcp) {
|
|
3685
|
-
var wcT = Math.min(srcT, wcp.duration || srcT)
|
|
3686
|
-
if (wcp.req !== wcT) {
|
|
3687
|
-
var wdp = wcp.seek(wcT)
|
|
3688
|
-
if (ns.pendingDecodes) {
|
|
3689
|
-
ns.pendingDecodes.add(wdp)
|
|
3690
|
-
wdp.finally(function () { ns.pendingDecodes.delete(wdp) })
|
|
3691
|
-
}
|
|
3692
|
-
}
|
|
3693
|
-
return
|
|
3694
|
-
}
|
|
3695
|
-
var target = Math.min(srcT, vid.duration || srcT)
|
|
3696
|
-
if (vid.readyState >= 1 && Math.abs(vid.currentTime - target) > 0.02) {
|
|
3697
|
-
if (ns.pendingDecodes) {
|
|
3698
|
-
var dp = new Promise(function (resolve) {
|
|
3699
|
-
var done = function () { vid.removeEventListener('seeked', done); resolve() }
|
|
3700
|
-
vid.addEventListener('seeked', done)
|
|
3701
|
-
setTimeout(done, 250) // fallback so a missed 'seeked' can't hang the export
|
|
3702
|
-
})
|
|
3703
|
-
ns.pendingDecodes.add(dp)
|
|
3704
|
-
dp.finally(function () { ns.pendingDecodes.delete(dp) })
|
|
3705
|
-
}
|
|
3706
|
-
vid.currentTime = target
|
|
3707
|
-
}
|
|
3811
|
+
// The paused step (FRAME_STEP_SRC): the provider by PTS, else the
|
|
3812
|
+
// element seek. A capture harness that ran the frame-prep hook has
|
|
3813
|
+
// already met this target, so nothing registers and the paint is
|
|
3814
|
+
// the one that counts.
|
|
3815
|
+
stepPaused(vid, srcT)
|
|
3708
3816
|
}
|
|
3709
3817
|
} catch (e) {}
|
|
3710
3818
|
}
|
|
@@ -3798,27 +3906,9 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
3798
3906
|
if (bgDur > 0 && !bgEl.seeking && bgDrift > 0.3 && bgDur - bgDrift > 0.3) bgEl.currentTime = bgT
|
|
3799
3907
|
if (bgEl.paused) { var bgP = bgEl.play(); if (bgP && bgP.catch) bgP.catch(function () {}) }
|
|
3800
3908
|
} else {
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
// currentTime ABORTS the in-flight seek \u2014 on a remote (assets.vos.so)
|
|
3805
|
-
// source that keeps the element mid-seek for the whole drag, so no
|
|
3806
|
-
// frame ever decodes and the background pops in seconds late. Issue
|
|
3807
|
-
// a seek only when none is in flight; the frame after 'seeked' fires
|
|
3808
|
-
// corrects toward the latest target, so seeks run serially and
|
|
3809
|
-
// converge on the release point.
|
|
3810
|
-
if (bgEl.readyState >= 1 && !bgEl.seeking && Math.abs(bgEl.currentTime - bgTarget) > 0.02) {
|
|
3811
|
-
if (ns.pendingDecodes) {
|
|
3812
|
-
var bgDp = new Promise(function (resolve) {
|
|
3813
|
-
var bgDone = function () { bgEl.removeEventListener('seeked', bgDone); resolve() }
|
|
3814
|
-
bgEl.addEventListener('seeked', bgDone)
|
|
3815
|
-
setTimeout(bgDone, 250) // fallback so a missed 'seeked' can't hang the export
|
|
3816
|
-
})
|
|
3817
|
-
ns.pendingDecodes.add(bgDp)
|
|
3818
|
-
bgDp.finally(function () { ns.pendingDecodes.delete(bgDp) })
|
|
3819
|
-
}
|
|
3820
|
-
bgEl.currentTime = bgTarget
|
|
3821
|
-
}
|
|
3909
|
+
// The background step (FRAME_STEP_SRC): a coalesced seek, met
|
|
3910
|
+
// already when a capture harness ran the frame-prep hook.
|
|
3911
|
+
stepBg(bgEl, bgT)
|
|
3822
3912
|
}
|
|
3823
3913
|
} catch (e) {}
|
|
3824
3914
|
}
|
|
@@ -3929,9 +4019,10 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
3929
4019
|
// the contain-fit card shows the background layer through the plane's alpha.
|
|
3930
4020
|
// Under overscan the canvas is larger than the frame and every frame-space
|
|
3931
4021
|
// draw below rides a base translation to its centre.
|
|
3932
|
-
|
|
4022
|
+
var okOn = cW !== W || cH !== H
|
|
4023
|
+
if (okOn) c.setTransform(1, 0, 0, 1, 0, 0)
|
|
3933
4024
|
c.clearRect(0, 0, cv.width, cv.height)
|
|
3934
|
-
if (
|
|
4025
|
+
if (okOn) c.setTransform(1, 0, 0, 1, (cW - W) / 2, (cH - H) / 2)
|
|
3935
4026
|
// Footage is nearly always drawn SMALLER than it was recorded (a 1920
|
|
3936
4027
|
// recording in a padded 1080 card, or a 440-wide tile): the default
|
|
3937
4028
|
// bilinear tap aliases and blurs a large downscale; 'high' resamples.
|
|
@@ -4442,8 +4533,10 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
4442
4533
|
card.mesh.rotation.y = ry
|
|
4443
4534
|
// The card's pose through an entrance or an end card: [scale, dy,
|
|
4444
4535
|
// opacity]; absent = the rest pose, exactly as before the track existed.
|
|
4536
|
+
// dy is a fraction of the FRAME plane's height (not the geometry's,
|
|
4537
|
+
// which the overscan grows).
|
|
4445
4538
|
var cpk = d.cardPoseTrack
|
|
4446
|
-
var cpH =
|
|
4539
|
+
var cpH = 2 * ${Math.abs(CARD_Z)} * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)
|
|
4447
4540
|
if (cpk && cpk.keyframes && cpk.keyframes.length && card.mesh.scale && card.mesh.position) {
|
|
4448
4541
|
var cpv = TL.sample(cpk, t, TL.lerpArray)
|
|
4449
4542
|
card.mesh.scale.x = cpv[0]; card.mesh.scale.y = cpv[0]
|