pixi-effects 0.26.1 → 0.26.3
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/CHANGELOG.md +22 -0
- package/README.md +6 -6
- package/ai/chat-template.html +3 -3
- package/ai/tools/render.mjs +16 -11
- package/dist/Composition-3O7L2VL5.js +7 -0
- package/dist/{Composition-Z2VY3KWZ.js.map → Composition-3O7L2VL5.js.map} +1 -1
- package/dist/Composition-EE3KEE2L.cjs +16 -0
- package/dist/{Composition-THQPG4E5.cjs.map → Composition-EE3KEE2L.cjs.map} +1 -1
- package/dist/Controller.d.cts +1 -1
- package/dist/Controller.d.ts +1 -1
- package/dist/{Movie-i05Oh9mJ.d.cts → Movie-BWLLWRRe.d.cts} +10 -3
- package/dist/{Movie-DVeDTdnc.d.ts → Movie-BkP82KXS.d.ts} +10 -3
- package/dist/Presenter.d.cts +1 -1
- package/dist/Presenter.d.ts +1 -1
- package/dist/{chunk-EABL3UKJ.cjs → chunk-A225HYSY.cjs} +351 -20
- package/dist/chunk-A225HYSY.cjs.map +1 -0
- package/dist/{chunk-JHJNEIDI.js → chunk-FXAHNPBP.js} +351 -20
- package/dist/chunk-FXAHNPBP.js.map +1 -0
- package/dist/{chunk-H3RHEH5W.cjs → chunk-IIDBEYQH.cjs} +59 -59
- package/dist/{chunk-H3RHEH5W.cjs.map → chunk-IIDBEYQH.cjs.map} +1 -1
- package/dist/{chunk-YEVAUQ6Y.js → chunk-WMMZDPWK.js} +3 -3
- package/dist/{chunk-YEVAUQ6Y.js.map → chunk-WMMZDPWK.js.map} +1 -1
- package/dist/index.cjs +144 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +121 -27
- package/dist/index.js.map +1 -1
- package/dist/loader.css +1 -1
- package/dist/{music-AF4VTFAF.js → music-3O5MDQXY.js} +52 -6
- package/dist/music-3O5MDQXY.js.map +1 -0
- package/dist/{music-KB7YZLHP.cjs → music-4O367SPR.cjs} +52 -6
- package/dist/music-4O367SPR.cjs.map +1 -0
- package/dist/three.cjs +5 -5
- package/dist/three.js +1 -1
- package/docs/api.md +11 -5
- package/docs/dsl.md +4 -2
- package/llms-full.txt +21 -12
- package/package.json +1 -1
- package/skills/pixi-effects/SKILL.md +1 -1
- package/skills/pixi-effects/reference/cheatsheet.md +4 -4
- package/skills/pixi-effects/reference/pitfalls.md +1 -0
- package/skills/pixi-effects/template.html +6 -6
- package/dist/Composition-THQPG4E5.cjs +0 -16
- package/dist/Composition-Z2VY3KWZ.js +0 -7
- package/dist/chunk-EABL3UKJ.cjs.map +0 -1
- package/dist/chunk-JHJNEIDI.js.map +0 -1
- package/dist/music-AF4VTFAF.js.map +0 -1
- package/dist/music-KB7YZLHP.cjs.map +0 -1
|
@@ -394,19 +394,29 @@ function evaluateExpr(source, scope) {
|
|
|
394
394
|
// src/expr/normalizeProps.ts
|
|
395
395
|
function normalizeProps(input, scope, options = {}) {
|
|
396
396
|
const skip = new Set(options.skipKeys ?? []);
|
|
397
|
-
return walk(input, scope, skip, null);
|
|
397
|
+
return walk(input, scope, skip, null, options);
|
|
398
398
|
}
|
|
399
|
-
|
|
399
|
+
var RELATIVE = /^\s*([+-])=([\s\S]*)$/;
|
|
400
|
+
function walk(value, scope, skip, currentKey, options) {
|
|
400
401
|
if (Array.isArray(value)) {
|
|
401
|
-
return value.map((v) => walk(v, scope, skip, currentKey));
|
|
402
|
+
return value.map((v) => walk(v, scope, skip, currentKey, options));
|
|
402
403
|
}
|
|
403
404
|
if (value !== null && typeof value === "object") {
|
|
404
405
|
const out = {};
|
|
405
406
|
for (const k of Object.keys(value)) {
|
|
406
|
-
out[k] = walk(value[k], scope, skip, k);
|
|
407
|
+
out[k] = walk(value[k], scope, skip, k, options);
|
|
407
408
|
}
|
|
408
409
|
return out;
|
|
409
410
|
}
|
|
411
|
+
if (typeof value === "string" && !(currentKey !== null && skip.has(currentKey))) {
|
|
412
|
+
const rel = RELATIVE.exec(value);
|
|
413
|
+
if (rel) {
|
|
414
|
+
const n = evaluateExpr(rel[2].trim(), scope);
|
|
415
|
+
if (options.relative === "allow") return `${rel[1]}=${n}`;
|
|
416
|
+
console.warn(`pixi-effects: "${value.trim()}"${currentKey ? ` (${currentKey})` : ""} is a relative value (measured from where the layer is): it works in a keyframe's to / from / set, not in ${options.where ?? "an initial or a style"}; it is read as the number ${rel[1] === "-" ? -n : n}`);
|
|
417
|
+
return rel[1] === "-" ? -n : n;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
410
420
|
if (isExpr(value) && !(currentKey !== null && skip.has(currentKey))) {
|
|
411
421
|
if (looksLikeColorString(value)) return value;
|
|
412
422
|
return evaluateExpr(value, scope);
|
|
@@ -637,7 +647,7 @@ function applyKeyframes(timeline, target, keyframes, parentDuration, scope, skip
|
|
|
637
647
|
const kf = normalizeKeyframe(raw, parentDuration);
|
|
638
648
|
const at = offset + kf.at;
|
|
639
649
|
if (kf.kind === "set") {
|
|
640
|
-
const resolved = normalizeProps(kf.set, scope, { skipKeys });
|
|
650
|
+
const resolved = normalizeProps(kf.set, scope, { skipKeys, relative: "allow" });
|
|
641
651
|
const { rest, routed } = splitRouted(resolved, routers);
|
|
642
652
|
for (const r of routed) timeline.set(r.target, { [r.prop]: r.value }, at);
|
|
643
653
|
const { ownProps, filterProps } = partitionProps(rest);
|
|
@@ -648,7 +658,7 @@ function applyKeyframes(timeline, target, keyframes, parentDuration, scope, skip
|
|
|
648
658
|
timeline.set(f, props, at);
|
|
649
659
|
}
|
|
650
660
|
} else if (kf.kind === "to") {
|
|
651
|
-
const resolved = normalizeProps(kf.to, scope, { skipKeys });
|
|
661
|
+
const resolved = normalizeProps(kf.to, scope, { skipKeys, relative: "allow" });
|
|
652
662
|
const { rest, routed } = splitRouted(resolved, routers);
|
|
653
663
|
for (const r of routed)
|
|
654
664
|
timeline.to(r.target, { [r.prop]: r.value, duration: kf.duration, ease: kf.ease, ...kf.loop }, at);
|
|
@@ -661,7 +671,7 @@ function applyKeyframes(timeline, target, keyframes, parentDuration, scope, skip
|
|
|
661
671
|
timeline.to(f, { ...props, duration: kf.duration, ease: kf.ease, ...kf.loop }, at);
|
|
662
672
|
}
|
|
663
673
|
} else if (kf.kind === "from") {
|
|
664
|
-
const resolved = normalizeProps(kf.from, scope, { skipKeys });
|
|
674
|
+
const resolved = normalizeProps(kf.from, scope, { skipKeys, relative: "allow" });
|
|
665
675
|
const { rest, routed } = splitRouted(resolved, routers);
|
|
666
676
|
for (const r of routed)
|
|
667
677
|
timeline.from(r.target, { [r.prop]: r.value, duration: kf.duration, ease: kf.ease, ...kf.loop }, at);
|
|
@@ -674,8 +684,8 @@ function applyKeyframes(timeline, target, keyframes, parentDuration, scope, skip
|
|
|
674
684
|
timeline.from(f, { ...props, duration: kf.duration, ease: kf.ease, ...kf.loop }, at);
|
|
675
685
|
}
|
|
676
686
|
} else {
|
|
677
|
-
const fromResolved = normalizeProps(kf.from, scope, { skipKeys });
|
|
678
|
-
const toResolved = normalizeProps(kf.to, scope, { skipKeys });
|
|
687
|
+
const fromResolved = normalizeProps(kf.from, scope, { skipKeys, where: "the from side of a from + to keyframe" });
|
|
688
|
+
const toResolved = normalizeProps(kf.to, scope, { skipKeys, relative: "allow" });
|
|
679
689
|
const fromRouted = splitRouted(fromResolved, routers);
|
|
680
690
|
const toRouted = splitRouted(toResolved, routers);
|
|
681
691
|
const fromByKey = new Map(fromRouted.routed.map((r) => [r.key, r]));
|
|
@@ -725,7 +735,7 @@ function applyKeyframes(timeline, target, keyframes, parentDuration, scope, skip
|
|
|
725
735
|
}
|
|
726
736
|
function applyInitial(target, initial, scope, skipKeys = [], routers) {
|
|
727
737
|
if (!initial) return;
|
|
728
|
-
const resolved = normalizeProps(initial, scope, { skipKeys });
|
|
738
|
+
const resolved = normalizeProps(initial, scope, { skipKeys, where: "initial" });
|
|
729
739
|
const { rest, routed } = splitRouted(resolved, routers);
|
|
730
740
|
for (const r of routed) gsap.gsap.set(r.target, { [r.prop]: r.value });
|
|
731
741
|
const { ownProps, filterProps } = partitionProps(rest);
|
|
@@ -2986,7 +2996,7 @@ var AudioSequence = class extends Sequence {
|
|
|
2986
2996
|
const raw = spec;
|
|
2987
2997
|
if (raw.asset !== void 0) console.warn(`pixi-effects: ${who}: has both asset and music \u2014 the music plays; remove one`);
|
|
2988
2998
|
if (raw.sfx !== void 0) console.warn(`pixi-effects: ${who}: has both sfx and music \u2014 the music plays; remove one`);
|
|
2989
|
-
const { resolveMusic, renderMusic, musicLength, musicKey } = await import('./music-
|
|
2999
|
+
const { resolveMusic, renderMusic, musicLength, musicKey } = await import('./music-4O367SPR.cjs');
|
|
2990
3000
|
const music = resolveMusic(spec.music, who);
|
|
2991
3001
|
if (!music) return;
|
|
2992
3002
|
const natural = musicLength(music);
|
|
@@ -3063,19 +3073,57 @@ var AudioSequence = class extends Sequence {
|
|
|
3063
3073
|
};
|
|
3064
3074
|
|
|
3065
3075
|
// src/core/FrameCache.ts
|
|
3076
|
+
var BUDGET_BYTES = 96 * 1024 * 1024;
|
|
3077
|
+
var MIN_HELD = 3;
|
|
3078
|
+
var JUMP_SECONDS = 1;
|
|
3079
|
+
var EPS = 1e-6;
|
|
3080
|
+
var LOW_BEHIND = 14;
|
|
3081
|
+
var MIN_HELD_TO_READ_BACK = 20;
|
|
3082
|
+
var MIN_HELD_TO_READ_START = 9;
|
|
3083
|
+
function reportDecodeError(err, time) {
|
|
3084
|
+
const isTransient = err instanceof DOMException && (err.name === "EncodingError" || err.name === "NotSupportedError");
|
|
3085
|
+
if (isTransient) console.debug("pixi-effects: transient video decode failure at", time, "s \u2014", err.name);
|
|
3086
|
+
else console.warn("pixi-effects: video frame decode failed at", time, "s \u2014", err);
|
|
3087
|
+
}
|
|
3066
3088
|
var FrameCache = class {
|
|
3067
3089
|
sink;
|
|
3068
3090
|
capacity;
|
|
3069
3091
|
cache = /* @__PURE__ */ new Map();
|
|
3070
3092
|
_pending = /* @__PURE__ */ new Map();
|
|
3093
|
+
// Sequential mode (the sink has `samples()`): one decoder pass that runs forward as the movie asks for frame after frame.
|
|
3094
|
+
_frames = [];
|
|
3095
|
+
_cur = null;
|
|
3096
|
+
_iter = null;
|
|
3097
|
+
_next = null;
|
|
3098
|
+
_held = 0;
|
|
3099
|
+
_lock = Promise.resolve();
|
|
3100
|
+
_lastTime = null;
|
|
3101
|
+
_dir = 0;
|
|
3102
|
+
_gen = 0;
|
|
3103
|
+
_backBusy = false;
|
|
3104
|
+
_backGen = 0;
|
|
3105
|
+
_standbyBusy = false;
|
|
3106
|
+
/** A second pass at the start of the file, ready for the moment a looping video comes round. */
|
|
3107
|
+
_standby = null;
|
|
3108
|
+
_duration;
|
|
3109
|
+
_loop;
|
|
3110
|
+
/** How often it had to wait for a decoder (`restarts`, `singles`) and how many reads ahead it made: for tests and for `check`. */
|
|
3111
|
+
stats = { restarts: 0, singles: 0, prefetches: 0 };
|
|
3071
3112
|
constructor(sink, options = {}) {
|
|
3072
3113
|
this.sink = sink;
|
|
3073
3114
|
this.capacity = options.capacity ?? 30;
|
|
3115
|
+
this._duration = options.duration ?? null;
|
|
3116
|
+
this._loop = !!options.loop;
|
|
3117
|
+
}
|
|
3118
|
+
/** How many decoded frames are held right now (the sequential reader's own list, plus the exact-time cache of the old path). */
|
|
3119
|
+
get heldFrames() {
|
|
3120
|
+
return this._frames.length + this.cache.size;
|
|
3074
3121
|
}
|
|
3075
3122
|
_key(time) {
|
|
3076
3123
|
return Math.round(time * 1e6);
|
|
3077
3124
|
}
|
|
3078
3125
|
async getFrameAt(time) {
|
|
3126
|
+
if (this.sink.samples) return this._enqueue(() => this._sequentialAt(time));
|
|
3079
3127
|
const key = this._key(time);
|
|
3080
3128
|
if (this.cache.has(key)) {
|
|
3081
3129
|
const v = this.cache.get(key);
|
|
@@ -3113,14 +3161,287 @@ var FrameCache = class {
|
|
|
3113
3161
|
sample.close?.();
|
|
3114
3162
|
return frame;
|
|
3115
3163
|
} catch (err) {
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3164
|
+
reportDecodeError(err, time);
|
|
3165
|
+
return null;
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
// ── sequential mode ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
3169
|
+
/** Requests are answered one at a time, in the order they arrive: one pass cannot serve two places at once. */
|
|
3170
|
+
_enqueue(fn) {
|
|
3171
|
+
const run = this._lock.then(fn, fn);
|
|
3172
|
+
this._lock = run.then(() => void 0, () => void 0);
|
|
3173
|
+
return run;
|
|
3174
|
+
}
|
|
3175
|
+
_covering(time) {
|
|
3176
|
+
for (const e of this._frames) {
|
|
3177
|
+
if (e.end === null ? e.req !== void 0 && Math.abs(e.req - time) < EPS : e.ts <= time + EPS && time < e.end - EPS) {
|
|
3178
|
+
this._frames.splice(this._frames.indexOf(e), 1);
|
|
3179
|
+
this._frames.push(e);
|
|
3180
|
+
return e;
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
return null;
|
|
3184
|
+
}
|
|
3185
|
+
async _sequentialAt(time) {
|
|
3186
|
+
const last = this._lastTime;
|
|
3187
|
+
this._lastTime = time;
|
|
3188
|
+
if (last !== null && Math.abs(time - last) <= JUMP_SECONDS) this._dir = time > last + EPS ? 1 : time < last - EPS ? -1 : this._dir;
|
|
3189
|
+
let frame;
|
|
3190
|
+
try {
|
|
3191
|
+
frame = await this._serve(time, last);
|
|
3192
|
+
} catch (err) {
|
|
3193
|
+
reportDecodeError(err, time);
|
|
3194
|
+
await this._closeIter();
|
|
3195
|
+
return null;
|
|
3196
|
+
}
|
|
3197
|
+
this._maybeReadAhead(time);
|
|
3198
|
+
return frame;
|
|
3199
|
+
}
|
|
3200
|
+
async _serve(time, last) {
|
|
3201
|
+
const hit = this._covering(time);
|
|
3202
|
+
if (hit) return hit.frame;
|
|
3203
|
+
const sb = this._standby;
|
|
3204
|
+
if (sb && time >= sb.cur.ts - EPS && time - sb.cur.ts <= JUMP_SECONDS && (!this._iter || !this._cur || this._cur.ts > time + JUMP_SECONDS)) return await this._adoptStandby(time);
|
|
3205
|
+
if (last === null || Math.abs(time - last) > JUMP_SECONDS) return await this._single(time);
|
|
3206
|
+
const cur = this._cur;
|
|
3207
|
+
if (this._iter && cur && time >= cur.ts && time - cur.ts <= JUMP_SECONDS) return await this._advance(time);
|
|
3208
|
+
const backwards = !!cur && time < cur.ts;
|
|
3209
|
+
return await this._restart(time, backwards ? Math.max(0, time - Math.max(1, this._held - 1) / 30) : time);
|
|
3210
|
+
}
|
|
3211
|
+
// ── reading ahead ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
3212
|
+
_maybeReadAhead(time) {
|
|
3213
|
+
if (this._held === 0) return;
|
|
3214
|
+
if (this._dir === -1) {
|
|
3215
|
+
this._dropStandby();
|
|
3216
|
+
let behind = 0, passed = 0, earliest = Infinity;
|
|
3217
|
+
for (const e of this._frames) {
|
|
3218
|
+
if (e.ts < time - EPS) behind++;
|
|
3219
|
+
else if (e.ts > time + EPS) passed++;
|
|
3220
|
+
if (e.ts < earliest) earliest = e.ts;
|
|
3221
|
+
}
|
|
3222
|
+
if (this._held >= MIN_HELD_TO_READ_BACK && behind < LOW_BEHIND && passed >= 3 && earliest > EPS && !this._backBusy) void this._readBack(earliest, passed);
|
|
3223
|
+
} else if (this._held >= MIN_HELD_TO_READ_START && this._dir === 1 && this._loop && this._duration !== null && time >= this._duration - JUMP_SECONDS && !this._standby && !this._standbyBusy) {
|
|
3224
|
+
void this._readStart();
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
/** In the background: the window of frames before the earliest one held (reverse play comes to it next). */
|
|
3228
|
+
async _readBack(earliest, room) {
|
|
3229
|
+
this._backBusy = true;
|
|
3230
|
+
this.stats.prefetches++;
|
|
3231
|
+
const gen = this._gen, bg = this._backGen;
|
|
3232
|
+
const want = Math.max(3, Math.min(Math.floor(this._held * 0.6), room));
|
|
3233
|
+
let iter = null;
|
|
3234
|
+
const got = [];
|
|
3235
|
+
try {
|
|
3236
|
+
iter = this.sink.samples(Math.max(0, earliest - want / 30));
|
|
3237
|
+
for (; ; ) {
|
|
3238
|
+
const r = await iter.next();
|
|
3239
|
+
if (r.done) break;
|
|
3240
|
+
if (gen !== this._gen || r.value.timestamp >= earliest - EPS) {
|
|
3241
|
+
r.value.close?.();
|
|
3242
|
+
break;
|
|
3243
|
+
}
|
|
3244
|
+
got.push(r.value);
|
|
3245
|
+
if (bg !== this._backGen) break;
|
|
3246
|
+
}
|
|
3247
|
+
if (gen === this._gen && bg === this._backGen) {
|
|
3248
|
+
for (let k = 0; k < got.length; k++) {
|
|
3249
|
+
const s = got[k];
|
|
3250
|
+
const frame = s.toVideoFrame();
|
|
3251
|
+
this._keep({ ts: s.timestamp, end: got[k + 1]?.timestamp ?? earliest, frame }, frame);
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
} catch (err) {
|
|
3255
|
+
reportDecodeError(err, earliest);
|
|
3256
|
+
} finally {
|
|
3257
|
+
for (const s of got) s.close?.();
|
|
3258
|
+
this._backBusy = false;
|
|
3259
|
+
try {
|
|
3260
|
+
await iter?.return?.(void 0);
|
|
3261
|
+
} catch {
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
/** In the background: the first frames of the file and a pass that stands behind them; held (pinned) until the loop comes round. */
|
|
3266
|
+
async _readStart() {
|
|
3267
|
+
this._standbyBusy = true;
|
|
3268
|
+
this.stats.prefetches++;
|
|
3269
|
+
const gen = this._gen;
|
|
3270
|
+
const want = Math.max(3, Math.min(10, Math.floor(this._held / 3)));
|
|
3271
|
+
let iter = null;
|
|
3272
|
+
const got = [];
|
|
3273
|
+
let keep = false;
|
|
3274
|
+
try {
|
|
3275
|
+
iter = this.sink.samples(0);
|
|
3276
|
+
for (; got.length < want + 1; ) {
|
|
3277
|
+
const r = await iter.next();
|
|
3278
|
+
if (r.done) break;
|
|
3279
|
+
if (gen !== this._gen) {
|
|
3280
|
+
r.value.close?.();
|
|
3281
|
+
break;
|
|
3282
|
+
}
|
|
3283
|
+
got.push(r.value);
|
|
3284
|
+
}
|
|
3285
|
+
if (gen === this._gen && got.length >= 2) {
|
|
3286
|
+
const next = got.pop();
|
|
3287
|
+
let cur = null;
|
|
3288
|
+
got.forEach((s, k) => {
|
|
3289
|
+
const frame = s.toVideoFrame();
|
|
3290
|
+
s.close?.();
|
|
3291
|
+
const entry = { ts: s.timestamp, end: (got[k + 1] ?? next).timestamp, frame, pinned: true };
|
|
3292
|
+
this._keep(entry, frame);
|
|
3293
|
+
cur = entry;
|
|
3294
|
+
});
|
|
3295
|
+
got.length = 0;
|
|
3296
|
+
if (cur) {
|
|
3297
|
+
this._standby = { iter, cur, next };
|
|
3298
|
+
keep = true;
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
} catch (err) {
|
|
3302
|
+
reportDecodeError(err, 0);
|
|
3303
|
+
} finally {
|
|
3304
|
+
for (const s of got) s.close?.();
|
|
3305
|
+
this._standbyBusy = false;
|
|
3306
|
+
if (!keep) {
|
|
3307
|
+
try {
|
|
3308
|
+
await iter?.return?.(void 0);
|
|
3309
|
+
} catch {
|
|
3310
|
+
}
|
|
3121
3311
|
}
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
async _adoptStandby(time) {
|
|
3315
|
+
const sb = this._standby;
|
|
3316
|
+
this._standby = null;
|
|
3317
|
+
await this._closeIter();
|
|
3318
|
+
for (const e of this._frames) e.pinned = false;
|
|
3319
|
+
this._iter = sb.iter;
|
|
3320
|
+
this._next = sb.next;
|
|
3321
|
+
this._cur = sb.cur;
|
|
3322
|
+
return this._advance(time);
|
|
3323
|
+
}
|
|
3324
|
+
_dropStandby() {
|
|
3325
|
+
const sb = this._standby;
|
|
3326
|
+
if (!sb) return;
|
|
3327
|
+
this._standby = null;
|
|
3328
|
+
sb.next?.close?.();
|
|
3329
|
+
for (const e of this._frames.filter((x) => x.pinned)) {
|
|
3330
|
+
e.pinned = false;
|
|
3331
|
+
}
|
|
3332
|
+
void sb.iter.return?.(void 0);
|
|
3333
|
+
}
|
|
3334
|
+
/** One frame by itself (a seek): getSample, kept so that asking again for the same time costs nothing. The running pass, if any, is closed: it no longer follows the playhead. */
|
|
3335
|
+
async _single(time) {
|
|
3336
|
+
this.stats.singles++;
|
|
3337
|
+
await this._closeIter();
|
|
3338
|
+
this._cur = null;
|
|
3339
|
+
const sample = await this.sink.getSample(time);
|
|
3340
|
+
if (!sample) return null;
|
|
3341
|
+
const frame = sample.toVideoFrame();
|
|
3342
|
+
sample.close?.();
|
|
3343
|
+
this._keep({ ts: sample.timestamp, end: null, frame, req: time }, frame);
|
|
3344
|
+
return frame;
|
|
3345
|
+
}
|
|
3346
|
+
/** Start a pass at `time` (a seek and the frames of one group of pictures), keeping what is still held for a step back. */
|
|
3347
|
+
async _restart(time, from = time) {
|
|
3348
|
+
this.stats.restarts++;
|
|
3349
|
+
this._backGen++;
|
|
3350
|
+
await this._closeIter();
|
|
3351
|
+
this._cur = null;
|
|
3352
|
+
const iter = this.sink.samples(from);
|
|
3353
|
+
this._iter = iter;
|
|
3354
|
+
const first = await iter.next();
|
|
3355
|
+
if (first.done) {
|
|
3356
|
+
await this._closeIter();
|
|
3122
3357
|
return null;
|
|
3123
3358
|
}
|
|
3359
|
+
if (first.value.timestamp > time + EPS) {
|
|
3360
|
+
first.value.close?.();
|
|
3361
|
+
await this._closeIter();
|
|
3362
|
+
return null;
|
|
3363
|
+
}
|
|
3364
|
+
this._take(first.value);
|
|
3365
|
+
return this._advance(time);
|
|
3366
|
+
}
|
|
3367
|
+
/** Run the pass forward until the frame that covers `time` is the current one; the next frame is read ahead (its start is where this one ends). */
|
|
3368
|
+
async _advance(time) {
|
|
3369
|
+
const cur = () => this._cur;
|
|
3370
|
+
for (; ; ) {
|
|
3371
|
+
if (!this._next) {
|
|
3372
|
+
let r;
|
|
3373
|
+
try {
|
|
3374
|
+
r = await this._iter.next();
|
|
3375
|
+
} catch (err) {
|
|
3376
|
+
reportDecodeError(err, time);
|
|
3377
|
+
await this._closeIter();
|
|
3378
|
+
const prev = this._frames.length > 1 ? this._frames[this._frames.length - 2] : null;
|
|
3379
|
+
const spacing = prev ? cur().ts - prev.ts : 1 / 24;
|
|
3380
|
+
return time < cur().ts + spacing - EPS ? cur().frame : null;
|
|
3381
|
+
}
|
|
3382
|
+
if (r.done) {
|
|
3383
|
+
cur().end = Infinity;
|
|
3384
|
+
this._iter = null;
|
|
3385
|
+
break;
|
|
3386
|
+
}
|
|
3387
|
+
this._next = r.value;
|
|
3388
|
+
}
|
|
3389
|
+
cur().end = this._next.timestamp;
|
|
3390
|
+
if (this._next.timestamp <= time + EPS) {
|
|
3391
|
+
const s = this._next;
|
|
3392
|
+
this._next = null;
|
|
3393
|
+
this._take(s);
|
|
3394
|
+
} else break;
|
|
3395
|
+
}
|
|
3396
|
+
return cur().frame;
|
|
3397
|
+
}
|
|
3398
|
+
_take(sample) {
|
|
3399
|
+
const frame = sample.toVideoFrame();
|
|
3400
|
+
sample.close?.();
|
|
3401
|
+
const entry = { ts: sample.timestamp, end: null, frame };
|
|
3402
|
+
this._keep(entry, frame);
|
|
3403
|
+
this._cur = entry;
|
|
3404
|
+
}
|
|
3405
|
+
/** Hold a frame (the first one sets how many fit: a byte budget, not a count) and close the oldest beyond that; the frame the pass stands on is never closed. */
|
|
3406
|
+
_keep(entry, frame) {
|
|
3407
|
+
if (this._held === 0) {
|
|
3408
|
+
const bytes = frame.allocationSize?.() ?? frame.displayWidth * frame.displayHeight * 1.5;
|
|
3409
|
+
this._held = Math.max(MIN_HELD, Math.min(this.capacity, Math.floor(BUDGET_BYTES / Math.max(1, bytes))));
|
|
3410
|
+
}
|
|
3411
|
+
this._frames.push(entry);
|
|
3412
|
+
while (this._frames.length > this._held) {
|
|
3413
|
+
const victim = this._victim(entry);
|
|
3414
|
+
if (!victim) break;
|
|
3415
|
+
this._frames.splice(this._frames.indexOf(victim), 1);
|
|
3416
|
+
victim.frame.close?.();
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
/**
|
|
3420
|
+
* The frame to let go: one the playhead has already passed (going forward those before it, going back those after it), the furthest
|
|
3421
|
+
* behind first; when there is none, the one furthest ahead of the playhead (the last to be needed). Least recently used when the
|
|
3422
|
+
* direction is not known. Frames of the standby (pinned) and the frame the pass stands on are never chosen.
|
|
3423
|
+
*/
|
|
3424
|
+
_victim(except) {
|
|
3425
|
+
const cand = this._frames.filter((e) => e !== this._cur && e !== except && !e.pinned);
|
|
3426
|
+
if (cand.length === 0) return null;
|
|
3427
|
+
if (this._dir === 0) return cand[0];
|
|
3428
|
+
const at = this._lastTime ?? 0;
|
|
3429
|
+
const passed = cand.filter((e) => this._dir === 1 ? e.ts < at - EPS : e.ts > at + EPS);
|
|
3430
|
+
const farthest = (list, sign) => list.reduce((a, b) => sign * (b.ts - a.ts) > 0 ? b : a);
|
|
3431
|
+
if (passed.length) return farthest(passed, this._dir === 1 ? -1 : 1);
|
|
3432
|
+
return farthest(cand, this._dir === 1 ? 1 : -1);
|
|
3433
|
+
}
|
|
3434
|
+
async _closeIter() {
|
|
3435
|
+
const iter = this._iter;
|
|
3436
|
+
this._iter = null;
|
|
3437
|
+
this._next?.close?.();
|
|
3438
|
+
this._next = null;
|
|
3439
|
+
if (iter) {
|
|
3440
|
+
try {
|
|
3441
|
+
await iter.return?.(void 0);
|
|
3442
|
+
} catch {
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3124
3445
|
}
|
|
3125
3446
|
_evictIfNeeded() {
|
|
3126
3447
|
while (this.cache.size > this.capacity) {
|
|
@@ -3132,9 +3453,19 @@ var FrameCache = class {
|
|
|
3132
3453
|
}
|
|
3133
3454
|
}
|
|
3134
3455
|
dispose() {
|
|
3456
|
+
this._gen++;
|
|
3457
|
+
this._dropStandby();
|
|
3135
3458
|
for (const f of this.cache.values()) f?.close?.();
|
|
3136
3459
|
this.cache.clear();
|
|
3137
3460
|
this._pending.clear();
|
|
3461
|
+
for (const e of this._frames) e.frame.close?.();
|
|
3462
|
+
this._frames = [];
|
|
3463
|
+
this._cur = null;
|
|
3464
|
+
this._next?.close?.();
|
|
3465
|
+
this._next = null;
|
|
3466
|
+
const iter = this._iter;
|
|
3467
|
+
this._iter = null;
|
|
3468
|
+
void iter?.return?.(void 0);
|
|
3138
3469
|
}
|
|
3139
3470
|
};
|
|
3140
3471
|
|
|
@@ -3165,7 +3496,7 @@ var VideoSequence = class extends Sequence {
|
|
|
3165
3496
|
this._sourceDuration = data.duration;
|
|
3166
3497
|
this._remap = remapOf(this.spec, "currentTime", this._sourceDuration);
|
|
3167
3498
|
this._audioBuffer = this.spec.audio !== false ? data.audioBuffer : null;
|
|
3168
|
-
this._cache = new FrameCache(data.sink, { capacity: 30 });
|
|
3499
|
+
this._cache = new FrameCache(data.sink, { capacity: 30, duration: data.duration, loop: !!this.spec.loop });
|
|
3169
3500
|
const probeFrame = await this._cache.getFrameAt(0);
|
|
3170
3501
|
const w = probeFrame?.displayWidth ?? 1920;
|
|
3171
3502
|
const h = probeFrame?.displayHeight ?? 1080;
|
|
@@ -4365,7 +4696,7 @@ function resolveDim(v, scope) {
|
|
|
4365
4696
|
var _CompositionSequence = null;
|
|
4366
4697
|
async function getCompositionSequence() {
|
|
4367
4698
|
if (!_CompositionSequence) {
|
|
4368
|
-
_CompositionSequence = (await import('./Composition-
|
|
4699
|
+
_CompositionSequence = (await import('./Composition-EE3KEE2L.cjs')).CompositionSequence;
|
|
4369
4700
|
}
|
|
4370
4701
|
return _CompositionSequence;
|
|
4371
4702
|
}
|
|
@@ -4450,5 +4781,5 @@ exports.registerSequenceType = registerSequenceType;
|
|
|
4450
4781
|
exports.remapOf = remapOf;
|
|
4451
4782
|
exports.resolveAt = resolveAt;
|
|
4452
4783
|
exports.timeRemapOf = timeRemapOf;
|
|
4453
|
-
//# sourceMappingURL=chunk-
|
|
4454
|
-
//# sourceMappingURL=chunk-
|
|
4784
|
+
//# sourceMappingURL=chunk-A225HYSY.cjs.map
|
|
4785
|
+
//# sourceMappingURL=chunk-A225HYSY.cjs.map
|