@vosjs/studio-core 0.8.0 → 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/dist/index.js +88 -50
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -3300,6 +3300,54 @@ function objectMotionPoseAt(o, t, clipDur) {
|
|
|
3300
3300
|
if (!track.keyframes.length) return null;
|
|
3301
3301
|
return [...sample(track, t, lerpArray)];
|
|
3302
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
|
+
}`;
|
|
3303
3351
|
var SETUP = `async (ctx) => {
|
|
3304
3352
|
;${timelineRuntimeCode2}
|
|
3305
3353
|
const ns = (window.__vos__ = window.__vos__ || {})
|
|
@@ -3312,6 +3360,7 @@ var SETUP = `async (ctx) => {
|
|
|
3312
3360
|
if (!ns.waitForVideosReady) ns.waitForVideosReady = async () => {
|
|
3313
3361
|
if (ns.pendingDecodes.size) await Promise.all([...ns.pendingDecodes])
|
|
3314
3362
|
}
|
|
3363
|
+
${FRAME_STEP_SRC}
|
|
3315
3364
|
const cache = ns.videoCache || (ns.videoCache = new Map())
|
|
3316
3365
|
// Server capture pages opt into BLOB-backed elements (data.videoFetchMode,
|
|
3317
3366
|
// merged in by the render queue \u2014 never stored in a doc or config): a
|
|
@@ -3505,6 +3554,36 @@ var SETUP = `async (ctx) => {
|
|
|
3505
3554
|
else (await load(bgm.key, true)).loop = true
|
|
3506
3555
|
} catch (e) { console.warn('[voila] background media failed to load', e) }
|
|
3507
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
|
+
})
|
|
3508
3587
|
return { video: video, cam: cam, mic: mic }
|
|
3509
3588
|
}`;
|
|
3510
3589
|
var CREATE_CONTENT = `(ctx, setupData) => {
|
|
@@ -3683,6 +3762,7 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
3683
3762
|
// correction covers cut-boundary jumps), else step to the exact frame (registering a
|
|
3684
3763
|
// decode promise so the deterministic export awaits it). Shared by the screen video
|
|
3685
3764
|
// and the webcam so both stay frame-accurate and in sync.
|
|
3765
|
+
${FRAME_STEP_SRC}
|
|
3686
3766
|
function syncVid(vid) {
|
|
3687
3767
|
try {
|
|
3688
3768
|
if (playing) {
|
|
@@ -3728,35 +3808,11 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
3728
3808
|
vid.__voilaAutoMuted = false
|
|
3729
3809
|
}
|
|
3730
3810
|
} else {
|
|
3731
|
-
|
|
3732
|
-
//
|
|
3733
|
-
//
|
|
3734
|
-
//
|
|
3735
|
-
|
|
3736
|
-
if (wcp) {
|
|
3737
|
-
var wcT = Math.min(srcT, wcp.duration || srcT)
|
|
3738
|
-
if (wcp.req !== wcT) {
|
|
3739
|
-
var wdp = wcp.seek(wcT)
|
|
3740
|
-
if (ns.pendingDecodes) {
|
|
3741
|
-
ns.pendingDecodes.add(wdp)
|
|
3742
|
-
wdp.finally(function () { ns.pendingDecodes.delete(wdp) })
|
|
3743
|
-
}
|
|
3744
|
-
}
|
|
3745
|
-
return
|
|
3746
|
-
}
|
|
3747
|
-
var target = Math.min(srcT, vid.duration || srcT)
|
|
3748
|
-
if (vid.readyState >= 1 && Math.abs(vid.currentTime - target) > 0.02) {
|
|
3749
|
-
if (ns.pendingDecodes) {
|
|
3750
|
-
var dp = new Promise(function (resolve) {
|
|
3751
|
-
var done = function () { vid.removeEventListener('seeked', done); resolve() }
|
|
3752
|
-
vid.addEventListener('seeked', done)
|
|
3753
|
-
setTimeout(done, 250) // fallback so a missed 'seeked' can't hang the export
|
|
3754
|
-
})
|
|
3755
|
-
ns.pendingDecodes.add(dp)
|
|
3756
|
-
dp.finally(function () { ns.pendingDecodes.delete(dp) })
|
|
3757
|
-
}
|
|
3758
|
-
vid.currentTime = target
|
|
3759
|
-
}
|
|
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)
|
|
3760
3816
|
}
|
|
3761
3817
|
} catch (e) {}
|
|
3762
3818
|
}
|
|
@@ -3850,27 +3906,9 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
3850
3906
|
if (bgDur > 0 && !bgEl.seeking && bgDrift > 0.3 && bgDur - bgDrift > 0.3) bgEl.currentTime = bgT
|
|
3851
3907
|
if (bgEl.paused) { var bgP = bgEl.play(); if (bgP && bgP.catch) bgP.catch(function () {}) }
|
|
3852
3908
|
} else {
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
// currentTime ABORTS the in-flight seek \u2014 on a remote (assets.vos.so)
|
|
3857
|
-
// source that keeps the element mid-seek for the whole drag, so no
|
|
3858
|
-
// frame ever decodes and the background pops in seconds late. Issue
|
|
3859
|
-
// a seek only when none is in flight; the frame after 'seeked' fires
|
|
3860
|
-
// corrects toward the latest target, so seeks run serially and
|
|
3861
|
-
// converge on the release point.
|
|
3862
|
-
if (bgEl.readyState >= 1 && !bgEl.seeking && Math.abs(bgEl.currentTime - bgTarget) > 0.02) {
|
|
3863
|
-
if (ns.pendingDecodes) {
|
|
3864
|
-
var bgDp = new Promise(function (resolve) {
|
|
3865
|
-
var bgDone = function () { bgEl.removeEventListener('seeked', bgDone); resolve() }
|
|
3866
|
-
bgEl.addEventListener('seeked', bgDone)
|
|
3867
|
-
setTimeout(bgDone, 250) // fallback so a missed 'seeked' can't hang the export
|
|
3868
|
-
})
|
|
3869
|
-
ns.pendingDecodes.add(bgDp)
|
|
3870
|
-
bgDp.finally(function () { ns.pendingDecodes.delete(bgDp) })
|
|
3871
|
-
}
|
|
3872
|
-
bgEl.currentTime = bgTarget
|
|
3873
|
-
}
|
|
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)
|
|
3874
3912
|
}
|
|
3875
3913
|
} catch (e) {}
|
|
3876
3914
|
}
|