@vosjs/studio-core 0.41.2 → 0.42.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.d.ts +155 -2
- package/dist/index.js +258 -66
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -623,7 +623,7 @@ function projectFromArtifact(artifact, videoUrl, opts = {}) {
|
|
|
623
623
|
const coverage = surface === "monitor" || artifact.meta.windowMovedDuringTake || unfocused ? 0 : norm.coverage;
|
|
624
624
|
if (artifact.cursor.length > 0 && coverage < CAPTURE_COVERAGE_MIN) {
|
|
625
625
|
console.warn(
|
|
626
|
-
"[
|
|
626
|
+
"[studio] dropping cursor track \u2014",
|
|
627
627
|
surface === "monitor" ? "entire-screen takes have no cursor effects (untrackable outside browser pages)" : artifact.meta.windowMovedDuringTake ? "window moved/resized during the take" : unfocused ? `browser window focused only ${Math.round((artifact.meta.windowFocusedFrac ?? 0) * 100)}% of the take \u2014 a different window was likely shared` : `coverage ${norm.coverage.toFixed(2)} (wrong surface shared, or unusable capture geometry)`
|
|
628
628
|
);
|
|
629
629
|
}
|
|
@@ -3403,6 +3403,13 @@ var TYPING_CLICK_ABSORB = 1.5;
|
|
|
3403
3403
|
var TYPING_REFOCUS_FRAC = 0.08;
|
|
3404
3404
|
function groupTrack(track, opts) {
|
|
3405
3405
|
const { width, height, clusterGap, typingGap, typingZoom, targetFill } = opts;
|
|
3406
|
+
const stepOf = (t) => opts.steps?.find(
|
|
3407
|
+
(st) => st.do !== "wait" && !st.skipped && st.tStart <= t && t <= st.tEnd
|
|
3408
|
+
) ?? null;
|
|
3409
|
+
const sameStep = (a, b) => {
|
|
3410
|
+
const sa = stepOf(a.t);
|
|
3411
|
+
return sa !== null && sa === stepOf(b.t);
|
|
3412
|
+
};
|
|
3406
3413
|
const minLevel = opts.minLevel ?? resolveZoomStyle().minLevel;
|
|
3407
3414
|
const clicks = dedupePresses(
|
|
3408
3415
|
track.filter((e) => e.type === "down")
|
|
@@ -3436,7 +3443,7 @@ function groupTrack(track, opts) {
|
|
|
3436
3443
|
for (const c of list) {
|
|
3437
3444
|
const last = clusters.at(-1);
|
|
3438
3445
|
const prev = last?.at(-1);
|
|
3439
|
-
const joins = last && prev && c.t - prev.t <= clusterGap && (!spatial || unionFitLevel(unionRect([...last, c]), width, height) >= minLevel);
|
|
3446
|
+
const joins = last && prev && (c.t - prev.t <= clusterGap || sameStep(prev, c)) && (!spatial || unionFitLevel(unionRect([...last, c]), width, height) >= minLevel);
|
|
3440
3447
|
if (joins) last.push(c);
|
|
3441
3448
|
else clusters.push([c]);
|
|
3442
3449
|
}
|
|
@@ -3515,7 +3522,8 @@ function planAutoZoom(track, options) {
|
|
|
3515
3522
|
typingGap,
|
|
3516
3523
|
typingZoom,
|
|
3517
3524
|
targetFill,
|
|
3518
|
-
minLevel
|
|
3525
|
+
minLevel,
|
|
3526
|
+
steps: options.steps
|
|
3519
3527
|
});
|
|
3520
3528
|
const { sessions } = grouped;
|
|
3521
3529
|
const drags = dragsFromTrack(track, width, height);
|
|
@@ -3630,10 +3638,11 @@ function planAutoZoom(track, options) {
|
|
|
3630
3638
|
const spans = within([...zSpans, ...kSpans, ...dragSpans]).sort(
|
|
3631
3639
|
(a, b) => a.in - b.in
|
|
3632
3640
|
);
|
|
3641
|
+
const firstStep = options.steps?.find((st) => !st.skipped)?.tStart;
|
|
3633
3642
|
const dwells = dwellSpans(track, width, height, maxLevel, [
|
|
3634
3643
|
...spans,
|
|
3635
3644
|
...dragReserved
|
|
3636
|
-
]);
|
|
3645
|
+
]).filter((d) => firstStep === void 0 || d.out > firstStep);
|
|
3637
3646
|
return within([...spans, ...dwells]).sort((a, b) => a.in - b.in);
|
|
3638
3647
|
}
|
|
3639
3648
|
function typingSessions(track, width, height, typingGap) {
|
|
@@ -4834,7 +4843,7 @@ var STUDIO_SETUP = `async (ctx) => {
|
|
|
4834
4843
|
// The MediaError rides along: code 4 is an unreadable/unsupported source
|
|
4835
4844
|
// (a dead blob URL, a 404), code 3 a decode failure, code 2 a network
|
|
4836
4845
|
// stall. A bare "failed to load" gave the fleet log nothing to act on.
|
|
4837
|
-
v.onerror = () => rej(new Error('[
|
|
4846
|
+
v.onerror = () => rej(new Error('[studio] video failed to load' + (v.error ? ' (' + v.error.code + (v.error.message ? ': ' + v.error.message : '') + ')' : '')))
|
|
4838
4847
|
v.load()
|
|
4839
4848
|
})
|
|
4840
4849
|
cache.set(src, v)
|
|
@@ -4847,7 +4856,7 @@ var STUDIO_SETUP = `async (ctx) => {
|
|
|
4847
4856
|
const img = new Image()
|
|
4848
4857
|
img.crossOrigin = 'anonymous'
|
|
4849
4858
|
img.onload = () => { cache.set(src, img); res(img) }
|
|
4850
|
-
img.onerror = () => rej(new Error('[
|
|
4859
|
+
img.onerror = () => rej(new Error('[studio] image failed to load'))
|
|
4851
4860
|
img.src = src
|
|
4852
4861
|
})
|
|
4853
4862
|
}
|
|
@@ -4861,7 +4870,7 @@ var STUDIO_SETUP = `async (ctx) => {
|
|
|
4861
4870
|
const faces = ctx.data.overlayFonts || ${JSON.stringify(OVERLAY_FONT_FACES)}
|
|
4862
4871
|
// Mark what SETUP registers, so ON_FRAME's live path (a text overlay
|
|
4863
4872
|
// arriving through SET_DATA) never adds the same face twice.
|
|
4864
|
-
const ofSet = window.
|
|
4873
|
+
const ofSet = window.__vosFontSet || (window.__vosFontSet = {})
|
|
4865
4874
|
const loads = faces.map((f) => {
|
|
4866
4875
|
ofSet[f.family + '|' + f.weight] = 1
|
|
4867
4876
|
const ff = new FontFace(f.family, 'url(' + f.url + ')', { weight: String(f.weight) })
|
|
@@ -4872,7 +4881,7 @@ var STUDIO_SETUP = `async (ctx) => {
|
|
|
4872
4881
|
Promise.all(loads).catch(() => {}),
|
|
4873
4882
|
new Promise((res) => setTimeout(res, 4000)),
|
|
4874
4883
|
])
|
|
4875
|
-
} catch (e) { console.warn('[
|
|
4884
|
+
} catch (e) { console.warn('[studio] overlay fonts failed to load', e) }
|
|
4876
4885
|
}
|
|
4877
4886
|
// An HTML layer arrives as SOURCE, and the picture is built HERE.
|
|
4878
4887
|
//
|
|
@@ -4915,7 +4924,7 @@ var STUDIO_SETUP = `async (ctx) => {
|
|
|
4915
4924
|
// Fail open per URL, and NOT sticky: the layer paints without it now
|
|
4916
4925
|
// and the next build asks again.
|
|
4917
4926
|
faceData.delete(url)
|
|
4918
|
-
console.warn('[
|
|
4927
|
+
console.warn('[studio] html layer ' + what + ' failed to load', url, e)
|
|
4919
4928
|
return ''
|
|
4920
4929
|
})
|
|
4921
4930
|
faceData.set(url, p)
|
|
@@ -4967,7 +4976,7 @@ var STUDIO_SETUP = `async (ctx) => {
|
|
|
4967
4976
|
ns.htmlSafe = true
|
|
4968
4977
|
} catch (e) {
|
|
4969
4978
|
ns.htmlSafe = false
|
|
4970
|
-
console.error('[
|
|
4979
|
+
console.error('[studio] html layers are off: this browser taints a canvas that draws a foreignObject SVG', e)
|
|
4971
4980
|
}
|
|
4972
4981
|
}
|
|
4973
4982
|
// One picture per clip in the cache: the previous key goes when a new
|
|
@@ -4984,7 +4993,7 @@ var STUDIO_SETUP = `async (ctx) => {
|
|
|
4984
4993
|
// recorded on the clip and said at the level a render page forwards.
|
|
4985
4994
|
ns.buildHtmlLayer = (oc, live) => buildHtmlLayer(oc, live).catch((e) => {
|
|
4986
4995
|
ns.htmlErrors[oc.id] = String((e && e.message) || e)
|
|
4987
|
-
console.error('[
|
|
4996
|
+
console.error('[studio] html layer ' + oc.id + ' ' + ns.htmlErrors[oc.id])
|
|
4988
4997
|
})
|
|
4989
4998
|
// The capture contract for a LIVE layer, on ANY anchor. A recording's own
|
|
4990
4999
|
// program installs the settle machinery (pendingDecodes, the wait a
|
|
@@ -5027,7 +5036,7 @@ var STUDIO_SETUP = `async (ctx) => {
|
|
|
5027
5036
|
try {
|
|
5028
5037
|
if (oc.kind === 'image') await loadImage(oc.key)
|
|
5029
5038
|
else await load(oc.key, true)
|
|
5030
|
-
} catch (e) { console.warn('[
|
|
5039
|
+
} catch (e) { console.warn('[studio] overlay media failed to load', oc.key, e) }
|
|
5031
5040
|
}
|
|
5032
5041
|
// GLB object assets (V3b): load via the engine's GLTFLoader addon into a
|
|
5033
5042
|
// shared cache, bbox-NORMALIZED (norm = 1/maxDim) so transform3d.scale means
|
|
@@ -5039,7 +5048,7 @@ var STUDIO_SETUP = `async (ctx) => {
|
|
|
5039
5048
|
if (!oc.asset || oc.asset.kind !== 'gltf' || !oc.asset.key || objCache.has(oc.asset.key)) continue
|
|
5040
5049
|
try {
|
|
5041
5050
|
const GL = ctx.loaders && ctx.loaders.GLTFLoader
|
|
5042
|
-
if (!GL) { console.warn('[
|
|
5051
|
+
if (!GL) { console.warn('[studio] GLTFLoader unavailable'); continue }
|
|
5043
5052
|
const gltf = await new Promise((res, rej) => new GL().load(oc.asset.key, res, undefined, rej))
|
|
5044
5053
|
const box = new ctx.THREE.Box3().setFromObject(gltf.scene)
|
|
5045
5054
|
const size = new ctx.THREE.Vector3()
|
|
@@ -5048,7 +5057,7 @@ var STUDIO_SETUP = `async (ctx) => {
|
|
|
5048
5057
|
box.getCenter(center)
|
|
5049
5058
|
const maxDim = Math.max(size.x, size.y, size.z) || 1
|
|
5050
5059
|
objCache.set(oc.asset.key, { scene: gltf.scene, norm: 1 / maxDim, center: center })
|
|
5051
|
-
} catch (e) { console.warn('[
|
|
5060
|
+
} catch (e) { console.warn('[studio] glb failed to load', oc.asset.key, e) }
|
|
5052
5061
|
}
|
|
5053
5062
|
// 3D-text typefaces: FontLoader JSONs into a shared cache, keyed by
|
|
5054
5063
|
// URL. Awaited here so frame 0 is complete on cold loads (export/chunk
|
|
@@ -5059,10 +5068,10 @@ var STUDIO_SETUP = `async (ctx) => {
|
|
|
5059
5068
|
if (!oc.asset || oc.asset.kind !== 'text3d' || !oc.asset.url || fontCache.has(oc.asset.url)) continue
|
|
5060
5069
|
try {
|
|
5061
5070
|
const FL = ctx.loaders && ctx.loaders.FontLoader
|
|
5062
|
-
if (!FL) { console.warn('[
|
|
5071
|
+
if (!FL) { console.warn('[studio] FontLoader unavailable'); continue }
|
|
5063
5072
|
const font = await new Promise((res, rej) => new FL().load(oc.asset.url, res, undefined, rej))
|
|
5064
5073
|
fontCache.set(oc.asset.url, font)
|
|
5065
|
-
} catch (e) { console.warn('[
|
|
5074
|
+
} catch (e) { console.warn('[studio] typeface failed to load', oc.asset.url, e) }
|
|
5066
5075
|
}
|
|
5067
5076
|
// Audio clips (music/SFX): start decoding into the shared cache now, but
|
|
5068
5077
|
// never hold the first frame for it. Playback starts on a press, so the
|
|
@@ -5087,7 +5096,7 @@ var STUDIO_SETUP = `async (ctx) => {
|
|
|
5087
5096
|
bufs.set(c.key, b)
|
|
5088
5097
|
if (ns.audioSched) ns.audioSched.sig = ''
|
|
5089
5098
|
})
|
|
5090
|
-
.catch((e) => console.warn('[
|
|
5099
|
+
.catch((e) => console.warn('[studio] audio decode failed', c.key, e))
|
|
5091
5100
|
.then(() => pend.delete(c.key))
|
|
5092
5101
|
}
|
|
5093
5102
|
}
|
|
@@ -5209,7 +5218,7 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
5209
5218
|
.then(function (res) { return res.arrayBuffer() })
|
|
5210
5219
|
.then(function (ab) { return actx.decodeAudioData(ab) })
|
|
5211
5220
|
.then(function (b) { bufs.set(cc.key, b); AS.sig = '' })
|
|
5212
|
-
.catch(function (e) { console.warn('[
|
|
5221
|
+
.catch(function (e) { console.warn('[studio] audio decode failed', cc.key, e) })
|
|
5213
5222
|
.then(function () { pend.delete(cc.key) })
|
|
5214
5223
|
})(clips[di])
|
|
5215
5224
|
}
|
|
@@ -5595,7 +5604,7 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
|
|
|
5595
5604
|
// Keyed per window, so SETUP's cold-load faces and repeat frames never
|
|
5596
5605
|
// re-register; fail-open, frames repaint as each face lands.
|
|
5597
5606
|
if (typeof FontFace !== 'undefined') {
|
|
5598
|
-
var ofSet = window.
|
|
5607
|
+
var ofSet = window.__vosFontSet || (window.__vosFontSet = {})
|
|
5599
5608
|
var ofBase = d.overlayFonts || ${JSON.stringify(OVERLAY_FONT_FACES)}
|
|
5600
5609
|
for (var ofi = 0; ofi < ofBase.length; ofi++) {
|
|
5601
5610
|
var ofB = ofBase[ofi]
|
|
@@ -6759,7 +6768,7 @@ var FRAME_STEP_SRC = `
|
|
|
6759
6768
|
// A WebCodecs provider (capture pages) services the frame by PTS at
|
|
6760
6769
|
// decode speed \u2014 no element seek, no 250ms settle cap. The element stays
|
|
6761
6770
|
// paused as the dimension source and fallback.
|
|
6762
|
-
var wcp = vid.
|
|
6771
|
+
var wcp = vid.__vosWc
|
|
6763
6772
|
if (wcp) {
|
|
6764
6773
|
var wcT = Math.min(srcT, wcp.duration || srcT)
|
|
6765
6774
|
if (wcp.req !== wcT) {
|
|
@@ -6790,7 +6799,7 @@ var FRAME_STEP_SRC = `
|
|
|
6790
6799
|
// Capture pages: the loop's frame by PTS from its own provider, like the
|
|
6791
6800
|
// footage. The loop clock only moves forward between wraps, so the walk
|
|
6792
6801
|
// stays sequential and a wrap is one re-seek to the keyframe at 0.
|
|
6793
|
-
var bwc = bgEl.
|
|
6802
|
+
var bwc = bgEl.__vosWc
|
|
6794
6803
|
if (bwc) {
|
|
6795
6804
|
var bwT = Math.min(bgT, bwc.duration || bgT)
|
|
6796
6805
|
if (bwc.req !== bwT) {
|
|
@@ -6837,9 +6846,9 @@ var FRAME_STEP_SRC = `
|
|
|
6837
6846
|
if (fired) return
|
|
6838
6847
|
fired = true
|
|
6839
6848
|
g.removeEventListener('loadeddata', done)
|
|
6840
|
-
if (g.
|
|
6841
|
-
var want = g.
|
|
6842
|
-
g.
|
|
6849
|
+
if (g.__vosWant != null && g.readyState >= 1) {
|
|
6850
|
+
var want = g.__vosWant
|
|
6851
|
+
g.__vosWant = null
|
|
6843
6852
|
var seeked = function () { g.removeEventListener('seeked', seeked); resolve() }
|
|
6844
6853
|
g.addEventListener('seeked', seeked)
|
|
6845
6854
|
setTimeout(seeked, 250)
|
|
@@ -6859,8 +6868,8 @@ var FRAME_STEP_SRC = `
|
|
|
6859
6868
|
return g
|
|
6860
6869
|
}
|
|
6861
6870
|
function stepGhost(g, at) {
|
|
6862
|
-
if (g.readyState >= 1) { g.
|
|
6863
|
-
g.
|
|
6871
|
+
if (g.readyState >= 1) { g.__vosWant = null; stepPaused(g, at); return }
|
|
6872
|
+
g.__vosWant = at
|
|
6864
6873
|
}`;
|
|
6865
6874
|
var SETUP = `async (ctx) => {
|
|
6866
6875
|
;${timelineRuntimeCode2}
|
|
@@ -6888,11 +6897,11 @@ var SETUP = `async (ctx) => {
|
|
|
6888
6897
|
const BG_BLOB_MAX = 64 * 1024 * 1024
|
|
6889
6898
|
const toBlobUrl = async (src) => {
|
|
6890
6899
|
const resp = await fetch(src)
|
|
6891
|
-
if (!resp.ok) throw new Error('[
|
|
6900
|
+
if (!resp.ok) throw new Error('[studio] blob fetch HTTP ' + resp.status)
|
|
6892
6901
|
const len = Number(resp.headers.get('content-length') || 0)
|
|
6893
6902
|
if (len > BLOB_FETCH_MAX) {
|
|
6894
6903
|
try { if (resp.body) await resp.body.cancel() } catch (e) { void e }
|
|
6895
|
-
throw new Error('[
|
|
6904
|
+
throw new Error('[studio] blob fetch over size cap')
|
|
6896
6905
|
}
|
|
6897
6906
|
const fetched = await resp.blob()
|
|
6898
6907
|
// Keep the Blob itself: the WebCodecs provider demuxes the SAME
|
|
@@ -6906,7 +6915,7 @@ var SETUP = `async (ctx) => {
|
|
|
6906
6915
|
let url = src
|
|
6907
6916
|
if (ctx.data.videoFetchMode === 'blob') {
|
|
6908
6917
|
try { url = await toBlobUrl(src) }
|
|
6909
|
-
catch (e) { console.warn('[
|
|
6918
|
+
catch (e) { console.warn('[studio] blob fetch failed, using network src', e) }
|
|
6910
6919
|
}
|
|
6911
6920
|
v = document.createElement('video')
|
|
6912
6921
|
v.src = url
|
|
@@ -6919,7 +6928,7 @@ var SETUP = `async (ctx) => {
|
|
|
6919
6928
|
// The MediaError rides along: code 4 is an unreadable/unsupported source
|
|
6920
6929
|
// (a dead blob URL, a 404), code 3 a decode failure, code 2 a network
|
|
6921
6930
|
// stall. A bare "failed to load" gave the fleet log nothing to act on.
|
|
6922
|
-
v.onerror = () => rej(new Error('[
|
|
6931
|
+
v.onerror = () => rej(new Error('[studio] video failed to load' + (v.error ? ' (' + v.error.code + (v.error.message ? ': ' + v.error.message : '') + ')' : '')))
|
|
6923
6932
|
v.load()
|
|
6924
6933
|
})
|
|
6925
6934
|
cache.set(src, v)
|
|
@@ -6934,7 +6943,7 @@ var SETUP = `async (ctx) => {
|
|
|
6934
6943
|
const img = new Image()
|
|
6935
6944
|
img.crossOrigin = 'anonymous'
|
|
6936
6945
|
img.onload = () => { cache.set(src, img); res(img) }
|
|
6937
|
-
img.onerror = () => rej(new Error('[
|
|
6946
|
+
img.onerror = () => rej(new Error('[studio] image failed to load'))
|
|
6938
6947
|
img.src = src
|
|
6939
6948
|
})
|
|
6940
6949
|
}
|
|
@@ -7010,7 +7019,7 @@ var SETUP = `async (ctx) => {
|
|
|
7010
7019
|
provider.req = t
|
|
7011
7020
|
chain = chain
|
|
7012
7021
|
.then(() => advanceTo(t))
|
|
7013
|
-
.catch((e) => { console.warn('[
|
|
7022
|
+
.catch((e) => { console.warn('[studio] webcodecs frame failed', e) })
|
|
7014
7023
|
return chain
|
|
7015
7024
|
},
|
|
7016
7025
|
// Draw the CURRENT frame into the card. Returns false (element path
|
|
@@ -7045,12 +7054,12 @@ var SETUP = `async (ctx) => {
|
|
|
7045
7054
|
(wcProvider.width === video.videoWidth &&
|
|
7046
7055
|
wcProvider.height === video.videoHeight))
|
|
7047
7056
|
) {
|
|
7048
|
-
video.
|
|
7057
|
+
video.__vosWc = wcProvider
|
|
7049
7058
|
} else if (wcProvider) {
|
|
7050
|
-
console.warn('[
|
|
7059
|
+
console.warn('[studio] webcodecs dims mismatch \u2014 element path keeps the frame')
|
|
7051
7060
|
}
|
|
7052
7061
|
} catch (e) {
|
|
7053
|
-
console.warn('[
|
|
7062
|
+
console.warn('[studio] webcodecs provider unavailable, seeks stay html5', e)
|
|
7054
7063
|
}
|
|
7055
7064
|
}
|
|
7056
7065
|
// The webcam is a separate recording (video-only) drawn as an editable bubble overlay.
|
|
@@ -7069,14 +7078,14 @@ var SETUP = `async (ctx) => {
|
|
|
7069
7078
|
if (!m.isImage && ctx.data.videoDecodeMode === 'webcodecs') {
|
|
7070
7079
|
try {
|
|
7071
7080
|
const wp = await makeWcProvider(m.src)
|
|
7072
|
-
if (wp && (!el.videoWidth || (wp.width === el.videoWidth && wp.height === el.videoHeight))) el.
|
|
7081
|
+
if (wp && (!el.videoWidth || (wp.width === el.videoWidth && wp.height === el.videoHeight))) el.__vosWc = wp
|
|
7073
7082
|
} catch (e) {
|
|
7074
|
-
console.warn('[
|
|
7083
|
+
console.warn('[studio] webcodecs provider unavailable for a media, seeks stay html5', e)
|
|
7075
7084
|
}
|
|
7076
7085
|
}
|
|
7077
7086
|
mediaEls[m.id] = el
|
|
7078
7087
|
} catch (e) {
|
|
7079
|
-
console.warn('[
|
|
7088
|
+
console.warn('[studio] a media failed to load', m.id, e)
|
|
7080
7089
|
}
|
|
7081
7090
|
}
|
|
7082
7091
|
// Background media (frame.backgroundMedia): warm-load so the first paint is
|
|
@@ -7096,7 +7105,7 @@ var SETUP = `async (ctx) => {
|
|
|
7096
7105
|
// into a Blob first when it is small enough (one request instead
|
|
7097
7106
|
// of ranged reads against a CDN). FAIL-OPEN to the element at
|
|
7098
7107
|
// every step, like the footage.
|
|
7099
|
-
if (ctx.data.videoDecodeMode === 'webcodecs' && !bgV.
|
|
7108
|
+
if (ctx.data.videoDecodeMode === 'webcodecs' && !bgV.__vosWc) {
|
|
7100
7109
|
try {
|
|
7101
7110
|
if (!(ns.videoBlobs && ns.videoBlobs.get(bgm.key)) && bgm.key.indexOf('blob:') !== 0 && bgm.key.indexOf('data:') !== 0) {
|
|
7102
7111
|
try {
|
|
@@ -7110,22 +7119,22 @@ var SETUP = `async (ctx) => {
|
|
|
7110
7119
|
} catch (e) { void e }
|
|
7111
7120
|
}
|
|
7112
7121
|
const bgWc = await makeWcProvider(bgm.key)
|
|
7113
|
-
if (bgWc && (!bgV.videoWidth || (bgWc.width === bgV.videoWidth && bgWc.height === bgV.videoHeight))) bgV.
|
|
7122
|
+
if (bgWc && (!bgV.videoWidth || (bgWc.width === bgV.videoWidth && bgWc.height === bgV.videoHeight))) bgV.__vosWc = bgWc
|
|
7114
7123
|
} catch (e) {
|
|
7115
|
-
console.warn('[
|
|
7124
|
+
console.warn('[studio] webcodecs provider unavailable for the background, seeks stay html5', e)
|
|
7116
7125
|
}
|
|
7117
7126
|
}
|
|
7118
7127
|
}
|
|
7119
|
-
} catch (e) { console.warn('[
|
|
7128
|
+
} catch (e) { console.warn('[studio] background media failed to load', e) }
|
|
7120
7129
|
}
|
|
7121
7130
|
// Which path decodes what, for a host's telemetry: 'provider' is the
|
|
7122
7131
|
// sequential WebCodecs walk, 'element' an HTMLVideoElement seek per frame.
|
|
7123
|
-
ns.
|
|
7124
|
-
recording: ctx.data.isImage ? 'none' : video.
|
|
7132
|
+
ns.studioDecode = {
|
|
7133
|
+
recording: ctx.data.isImage ? 'none' : video.__vosWc ? 'provider' : 'element',
|
|
7125
7134
|
backdrop: (function () {
|
|
7126
7135
|
if (!bgm || !bgm.key || bgm.kind === 'image') return 'none'
|
|
7127
7136
|
const bgHit = cache.get(bgm.key)
|
|
7128
|
-
return bgHit && bgHit.
|
|
7137
|
+
return bgHit && bgHit.__vosWc ? 'provider' : 'element'
|
|
7129
7138
|
})(),
|
|
7130
7139
|
}
|
|
7131
7140
|
// Frame prep: a capture harness calls every ns.framePrep hook right after
|
|
@@ -7141,7 +7150,7 @@ var SETUP = `async (ctx) => {
|
|
|
7141
7150
|
// chaining; playback never calls it; a harness without it keeps the
|
|
7142
7151
|
// two-phase loop and stays correct.
|
|
7143
7152
|
ns.framePrep = ns.framePrep || new Map()
|
|
7144
|
-
ns.framePrep.set('
|
|
7153
|
+
ns.framePrep.set('studio.card', (t) => {
|
|
7145
7154
|
if (ns.isPaused === false) return
|
|
7146
7155
|
const d = ctx.data
|
|
7147
7156
|
const TL = globalThis.__vosTimeline
|
|
@@ -7429,17 +7438,17 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
7429
7438
|
// allow="autoplay" so the top frame's activation counts here).
|
|
7430
7439
|
if (!vid.muted && err && err.name === 'NotAllowedError') {
|
|
7431
7440
|
vid.muted = true
|
|
7432
|
-
vid.
|
|
7441
|
+
vid.__vosAutoMuted = true
|
|
7433
7442
|
var p2 = vid.play()
|
|
7434
7443
|
if (p2 && p2.catch) p2.catch(function () {})
|
|
7435
7444
|
}
|
|
7436
7445
|
})
|
|
7437
|
-
} else if (vid.
|
|
7446
|
+
} else if (vid.__vosAutoMuted && d.audioUnlocked) {
|
|
7438
7447
|
// First user gesture happened \u2014 lift the policy fallback. If the
|
|
7439
7448
|
// browser still objects it pauses the element, and the paused branch
|
|
7440
7449
|
// above self-heals (re-mute + resume) on the next frame.
|
|
7441
7450
|
vid.muted = false
|
|
7442
|
-
vid.
|
|
7451
|
+
vid.__vosAutoMuted = false
|
|
7443
7452
|
}
|
|
7444
7453
|
} else {
|
|
7445
7454
|
// The paused step (FRAME_STEP_SRC): the provider by PTS, else the
|
|
@@ -7759,7 +7768,7 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
7759
7768
|
// The loop's provider draws its current sample (paused capture
|
|
7760
7769
|
// pages); false \u21D2 the element (playback, or before the first ask).
|
|
7761
7770
|
try {
|
|
7762
|
-
var bgWcp = !bgIsImg && !playing ? bgEl.
|
|
7771
|
+
var bgWcp = !bgIsImg && !playing ? bgEl.__vosWc : null
|
|
7763
7772
|
if (!(bgWcp && bgWcp.draw(bgC, null, (W - bgDw) / 2 + bgOx, (H - bgDh) / 2 + bgOy, bgDw, bgDh))) {
|
|
7764
7773
|
bgC.drawImage(bgEl, (W - bgDw) / 2 + bgOx, (H - bgDh) / 2 + bgOy, bgDw, bgDh)
|
|
7765
7774
|
}
|
|
@@ -8021,7 +8030,7 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
8021
8030
|
// The provider draws the current sample directly (crop-capable,
|
|
8022
8031
|
// dimension-checked at attach); false \u21D2 element path (pre-first-seek,
|
|
8023
8032
|
// or the provider never attached).
|
|
8024
|
-
var wcp3 = video.
|
|
8033
|
+
var wcp3 = video.__vosWc
|
|
8025
8034
|
if (!(wcp3 && wcp3.draw(c, crp, ceX, ceY, ceW, ceH))) {
|
|
8026
8035
|
if (crp) c.drawImage(video, crp.x, crp.y, crp.w, crp.h, ceX, ceY, ceW, ceH)
|
|
8027
8036
|
else c.drawImage(video, ceX, ceY, ceW, ceH)
|
|
@@ -8580,10 +8589,10 @@ var ON_FRAME = `(ctx, content, dt) => {
|
|
|
8580
8589
|
// gated inside their blocks (dirty-tracking above).
|
|
8581
8590
|
if (card.texture) card.texture.needsUpdate = true
|
|
8582
8591
|
|
|
8583
|
-
// verification hook (no-op unless the harness sets window.
|
|
8592
|
+
// verification hook (no-op unless the harness sets window.__STUDIO_DEBUG__).
|
|
8584
8593
|
// cv = the CARD 2D canvas; bgCv/ovCv are the background/overlay layers (v2).
|
|
8585
|
-
if (typeof window !== 'undefined' && window.
|
|
8586
|
-
window.
|
|
8594
|
+
if (typeof window !== 'undefined' && window.__STUDIO_DEBUG__) {
|
|
8595
|
+
window.__studioDebug = { cv: cv, bgCv: bg.canvas, ovCv: ov.canvas, W: W, H: H, glW: gl && gl.width, glH: gl && gl.height, t: t, canvases: document.querySelectorAll('canvas').length }
|
|
8587
8596
|
}
|
|
8588
8597
|
}`;
|
|
8589
8598
|
function studioLayerData(input, duration, pins) {
|
|
@@ -9238,13 +9247,15 @@ var DEFAULT_SPEED_PARAMS = {
|
|
|
9238
9247
|
typingMin: 3,
|
|
9239
9248
|
typingRate: 3,
|
|
9240
9249
|
scrollMin: 2.5,
|
|
9241
|
-
scrollRate: 2
|
|
9250
|
+
scrollRate: 2,
|
|
9251
|
+
stillRate: 2
|
|
9242
9252
|
};
|
|
9243
9253
|
var TYPING_GAP = 1.5;
|
|
9244
9254
|
var SCROLL_GAP = 0.8;
|
|
9245
9255
|
var IDLE_PAD = 0.6;
|
|
9246
9256
|
var MIN_SPAN = 1;
|
|
9247
9257
|
var PLAYBACK_ACTIVITY = 0.1;
|
|
9258
|
+
var STILL_ACTIVITY = 0.02;
|
|
9248
9259
|
function planAutoSpeed(track, opts) {
|
|
9249
9260
|
const p = { ...DEFAULT_SPEED_PARAMS, ...opts.params };
|
|
9250
9261
|
const durS = opts.durationMs / 1e3;
|
|
@@ -9263,6 +9274,9 @@ function planAutoSpeed(track, opts) {
|
|
|
9263
9274
|
p.scrollMin,
|
|
9264
9275
|
(start, last) => cands.push({ in: start, out: last, rate: p.scrollRate })
|
|
9265
9276
|
);
|
|
9277
|
+
for (const [a, b] of stillSteps(opts.steps, opts.activity)) {
|
|
9278
|
+
if (b - a >= MIN_SPAN) cands.push({ in: a, out: b, rate: p.stillRate });
|
|
9279
|
+
}
|
|
9266
9280
|
for (const [a, b] of idleGaps(evs, durS, p.idleMin)) {
|
|
9267
9281
|
if (isPlayback(opts.activity, a, b)) continue;
|
|
9268
9282
|
const start = a + IDLE_PAD;
|
|
@@ -9316,6 +9330,29 @@ function idleGaps(track, durationS, idleMin = DEFAULT_SPEED_PARAMS.idleMin) {
|
|
|
9316
9330
|
if (durationS - prev >= idleMin) gaps.push([prev, durationS]);
|
|
9317
9331
|
return gaps;
|
|
9318
9332
|
}
|
|
9333
|
+
function stillSteps(steps, activity) {
|
|
9334
|
+
if (!steps?.length || !activity?.length) return [];
|
|
9335
|
+
const live = steps.filter((st) => !st.skipped);
|
|
9336
|
+
const doing = live.map((st) => st.do !== "wait");
|
|
9337
|
+
const first = doing.indexOf(true);
|
|
9338
|
+
const last = doing.lastIndexOf(true);
|
|
9339
|
+
const out = [];
|
|
9340
|
+
for (const [i, st] of live.entries()) {
|
|
9341
|
+
if (st.do !== "wait" && st.do !== "hover") continue;
|
|
9342
|
+
if (st.do === "wait" && (first < 0 || i < first || i > last)) continue;
|
|
9343
|
+
if (meanActivity(activity, st.tStart, st.tEnd) >= STILL_ACTIVITY) continue;
|
|
9344
|
+
out.push([st.tStart, st.tEnd]);
|
|
9345
|
+
}
|
|
9346
|
+
return out;
|
|
9347
|
+
}
|
|
9348
|
+
function meanActivity(activity, a, b) {
|
|
9349
|
+
const lo = Math.max(0, Math.floor(a));
|
|
9350
|
+
const hi = Math.min(activity.length, Math.ceil(b));
|
|
9351
|
+
if (hi <= lo) return 0;
|
|
9352
|
+
let sum = 0;
|
|
9353
|
+
for (let i = lo; i < hi; i++) sum += activity[i];
|
|
9354
|
+
return sum / (hi - lo);
|
|
9355
|
+
}
|
|
9319
9356
|
function isPlayback(activity, a, b) {
|
|
9320
9357
|
if (!activity?.length) return false;
|
|
9321
9358
|
const lo = Math.max(0, Math.floor(a));
|
|
@@ -9354,12 +9391,14 @@ function planForDigest(doc, activity) {
|
|
|
9354
9391
|
width: meta.width,
|
|
9355
9392
|
height: meta.height,
|
|
9356
9393
|
style: doc.zoomStyle,
|
|
9357
|
-
params: doc.zoomParams
|
|
9394
|
+
params: doc.zoomParams,
|
|
9395
|
+
steps: meta.steps
|
|
9358
9396
|
});
|
|
9359
9397
|
const speed = planAutoSpeed(cursor, {
|
|
9360
9398
|
durationMs: meta.durationMs,
|
|
9361
9399
|
params: doc.speedParams,
|
|
9362
|
-
activity
|
|
9400
|
+
activity,
|
|
9401
|
+
steps: meta.steps
|
|
9363
9402
|
});
|
|
9364
9403
|
const style = resolveZoomStyle(doc.zoomStyle, doc.zoomParams);
|
|
9365
9404
|
const intensity = doc.tiltStyle ?? style.tilt.intensity;
|
|
@@ -9399,7 +9438,8 @@ function momentsFromDoc(doc, plan, opts = {}) {
|
|
|
9399
9438
|
typingGap: style.typingGap,
|
|
9400
9439
|
typingZoom: style.typingZoom,
|
|
9401
9440
|
targetFill: style.targetFill,
|
|
9402
|
-
minLevel: style.minLevel
|
|
9441
|
+
minLevel: style.minLevel,
|
|
9442
|
+
steps: meta.steps
|
|
9403
9443
|
});
|
|
9404
9444
|
const pressed = (c, surface) => {
|
|
9405
9445
|
const f = clusterFocus(c, width, height);
|
|
@@ -9495,15 +9535,30 @@ function momentsFromDoc(doc, plan, opts = {}) {
|
|
|
9495
9535
|
rect: null
|
|
9496
9536
|
});
|
|
9497
9537
|
}
|
|
9538
|
+
for (const c of opts.changes ?? []) {
|
|
9539
|
+
if (!(c.at >= 0 && c.at < dur)) continue;
|
|
9540
|
+
const peak = Math.min(dur, Math.max(c.at, c.peakAt));
|
|
9541
|
+
drafts.push({
|
|
9542
|
+
kind: "changed",
|
|
9543
|
+
in: c.at,
|
|
9544
|
+
out: peak,
|
|
9545
|
+
at: peak,
|
|
9546
|
+
focus: { cx: c.rect.x + c.rect.w / 2, cy: c.rect.y + c.rect.h / 2 },
|
|
9547
|
+
rect: c.rect,
|
|
9548
|
+
seedAt: c.at,
|
|
9549
|
+
fraction: c.fraction
|
|
9550
|
+
});
|
|
9551
|
+
}
|
|
9498
9552
|
const order = {
|
|
9499
9553
|
head: 0,
|
|
9500
9554
|
click: 1,
|
|
9501
9555
|
typing: 2,
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
|
|
9505
|
-
|
|
9506
|
-
|
|
9556
|
+
changed: 3,
|
|
9557
|
+
scroll: 4,
|
|
9558
|
+
dwell: 5,
|
|
9559
|
+
scene: 6,
|
|
9560
|
+
idle: 7,
|
|
9561
|
+
tail: 8
|
|
9507
9562
|
};
|
|
9508
9563
|
drafts.sort((a, b) => a.in - b.in || order[a.kind] - order[b.kind]);
|
|
9509
9564
|
const rated = ratedSegments(doc);
|
|
@@ -9512,8 +9567,17 @@ function momentsFromDoc(doc, plan, opts = {}) {
|
|
|
9512
9567
|
return spanOutputExtent(rated, lo, Math.max(b, lo + 1e-3));
|
|
9513
9568
|
};
|
|
9514
9569
|
const covers = (s, d) => d.in === d.out ? s.in <= d.in && d.in < s.out : s.in < d.out && s.out > d.in;
|
|
9570
|
+
const ids = drafts.map((_, i) => `m${String(i + 1).padStart(2, "0")}`);
|
|
9571
|
+
const seedOf = (d) => {
|
|
9572
|
+
if (d.kind !== "changed" || d.seedAt === void 0) return void 0;
|
|
9573
|
+
const j = drafts.findIndex(
|
|
9574
|
+
(x) => x.kind === "click" && x.at === d.seedAt || x.kind === "typing" && x.out === d.seedAt
|
|
9575
|
+
);
|
|
9576
|
+
return j >= 0 ? ids[j] : void 0;
|
|
9577
|
+
};
|
|
9515
9578
|
return drafts.map((d, i) => {
|
|
9516
9579
|
const output = outputOf(d.in, d.out);
|
|
9580
|
+
const seed = seedOf(d);
|
|
9517
9581
|
const outAt = d.at === null ? null : outputOf(d.at, d.at);
|
|
9518
9582
|
const proposed = {};
|
|
9519
9583
|
const z = plan.zoom.find((s) => covers(s, d));
|
|
@@ -9523,7 +9587,7 @@ function momentsFromDoc(doc, plan, opts = {}) {
|
|
|
9523
9587
|
const tl = plan.tilt.find((s) => covers(s, d));
|
|
9524
9588
|
if (tl) proposed.tilt = tl.id;
|
|
9525
9589
|
return {
|
|
9526
|
-
id:
|
|
9590
|
+
id: ids[i],
|
|
9527
9591
|
kind: d.kind,
|
|
9528
9592
|
source: { in: round10(d.in), out: round10(d.out) },
|
|
9529
9593
|
output: output ? { in: round10(output.start), out: round10(output.end) } : null,
|
|
@@ -9539,9 +9603,10 @@ function momentsFromDoc(doc, plan, opts = {}) {
|
|
|
9539
9603
|
...d.clicks !== void 0 ? { clicks: d.clicks } : {},
|
|
9540
9604
|
...d.surface ? { surface: true } : {},
|
|
9541
9605
|
...d.pings !== void 0 ? { pings: d.pings } : {},
|
|
9542
|
-
activity: activityOf(opts.bins, d),
|
|
9606
|
+
activity: d.kind === "changed" && d.fraction !== void 0 ? round10(d.fraction) : activityOf(opts.bins, d),
|
|
9543
9607
|
proposed,
|
|
9544
|
-
said: saidOver(opts.transcript, d)
|
|
9608
|
+
said: saidOver(opts.transcript, d),
|
|
9609
|
+
...seed ? { seed } : {}
|
|
9545
9610
|
};
|
|
9546
9611
|
});
|
|
9547
9612
|
}
|
|
@@ -9568,6 +9633,105 @@ function round10(v) {
|
|
|
9568
9633
|
return Math.round(v * 1e3) / 1e3;
|
|
9569
9634
|
}
|
|
9570
9635
|
|
|
9636
|
+
// src/digest/changes.ts
|
|
9637
|
+
var CHANGE_GRID = { w: 128, h: 72 };
|
|
9638
|
+
var BLOCK_FILL = 0.5;
|
|
9639
|
+
var CHANGE_FLOOR = 0.02;
|
|
9640
|
+
var CHANGE_OFFSETS = [0.3, 0.8, 1.5, 2.5];
|
|
9641
|
+
var CHANGE_LEAD = 0.5;
|
|
9642
|
+
function changedRegion(before, after, delta, minBlocks = 1, ambient = null, block = Math.max(1, Math.round(before.w / 16))) {
|
|
9643
|
+
const { w, h } = before;
|
|
9644
|
+
if (after.w !== w || after.h !== h || w <= 0 || h <= 0) return null;
|
|
9645
|
+
const cols = Math.ceil(w / block);
|
|
9646
|
+
const rows = Math.ceil(h / block);
|
|
9647
|
+
const moved = new Uint16Array(cols * rows);
|
|
9648
|
+
const cells = new Uint16Array(cols * rows);
|
|
9649
|
+
for (let y = 0; y < h; y++) {
|
|
9650
|
+
const by = Math.floor(y / block);
|
|
9651
|
+
for (let x = 0; x < w; x++) {
|
|
9652
|
+
const i = y * w + x;
|
|
9653
|
+
const b = by * cols + Math.floor(x / block);
|
|
9654
|
+
cells[b]++;
|
|
9655
|
+
const drift = ambient ? ambient.rate[i] * ambient.dt : 0;
|
|
9656
|
+
if (Math.abs(after.luma[i] - before.luma[i]) - drift > delta) moved[b]++;
|
|
9657
|
+
}
|
|
9658
|
+
}
|
|
9659
|
+
const filled = new Uint8Array(cols * rows);
|
|
9660
|
+
for (let b = 0; b < filled.length; b++)
|
|
9661
|
+
if (moved[b] >= cells[b] * BLOCK_FILL) filled[b] = 1;
|
|
9662
|
+
const seen = new Uint8Array(cols * rows);
|
|
9663
|
+
let best = null;
|
|
9664
|
+
for (let start = 0; start < filled.length; start++) {
|
|
9665
|
+
if (!filled[start] || seen[start]) continue;
|
|
9666
|
+
const stack = [start];
|
|
9667
|
+
seen[start] = 1;
|
|
9668
|
+
let size = 0;
|
|
9669
|
+
let minX = cols;
|
|
9670
|
+
let minY = rows;
|
|
9671
|
+
let maxX = -1;
|
|
9672
|
+
let maxY = -1;
|
|
9673
|
+
while (stack.length) {
|
|
9674
|
+
const b = stack.pop();
|
|
9675
|
+
size++;
|
|
9676
|
+
const bx = b % cols;
|
|
9677
|
+
const by = Math.floor(b / cols);
|
|
9678
|
+
if (bx < minX) minX = bx;
|
|
9679
|
+
if (bx > maxX) maxX = bx;
|
|
9680
|
+
if (by < minY) minY = by;
|
|
9681
|
+
if (by > maxY) maxY = by;
|
|
9682
|
+
for (const [dx, dy] of [
|
|
9683
|
+
[1, 0],
|
|
9684
|
+
[-1, 0],
|
|
9685
|
+
[0, 1],
|
|
9686
|
+
[0, -1]
|
|
9687
|
+
]) {
|
|
9688
|
+
const nx = bx + dx;
|
|
9689
|
+
const ny = by + dy;
|
|
9690
|
+
if (nx < 0 || ny < 0 || nx >= cols || ny >= rows) continue;
|
|
9691
|
+
const n = ny * cols + nx;
|
|
9692
|
+
if (filled[n] && !seen[n]) {
|
|
9693
|
+
seen[n] = 1;
|
|
9694
|
+
stack.push(n);
|
|
9695
|
+
}
|
|
9696
|
+
}
|
|
9697
|
+
}
|
|
9698
|
+
if (!best || size > best.size) best = { size, minX, minY, maxX, maxY };
|
|
9699
|
+
}
|
|
9700
|
+
if (!best || best.size < minBlocks) return null;
|
|
9701
|
+
return {
|
|
9702
|
+
fraction: best.size / (cols * rows),
|
|
9703
|
+
rect: {
|
|
9704
|
+
x: best.minX * block / w,
|
|
9705
|
+
y: best.minY * block / h,
|
|
9706
|
+
w: Math.min(w, (best.maxX + 1) * block) / w - best.minX * block / w,
|
|
9707
|
+
h: Math.min(h, (best.maxY + 1) * block) / h - best.minY * block / h
|
|
9708
|
+
}
|
|
9709
|
+
};
|
|
9710
|
+
}
|
|
9711
|
+
function ambientDrift(earlier, before, lead) {
|
|
9712
|
+
const { w, h } = before;
|
|
9713
|
+
if (earlier.w !== w || earlier.h !== h || !(lead > 0)) return null;
|
|
9714
|
+
const rate = new Array(w * h);
|
|
9715
|
+
for (let i = 0; i < rate.length; i++)
|
|
9716
|
+
rate[i] = Math.abs(before.luma[i] - earlier.luma[i]) / lead;
|
|
9717
|
+
return rate;
|
|
9718
|
+
}
|
|
9719
|
+
function pickChange(seed, samples, floor = CHANGE_FLOOR) {
|
|
9720
|
+
let best = null;
|
|
9721
|
+
for (const s of samples) {
|
|
9722
|
+
if (!s.region || s.region.fraction < floor) continue;
|
|
9723
|
+
if (!best || s.region.fraction > (best.region?.fraction ?? 0)) best = s;
|
|
9724
|
+
}
|
|
9725
|
+
if (!best?.region) return null;
|
|
9726
|
+
return {
|
|
9727
|
+
seed: seed.id,
|
|
9728
|
+
at: seed.at,
|
|
9729
|
+
peakAt: seed.at + best.dt,
|
|
9730
|
+
fraction: best.region.fraction,
|
|
9731
|
+
rect: best.region.rect
|
|
9732
|
+
};
|
|
9733
|
+
}
|
|
9734
|
+
|
|
9571
9735
|
// src/digest/scenes.ts
|
|
9572
9736
|
var SCENE_MOTION = 0.25;
|
|
9573
9737
|
var SCENE_QUIET = 0.1;
|
|
@@ -10099,6 +10263,7 @@ function buildDigest(input) {
|
|
|
10099
10263
|
activity: "fraction of pixels that changed, per SOURCE second"
|
|
10100
10264
|
},
|
|
10101
10265
|
moments: withFiles,
|
|
10266
|
+
steps: digestSteps(doc),
|
|
10102
10267
|
activity: input.bins,
|
|
10103
10268
|
plan: input.plan,
|
|
10104
10269
|
doc: {
|
|
@@ -10122,6 +10287,26 @@ function buildDigest(input) {
|
|
|
10122
10287
|
}
|
|
10123
10288
|
};
|
|
10124
10289
|
}
|
|
10290
|
+
function digestSteps(doc) {
|
|
10291
|
+
const steps = doc.source.meta.steps ?? [];
|
|
10292
|
+
if (!steps.length) return [];
|
|
10293
|
+
const rated = ratedSegments(doc);
|
|
10294
|
+
return steps.map((s) => {
|
|
10295
|
+
const out = spanOutputExtent(rated, s.tStart, s.tEnd);
|
|
10296
|
+
return {
|
|
10297
|
+
step: s.step,
|
|
10298
|
+
id: s.id ?? null,
|
|
10299
|
+
do: s.do,
|
|
10300
|
+
selector: s.selector ?? null,
|
|
10301
|
+
source: { in: round11(s.tStart), out: round11(s.tEnd) },
|
|
10302
|
+
output: out ? { in: round11(out.start), out: round11(out.end) } : null,
|
|
10303
|
+
skipped: s.skipped === true,
|
|
10304
|
+
navigated: s.navigated === true,
|
|
10305
|
+
url: s.url ?? null,
|
|
10306
|
+
text: s.text ?? null
|
|
10307
|
+
};
|
|
10308
|
+
});
|
|
10309
|
+
}
|
|
10125
10310
|
function outputDurationOf(doc) {
|
|
10126
10311
|
return docOutputDuration(doc);
|
|
10127
10312
|
}
|
|
@@ -11956,6 +12141,10 @@ export {
|
|
|
11956
12141
|
CARD_FADE_SECONDS,
|
|
11957
12142
|
CARD_FOV,
|
|
11958
12143
|
CARD_Z,
|
|
12144
|
+
CHANGE_FLOOR,
|
|
12145
|
+
CHANGE_GRID,
|
|
12146
|
+
CHANGE_LEAD,
|
|
12147
|
+
CHANGE_OFFSETS,
|
|
11959
12148
|
CHANNEL_SPECS_HASH,
|
|
11960
12149
|
CHANNEL_SPECS_VERIFIED,
|
|
11961
12150
|
CLICK_FX_INTENSITY,
|
|
@@ -12105,6 +12294,7 @@ export {
|
|
|
12105
12294
|
ZOOM_SPAN_MIN,
|
|
12106
12295
|
ZOOM_STYLES,
|
|
12107
12296
|
ZOOM_STYLE_OPTIONS,
|
|
12297
|
+
ambientDrift,
|
|
12108
12298
|
anchorKindOf,
|
|
12109
12299
|
anchorSourceDuration,
|
|
12110
12300
|
animStep,
|
|
@@ -12137,6 +12327,7 @@ export {
|
|
|
12137
12327
|
cardPoseTrack,
|
|
12138
12328
|
caretAt,
|
|
12139
12329
|
caretStops,
|
|
12330
|
+
changedRegion,
|
|
12140
12331
|
clampCamFrac,
|
|
12141
12332
|
clampCamSize,
|
|
12142
12333
|
clampFocus,
|
|
@@ -12289,6 +12480,7 @@ export {
|
|
|
12289
12480
|
pageDisplayUrl,
|
|
12290
12481
|
parseCssColour,
|
|
12291
12482
|
parsePoseId,
|
|
12483
|
+
pickChange,
|
|
12292
12484
|
pickStyle,
|
|
12293
12485
|
pinBox,
|
|
12294
12486
|
pinCandidates,
|