@twick/2d 0.15.17 → 0.15.19

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
@@ -5502,6 +5502,8 @@ 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
5508
  setTimeout(() => this.scheduleSeek(this.time()), 0);
5507
5509
  }
@@ -5509,6 +5511,8 @@ var Media = class extends Rect {
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
  }
@@ -5550,14 +5554,27 @@ var Media = class extends Rect {
5550
5554
  }
5551
5555
  /**
5552
5556
  * 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).
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.
5555
5561
  * When omitted, falls back to this.time().
5556
- * Uses skipCollectPromise so we don't leave a promise in DependencyContext.
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.
5557
5564
  */
5558
- syncToCurrentTime(time) {
5559
- const syncTime = time ?? this.time();
5560
- this.setCurrentTime(syncTime, { skipCollectPromise: true });
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
+ }
5561
5578
  }
5562
5579
  setCurrentTime(value, options) {
5563
5580
  try {
@@ -5566,30 +5583,41 @@ var Media = class extends Rect {
5566
5583
  if (media.readyState < 2) {
5567
5584
  this.lastTime = value;
5568
5585
  this.time(value);
5569
- this.waitForCanPlay(media, () => {
5570
- media.currentTime = value;
5571
- this.lastTime = value;
5572
- 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);
5593
+ resolve();
5594
+ };
5595
+ if (media.seeking) {
5596
+ media.addEventListener("seeked", onSeeked);
5597
+ } else {
5598
+ resolve();
5599
+ }
5600
+ });
5573
5601
  });
5574
- return;
5575
5602
  }
5576
5603
  media.currentTime = value;
5577
5604
  this.lastTime = value;
5578
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();
5579
5613
  if (media.seeking && !options?.skipCollectPromise) {
5580
- import_core32.DependencyContext.collectPromise(
5581
- new Promise((resolve) => {
5582
- const listener = () => {
5583
- resolve();
5584
- media.removeEventListener("seeked", listener);
5585
- };
5586
- media.addEventListener("seeked", listener);
5587
- })
5588
- );
5614
+ import_core32.DependencyContext.collectPromise(seekPromise);
5589
5615
  }
5616
+ return seekPromise;
5590
5617
  } catch (error) {
5591
5618
  this.lastTime = value;
5592
5619
  this.time(value);
5620
+ return Promise.resolve();
5593
5621
  }
5594
5622
  }
5595
5623
  setVolume(volume) {
@@ -10729,7 +10757,7 @@ var Scene2D = class extends import_core60.GeneratorScene {
10729
10757
  this.renderLifecycle.dispatch([import_core60.SceneRenderEvent.BeginRender, context]);
10730
10758
  this.getView().playbackState(this.playback.state).globalTime(this.playback.time).fps(this.playback.fps);
10731
10759
  if (this.playback.state === import_core60.PlaybackState.Paused) {
10732
- this.syncAllMediaToCurrentTime();
10760
+ await this.syncAllMediaToCurrentTime(true);
10733
10761
  }
10734
10762
  await this.getView().render(context);
10735
10763
  this.renderLifecycle.dispatch([import_core60.SceneRenderEvent.FinishRender, context]);
@@ -10862,23 +10890,31 @@ var Scene2D = class extends import_core60.GeneratorScene {
10862
10890
  return returnObjects;
10863
10891
  }
10864
10892
  /**
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.
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.
10867
10896
  */
10868
- syncAllMediaToCurrentTime() {
10897
+ async syncAllMediaToCurrentTime(waitForSeek) {
10869
10898
  const drawTime = this.playback.time;
10870
10899
  const mediaNodes = Array.from(this.registeredNodes.values()).filter(
10871
10900
  (node) => node instanceof Media
10872
10901
  );
10873
- for (const media of mediaNodes) {
10902
+ const results = mediaNodes.map((media) => {
10874
10903
  try {
10875
- media.syncToCurrentTime(drawTime);
10904
+ return media.syncToCurrentTime(drawTime, { waitForSeek });
10876
10905
  } catch (e) {
10877
10906
  this.logger.warn({
10878
10907
  message: `syncAllMediaToCurrentTime: skipped node ${media.key ?? "unknown"}`,
10879
10908
  object: e
10880
10909
  });
10910
+ return void 0;
10881
10911
  }
10912
+ });
10913
+ if (waitForSeek) {
10914
+ const promises = results.filter(
10915
+ (p) => p !== void 0
10916
+ );
10917
+ await Promise.all(promises);
10882
10918
  }
10883
10919
  }
10884
10920
  stopAllMedia() {