@twick/2d 0.15.15 → 0.15.17

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 CHANGED
@@ -5503,7 +5503,7 @@ var Media = class extends Rect {
5503
5503
  this.lastTime = -1;
5504
5504
  this.isSchedulingPlay = false;
5505
5505
  if (!this.awaitCanPlay()) {
5506
- this.scheduleSeek(this.time());
5506
+ setTimeout(() => this.scheduleSeek(this.time()), 0);
5507
5507
  }
5508
5508
  if (props.play) {
5509
5509
  this.play();
@@ -5548,13 +5548,35 @@ var Media = class extends Rect {
5548
5548
  completion() {
5549
5549
  return this.clampTime(this.time()) / this.getDuration();
5550
5550
  }
5551
- setCurrentTime(value) {
5551
+ /**
5552
+ * Sync the underlying media element to the given time (e.g. draw/playback time).
5553
+ * When `time` is passed (e.g. from Scene2D.draw), that value is used so sync
5554
+ * does not depend on the node's time signal (which may only update on projectData change).
5555
+ * When omitted, falls back to this.time().
5556
+ * Uses skipCollectPromise so we don't leave a promise in DependencyContext.
5557
+ */
5558
+ syncToCurrentTime(time) {
5559
+ const syncTime = time ?? this.time();
5560
+ this.setCurrentTime(syncTime, { skipCollectPromise: true });
5561
+ }
5562
+ setCurrentTime(value, options) {
5552
5563
  try {
5553
5564
  const media = this.mediaElement();
5554
- if (media.readyState < 2) return;
5565
+ const key = this.key ?? "media";
5566
+ if (media.readyState < 2) {
5567
+ this.lastTime = value;
5568
+ this.time(value);
5569
+ this.waitForCanPlay(media, () => {
5570
+ media.currentTime = value;
5571
+ this.lastTime = value;
5572
+ this.time(value);
5573
+ });
5574
+ return;
5575
+ }
5555
5576
  media.currentTime = value;
5556
5577
  this.lastTime = value;
5557
- if (media.seeking) {
5578
+ this.time(value);
5579
+ if (media.seeking && !options?.skipCollectPromise) {
5558
5580
  import_core32.DependencyContext.collectPromise(
5559
5581
  new Promise((resolve) => {
5560
5582
  const listener = () => {
@@ -5567,6 +5589,7 @@ var Media = class extends Rect {
5567
5589
  }
5568
5590
  } catch (error) {
5569
5591
  this.lastTime = value;
5592
+ this.time(value);
5570
5593
  }
5571
5594
  }
5572
5595
  setVolume(volume) {
@@ -10444,11 +10467,13 @@ var Video = class extends Media {
10444
10467
  fastSeekedVideo() {
10445
10468
  const video = this.video();
10446
10469
  const time = this.clampTime(this.time());
10470
+ const playing = this.playing() && time < video.duration && video.playbackRate > 0;
10471
+ const wouldResetToZero = time < 0.5 && video.currentTime > 1 && Math.abs(video.currentTime - this.lastTime) < 0.5;
10472
+ const outOfSyncBig = Math.abs(video.currentTime - time) > 1;
10447
10473
  video.playbackRate = this.playbackRate();
10448
10474
  if (this.lastTime === time) {
10449
10475
  return video;
10450
10476
  }
10451
- const playing = this.playing() && time < video.duration && video.playbackRate > 0;
10452
10477
  if (playing) {
10453
10478
  if (video.paused) {
10454
10479
  import_core59.DependencyContext.collectPromise(video.play());
@@ -10458,10 +10483,11 @@ var Video = class extends Media {
10458
10483
  video.pause();
10459
10484
  }
10460
10485
  }
10461
- if (Math.abs(video.currentTime - time) > 1) {
10486
+ if (wouldResetToZero) {
10487
+ } else if (outOfSyncBig) {
10462
10488
  this.setCurrentTime(time);
10463
10489
  } else if (!playing) {
10464
- video.currentTime = time;
10490
+ this.setCurrentTime(time);
10465
10491
  }
10466
10492
  return video;
10467
10493
  }
@@ -10702,6 +10728,9 @@ var Scene2D = class extends import_core60.GeneratorScene {
10702
10728
  context.save();
10703
10729
  this.renderLifecycle.dispatch([import_core60.SceneRenderEvent.BeginRender, context]);
10704
10730
  this.getView().playbackState(this.playback.state).globalTime(this.playback.time).fps(this.playback.fps);
10731
+ if (this.playback.state === import_core60.PlaybackState.Paused) {
10732
+ this.syncAllMediaToCurrentTime();
10733
+ }
10705
10734
  await this.getView().render(context);
10706
10735
  this.renderLifecycle.dispatch([import_core60.SceneRenderEvent.FinishRender, context]);
10707
10736
  context.restore();
@@ -10832,6 +10861,26 @@ var Scene2D = class extends import_core60.GeneratorScene {
10832
10861
  );
10833
10862
  return returnObjects;
10834
10863
  }
10864
+ /**
10865
+ * Seek all registered Media nodes to the current playback time.
10866
+ * Passes draw time (playback.time) so media sync does not depend on node time signal.
10867
+ */
10868
+ syncAllMediaToCurrentTime() {
10869
+ const drawTime = this.playback.time;
10870
+ const mediaNodes = Array.from(this.registeredNodes.values()).filter(
10871
+ (node) => node instanceof Media
10872
+ );
10873
+ for (const media of mediaNodes) {
10874
+ try {
10875
+ media.syncToCurrentTime(drawTime);
10876
+ } catch (e) {
10877
+ this.logger.warn({
10878
+ message: `syncAllMediaToCurrentTime: skipped node ${media.key ?? "unknown"}`,
10879
+ object: e
10880
+ });
10881
+ }
10882
+ }
10883
+ }
10835
10884
  stopAllMedia() {
10836
10885
  const playingMedia = Array.from(this.registeredNodes.values()).filter((node) => node instanceof Media).filter((video) => video.isPlaying());
10837
10886
  for (const media of playingMedia) {