@zcomponent/core 1.3.0-beta → 1.4.1-beta

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.
@@ -1,7 +1,11 @@
1
1
  import * as Data from '../../data';
2
2
  import { Observable } from '../../observable';
3
3
  import { Animation } from '../animation';
4
+ import { LayerClip } from '../layerclip';
4
5
  import { StreamState } from '../stream';
6
+ import { ClipTrack } from '../tracks/cliptrack';
7
+ import { FunctionTrack } from '../tracks/functiontrack';
8
+ import { StreamTrack } from '../tracks/streamtrack';
5
9
  import { Track } from '../tracks/track';
6
10
  export declare class Clip {
7
11
  animation: Animation;
@@ -10,11 +14,12 @@ export declare class Clip {
10
14
  readonly id?: string | undefined;
11
15
  influencedPathsDirty: Observable<boolean, never>;
12
16
  defaultFadeParameters?: Partial<Data.FadeParameters>;
17
+ /** @internal */
18
+ _streamTracks: (StreamTrack | ClipTrack | FunctionTrack)[];
13
19
  private _influencedPaths;
14
20
  private _tracksByPath;
15
21
  private _tracksByPathDirty;
16
22
  private _tracks;
17
- private _streamTracks;
18
23
  constructor(animation: Animation, length: number, scriptName?: string | undefined, id?: string | undefined);
19
24
  computePathProperty(t: number, p: string, valueBefore: any, parentWeight?: number): any;
20
25
  addTrack(t: Track): void;
@@ -26,7 +31,7 @@ export declare class Clip {
26
31
  [id: string]: Partial<Data.FadeParameters>;
27
32
  }): void;
28
33
  resolveClipTime(t: number, t0: number, s0: number, rate: number, t1?: number): number;
29
- streamPause(): void;
30
- streamSeek(t: number): void;
31
- streamTick(state: StreamState, ct: number, rate: number, force?: boolean, parentWeight?: number): void;
34
+ streamPause(isActive: boolean, active: LayerClip | null | undefined): void;
35
+ streamSeek(t: number, rate: number, isActive: boolean, active: LayerClip | null | undefined): void;
36
+ streamTick(clipState: StreamState, t: number, rate: number, isActive: boolean, active: LayerClip | null | undefined, force?: boolean, parentWeight?: number): void;
32
37
  }
@@ -1,5 +1,6 @@
1
1
  import { Observable } from '../../observable';
2
2
  import { ClipTrack } from '../tracks/cliptrack';
3
+ import { FunctionTrack } from '../tracks/functiontrack';
3
4
  import { PropertyTrack } from '../tracks/propertytrack';
4
5
  import { StreamTrack } from '../tracks/streamtrack';
