pixi-effects 0.7.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/CHANGELOG.md +21 -0
- package/README.md +8 -7
- package/ai/SKILL.md +6 -5
- package/ai/reference/cheatsheet.md +9 -1
- package/ai/template.html +4 -3
- package/ai/tools/check.mjs +8 -0
- package/dist/Composition-CM62XJKT.cjs +14 -0
- package/dist/{Composition-3KHQPRBA.cjs.map → Composition-CM62XJKT.cjs.map} +1 -1
- package/dist/Composition-RJDSQYTV.js +5 -0
- package/dist/{Composition-DT657FO5.js.map → Composition-RJDSQYTV.js.map} +1 -1
- package/dist/Controller.cjs +55 -20
- package/dist/Controller.cjs.map +1 -1
- package/dist/Controller.d.cts +31 -2
- package/dist/Controller.d.ts +31 -2
- package/dist/Controller.js +55 -21
- package/dist/Controller.js.map +1 -1
- package/dist/{Movie-B0OUKlZI.d.ts → Movie-QMlFyHkf.d.ts} +49 -1
- package/dist/{Movie-BK9daiTt.d.cts → Movie-qGHp15c_.d.cts} +49 -1
- package/dist/{chunk-6I55T4OH.cjs → chunk-AGMNSJJH.cjs} +13 -4
- package/dist/chunk-AGMNSJJH.cjs.map +1 -0
- package/dist/{chunk-4O2NYDJG.js → chunk-FNKSXBKB.js} +13 -4
- package/dist/chunk-FNKSXBKB.js.map +1 -0
- package/dist/{chunk-36Z4RIRK.js → chunk-O4AJYUPK.js} +3 -3
- package/dist/{chunk-36Z4RIRK.js.map → chunk-O4AJYUPK.js.map} +1 -1
- package/dist/{chunk-4VSIJLU3.cjs → chunk-PDBDDGG5.cjs} +21 -21
- package/dist/{chunk-4VSIJLU3.cjs.map → chunk-PDBDDGG5.cjs.map} +1 -1
- package/dist/index.cjs +141 -26
- 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 +130 -15
- package/dist/index.js.map +1 -1
- package/dist/three.cjs +5 -5
- package/dist/three.js +1 -1
- package/docs/api.md +67 -0
- package/docs/dsl.md +14 -2
- package/llms-full.txt +96 -8
- package/package.json +4 -1
- package/dist/Composition-3KHQPRBA.cjs +0 -14
- package/dist/Composition-DT657FO5.js +0 -5
- package/dist/chunk-4O2NYDJG.js.map +0 -1
- package/dist/chunk-6I55T4OH.cjs.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FrameEvent, I as InspectOptions, c as InspectReport, L as LayerInfo, M as Movie, d as MovieOptions, P as ProgressEvent, R as RenderOptions, S as SnapshotOptions,
|
|
1
|
+
export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FrameEvent, I as InspectOptions, c as InspectReport, L as LayerInfo, M as Movie, d as MovieErrorEvent, e as MovieOptions, P as ProgressEvent, R as RenderOptions, S as SnapshotOptions, f as SoundMeasure, T as TimelineData, g as TimelineHtmlOptions, h as TimelineRow, i as TimelineSvgOptions, j as TimelineTransition, V as VolumeEvent } from './Movie-qGHp15c_.cjs';
|
|
2
2
|
import { S as Sequence } from './Base-CSIulyIq.cjs';
|
|
3
3
|
import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, b as SequenceCommon } from './types-oJijbn-K.cjs';
|
|
4
4
|
export { A as ArcShapeSpec, c as AssetSpec, d as AudioAssetSpec, e as AudioSequenceSpec, f as AudioSfxSpec, g as ChromaKeyFilterSpec, h as CircleShapeSpec, i as CompositionSequenceSpec, j as CompositionSpec, k as CrossfadeTransition, l as CustomFilterSpec, D as DipTransition, m as DissolveTransition, E as EllipseShapeSpec, n as Expr, F as FilterSpec, G as GradientSpec, o as IrisTransition, K as Keyframe, L as LineShapeSpec, P as PathShapeSpec, p as PolygonShapeSpec, q as PropValue, r as Props, R as RectShapeSpec, s as SfxKnobs, t as SfxOptions, u as SfxPreset, v as SfxVoice, w as ShapeSequenceSpec, x as SlideTransition, T as TextSequenceSpec, y as TransitionCommon, z as TransitionSpec, V as VideoSequenceSpec, W as WipeTransition, Z as ZoomTransition } from './types-oJijbn-K.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FrameEvent, I as InspectOptions, c as InspectReport, L as LayerInfo, M as Movie, d as MovieOptions, P as ProgressEvent, R as RenderOptions, S as SnapshotOptions,
|
|
1
|
+
export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FrameEvent, I as InspectOptions, c as InspectReport, L as LayerInfo, M as Movie, d as MovieErrorEvent, e as MovieOptions, P as ProgressEvent, R as RenderOptions, S as SnapshotOptions, f as SoundMeasure, T as TimelineData, g as TimelineHtmlOptions, h as TimelineRow, i as TimelineSvgOptions, j as TimelineTransition, V as VolumeEvent } from './Movie-QMlFyHkf.js';
|
|
2
2
|
import { S as Sequence } from './Base-DVgwyIUj.js';
|
|
3
3
|
import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, b as SequenceCommon } from './types-oJijbn-K.js';
|
|
4
4
|
export { A as ArcShapeSpec, c as AssetSpec, d as AudioAssetSpec, e as AudioSequenceSpec, f as AudioSfxSpec, g as ChromaKeyFilterSpec, h as CircleShapeSpec, i as CompositionSequenceSpec, j as CompositionSpec, k as CrossfadeTransition, l as CustomFilterSpec, D as DipTransition, m as DissolveTransition, E as EllipseShapeSpec, n as Expr, F as FilterSpec, G as GradientSpec, o as IrisTransition, K as Keyframe, L as LineShapeSpec, P as PathShapeSpec, p as PolygonShapeSpec, q as PropValue, r as Props, R as RectShapeSpec, s as SfxKnobs, t as SfxOptions, u as SfxPreset, v as SfxVoice, w as ShapeSequenceSpec, x as SlideTransition, T as TextSequenceSpec, y as TransitionCommon, z as TransitionSpec, V as VideoSequenceSpec, W as WipeTransition, Z as ZoomTransition } from './types-oJijbn-K.js';
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { CompositionSequence } from './chunk-
|
|
2
|
-
import { ensureFilterLibrary, homeDistance, DEFAULT_FOV, DEG, resolveAt, collectPropKeys } from './chunk-
|
|
3
|
-
export { registerSequenceType } from './chunk-
|
|
1
|
+
import { CompositionSequence } from './chunk-O4AJYUPK.js';
|
|
2
|
+
import { ensureFilterLibrary, homeDistance, DEFAULT_FOV, DEG, resolveAt, collectPropKeys } from './chunk-FNKSXBKB.js';
|
|
3
|
+
export { registerSequenceType } from './chunk-FNKSXBKB.js';
|
|
4
4
|
import { warnUnknownOptions, suggestName } from './chunk-DKGG7WD4.js';
|
|
5
5
|
import * as PIXI from 'pixi.js';
|
|
6
6
|
import { extensions, CullerPlugin, Application, Container, Rectangle, Culler, CanvasTextMetrics, TextStyle, Assets, ExtensionType, Filter, UniformGroup, GpuProgram, GlProgram, defaultFilterVert } from 'pixi.js';
|
|
7
7
|
import { gsap } from 'gsap';
|
|
8
8
|
import { PixiPlugin } from 'gsap/PixiPlugin';
|
|
9
|
-
import { QUALITY_VERY_HIGH, QUALITY_HIGH, QUALITY_MEDIUM, QUALITY_LOW, QUALITY_VERY_LOW, Output, BufferTarget, CanvasSource, AudioBufferSource, Input, ALL_FORMATS, BlobSource, WavOutputFormat, Conversion, VideoSampleSink, MkvOutputFormat, WebMOutputFormat, MovOutputFormat, Mp4OutputFormat } from 'mediabunny';
|
|
9
|
+
import { QUALITY_VERY_HIGH, QUALITY_HIGH, QUALITY_MEDIUM, QUALITY_LOW, QUALITY_VERY_LOW, Output, BufferTarget, CanvasSource, AudioBufferSource, Input, ALL_FORMATS, BlobSource, WavOutputFormat, Conversion, VideoSampleSink, MkvOutputFormat, WebMOutputFormat, MovOutputFormat, Mp4OutputFormat, getFirstEncodableAudioCodec } from 'mediabunny';
|
|
10
10
|
|
|
11
11
|
var registered = false;
|
|
12
12
|
function ensureLoadersRegistered(audioContext) {
|
|
@@ -288,6 +288,28 @@ var AUDIO_CODEC_BY_FORMAT = {
|
|
|
288
288
|
webm: "opus",
|
|
289
289
|
mkv: "opus"
|
|
290
290
|
};
|
|
291
|
+
var AUDIO_PREFERENCE = {
|
|
292
|
+
mp4: ["aac", "opus", "mp3", "flac"],
|
|
293
|
+
mov: ["aac", "mp3"],
|
|
294
|
+
webm: ["opus", "vorbis"],
|
|
295
|
+
mkv: ["opus", "vorbis", "aac", "flac", "mp3"]
|
|
296
|
+
};
|
|
297
|
+
async function chooseAudioCodec(fmt, requested, container, audio, bitrate) {
|
|
298
|
+
const supported = container.getSupportedAudioCodecs?.();
|
|
299
|
+
const preferred = AUDIO_PREFERENCE[fmt].filter((c) => !supported || supported.includes(c));
|
|
300
|
+
const canEncode = (list) => getFirstEncodableAudioCodec(list, { numberOfChannels: audio.numberOfChannels ?? 2, sampleRate: audio.sampleRate, bitrate });
|
|
301
|
+
if (requested) {
|
|
302
|
+
if (await canEncode([requested])) return requested;
|
|
303
|
+
const alt = await canEncode(preferred.filter((c) => c !== requested));
|
|
304
|
+
throw new Error(`pixi-effects: this browser cannot encode "${requested}" audio for ${fmt}` + (alt ? `; "${alt}" would work: render({ format: '${fmt}', audio: { codec: '${alt}' } }), or leave audio.codec out and the best one is chosen.` : "; it has no audio encoder for this format at all (try another browser, or a movie without sound)."));
|
|
305
|
+
}
|
|
306
|
+
const chosen = await canEncode(preferred);
|
|
307
|
+
if (!chosen) throw new Error(`pixi-effects: this browser has no audio encoder for ${fmt} (it tried ${preferred.join(", ")}): try another browser, or render a movie without sound.`);
|
|
308
|
+
if (chosen !== preferred[0]) {
|
|
309
|
+
console.warn(`pixi-effects: this browser cannot encode ${preferred[0].toUpperCase()} audio (Chrome on Linux has no AAC encoder), so the ${fmt} carries ${chosen} instead; current players play it. A webm export needs no AAC.`);
|
|
310
|
+
}
|
|
311
|
+
return chosen;
|
|
312
|
+
}
|
|
291
313
|
var qualityMap = {
|
|
292
314
|
"very-low": QUALITY_VERY_LOW,
|
|
293
315
|
"low": QUALITY_LOW,
|
|
@@ -320,8 +342,10 @@ async function exportFrames(movie, options = {}) {
|
|
|
320
342
|
bitrate: qualityMap[options.audio?.bitrate ?? "high"] ?? QUALITY_HIGH
|
|
321
343
|
}
|
|
322
344
|
};
|
|
345
|
+
const container = makeOutputFormat(opts.format);
|
|
346
|
+
if (movie.audioBuffer) opts.audio.codec = await chooseAudioCodec(fmt, options.audio?.codec, container, movie.audioBuffer, opts.audio.bitrate);
|
|
323
347
|
const output = new Output({
|
|
324
|
-
format:
|
|
348
|
+
format: container,
|
|
325
349
|
target: new BufferTarget()
|
|
326
350
|
});
|
|
327
351
|
const canvasSource = new CanvasSource(movie.app.canvas, {
|
|
@@ -1405,6 +1429,21 @@ function startWhenRunning(ctx, start, gesture) {
|
|
|
1405
1429
|
}
|
|
1406
1430
|
var GESTURES = ["pointerdown", "mousedown", "touchend", "keydown"];
|
|
1407
1431
|
|
|
1432
|
+
// src/core/poster.ts
|
|
1433
|
+
function normalizePoster(poster, duration, frameRate) {
|
|
1434
|
+
if (poster === void 0 || poster === null) return { seconds: null, frame: null };
|
|
1435
|
+
if (typeof poster !== "number" || !Number.isFinite(poster)) {
|
|
1436
|
+
console.warn(`pixi-effects: movie.init({ poster }): ${JSON.stringify(poster)} is not a time in seconds; ignored`);
|
|
1437
|
+
return { seconds: null, frame: null };
|
|
1438
|
+
}
|
|
1439
|
+
let seconds = poster < 0 ? Math.max(0, duration + poster) : poster;
|
|
1440
|
+
if (seconds > duration) {
|
|
1441
|
+
console.warn(`pixi-effects: movie.init({ poster }): the poster time ${poster} s is past the end of the movie (${duration} s); using the last frame`);
|
|
1442
|
+
seconds = duration;
|
|
1443
|
+
}
|
|
1444
|
+
return { seconds, frame: Math.round(seconds * frameRate) };
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1408
1447
|
// src/core/Movie.ts
|
|
1409
1448
|
extensions.add(CullerPlugin);
|
|
1410
1449
|
gsap.registerPlugin(PixiPlugin);
|
|
@@ -1418,6 +1457,13 @@ var Movie = class {
|
|
|
1418
1457
|
audioSource = null;
|
|
1419
1458
|
gainNode = null;
|
|
1420
1459
|
_cancelAudioStart = null;
|
|
1460
|
+
/** The poster time in seconds (`movie.init({ poster })`), or null. */
|
|
1461
|
+
poster = null;
|
|
1462
|
+
_posterFrame = null;
|
|
1463
|
+
/** The canvas shows the poster picture while the playhead is still at frame 0 (like `<video poster>`): the first seek or play replaces it. */
|
|
1464
|
+
_atPoster = false;
|
|
1465
|
+
/** > 0 while the movie moves its own playhead (playback, render, snapshot, contactSheet): those are not the seeks `seeking` / `seeked` report. */
|
|
1466
|
+
_quiet = 0;
|
|
1421
1467
|
_volume = 1;
|
|
1422
1468
|
_muted = false;
|
|
1423
1469
|
isPlaying = false;
|
|
@@ -1459,6 +1505,36 @@ var Movie = class {
|
|
|
1459
1505
|
get isReady() {
|
|
1460
1506
|
return this._initState === "ready";
|
|
1461
1507
|
}
|
|
1508
|
+
/** The poster time as a frame number, or null. */
|
|
1509
|
+
get posterFrame() {
|
|
1510
|
+
return this._posterFrame;
|
|
1511
|
+
}
|
|
1512
|
+
/** Draw the poster frame on the canvas and leave the playhead where it is (frame 0): the picture before the movie plays. */
|
|
1513
|
+
async _showPoster() {
|
|
1514
|
+
if (this._posterFrame === null || !this.timeline) return;
|
|
1515
|
+
const back = this.currentFrame;
|
|
1516
|
+
this.currentFrame = this._posterFrame;
|
|
1517
|
+
try {
|
|
1518
|
+
this.timeline.time(this._posterFrame / this.frameRate);
|
|
1519
|
+
await this._awaitVideoFrames();
|
|
1520
|
+
this._updateSpace();
|
|
1521
|
+
this._renderNow();
|
|
1522
|
+
} finally {
|
|
1523
|
+
this.currentFrame = back;
|
|
1524
|
+
}
|
|
1525
|
+
this._atPoster = true;
|
|
1526
|
+
}
|
|
1527
|
+
async posterImage(opts = {}) {
|
|
1528
|
+
this._requireReady("posterImage");
|
|
1529
|
+
const back = this.currentFrame, wasAtPoster = this._atPoster;
|
|
1530
|
+
try {
|
|
1531
|
+
return await this.snapshot(this._posterFrame ?? 0, opts);
|
|
1532
|
+
} finally {
|
|
1533
|
+
this.currentFrame = back;
|
|
1534
|
+
if (wasAtPoster) await this._showPoster();
|
|
1535
|
+
else await this._quietly(() => this.gotoFrame(back, true));
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1462
1538
|
_ensureAudioContext() {
|
|
1463
1539
|
if (!this._audioContext) {
|
|
1464
1540
|
const Ctx = window.AudioContext ?? window.webkitAudioContext;
|
|
@@ -1467,7 +1543,7 @@ var Movie = class {
|
|
|
1467
1543
|
return this._audioContext;
|
|
1468
1544
|
}
|
|
1469
1545
|
async init(options = {}) {
|
|
1470
|
-
warnUnknownOptions("movie.init()", options, ["width", "height", "duration", "frameRate", "background", "canvas", "assets", "composition"]);
|
|
1546
|
+
warnUnknownOptions("movie.init()", options, ["width", "height", "duration", "frameRate", "background", "canvas", "assets", "composition", "poster"]);
|
|
1471
1547
|
this._initState = "pending";
|
|
1472
1548
|
try {
|
|
1473
1549
|
this.width = options.width ?? 1920;
|
|
@@ -1475,6 +1551,9 @@ var Movie = class {
|
|
|
1475
1551
|
this.duration = options.duration ?? 10;
|
|
1476
1552
|
this.frameRate = options.frameRate ?? 30;
|
|
1477
1553
|
this.totalFrames = Math.round(this.duration * this.frameRate);
|
|
1554
|
+
const poster = normalizePoster(options.poster, this.duration, this.frameRate);
|
|
1555
|
+
this.poster = poster.seconds;
|
|
1556
|
+
this._posterFrame = poster.frame;
|
|
1478
1557
|
this.background = options.background ?? "#000000";
|
|
1479
1558
|
this.timeline = gsap.timeline({ paused: true, defaults: { ease: "none" } });
|
|
1480
1559
|
this.timeline.add(gsap.to({}, { duration: this.duration }));
|
|
@@ -1538,6 +1617,7 @@ var Movie = class {
|
|
|
1538
1617
|
this._updateSpace();
|
|
1539
1618
|
this._renderNow();
|
|
1540
1619
|
this._initState = "ready";
|
|
1620
|
+
if (this._posterFrame !== null) await this._showPoster();
|
|
1541
1621
|
this.emit("ready");
|
|
1542
1622
|
} catch (err) {
|
|
1543
1623
|
try {
|
|
@@ -1545,12 +1625,33 @@ var Movie = class {
|
|
|
1545
1625
|
} catch (cleanupErr) {
|
|
1546
1626
|
console.warn("pixi-effects: cleanup after init failure also threw:", cleanupErr);
|
|
1547
1627
|
}
|
|
1628
|
+
this._emitError("init", err);
|
|
1548
1629
|
throw err;
|
|
1549
1630
|
}
|
|
1550
1631
|
}
|
|
1551
1632
|
async gotoFrame(frame, force = false) {
|
|
1552
1633
|
if (this._initState !== "ready") return;
|
|
1553
|
-
if (!force && this.currentFrame === frame) return;
|
|
1634
|
+
if (!force && !this._atPoster && this.currentFrame === frame) return;
|
|
1635
|
+
if (this._quiet > 0) return this._goto(frame);
|
|
1636
|
+
const target = Math.max(0, Math.min(frame, this.totalFrames));
|
|
1637
|
+
this.emit("seeking", { frame: target, totalFrames: this.totalFrames });
|
|
1638
|
+
await this._goto(frame);
|
|
1639
|
+
this.emit("seeked", { frame: this.currentFrame, totalFrames: this.totalFrames });
|
|
1640
|
+
}
|
|
1641
|
+
/** Run `fn` with `seeking` / `seeked` silenced: the movie is moving its own playhead, not answering a seek. */
|
|
1642
|
+
async _quietly(fn) {
|
|
1643
|
+
this._quiet++;
|
|
1644
|
+
try {
|
|
1645
|
+
return await fn();
|
|
1646
|
+
} finally {
|
|
1647
|
+
this._quiet--;
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
_emitError(where, error) {
|
|
1651
|
+
this.emit("error", { where, message: error instanceof Error ? error.message : String(error), error });
|
|
1652
|
+
}
|
|
1653
|
+
async _goto(frame) {
|
|
1654
|
+
this._atPoster = false;
|
|
1554
1655
|
this.currentFrame = Math.max(0, Math.min(frame, this.totalFrames));
|
|
1555
1656
|
this.timeline.time(this.currentFrame / this.frameRate);
|
|
1556
1657
|
await this._awaitVideoFrames();
|
|
@@ -1572,7 +1673,7 @@ var Movie = class {
|
|
|
1572
1673
|
async snapshot(frame = this.currentFrame, opts = {}) {
|
|
1573
1674
|
warnUnknownOptions("movie.snapshot()", opts, ["scale", "type", "as"]);
|
|
1574
1675
|
this._requireReady("snapshot");
|
|
1575
|
-
await this.gotoFrame(clampFrame(frame, this.totalFrames), true);
|
|
1676
|
+
await this._quietly(() => this.gotoFrame(clampFrame(frame, this.totalFrames), true));
|
|
1576
1677
|
const scale = opts.scale ?? 1;
|
|
1577
1678
|
const out = document.createElement("canvas");
|
|
1578
1679
|
out.width = Math.max(1, Math.round(this.width * scale));
|
|
@@ -1592,11 +1693,11 @@ var Movie = class {
|
|
|
1592
1693
|
const g = sheet.getContext("2d");
|
|
1593
1694
|
g.fillStyle = "#10131c";
|
|
1594
1695
|
g.fillRect(0, 0, L.width, L.height);
|
|
1595
|
-
const back = this.currentFrame;
|
|
1696
|
+
const back = this.currentFrame, wasAtPoster = this._atPoster;
|
|
1596
1697
|
try {
|
|
1597
1698
|
for (let i = 0; i < frames.length; i++) {
|
|
1598
1699
|
const f = frames[i];
|
|
1599
|
-
await this.gotoFrame(f, true);
|
|
1700
|
+
await this._quietly(() => this.gotoFrame(f, true));
|
|
1600
1701
|
const { x, y } = L.positions[i];
|
|
1601
1702
|
g.drawImage(this.app.canvas, x, y + LABEL_H, L.cellW, L.cellH);
|
|
1602
1703
|
g.fillStyle = "#e8ecf8";
|
|
@@ -1605,7 +1706,8 @@ var Movie = class {
|
|
|
1605
1706
|
g.fillText(`frame ${f} \xB7 ${(f / this.frameRate).toFixed(2)}s`, x + 8, y + LABEL_H / 2);
|
|
1606
1707
|
}
|
|
1607
1708
|
} finally {
|
|
1608
|
-
await this.
|
|
1709
|
+
if (wasAtPoster) await this._showPoster();
|
|
1710
|
+
else await this._quietly(() => this.gotoFrame(back, true));
|
|
1609
1711
|
}
|
|
1610
1712
|
return encodeCanvas(sheet, "image/png", opts.as ?? "blob");
|
|
1611
1713
|
}
|
|
@@ -1688,17 +1790,20 @@ var Movie = class {
|
|
|
1688
1790
|
const frame = Math.floor(elapsed * this.frameRate);
|
|
1689
1791
|
if (frame <= this.totalFrames) {
|
|
1690
1792
|
inFlight = true;
|
|
1691
|
-
this.
|
|
1793
|
+
this._goto(frame).catch((err) => {
|
|
1692
1794
|
console.warn("pixi-effects: gotoFrame failed during playback:", err);
|
|
1795
|
+
this._emitError("playback", err);
|
|
1693
1796
|
}).finally(() => {
|
|
1694
1797
|
inFlight = false;
|
|
1695
1798
|
});
|
|
1696
1799
|
this._raf = requestAnimationFrame(tick);
|
|
1697
1800
|
} else {
|
|
1698
1801
|
this.pause();
|
|
1699
|
-
this.
|
|
1802
|
+
this._goto(this.totalFrames).catch((err) => {
|
|
1700
1803
|
console.warn("pixi-effects: final gotoFrame failed:", err);
|
|
1804
|
+
this._emitError("playback", err);
|
|
1701
1805
|
});
|
|
1806
|
+
this.emit("ended");
|
|
1702
1807
|
}
|
|
1703
1808
|
};
|
|
1704
1809
|
this._raf = requestAnimationFrame(tick);
|
|
@@ -1742,15 +1847,20 @@ var Movie = class {
|
|
|
1742
1847
|
if (wasPlaying) this.emit("pause");
|
|
1743
1848
|
}
|
|
1744
1849
|
set volume(v) {
|
|
1745
|
-
|
|
1850
|
+
const next = Math.max(0, Math.min(1, v));
|
|
1851
|
+
const changed = next !== this._volume;
|
|
1852
|
+
this._volume = next;
|
|
1746
1853
|
if (this.gainNode) this.gainNode.gain.value = this._muted ? 0 : this._volume;
|
|
1854
|
+
if (changed) this.emit("volumechange", { volume: this._volume, muted: this._muted });
|
|
1747
1855
|
}
|
|
1748
1856
|
get volume() {
|
|
1749
1857
|
return this._volume;
|
|
1750
1858
|
}
|
|
1751
1859
|
set muted(v) {
|
|
1860
|
+
const changed = !!v !== this._muted;
|
|
1752
1861
|
this._muted = !!v;
|
|
1753
1862
|
if (this.gainNode) this.gainNode.gain.value = this._muted ? 0 : this._volume;
|
|
1863
|
+
if (changed) this.emit("volumechange", { volume: this._volume, muted: this._muted });
|
|
1754
1864
|
}
|
|
1755
1865
|
get muted() {
|
|
1756
1866
|
return this._muted;
|
|
@@ -1761,7 +1871,12 @@ var Movie = class {
|
|
|
1761
1871
|
}
|
|
1762
1872
|
async render(options) {
|
|
1763
1873
|
warnUnknownOptions("movie.render()", options, ["format", "video", "audio"]);
|
|
1764
|
-
|
|
1874
|
+
try {
|
|
1875
|
+
return await this._quietly(() => exportFrames(this, options));
|
|
1876
|
+
} catch (err) {
|
|
1877
|
+
this._emitError("render", err);
|
|
1878
|
+
throw err;
|
|
1879
|
+
}
|
|
1765
1880
|
}
|
|
1766
1881
|
async destroy() {
|
|
1767
1882
|
safeRun(() => this.pause());
|