@twick/2d 0.15.16 → 0.15.18
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.cjs +101 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -1
- package/dist/index.d.ts +30 -1
- package/dist/index.js +101 -16
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -5502,13 +5502,17 @@ var Media = class extends Rect {
|
|
|
5502
5502
|
super(props);
|
|
5503
5503
|
this.lastTime = -1;
|
|
5504
5504
|
this.isSchedulingPlay = false;
|
|
5505
|
+
/** Used with clipStart for sync (trim offset in source). */
|
|
5506
|
+
this.trimStart = 0;
|
|
5505
5507
|
if (!this.awaitCanPlay()) {
|
|
5506
|
-
this.scheduleSeek(this.time());
|
|
5508
|
+
setTimeout(() => this.scheduleSeek(this.time()), 0);
|
|
5507
5509
|
}
|
|
5508
5510
|
if (props.play) {
|
|
5509
5511
|
this.play();
|
|
5510
5512
|
}
|
|
5511
5513
|
this.volume = props.volume ?? 1;
|
|
5514
|
+
this.clipStart = props.clipStart;
|
|
5515
|
+
this.trimStart = props.trimStart ?? 0;
|
|
5512
5516
|
if (!this.awaitCanPlay()) {
|
|
5513
5517
|
this.setVolume(this.volume);
|
|
5514
5518
|
}
|
|
@@ -5548,25 +5552,72 @@ var Media = class extends Rect {
|
|
|
5548
5552
|
completion() {
|
|
5549
5553
|
return this.clampTime(this.time()) / this.getDuration();
|
|
5550
5554
|
}
|
|
5551
|
-
|
|
5555
|
+
/**
|
|
5556
|
+
* Sync the underlying media element to the given time (e.g. draw/playback time).
|
|
5557
|
+
* Used for both Video and Audio. When `time` is passed (e.g. from Scene2D.draw), that
|
|
5558
|
+
* value is used so sync does not depend on the node's time signal.
|
|
5559
|
+
* If this node has clipStart set, global time is converted to clip-relative time:
|
|
5560
|
+
* syncTime = trimStart + (time - clipStart) * playbackRate.
|
|
5561
|
+
* When omitted, falls back to this.time().
|
|
5562
|
+
* When waitForSeek is true, returns a promise that resolves when the seek completes so
|
|
5563
|
+
* the draw can wait and show the correct frame/sample when paused.
|
|
5564
|
+
*/
|
|
5565
|
+
syncToCurrentTime(time, options) {
|
|
5566
|
+
let syncTime;
|
|
5567
|
+
if (time !== void 0 && this.clipStart !== void 0) {
|
|
5568
|
+
syncTime = this.trimStart + (time - this.clipStart) * this.playbackRate();
|
|
5569
|
+
} else {
|
|
5570
|
+
syncTime = time ?? this.time();
|
|
5571
|
+
}
|
|
5572
|
+
const promise = this.setCurrentTime(syncTime, {
|
|
5573
|
+
skipCollectPromise: options?.waitForSeek ?? true
|
|
5574
|
+
});
|
|
5575
|
+
if (options?.waitForSeek) {
|
|
5576
|
+
return promise;
|
|
5577
|
+
}
|
|
5578
|
+
}
|
|
5579
|
+
setCurrentTime(value, options) {
|
|
5552
5580
|
try {
|
|
5553
5581
|
const media = this.mediaElement();
|
|
5554
|
-
|
|
5555
|
-
media.
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5582
|
+
const key = this.key ?? "media";
|
|
5583
|
+
if (media.readyState < 2) {
|
|
5584
|
+
this.lastTime = value;
|
|
5585
|
+
this.time(value);
|
|
5586
|
+
return new Promise((resolve) => {
|
|
5587
|
+
this.waitForCanPlay(media, () => {
|
|
5588
|
+
media.currentTime = value;
|
|
5589
|
+
this.lastTime = value;
|
|
5590
|
+
this.time(value);
|
|
5591
|
+
const onSeeked = () => {
|
|
5592
|
+
media.removeEventListener("seeked", onSeeked);
|
|
5561
5593
|
resolve();
|
|
5562
|
-
media.removeEventListener("seeked", listener);
|
|
5563
5594
|
};
|
|
5564
|
-
media.
|
|
5565
|
-
|
|
5566
|
-
|
|
5595
|
+
if (media.seeking) {
|
|
5596
|
+
media.addEventListener("seeked", onSeeked);
|
|
5597
|
+
} else {
|
|
5598
|
+
resolve();
|
|
5599
|
+
}
|
|
5600
|
+
});
|
|
5601
|
+
});
|
|
5602
|
+
}
|
|
5603
|
+
media.currentTime = value;
|
|
5604
|
+
this.lastTime = value;
|
|
5605
|
+
this.time(value);
|
|
5606
|
+
const seekPromise = media.seeking ? new Promise((resolve) => {
|
|
5607
|
+
const listener = () => {
|
|
5608
|
+
resolve();
|
|
5609
|
+
media.removeEventListener("seeked", listener);
|
|
5610
|
+
};
|
|
5611
|
+
media.addEventListener("seeked", listener);
|
|
5612
|
+
}) : Promise.resolve();
|
|
5613
|
+
if (media.seeking && !options?.skipCollectPromise) {
|
|
5614
|
+
import_core32.DependencyContext.collectPromise(seekPromise);
|
|
5567
5615
|
}
|
|
5616
|
+
return seekPromise;
|
|
5568
5617
|
} catch (error) {
|
|
5569
5618
|
this.lastTime = value;
|
|
5619
|
+
this.time(value);
|
|
5620
|
+
return Promise.resolve();
|
|
5570
5621
|
}
|
|
5571
5622
|
}
|
|
5572
5623
|
setVolume(volume) {
|
|
@@ -10444,11 +10495,13 @@ var Video = class extends Media {
|
|
|
10444
10495
|
fastSeekedVideo() {
|
|
10445
10496
|
const video = this.video();
|
|
10446
10497
|
const time = this.clampTime(this.time());
|
|
10498
|
+
const playing = this.playing() && time < video.duration && video.playbackRate > 0;
|
|
10499
|
+
const wouldResetToZero = time < 0.5 && video.currentTime > 1 && Math.abs(video.currentTime - this.lastTime) < 0.5;
|
|
10500
|
+
const outOfSyncBig = Math.abs(video.currentTime - time) > 1;
|
|
10447
10501
|
video.playbackRate = this.playbackRate();
|
|
10448
10502
|
if (this.lastTime === time) {
|
|
10449
10503
|
return video;
|
|
10450
10504
|
}
|
|
10451
|
-
const playing = this.playing() && time < video.duration && video.playbackRate > 0;
|
|
10452
10505
|
if (playing) {
|
|
10453
10506
|
if (video.paused) {
|
|
10454
10507
|
import_core59.DependencyContext.collectPromise(video.play());
|
|
@@ -10458,10 +10511,11 @@ var Video = class extends Media {
|
|
|
10458
10511
|
video.pause();
|
|
10459
10512
|
}
|
|
10460
10513
|
}
|
|
10461
|
-
if (
|
|
10514
|
+
if (wouldResetToZero) {
|
|
10515
|
+
} else if (outOfSyncBig) {
|
|
10462
10516
|
this.setCurrentTime(time);
|
|
10463
10517
|
} else if (!playing) {
|
|
10464
|
-
|
|
10518
|
+
this.setCurrentTime(time);
|
|
10465
10519
|
}
|
|
10466
10520
|
return video;
|
|
10467
10521
|
}
|
|
@@ -10702,6 +10756,9 @@ var Scene2D = class extends import_core60.GeneratorScene {
|
|
|
10702
10756
|
context.save();
|
|
10703
10757
|
this.renderLifecycle.dispatch([import_core60.SceneRenderEvent.BeginRender, context]);
|
|
10704
10758
|
this.getView().playbackState(this.playback.state).globalTime(this.playback.time).fps(this.playback.fps);
|
|
10759
|
+
if (this.playback.state === import_core60.PlaybackState.Paused) {
|
|
10760
|
+
await this.syncAllMediaToCurrentTime(true);
|
|
10761
|
+
}
|
|
10705
10762
|
await this.getView().render(context);
|
|
10706
10763
|
this.renderLifecycle.dispatch([import_core60.SceneRenderEvent.FinishRender, context]);
|
|
10707
10764
|
context.restore();
|
|
@@ -10832,6 +10889,34 @@ var Scene2D = class extends import_core60.GeneratorScene {
|
|
|
10832
10889
|
);
|
|
10833
10890
|
return returnObjects;
|
|
10834
10891
|
}
|
|
10892
|
+
/**
|
|
10893
|
+
* Seek all registered Media nodes (Video and Audio) to the current playback time.
|
|
10894
|
+
* Passes draw time; nodes with clipStart convert it to clip-relative time.
|
|
10895
|
+
* When waitForSeek is true, waits for each media seek to complete so the next draw shows the correct frame/sample.
|
|
10896
|
+
*/
|
|
10897
|
+
async syncAllMediaToCurrentTime(waitForSeek) {
|
|
10898
|
+
const drawTime = this.playback.time;
|
|
10899
|
+
const mediaNodes = Array.from(this.registeredNodes.values()).filter(
|
|
10900
|
+
(node) => node instanceof Media
|
|
10901
|
+
);
|
|
10902
|
+
const results = mediaNodes.map((media) => {
|
|
10903
|
+
try {
|
|
10904
|
+
return media.syncToCurrentTime(drawTime, { waitForSeek });
|
|
10905
|
+
} catch (e) {
|
|
10906
|
+
this.logger.warn({
|
|
10907
|
+
message: `syncAllMediaToCurrentTime: skipped node ${media.key ?? "unknown"}`,
|
|
10908
|
+
object: e
|
|
10909
|
+
});
|
|
10910
|
+
return void 0;
|
|
10911
|
+
}
|
|
10912
|
+
});
|
|
10913
|
+
if (waitForSeek) {
|
|
10914
|
+
const promises = results.filter(
|
|
10915
|
+
(p) => p !== void 0
|
|
10916
|
+
);
|
|
10917
|
+
await Promise.all(promises);
|
|
10918
|
+
}
|
|
10919
|
+
}
|
|
10835
10920
|
stopAllMedia() {
|
|
10836
10921
|
const playingMedia = Array.from(this.registeredNodes.values()).filter((node) => node instanceof Media).filter((video) => video.isPlaying());
|
|
10837
10922
|
for (const media of playingMedia) {
|