5
6
  export class Clip {
@@ -9,11 +10,12 @@ export class Clip {
9
10
  this.scriptName = scriptName;
10
11
  this.id = id;
11
12
  this.influencedPathsDirty = new Observable(false);
13
+ /** @internal */
14
+ this._streamTracks = [];
12
15
  this._influencedPaths = [];
13
16
  this._tracksByPath = new Map();
14
17
  this._tracksByPathDirty = false;
15
18
  this._tracks = [];
16
- this._streamTracks = [];
17
19
  this._dirty = (v) => {
18
20
  if (v) {
19
21
  this.influencedPathsDirty.value = true;
@@ -37,7 +39,7 @@ export class Clip {
37
39
  t.influencedPathsDirty.addListener(this._dirty);
38
40
  this.influencedPathsDirty.value = true;
39
41
  this._tracksByPathDirty = true;
40
- if (t instanceof StreamTrack || t instanceof ClipTrack)
42
+ if (t instanceof StreamTrack || t instanceof ClipTrack || t instanceof FunctionTrack)
41
43
  this._streamTracks.push(t);
42
44
  }
43
45
  clearTracks() {
@@ -100,6 +102,7 @@ export class Clip {
100
102
  }
101
103
  }
102
104
  resolveClipTime(t, t0, s0, rate, t1 = Infinity) {
105
+ // return this.resolveClipTimeLoops(t, t0, s0, rate, t1)[0];
103
106
  if (this.length === 0)
104
107
  return 0;
105
108
  t = Math.min(t, t1);
@@ -113,19 +116,19 @@ export class Clip {
113
116
  bounded += this.length;
114
117
  return bounded;
115
118
  }
116
- streamPause() {
119
+ streamPause(isActive, active) {
117
120
  for (const track of this._streamTracks) {
118
- track.streamPause();
121
+ track.streamPause(isActive, active);
119
122
  }
120
123
  }
121
- streamSeek(t) {
124
+ streamSeek(t, rate, isActive, active) {
122
125
  for (const track of this._streamTracks) {
123
- track.streamSeek(t);
126
+ track.streamSeek(t, rate, isActive, active);
124
127
  }
125
128
  }
126
- streamTick(state, ct, rate, force = false, parentWeight) {
129
+ streamTick(clipState, t, rate, isActive, active, force = false, parentWeight) {
127
130
  for (const track of this._streamTracks) {
128
- track.streamTick(state, ct, rate, force, parentWeight);
131
+ track.streamTick(clipState, t, rate, isActive, active, force, parentWeight);
129
132
  }
130
133
  }
131
134
  }
@@ -21,6 +21,7 @@ export declare class LayerClip implements Stream {
21
21
  private _loop;
22
22
  private _lastTickTime;
23
23
  private _lastClipTime;
24
+ private _lastLoop;
24
25
  private _stopped;
25
26
  state: StreamState;
26
27
  constructor(layer: Layer, clip: Clip, scriptName?: string | undefined, id?: string | undefined);
@@ -1,5 +1,6 @@
1
1
  import { AnimationEvents } from './animation';
2
2
  import { StreamState } from './stream';
3
+ import { resolveClipTimeLoops } from './tracks/track';
3
4
  export class LayerClip {
4
5
  constructor(layer, clip, scriptName, id) {
5
6
  this.layer = layer;
@@ -40,7 +41,11 @@ export class LayerClip {
40
41
  this.pause();
41
42
  this.state = StreamState.Ended;
42
43
  }
43
- this.clip.streamTick(this.state, ct, this._rate);
44
+ else {
45
+ const [bt, loop] = resolveClipTimeLoops(t, t0, this._rate >= 0 ? 0 : this.clip.length, this._rate, this.clip.length, this._rate < 0, t1);
46
+ this.clip.streamTick(this.state, bt, this._rate, this.layer.active === this, this.layer.active, loop !== this._lastLoop, 1);
47
+ this._lastLoop = loop;
48
+ }
44
49
  }
45
50
  computePathProperty(p, valueBefore) {
46
51
  const t = this._getTime();
@@ -71,7 +76,6 @@ export class LayerClip {
71
76
  }
72
77
  this._stopped = false;
73
78
  this._loop = opts?.loop ?? this.defaultLoop;
74
- this._rate = opts?.speed ?? this.defaultPlaySpeed;
75
79
  const t = this._getTime();
76
80
  if (this.state === StreamState.Ended) {
77
81
  this._t0 = t;
@@ -79,7 +83,11 @@ export class LayerClip {
79
83
  else if (this._pauseTime !== undefined) {
80
84
  this._t0 = t - (this._pauseTime - this._t0);
81
85
  }
86
+ if (Math.sign(this._rate) !== Math.sign(opts?.speed ?? 1)) {
87
+ this._t0 = 2 * t - this.clip.length - this._t0;
88
+ }
82
89
  delete this._pauseTime;
90
+ this._rate = opts?.speed ?? this.defaultPlaySpeed;
83
91
  if (this._loop)
84
92
  this._t1 = Infinity;
85
93
  else {
@@ -92,8 +100,6 @@ export class LayerClip {
92
100
  }
93
101
  const t0 = this._t0 + (t - (this._pauseTime ?? t));
94
102
  const t1 = this._t1 + (t - (this._pauseTime ?? t));
95
- const ct = this.clip.resolveClipTime(t, t0, this._rate >= 0 ? 0 : this.clip.length, this._rate, t1);
96
- this.clip.streamTick(StreamState.Playing, ct, this._rate, true);
97
103
  this.state = StreamState.Playing;
98
104
  this.animation._pendingEvents.push({ evt: AnimationEvents.onLayerClipState, args: [this] });
99
105
  this._lastClipTime = undefined;
@@ -102,14 +108,18 @@ export class LayerClip {
102
108
  this.layer._activateLayerClip(this, opts);
103
109
  this.layer._evaulate();
104
110
  }
111
+ const [bt, loop] = resolveClipTimeLoops(t, t0, this._rate >= 0 ? 0 : this.clip.length, this._rate, this.clip.length, this._rate < 0, t1);
112
+ this.clip.streamTick(this.state, bt, this._rate, this.layer.active === this, this.layer.active, loop !== this._lastLoop, 1);
113
+ this._lastLoop = loop;
105
114
  }
106
115
  pause() {
116
+ delete this._lastLoop;
107
117
  if (this._pauseTime !== undefined || this.state !== StreamState.Playing)
108
118
  return;
109
119
  this.state = StreamState.Paused;
110
120
  this.animation._pendingEvents.push({ evt: AnimationEvents.onLayerClipState, args: [this] });
111
121
  this._pauseTime = this._getTime();
112
- this.clip.streamPause();
122
+ this.clip.streamPause(this.layer.active === this, this.layer.active);
113
123
  }
114
124
  seek(ct, opts) {
115
125
  const t = this._getTime();
@@ -132,7 +142,8 @@ export class LayerClip {
132
142
  if (this.layer.active !== this && !opts?.dontActivateLayer) {
133
143
  this.layer._activateLayerClip(this, { fade: opts?.fade });
134
144
  }
135
- this.clip.streamSeek(ct);
145
+ delete this._lastLoop;
146
+ this.clip.streamSeek(ct, this._rate, this.layer.active === this, this.layer.active);
136
147
  this.layer._evaulate();
137
148
  }
138
149
  /** @internal */
@@ -178,8 +189,9 @@ export class LayerClip {
178
189
  this._pauseTime = this._t0;
179
190
  this._lastClipTime = undefined;
180
191
  this._lastTickTime = undefined;
192
+ delete this._lastLoop;
181
193
  this._rate = 1;
182
- this.clip.streamTick(StreamState.Ended, this._t0, 1, true);
194
+ this.clip.streamTick(StreamState.Ended, this._t0, 1, this.layer.active === this, this.layer.active, true);
183
195
  this.state = StreamState.Ended;
184
196
  this.animation._pendingEvents.push({ evt: AnimationEvents.onLayerClipState, args: [this] });
185
197
  }
@@ -1,6 +1,7 @@
1
1
  import { ClipTrackEntity } from '../../data';
2
2
  import { Clip } from '../clips/clip';
3
3
  import { Keyframe } from '../keyframe';
4
+ import { LayerClip } from '../layerclip';
4
5
  import { StreamState } from '../stream';
5
6
  import { Track } from './track';
6
7
  export declare class ClipTrack extends Track {
@@ -11,6 +12,8 @@ export declare class ClipTrack extends Track {
11
12
  private _entities;
12
13
  private _entitiesById;
13
14
  private _entitiesDirty;
15
+ private _lastTick?;
16
+ private _lastLoopNumber?;
14
17
  constructor(clip: Clip, id?: string);
15
18
  get influencedPaths(): string[];
16
19
  addWeight(k: Keyframe): void;
@@ -24,7 +27,8 @@ export declare class ClipTrack extends Track {
24
27
  }): void;
25
28
  get sortedBlocks(): ClipTrackEntity[];
26
29
  computePathProperty(t: number, p: string, valueBefore: any, parentWeight?: number): any;
27
- streamPause(): void;
28
- streamSeek(t: number): void;
29
- streamTick(clipState: StreamState, t: number, rate: number, force?: boolean, parentWeight?: number): void;
30
+ streamPause(isActive: boolean, active: LayerClip | null | undefined): void;
31
+ streamSeek(t: number, rate: number, isActive: boolean, active: LayerClip | null | undefined): void;
32
+ streamTick(clipState: StreamState, t: number, rate: number, isActive: boolean, active: LayerClip | null | undefined, force?: boolean, parentWeight?: number): void;
33
+ private _resolveBlock;
30
34
  }
@@ -1,4 +1,4 @@
1
- import { resolveKeyframes, resolveKeyframeValue, Track } from './track';
1
+ import { resolveClipTimeLoops, resolveKeyframes, resolveKeyframeValue, Track } from './track';
2
2
  export class ClipTrack extends Track {
3
3
  constructor(clip, id) {
4
4
  super(clip.animation, id);
@@ -65,29 +65,52 @@ export class ClipTrack extends Track {
65
65
  }
66
66
  return valueBefore;
67
67
  }
68
- streamPause() {
69
- this.clip.streamPause();
68
+ streamPause(isActive, active) {
69
+ this.clip.streamPause(isActive, active);
70
70
  }
71
- streamSeek(t) {
71
+ streamSeek(t, rate, isActive, active) {
72
72
  const blocks = this.sortedBlocks;
73
73
  for (let i = blocks.length - 1; i >= 0; i--) {
74
74
  const block = blocks[i];
75
75
  if (block.t0 > t)
76
76
  continue;
77
77
  const ct = this.clip.resolveClipTime(t, block.t0, block.s0, block.rate, block.t1);
78
- this.clip.streamSeek(ct);
78
+ this.clip.streamSeek(ct, rate, isActive, active);
79
79
  return;
80
80
  }
81
81
  }
82
- streamTick(clipState, t, rate, force = false, parentWeight) {
82
+ streamTick(clipState, t, rate, isActive, active, force = false, parentWeight) {
83
+ if (force)
84
+ delete this._lastLoopNumber;
85
+ const block = this._resolveBlock(t, rate);
86
+ const lastBlock = this._lastTick === undefined ? undefined : this._resolveBlock(this._lastTick, rate);
87
+ this._lastTick = t;
88
+ if (!block) {
89
+ this.clip.streamPause(isActive, active);
90
+ }
91
+ else {
92
+ const [bt, loopNumber] = resolveClipTimeLoops(t, block.t0, block.s0, block.rate, this.clip.length, (rate * block.rate) < 0, block.t1);
93
+ if ((t === block.t1 && rate >= +0) || (t === block.t0 && rate < 0)) {
94
+ this.clip.streamPause(isActive, active);
95
+ }
96
+ else {
97
+ this.clip.streamTick(clipState, bt, rate * block.rate, isActive, active, this._lastLoopNumber !== loopNumber || block !== lastBlock, parentWeight);
98
+ this._lastLoopNumber = loopNumber;
99
+ }
100
+ }
101
+ }
102
+ _resolveBlock(t, rate) {
83
103
  const blocks = this.sortedBlocks;
84
104
  for (let i = blocks.length - 1; i >= 0; i--) {
85
105
  const block = blocks[i];
86
- if (block.t0 > t)
106
+ if (rate >= +0 && block.t0 > t)
87
107
  continue;
88
- const ct = this.clip.resolveClipTime(t, block.t0, block.s0, block.rate, block.t1);
89
- this.clip.streamTick(clipState, ct, block.rate, force);
90
- return;
108
+ if (rate < 0 && block.t0 >= t)
109
+ continue;
110
+ if (block.t1 !== undefined && block.t1 < t && rate >= +0)
111
+ return undefined;
112
+ return block;
91
113
  }
114
+ return undefined;
92
115
  }
93
116
  }
@@ -0,0 +1,32 @@
1
+ import { ByID } from '../../data';
2
+ import { Animation } from '../animation';
3
+ import { LayerClip } from '../layerclip';
4
+ import { StreamState } from '../stream';
5
+ import { Track } from './track';
6
+ export interface ResolvedFunctionTrackEntity {
7
+ id: string;
8
+ obj: any;
9
+ func: (...args: any[]) => any;
10
+ t0: number;
11
+ args: ByID<any>;
12
+ }
13
+ export declare class FunctionTrack extends Track {
14
+ readonly animation: Animation;
15
+ private _entities;
16
+ private _entitiesById;
17
+ private _entitiesDirty;
18
+ private _lastTick?;
19
+ constructor(animation: Animation);
20
+ get influencedPaths(): string[];
21
+ computePathProperty(t: number, p: string, valueBefore: any, parentWeight?: number): any;
22
+ addBlock(k: ResolvedFunctionTrackEntity): void;
23
+ setBlocksById(weights: {
24
+ [id: string]: ResolvedFunctionTrackEntity;
25
+ }): void;
26
+ get sortedBlocks(): ResolvedFunctionTrackEntity[];
27
+ streamSeek(t: number, rate: number, isActive: boolean, active: LayerClip | null | undefined): void;
28
+ streamTick(clipState: StreamState, t: number, rate: number, isActive: boolean, active: LayerClip | null | undefined, force?: boolean, parentWeight?: number): void;
29
+ streamPause(isActive: boolean, active: LayerClip | null | undefined): void;
30
+ private _resolveBlocksBetween;
31
+ private _getBoundedT;
32
+ }
@@ -0,0 +1,95 @@
1
+ import { Track } from './track';
2
+ export class FunctionTrack extends Track {
3
+ constructor(animation) {
4
+ super(animation);
5
+ this.animation = animation;
6
+ this._entities = [];
7
+ this._entitiesById = {};
8
+ this._entitiesDirty = false;
9
+ }
10
+ get influencedPaths() {
11
+ return [];
12
+ }
13
+ computePathProperty(t, p, valueBefore, parentWeight) {
14
+ // n/a
15
+ }
16
+ addBlock(k) {
17
+ this._entitiesById[k.id] = k;
18
+ this._entitiesDirty = true;
19
+ }
20
+ setBlocksById(weights) {
21
+ this._entitiesById = weights;
22
+ this._entitiesDirty = true;
23
+ }
24
+ get sortedBlocks() {
25
+ if (!this._entitiesDirty)
26
+ return this._entities;
27
+ this._entities = Object.values(this._entitiesById);
28
+ this._entities.sort((a, b) => a.t0 - b.t0);
29
+ this._entitiesDirty = false;
30
+ return this._entities;
31
+ }
32
+ streamSeek(t, rate, isActive, active) {
33
+ this._lastTick = t;
34
+ }
35
+ streamTick(clipState, t, rate, isActive, active, force = false, parentWeight) {
36
+ // if (clipState !== StreamState.Playing || this._lastTick === undefined) {
37
+ // this._lastTick = t;
38
+ // return;
39
+ // }
40
+ // if (this._lastTick === undefined) {
41
+ // this._lastTick = rate > +0 ? 0 : clipLength;
42
+ // }
43
+ // const blocks = this._resolveBlocksBetween(this._lastTick, t, rate, clipLength);
44
+ // for (const block of blocks) {
45
+ // const args: any[] = [];
46
+ // for (let i = 0; i < block.args.length; i++) {
47
+ // args[i] = block.args[i];
48
+ // }
49
+ // block.func.call(block.obj, ...args);
50
+ // }
51
+ // this._lastTick = t;
52
+ }
53
+ streamPause(isActive, active) {
54
+ }
55
+ _resolveBlocksBetween(tstart, tend, rate, clipLength) {
56
+ const blocks = this.sortedBlocks;
57
+ if (rate < 0) {
58
+ const tmp = tstart;
59
+ tstart = tend;
60
+ tend = tmp;
61
+ }
62
+ const [boundedEnd, loopNoEnd] = this._getBoundedT(tend, clipLength, rate);
63
+ let [bounded, loopNo] = this._getBoundedT(tstart, clipLength, rate);
64
+ const ret = [];
65
+ while (loopNo < loopNoEnd || (loopNo === loopNoEnd && bounded <= boundedEnd)) {
66
+ const t1 = (loopNoEnd > loopNo) ? Infinity : boundedEnd;
67
+ for (let i = 0; i < blocks.length; i++) {
68
+ const b = blocks[i];
69
+ if (b.t0 >= bounded && b.t0 <= t1) {
70
+ ret.push(b);
71
+ }
72
+ }
73
+ if (loopNo >= loopNoEnd) {
74
+ break;
75
+ }
76
+ // Move to the start of the next loop
77
+ loopNo++;
78
+ bounded = 0;
79
+ }
80
+ return ret;
81
+ }
82
+ _getBoundedT(ct, clipLength, rate) {
83
+ let t = ct;
84
+ let loop = 0;
85
+ while (t >= clipLength) {
86
+ t -= clipLength;
87
+ loop++;
88
+ }
89
+ while (t < 0) {
90
+ t += clipLength;
91
+ loop++;
92
+ }
93
+ return [t, loop];
94
+ }
95
+ }
@@ -1,6 +1,7 @@
1
1
  import { StreamTrackEntity } from '../../data';
2
2
  import { Animation } from '../animation';
3
3
  import { Keyframe } from '../keyframe';
4
+ import { LayerClip } from '../layerclip';
4
5
  import { Stream, StreamState } from '../stream';
5
6
  import { Track } from './track';
6
7
  export declare class StreamTrack extends Track {
@@ -16,6 +17,7 @@ export declare class StreamTrack extends Track {
16
17
  private _weightsDirty;
17
18
  private _lastTick?;
18
19
  private _lastLoopNumber?;
20
+ private _lastState;
19
21
  private _lastRate;
20
22
  constructor(animation: Animation, _entity: any, _property: (string | number)[]);
21
23
  get influencedPaths(): string[];
@@ -30,10 +32,11 @@ export declare class StreamTrack extends Track {
30
32
  [id: string]: StreamTrackEntity;
31
33
  }): void;
32
34
  get sortedBlocks(): StreamTrackEntity[];
33
- streamPause(): void;
34
- streamSeek(t: number): void;
35
- streamTick(clipState: StreamState, t: number, rate: number, force?: boolean, parentWeight?: number): void;
35
+ streamPause(isActive: boolean, active: LayerClip | null | undefined): void;
36
+ streamSeek(t: number, resolvedRate: number, isActive: boolean, active: LayerClip | null | undefined): void;
37
+ private _isPlayingInClip;
38
+ private _isPlayingInLayerClip;
39
+ streamTick(clipState: StreamState, t: number, rate: number, isActive: boolean, active: LayerClip | null | undefined, force?: boolean, parentWeight?: number): void;
36
40
  private _applyState;
37
41
  private _resolveBlock;
38
- private _resolveBlockTime;
39
42
  }
@@ -1,5 +1,6 @@
1
1
  import { StreamState } from '../stream';
2
- import { resolveKeyframes, resolveKeyframeValue, Track } from './track';
2
+ import { ClipTrack } from './cliptrack';
3
+ import { resolveClipTimeLoops, Track } from './track';
3
4
  export class StreamTrack extends Track {
4
5
  constructor(animation, _entity, _property) {
5
6
  super(animation);
@@ -12,7 +13,6 @@ export class StreamTrack extends Track {
12
13
  this._weights = [];
13
14
  this._weightsById = {};
14
15
  this._weightsDirty = false;
15
- this._lastRate = 1;
16
16
  }
17
17
  get influencedPaths() {
18
18
  return [];
@@ -60,47 +60,76 @@ export class StreamTrack extends Track {
60
60
  this._entitiesDirty = false;
61
61
  return this._entities;
62
62
  }
63
- streamPause() {
63
+ streamPause(isActive, active) {
64
+ if (!isActive && this._isPlayingInLayerClip(active)) {
65
+ delete this._lastLoopNumber;
66
+ return;
67
+ }
68
+ if (this._lastState === StreamState.Paused)
69
+ return;
64
70
  this.stream?.pause();
71
+ this._lastState = StreamState.Paused;
65
72
  }
66
- streamSeek(t) {
67
- const blockNow = this._resolveBlock(t);
73
+ streamSeek(t, resolvedRate, isActive, active) {
74
+ if (!isActive && this._isPlayingInLayerClip(active)) {
75
+ delete this._lastLoopNumber;
76
+ return;
77
+ }
78
+ const blockNow = this._resolveBlock(t, 1);
68
79
  if (!blockNow)
69
80
  return;
70
- const [bt, loopNumber] = this._resolveBlockTime(t, blockNow.t0, blockNow.s0, blockNow.rate, blockNow.t1);
81
+ const [bt, loopNumber] = resolveClipTimeLoops(t, blockNow.t0, blockNow.s0, blockNow.rate, this.stream?.length?.() ?? Infinity, (resolvedRate * blockNow.rate) < 0, blockNow.t1);
71
82
  this.stream?.seek(bt);
72
83
  this._lastLoopNumber = loopNumber;
73
84
  }
74
- streamTick(clipState, t, rate, force = false, parentWeight) {
75
- let weight = 1;
76
- const [weightBefore, weightAfter] = resolveKeyframes(this.sortedWeights, t);
77
- if (weightBefore)
78
- weight = resolveKeyframeValue(t, weightBefore, weightAfter);
79
- weight *= parentWeight ?? 1;
80
- const blockBefore = this._lastTick === undefined ? undefined : this._resolveBlock(this._lastTick);
85
+ _isPlayingInClip(stream, clip) {
86
+ for (let s of clip._streamTracks) {
87
+ if (s instanceof StreamTrack && s.stream === stream)
88
+ return true;
89
+ if (s instanceof ClipTrack && this._isPlayingInClip(stream, s.clip))
90
+ return true;
91
+ }
92
+ return false;
93
+ }
94
+ _isPlayingInLayerClip(layerClip) {
95
+ if (!layerClip)
96
+ return false;
97
+ const stream = this.stream;
98
+ if (!stream)
99
+ return false;
100
+ return this._isPlayingInClip(stream, layerClip.clip);
101
+ }
102
+ streamTick(clipState, t, rate, isActive, active, force = false, parentWeight) {
103
+ if (!isActive && this._isPlayingInLayerClip(active)) {
104
+ delete this._lastLoopNumber;
105
+ return;
106
+ }
107
+ if (force)
108
+ delete this._lastLoopNumber;
109
+ const block = this._resolveBlock(t, rate);
110
+ let desiredState = clipState;
111
+ const lastBlock = this._lastTick === undefined ? undefined : this._resolveBlock(this._lastTick, rate);
81
112
  this._lastTick = t;
82
- const blockNow = this._resolveBlock(t);
83
- if (blockBefore !== blockNow || force) {
84
- let desiredState = clipState;
85
- if (!blockNow)
113
+ if (!block) {
114
+ desiredState = StreamState.Paused;
115
+ }
116
+ else {
117
+ const [bt, loopNumber] = resolveClipTimeLoops(t, block.t0, block.s0, block.rate, this.stream?.length?.() ?? Infinity, (rate * block.rate) < 0, block.t1);
118
+ if ((t === block.t1 && rate >= +0) || (t === block.t0 && rate < 0)) {
86
119
  desiredState = StreamState.Paused;
87
- else {
88
- const [bt, loopNumber] = this._resolveBlockTime(t, blockNow.t0, blockNow.s0, blockNow.rate, blockNow.t1);
89
- this._lastLoopNumber = loopNumber;
90
- this.stream?.seek(bt);
120
+ force = true;
91
121
  }
92
- this._applyState(desiredState, (blockNow?.rate ?? 1) * rate);
93
- }
94
- if (blockNow) {
95
- const [bt, loopNumber] = this._resolveBlockTime(t, blockNow.t0, blockNow.s0, blockNow.rate, blockNow.t1);
96
- if (loopNumber !== this._lastLoopNumber) {
97
- console.log('Loop number changed', loopNumber, 'last', this._lastLoopNumber, 'seek', bt);
122
+ else if (this._lastLoopNumber !== loopNumber || lastBlock !== block) {
98
123
  this._lastLoopNumber = loopNumber;
124
+ force = true;
99
125
  this.stream?.seek(bt);
100
126
  }
101
127
  }
128
+ this._applyState(desiredState, (block?.rate ?? 1) * rate, force);
102
129
  }
103
- _applyState(s, rate) {
130
+ _applyState(s, rate, force) {
131
+ if (s === this._lastState && !force && (s !== StreamState.Playing || this._lastRate === rate))
132
+ return;
104
133
  switch (s) {
105
134
  case StreamState.Playing:
106
135
  this.stream?.play({ speed: rate });
@@ -112,33 +141,21 @@ export class StreamTrack extends Track {
112
141
  this.stream?.stop();
113
142
  break;
114
143
  }
144
+ this._lastState = s;
145
+ this._lastRate = rate;
115
146
  }
116
- _resolveBlock(t) {
147
+ _resolveBlock(t, rate) {
117
148
  const blocks = this.sortedBlocks;
118
149
  for (let i = blocks.length - 1; i >= 0; i--) {
119
150
  const block = blocks[i];
120
- if (block.t0 > t)
151
+ if (rate >= +0 && block.t0 > t)
121
152
  continue;
122
- if (block.t1 !== undefined && block.t1 <= t)
153
+ if (rate < 0 && block.t0 >= t)
154
+ continue;
155
+ if (block.t1 !== undefined && block.t1 < t && rate >= +0)
123
156
  return undefined;
124
157
  return block;
125
158
  }
126
159
  return undefined;
127
160
  }
128
- _resolveBlockTime(t, t0, s0, rate, t1 = Infinity) {
129
- const length = this.stream?.length?.() ?? Infinity;
130
- if (length === 0)
131
- return [0, 0];
132
- t = Math.min(t, t1);
133
- const ct = s0 + (t - t0) * rate;
134
- const loopNumber = Math.floor(ct / length);
135
- let bounded = ct % length;
136
- if (bounded < 0)
137
- bounded += length;
138
- if (bounded === 0 && rate >= +0 && t === t1)
139
- bounded += length;
140
- if (bounded === 0 && rate < 0 && t === t0)
141
- bounded += length;
142
- return [bounded, loopNumber];
143
- }
144
161
  }
@@ -10,3 +10,4 @@ export declare abstract class Track {
10
10
  }
11
11
  export declare function resolveKeyframeValue(t: number, before: Keyframe, after: Keyframe | undefined): any;
12
12
  export declare function resolveKeyframes(keyframes: Keyframe[], t: number): [before: Keyframe | undefined, after: Keyframe | undefined];
13
+ export declare function resolveClipTimeLoops(t: number, t0: number, s0: number, rate: number, length: number, timeBackwards: boolean, t1?: number): [ct: number, loop: number];
@@ -31,3 +31,28 @@ export function resolveKeyframes(keyframes, t) {
31
31
  }
32
32
  return [keyframeBefore, keyframeAfter];
33
33
  }
34
+ export function resolveClipTimeLoops(t, t0, s0, rate, length, timeBackwards, t1 = Infinity) {
35
+ if (length === 0)
36
+ return [0, 0];
37
+ t = Math.min(t, t1);
38
+ let ct = s0 + (t - t0) * rate;
39
+ let loop = 0;
40
+ // if (rate < 0) ct += length;
41
+ while ((ct >= length && !timeBackwards) || (ct > length && timeBackwards)) {
42
+ ct -= length;
43
+ loop++;
44
+ }
45
+ while ((ct < 0 && !timeBackwards) || (ct <= 0 && timeBackwards)) {
46
+ ct += length;
47
+ loop++;
48
+ }
49
+ if (ct === 0 && !timeBackwards && t === t1) {
50
+ ct += length;
51
+ loop--;
52
+ }
53
+ if (ct === 0 && timeBackwards && t === t1) {
54
+ ct += length;
55
+ loop--;
56
+ }
57
+ return [ct, loop];
58
+ }
@@ -29,17 +29,20 @@ export declare class Audio extends Component<undefined, AudioConstructorProps> i
29
29
  destination: AudioNode;
30
30
  audioBuffer?: AudioBuffer;
31
31
  private _gain;
32
+ private _reversed?;
32
33
  private _playhead;
34
+ private _rate;
33
35
  private _session?;
36
+ constructor(mgr: ContextManager, props: AudioConstructorProps);
37
+ private _load;
38
+ private _updateVolume;
34
39
  /** @zprop
35
40
  * @zdefault false
36
41
  * @zgroup Audio
37
42
  * @zgrouppriority 20
38
43
  */
39
44
  muted: Observable<boolean, never>;
40
- constructor(mgr: ContextManager, props: AudioConstructorProps);
41
- private _load;
42
- private _updateVolume;
45
+ private _getReversed;
43
46
  /**
44
47
  * @zprop
45
48
  * @ztype proportion
@@ -14,15 +14,16 @@ export class Audio extends Component {
14
14
  constructor(mgr, props) {
15
15
  super(mgr, props);
16
16
  this._playhead = 0;
17
+ this._rate = 1;
18
+ this._updateVolume = () => {
19
+ this._gain.gain.value = this.muted.value === true ? 0 : this.volume.value;
20
+ };
17
21
  /** @zprop
18
22
  * @zdefault false
19
23
  * @zgroup Audio
20
24
  * @zgrouppriority 20
21
25
  */
22
- this.muted = new Observable(false);
23
- this._updateVolume = () => {
24
- this._gain.gain.value = this.muted.value === true ? 0 : this.volume.value;
25
- };
26
+ this.muted = new Observable(false, this._updateVolume);
26
27
  /**
27
28
  * @zprop
28
29
  * @ztype proportion
@@ -46,20 +47,43 @@ export class Audio extends Component {
46
47
  const audioContext = useAudioContext(this.contextManager);
47
48
  this.audioBuffer = await audioContext.decodeAudioData(data);
48
49
  }
50
+ _getReversed(inp) {
51
+ if (!this._reversed) {
52
+ const audioContext = useAudioContext(this.contextManager);
53
+ this._reversed = audioContext.createBuffer(inp.numberOfChannels, inp.length, inp.sampleRate);
54
+ for (let i = 0; i < inp.numberOfChannels; i++) {
55
+ const source = inp.getChannelData(i);
56
+ const destination = this._reversed.getChannelData(i);
57
+ for (let j = 0; j < inp.length; j++) {
58
+ destination[j] = source[inp.length - j];
59
+ }
60
+ }
61
+ }
62
+ return this._reversed;
63
+ }
49
64
  /** @zprop */
50
65
  play(opts) {
51
66
  if (!this.audioBuffer)
52
67
  return;
53
68
  if (this._session)
54
- this.stop();
55
- if (this._playhead >= this.audioBuffer.duration)
56
- this._playhead = 0;
69
+ this.pause();
70
+ this._rate = opts?.speed ?? 1;
71
+ let resolvedPlayhead = this._playhead;
72
+ if (this._rate >= +0) {
73
+ if (this._playhead >= this.audioBuffer.duration)
74
+ this._playhead = 0;
75
+ }
76
+ else {
77
+ if (this._playhead <= 0)
78
+ this._playhead = this.audioBuffer.duration;
79
+ resolvedPlayhead = Math.max(this.audioBuffer.duration - this._playhead, 0);
80
+ }
57
81
  const source = this.audioContext.createBufferSource();
58
- source.buffer = this.audioBuffer;
59
- source.playbackRate.value = opts?.speed ?? 1;
82
+ source.playbackRate.value = Math.abs(this._rate);
83
+ source.buffer = this._rate < 0 ? this._getReversed(this.audioBuffer) : this.audioBuffer;
60
84
  source.connect(this.destination);
61
- const startedAt = this.audioContext.currentTime - (this._playhead / source.playbackRate.value);
62
- source.start(0, this._playhead);
85
+ const startedAt = this.audioContext.currentTime - (resolvedPlayhead / source.playbackRate.value);
86
+ source.start(0, resolvedPlayhead);
63
87
  this._session = { source, startedAt };
64
88
  }
65
89
  /** @zprop */
@@ -67,6 +91,8 @@ export class Audio extends Component {
67
91
  if (!this._session)
68
92
  return;
69
93
  this._playhead = this._session.source.playbackRate.value * (this.audioContext.currentTime - this._session.startedAt);
94
+ if (this._rate < 0)
95
+ this._playhead = (this.audioBuffer?.length ?? 0) - this._playhead;
70
96
  this._session.source.stop();
71
97
  this._session.source.disconnect();
72
98
  delete this._session;
@@ -83,7 +109,7 @@ export class Audio extends Component {
83
109
  this.pause();
84
110
  this._playhead = t / 1000;
85
111
  if (previousSession)
86
- this.play({ speed: previousSession.source.playbackRate.value });
112
+ this.play({ speed: this._rate });
87
113
  }
88
114
  length() {
89
115
  return this.audioBuffer ? (this.audioBuffer.duration * 1000) : undefined;
@@ -141,6 +141,7 @@ export interface BaseFunctionTrackEntity {
141
141
  t0: number;
142
142
  type: 'entity' | 'import';
143
143
  args: ByID<any>;
144
+ runAtDesignTime?: boolean;
144
145
  }
145
146
  export interface EntityFunctionTrackEntity extends BaseFunctionTrackEntity {
146
147
  type: 'entity';
package/lib/inflate.js CHANGED
@@ -6,6 +6,7 @@ import { Layer } from './animation/layer';
6
6
  import { ClipTrack } from './animation/tracks/cliptrack';
7
7
  import { Bezier } from './animation/bezier';
8
8
  import { StreamTrack } from './animation/tracks/streamtrack';
9
+ import { FunctionTrack } from './animation/tracks/functiontrack';
9
10
  export function createTrackFromData(zcomp, anim, data) {
10
11
  switch (data.type) {
11
12
  case Data.TrackType.Property: {
@@ -37,6 +38,28 @@ export function createTrackFromData(zcomp, anim, data) {
37
38
  ret.setWeightsById(processKeyframes(data.weight, anim.curves));
38
39
  return ret;
39
40
  }
41
+ case Data.TrackType.Function: {
42
+ const ret = new FunctionTrack(anim);
43
+ const blocks = Object.create(null);
44
+ const dt = false; //isDesignTime(zcomp.contextManager);
45
+ for (const block of Object.values(data.data)) {
46
+ if (dt && !block?.runAtDesignTime)
47
+ continue;
48
+ if (block?.type !== 'entity')
49
+ continue;
50
+ const obj = zcomp.entityByID.get(block.entityID);
51
+ const func = obj?.[block.functionName];
52
+ if (typeof func !== 'function')
53
+ continue;
54
+ blocks[block.id] = {
55
+ ...block,
56
+ obj,
57
+ func,
58
+ };
59
+ }
60
+ ret.setBlocksById(blocks);
61
+ return ret;
62
+ }
40
63
  }
41
64
  return undefined;
42
65
  }
@@ -1,6 +1,5 @@
1
1
  import { BehaviorByID, EntityPropOverride, Import, NodeByID, ParsedImport, Props, ZComponentData } from './data/core';
2
2
  import * as Data from './data';
3
- import { Layer } from './animation';
4
3
  import { ZValues } from './values/values';
5
4
  export declare function parseImport(i: Import): ParsedImport;
6
5
  export declare function constructImport(from: string, destFile: string, imp: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zcomponent/core",
3
- "version": "1.3.0-beta",
3
+ "version": "1.4.1-beta",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -21,14 +21,14 @@
21
21
  "scripts": {
22
22
  "build": "rm -rf lib && tsc",
23
23
  "build-animation-tests": "tsc -p ./tsconfig.test.animation.json",
24
- "test": "PW_EXPERIMENTAL_TS_ESM=1 playwright test",
25
- "tests": "parcel serve tests/index.html -p 8088"
24
+ "test": "env TS_NODE_PROJECT=\"tsconfig.testing.json\" mocha --es-module-specifier-resolution=node"
26
25
  },
27
26
  "author": "Zappar Limited",
28
27
  "license": "Proprietary",
29
28
  "devDependencies": {
30
- "@playwright/test": "^1.25.0",
31
- "parcel": "^2.8.2",
29
+ "@types/mocha": "^10.0.1",
30
+ "mocha": "^10.2.0",
31
+ "ts-node": "^10.9.1",
32
32
  "typescript": "^4.9.4"
33
33
  },
34
34
  "zexports": [