@twick/2d 0.15.17 → 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 +62 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -7
- package/dist/index.d.ts +21 -7
- package/dist/index.js +62 -26
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -58,6 +58,10 @@ interface MediaProps extends RectProps {
|
|
|
58
58
|
play?: boolean;
|
|
59
59
|
awaitCanPlay?: SignalValue<boolean>;
|
|
60
60
|
allowVolumeAmplificationInPreview?: SignalValue<boolean>;
|
|
61
|
+
/** Timeline time when this clip starts; when set, syncToCurrentTime(globalTime) uses clip-relative time. */
|
|
62
|
+
clipStart?: number;
|
|
63
|
+
/** Start offset in the media file (e.g. trim); used with clipStart for sync. */
|
|
64
|
+
trimStart?: number;
|
|
61
65
|
}
|
|
62
66
|
declare abstract class Media extends Rect {
|
|
63
67
|
readonly src: SimpleSignal<string, this>;
|
|
@@ -75,6 +79,10 @@ declare abstract class Media extends Rect {
|
|
|
75
79
|
}>;
|
|
76
80
|
protected lastTime: number;
|
|
77
81
|
private isSchedulingPlay;
|
|
82
|
+
/** When set, syncToCurrentTime(globalTime) converts to clip-relative time. */
|
|
83
|
+
protected clipStart: number | undefined;
|
|
84
|
+
/** Used with clipStart for sync (trim offset in source). */
|
|
85
|
+
protected trimStart: number;
|
|
78
86
|
constructor(props: MediaProps);
|
|
79
87
|
isPlaying(): boolean;
|
|
80
88
|
getCurrentTime(): number;
|
|
@@ -88,16 +96,21 @@ declare abstract class Media extends Rect {
|
|
|
88
96
|
protected abstract fastSeekedMedia(): HTMLMediaElement;
|
|
89
97
|
/**
|
|
90
98
|
* Sync the underlying media element to the given time (e.g. draw/playback time).
|
|
91
|
-
* When `time` is passed (e.g. from Scene2D.draw), that
|
|
92
|
-
* does not depend on the node's time signal
|
|
99
|
+
* Used for both Video and Audio. When `time` is passed (e.g. from Scene2D.draw), that
|
|
100
|
+
* value is used so sync does not depend on the node's time signal.
|
|
101
|
+
* If this node has clipStart set, global time is converted to clip-relative time:
|
|
102
|
+
* syncTime = trimStart + (time - clipStart) * playbackRate.
|
|
93
103
|
* When omitted, falls back to this.time().
|
|
94
|
-
*
|
|
104
|
+
* When waitForSeek is true, returns a promise that resolves when the seek completes so
|
|
105
|
+
* the draw can wait and show the correct frame/sample when paused.
|
|
95
106
|
*/
|
|
96
|
-
syncToCurrentTime(time?: number
|
|
107
|
+
syncToCurrentTime(time?: number, options?: {
|
|
108
|
+
waitForSeek?: boolean;
|
|
109
|
+
}): void | Promise<void>;
|
|
97
110
|
protected abstract draw(context: CanvasRenderingContext2D): Promise<void>;
|
|
98
111
|
protected setCurrentTime(value: number, options?: {
|
|
99
112
|
skipCollectPromise?: boolean;
|
|
100
|
-
}): void
|
|
113
|
+
}): Promise<void>;
|
|
101
114
|
setVolume(volume: number): void;
|
|
102
115
|
protected amplify(node: HTMLMediaElement, volume: number): void;
|
|
103
116
|
protected setPlaybackRate(playbackRate: number): void;
|
|
@@ -2539,8 +2552,9 @@ declare class Scene2D extends GeneratorScene<View2D> implements Inspectable {
|
|
|
2539
2552
|
getDetachedNodes(): Generator<Node, void, unknown>;
|
|
2540
2553
|
getMediaAssets(): Array<AssetInfo>;
|
|
2541
2554
|
/**
|
|
2542
|
-
* Seek all registered Media nodes to the current playback time.
|
|
2543
|
-
* Passes draw time
|
|
2555
|
+
* Seek all registered Media nodes (Video and Audio) to the current playback time.
|
|
2556
|
+
* Passes draw time; nodes with clipStart convert it to clip-relative time.
|
|
2557
|
+
* When waitForSeek is true, waits for each media seek to complete so the next draw shows the correct frame/sample.
|
|
2544
2558
|
*/
|
|
2545
2559
|
private syncAllMediaToCurrentTime;
|
|
2546
2560
|
stopAllMedia(): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -58,6 +58,10 @@ interface MediaProps extends RectProps {
|
|
|
58
58
|
play?: boolean;
|
|
59
59
|
awaitCanPlay?: SignalValue<boolean>;
|
|
60
60
|
allowVolumeAmplificationInPreview?: SignalValue<boolean>;
|
|
61
|
+
/** Timeline time when this clip starts; when set, syncToCurrentTime(globalTime) uses clip-relative time. */
|
|
62
|
+
clipStart?: number;
|
|
63
|
+
/** Start offset in the media file (e.g. trim); used with clipStart for sync. */
|
|
64
|
+
trimStart?: number;
|
|
61
65
|
}
|
|
62
66
|
declare abstract class Media extends Rect {
|
|
63
67
|
readonly src: SimpleSignal<string, this>;
|
|
@@ -75,6 +79,10 @@ declare abstract class Media extends Rect {
|
|
|
75
79
|
}>;
|
|
76
80
|
protected lastTime: number;
|
|
77
81
|
private isSchedulingPlay;
|
|
82
|
+
/** When set, syncToCurrentTime(globalTime) converts to clip-relative time. */
|
|
83
|
+
protected clipStart: number | undefined;
|
|
84
|
+
/** Used with clipStart for sync (trim offset in source). */
|
|
85
|
+
protected trimStart: number;
|
|
78
86
|
constructor(props: MediaProps);
|
|
79
87
|
isPlaying(): boolean;
|
|
80
88
|
getCurrentTime(): number;
|
|
@@ -88,16 +96,21 @@ declare abstract class Media extends Rect {
|
|
|
88
96
|
protected abstract fastSeekedMedia(): HTMLMediaElement;
|
|
89
97
|
/**
|
|
90
98
|
* Sync the underlying media element to the given time (e.g. draw/playback time).
|
|
91
|
-
* When `time` is passed (e.g. from Scene2D.draw), that
|
|
92
|
-
* does not depend on the node's time signal
|
|
99
|
+
* Used for both Video and Audio. When `time` is passed (e.g. from Scene2D.draw), that
|
|
100
|
+
* value is used so sync does not depend on the node's time signal.
|
|
101
|
+
* If this node has clipStart set, global time is converted to clip-relative time:
|
|
102
|
+
* syncTime = trimStart + (time - clipStart) * playbackRate.
|
|
93
103
|
* When omitted, falls back to this.time().
|
|
94
|
-
*
|
|
104
|
+
* When waitForSeek is true, returns a promise that resolves when the seek completes so
|
|
105
|
+
* the draw can wait and show the correct frame/sample when paused.
|
|
95
106
|
*/
|
|
96
|
-
syncToCurrentTime(time?: number
|
|
107
|
+
syncToCurrentTime(time?: number, options?: {
|
|
108
|
+
waitForSeek?: boolean;
|
|
109
|
+
}): void | Promise<void>;
|
|
97
110
|
protected abstract draw(context: CanvasRenderingContext2D): Promise<void>;
|
|
98
111
|
protected setCurrentTime(value: number, options?: {
|
|
99
112
|
skipCollectPromise?: boolean;
|
|
100
|
-
}): void
|
|
113
|
+
}): Promise<void>;
|
|
101
114
|
setVolume(volume: number): void;
|
|
102
115
|
protected amplify(node: HTMLMediaElement, volume: number): void;
|
|
103
116
|
protected setPlaybackRate(playbackRate: number): void;
|
|
@@ -2539,8 +2552,9 @@ declare class Scene2D extends GeneratorScene<View2D> implements Inspectable {
|
|
|
2539
2552
|
getDetachedNodes(): Generator<Node, void, unknown>;
|
|
2540
2553
|
getMediaAssets(): Array<AssetInfo>;
|
|
2541
2554
|
/**
|
|
2542
|
-
* Seek all registered Media nodes to the current playback time.
|
|
2543
|
-
* Passes draw time
|
|
2555
|
+
* Seek all registered Media nodes (Video and Audio) to the current playback time.
|
|
2556
|
+
* Passes draw time; nodes with clipStart convert it to clip-relative time.
|
|
2557
|
+
* When waitForSeek is true, waits for each media seek to complete so the next draw shows the correct frame/sample.
|
|
2544
2558
|
*/
|
|
2545
2559
|
private syncAllMediaToCurrentTime;
|
|
2546
2560
|
stopAllMedia(): void;
|
package/dist/index.js
CHANGED
|
@@ -5401,6 +5401,8 @@ var Media = class extends Rect {
|
|
|
5401
5401
|
super(props);
|
|
5402
5402
|
this.lastTime = -1;
|
|
5403
5403
|
this.isSchedulingPlay = false;
|
|
5404
|
+
/** Used with clipStart for sync (trim offset in source). */
|
|
5405
|
+
this.trimStart = 0;
|
|
5404
5406
|
if (!this.awaitCanPlay()) {
|
|
5405
5407
|
setTimeout(() => this.scheduleSeek(this.time()), 0);
|
|
5406
5408
|
}
|
|
@@ -5408,6 +5410,8 @@ var Media = class extends Rect {
|
|
|
5408
5410
|
this.play();
|
|
5409
5411
|
}
|
|
5410
5412
|
this.volume = props.volume ?? 1;
|
|
5413
|
+
this.clipStart = props.clipStart;
|
|
5414
|
+
this.trimStart = props.trimStart ?? 0;
|
|
5411
5415
|
if (!this.awaitCanPlay()) {
|
|
5412
5416
|
this.setVolume(this.volume);
|
|
5413
5417
|
}
|
|
@@ -5449,14 +5453,27 @@ var Media = class extends Rect {
|
|
|
5449
5453
|
}
|
|
5450
5454
|
/**
|
|
5451
5455
|
* Sync the underlying media element to the given time (e.g. draw/playback time).
|
|
5452
|
-
* When `time` is passed (e.g. from Scene2D.draw), that
|
|
5453
|
-
* does not depend on the node's time signal
|
|
5456
|
+
* Used for both Video and Audio. When `time` is passed (e.g. from Scene2D.draw), that
|
|
5457
|
+
* value is used so sync does not depend on the node's time signal.
|
|
5458
|
+
* If this node has clipStart set, global time is converted to clip-relative time:
|
|
5459
|
+
* syncTime = trimStart + (time - clipStart) * playbackRate.
|
|
5454
5460
|
* When omitted, falls back to this.time().
|
|
5455
|
-
*
|
|
5461
|
+
* When waitForSeek is true, returns a promise that resolves when the seek completes so
|
|
5462
|
+
* the draw can wait and show the correct frame/sample when paused.
|
|
5456
5463
|
*/
|
|
5457
|
-
syncToCurrentTime(time) {
|
|
5458
|
-
|
|
5459
|
-
this.
|
|
5464
|
+
syncToCurrentTime(time, options) {
|
|
5465
|
+
let syncTime;
|
|
5466
|
+
if (time !== void 0 && this.clipStart !== void 0) {
|
|
5467
|
+
syncTime = this.trimStart + (time - this.clipStart) * this.playbackRate();
|
|
5468
|
+
} else {
|
|
5469
|
+
syncTime = time ?? this.time();
|
|
5470
|
+
}
|
|
5471
|
+
const promise = this.setCurrentTime(syncTime, {
|
|
5472
|
+
skipCollectPromise: options?.waitForSeek ?? true
|
|
5473
|
+
});
|
|
5474
|
+
if (options?.waitForSeek) {
|
|
5475
|
+
return promise;
|
|
5476
|
+
}
|
|
5460
5477
|
}
|
|
5461
5478
|
setCurrentTime(value, options) {
|
|
5462
5479
|
try {
|
|
@@ -5465,30 +5482,41 @@ var Media = class extends Rect {
|
|
|
5465
5482
|
if (media.readyState < 2) {
|
|
5466
5483
|
this.lastTime = value;
|
|
5467
5484
|
this.time(value);
|
|
5468
|
-
|
|
5469
|
-
media
|
|
5470
|
-
|
|
5471
|
-
|
|
5485
|
+
return new Promise((resolve) => {
|
|
5486
|
+
this.waitForCanPlay(media, () => {
|
|
5487
|
+
media.currentTime = value;
|
|
5488
|
+
this.lastTime = value;
|
|
5489
|
+
this.time(value);
|
|
5490
|
+
const onSeeked = () => {
|
|
5491
|
+
media.removeEventListener("seeked", onSeeked);
|
|
5492
|
+
resolve();
|
|
5493
|
+
};
|
|
5494
|
+
if (media.seeking) {
|
|
5495
|
+
media.addEventListener("seeked", onSeeked);
|
|
5496
|
+
} else {
|
|
5497
|
+
resolve();
|
|
5498
|
+
}
|
|
5499
|
+
});
|
|
5472
5500
|
});
|
|
5473
|
-
return;
|
|
5474
5501
|
}
|
|
5475
5502
|
media.currentTime = value;
|
|
5476
5503
|
this.lastTime = value;
|
|
5477
5504
|
this.time(value);
|
|
5505
|
+
const seekPromise = media.seeking ? new Promise((resolve) => {
|
|
5506
|
+
const listener = () => {
|
|
5507
|
+
resolve();
|
|
5508
|
+
media.removeEventListener("seeked", listener);
|
|
5509
|
+
};
|
|
5510
|
+
media.addEventListener("seeked", listener);
|
|
5511
|
+
}) : Promise.resolve();
|
|
5478
5512
|
if (media.seeking && !options?.skipCollectPromise) {
|
|
5479
|
-
DependencyContext4.collectPromise(
|
|
5480
|
-
new Promise((resolve) => {
|
|
5481
|
-
const listener = () => {
|
|
5482
|
-
resolve();
|
|
5483
|
-
media.removeEventListener("seeked", listener);
|
|
5484
|
-
};
|
|
5485
|
-
media.addEventListener("seeked", listener);
|
|
5486
|
-
})
|
|
5487
|
-
);
|
|
5513
|
+
DependencyContext4.collectPromise(seekPromise);
|
|
5488
5514
|
}
|
|
5515
|
+
return seekPromise;
|
|
5489
5516
|
} catch (error) {
|
|
5490
5517
|
this.lastTime = value;
|
|
5491
5518
|
this.time(value);
|
|
5519
|
+
return Promise.resolve();
|
|
5492
5520
|
}
|
|
5493
5521
|
}
|
|
5494
5522
|
setVolume(volume) {
|
|
@@ -10668,7 +10696,7 @@ var Scene2D = class extends GeneratorScene {
|
|
|
10668
10696
|
this.renderLifecycle.dispatch([SceneRenderEvent.BeginRender, context]);
|
|
10669
10697
|
this.getView().playbackState(this.playback.state).globalTime(this.playback.time).fps(this.playback.fps);
|
|
10670
10698
|
if (this.playback.state === PlaybackState5.Paused) {
|
|
10671
|
-
this.syncAllMediaToCurrentTime();
|
|
10699
|
+
await this.syncAllMediaToCurrentTime(true);
|
|
10672
10700
|
}
|
|
10673
10701
|
await this.getView().render(context);
|
|
10674
10702
|
this.renderLifecycle.dispatch([SceneRenderEvent.FinishRender, context]);
|
|
@@ -10801,23 +10829,31 @@ var Scene2D = class extends GeneratorScene {
|
|
|
10801
10829
|
return returnObjects;
|
|
10802
10830
|
}
|
|
10803
10831
|
/**
|
|
10804
|
-
* Seek all registered Media nodes to the current playback time.
|
|
10805
|
-
* Passes draw time
|
|
10832
|
+
* Seek all registered Media nodes (Video and Audio) to the current playback time.
|
|
10833
|
+
* Passes draw time; nodes with clipStart convert it to clip-relative time.
|
|
10834
|
+
* When waitForSeek is true, waits for each media seek to complete so the next draw shows the correct frame/sample.
|
|
10806
10835
|
*/
|
|
10807
|
-
syncAllMediaToCurrentTime() {
|
|
10836
|
+
async syncAllMediaToCurrentTime(waitForSeek) {
|
|
10808
10837
|
const drawTime = this.playback.time;
|
|
10809
10838
|
const mediaNodes = Array.from(this.registeredNodes.values()).filter(
|
|
10810
10839
|
(node) => node instanceof Media
|
|
10811
10840
|
);
|
|
10812
|
-
|
|
10841
|
+
const results = mediaNodes.map((media) => {
|
|
10813
10842
|
try {
|
|
10814
|
-
media.syncToCurrentTime(drawTime);
|
|
10843
|
+
return media.syncToCurrentTime(drawTime, { waitForSeek });
|
|
10815
10844
|
} catch (e) {
|
|
10816
10845
|
this.logger.warn({
|
|
10817
10846
|
message: `syncAllMediaToCurrentTime: skipped node ${media.key ?? "unknown"}`,
|
|
10818
10847
|
object: e
|
|
10819
10848
|
});
|
|
10849
|
+
return void 0;
|
|
10820
10850
|
}
|
|
10851
|
+
});
|
|
10852
|
+
if (waitForSeek) {
|
|
10853
|
+
const promises = results.filter(
|
|
10854
|
+
(p) => p !== void 0
|
|
10855
|
+
);
|
|
10856
|
+
await Promise.all(promises);
|
|
10821
10857
|
}
|
|
10822
10858
|
}
|
|
10823
10859
|
stopAllMedia() {
|