@vosjs/studio-core 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -3384,7 +3433,7 @@ var SETUP = `async (ctx) => {
3384
3433
  // exactly as it was; preview/scrub never sets the flag.
3385
3434
  const makeWcProvider = async (src) => {
3386
3435
  if (!window.VideoDecoder) return null
3387
- const MB = await import('https://esm.sh/mediabunny@1.27.3?target=es2022')
3436
+ const MB = await import('https://esm.sh/mediabunny@1.55.7?target=es2022')
3388
3437
  const wcBlob = ns.videoBlobs && ns.videoBlobs.get(src)
3389
3438
  const input = new MB.Input({
3390
3439
  formats: MB.ALL_FORMATS,
@@ -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
- if (!vid.paused) vid.pause()
3732
- // A WebCodecs provider (capture pages) services the frame by
3733
- // PTS at decode speed \u2014 no element seek, no 250ms settle cap. The
3734
- // element stays paused as the dimension source and fallback.
3735
- var wcp = vid.__voilaWc
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
- if (!bgEl.paused) bgEl.pause()
3854
- var bgTarget = Math.min(bgT, bgEl.duration || bgT)
3855
- // COALESCE seeks: a scrub moves t every frame, and re-assigning
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
  }