@hkdigital/lib-core 0.4.25 → 0.4.27

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.
@@ -13,6 +13,30 @@ export default class AudioScene {
13
13
  loaded: boolean;
14
14
  muted: boolean;
15
15
  targetGain: number;
16
+ /**
17
+ * Get audio scene loading progress
18
+ */
19
+ get progress(): {
20
+ totalBytesLoaded: number;
21
+ totalSize: number;
22
+ sourcesLoaded: number;
23
+ numberOfSources: number;
24
+ };
25
+ /**
26
+ * Get audio scene abort progress
27
+ */
28
+ get abortProgress(): {
29
+ sourcesAborted: number;
30
+ numberOfSources: number;
31
+ };
32
+ /**
33
+ * Start loading all audio sources
34
+ */
35
+ load(): void;
36
+ /**
37
+ * Abort loading all audio sources
38
+ */
39
+ abort(): void;
16
40
  destroy(): void;
17
41
  /**
18
42
  * Add in-memory audio source
@@ -28,16 +52,6 @@ export default class AudioScene {
28
52
  url: string;
29
53
  config?: SourceConfig;
30
54
  }): void;
31
- /**
32
- * Start loading all audio sources
33
- */
34
- load(): void;
35
- /**
36
- * Set an audio context to use
37
- *
38
- * @param {AudioContext} [audioContext]
39
- */
40
- setAudioContext(audioContext?: AudioContext): void;
41
55
  /**
42
56
  * Get a source that can be used to play the audio once
43
57
  *
@@ -46,6 +60,12 @@ export default class AudioScene {
46
60
  * @returns {Promise<AudioBufferSourceNode>}
47
61
  */
48
62
  getSourceNode(label: string): Promise<AudioBufferSourceNode>;
63
+ /**
64
+ * Set an audio context to use
65
+ *
66
+ * @param {AudioContext} [audioContext]
67
+ */
68
+ setAudioContext(audioContext?: AudioContext): void;
49
69
  /**
50
70
  * Set target gain
51
71
  *
@@ -5,12 +5,14 @@ import { LoadingStateMachine } from '../../../state/machines.js';
5
5
  import {
6
6
  STATE_INITIAL,
7
7
  STATE_LOADING,
8
- STATE_UNLOADING,
9
8
  STATE_LOADED,
10
- STATE_CANCELLED,
9
+ STATE_ABORTING,
10
+ STATE_ABORTED,
11
11
  STATE_ERROR,
12
12
  LOAD,
13
- LOADED
13
+ LOADED,
14
+ ABORT,
15
+ ABORTED
14
16
  } from '../../../state/machines.js';
15
17
 
16
18
  import AudioLoader from './AudioLoader.svelte.js';
@@ -30,10 +32,9 @@ import AudioLoader from './AudioLoader.svelte.js';
30
32
  export default class AudioScene {
31
33
  #state = new LoadingStateMachine();
32
34
 
33
- // @note this exported state is set by $effect's
35
+ // @note this exported state is set by onenter
34
36
  state = $state(STATE_INITIAL);
35
37
 
36
- // @note this exported state is set by $effect's
37
38
  loaded = $derived.by(() => {
38
39
  return this.state === STATE_LOADED;
39
40
  });
@@ -87,6 +88,27 @@ export default class AudioScene {
87
88
  };
88
89
  });
89
90
 
91
+ #abortProgress = $derived.by(() => {
92
+ let sourcesAborted = 0;
93
+ const sources = this.#memorySources;
94
+ const numberOfSources = sources.length;
95
+
96
+ for (let j = 0; j < numberOfSources; j++) {
97
+ const source = sources[j];
98
+ const { audioLoader } = source;
99
+ const loaderState = audioLoader.state;
100
+
101
+ if (loaderState === STATE_ABORTED || loaderState === STATE_ERROR) {
102
+ sourcesAborted++;
103
+ }
104
+ } // end for
105
+
106
+ return {
107
+ sourcesAborted,
108
+ numberOfSources
109
+ };
110
+ });
111
+
90
112
  /**
91
113
  * Construct AudioScene
92
114
  */
@@ -110,46 +132,62 @@ export default class AudioScene {
110
132
  });
111
133
 
112
134
  $effect(() => {
113
- switch (state.current) {
114
- case STATE_LOADING:
115
- {
116
- // console.log('AudioScene:loading');
117
- this.#startLoading();
118
- }
119
- break;
120
-
121
- case STATE_UNLOADING:
122
- {
123
- // console.log('AudioScene:unloading');
124
- // this.#startUnLoading();
125
- }
126
- break;
127
-
128
- case STATE_LOADED:
129
- {
130
- // console.log('AudioScene:loaded');
131
-
132
- // tODO
133
- // this.#abortLoading = null;
134
- }
135
- break;
136
-
137
- case STATE_CANCELLED:
138
- {
139
- // console.log('AudioScene:cancelled');
140
- // TODO
141
- }
142
- break;
143
-
144
- case STATE_ERROR:
145
- {
146
- console.error('AudioScene:error', state.error);
147
- }
148
- break;
149
- } // end switch
135
+ if (state.current === STATE_ABORTING) {
136
+ const { sourcesAborted, numberOfSources } = this.#abortProgress;
150
137
 
151
- this.state = state.current;
138
+ if (sourcesAborted === numberOfSources) {
139
+ // console.debug(`AudioScene: ${numberOfSources} sources aborted`);
140
+ this.#state.send(ABORTED);
141
+ }
142
+ }
152
143
  });
144
+
145
+ state.onenter = ( currentState ) => {
146
+ // console.log('onenter', currentState );
147
+
148
+ if(currentState === STATE_LOADING )
149
+ {
150
+ // console.log('AudioScene:loading');
151
+ this.#startLoading();
152
+ }
153
+ else if(currentState === STATE_ABORTING )
154
+ {
155
+ // console.log('AudioScene:aborting');
156
+ this.#startAbort();
157
+ }
158
+
159
+ this.state = state.current;
160
+ };
161
+ }
162
+
163
+ /* ==== Common loader interface */
164
+
165
+ /**
166
+ * Get audio scene loading progress
167
+ */
168
+ get progress() {
169
+ return this.#progress;
170
+ }
171
+
172
+ /**
173
+ * Get audio scene abort progress
174
+ */
175
+ get abortProgress() {
176
+ return this.#abortProgress;
177
+ }
178
+
179
+ /**
180
+ * Start loading all audio sources
181
+ */
182
+ load() {
183
+ this.#state.send(LOAD);
184
+ }
185
+
186
+ /**
187
+ * Abort loading all audio sources
188
+ */
189
+ abort() {
190
+ this.#state.send(ABORT);
153
191
  }
154
192
 
155
193
  destroy() {
@@ -157,6 +195,8 @@ export default class AudioScene {
157
195
  // TODO: Unload AUdioLoaders?
158
196
  }
159
197
 
198
+ /* ==== Source definitions */
199
+
160
200
  /**
161
201
  * Add in-memory audio source
162
202
  * - Uses an AudioLoader instance to load audio data from network
@@ -175,36 +215,7 @@ export default class AudioScene {
175
215
  this.#memorySources.push({ label, audioLoader, config });
176
216
  }
177
217
 
178
- /**
179
- * Start loading all audio sources
180
- */
181
- load() {
182
- this.#state.send(LOAD);
183
-
184
- // FIXME: in unit test when moved to startloading it hangs!
185
-
186
- for (const { audioLoader } of this.#memorySources) {
187
- audioLoader.load();
188
- }
189
- }
190
-
191
- /**
192
- * Set an audio context to use
193
- *
194
- * @param {AudioContext} [audioContext]
195
- */
196
- setAudioContext( audioContext ) {
197
- this.#audioContext = audioContext;
198
- }
199
-
200
- async #startLoading() {
201
- // console.log('#startLoading');
202
-
203
- // FIXME: in unit test when moved to startloading it hangs!
204
- // for (const { audioLoader } of this.#memorySources) {
205
- // audioLoader.load();
206
- // }
207
- }
218
+ /* ==== Resource access */
208
219
 
209
220
  /**
210
221
  * Get a source that can be used to play the audio once
@@ -240,6 +251,33 @@ export default class AudioScene {
240
251
  return sourceNode;
241
252
  }
242
253
 
254
+ /**
255
+ * Set an audio context to use
256
+ *
257
+ * @param {AudioContext} [audioContext]
258
+ */
259
+ setAudioContext( audioContext ) {
260
+ this.#audioContext = audioContext;
261
+ }
262
+
263
+ async #startLoading() {
264
+ // console.log('#startLoading');
265
+
266
+ for (const { audioLoader } of this.#memorySources) {
267
+ audioLoader.load();
268
+ }
269
+ }
270
+
271
+ #startAbort() {
272
+ // console.log('#startAbort');
273
+
274
+ for (const { audioLoader } of this.#memorySources) {
275
+ audioLoader.abort();
276
+ }
277
+ }
278
+
279
+ /* ==== Audio specific */
280
+
243
281
  /**
244
282
  * Set target gain
245
283
  *
@@ -281,6 +319,7 @@ export default class AudioScene {
281
319
  this.setTargetGain(this.#unmutedTargetGain);
282
320
  }
283
321
 
322
+ /* ==== Internals */
284
323
 
285
324
  #getGainNode()
286
325
  {
@@ -322,4 +361,4 @@ export default class AudioScene {
322
361
 
323
362
  throw new Error(`Source [${label}] has not been defined`);
324
363
  }
325
- }
364
+ } // end class
@@ -10,6 +10,30 @@
10
10
  export default class ImageScene {
11
11
  state: string;
12
12
  loaded: boolean;
13
+ /**
14
+ * Get image scene loading progress
15
+ */
16
+ get progress(): {
17
+ totalBytesLoaded: number;
18
+ totalSize: number;
19
+ sourcesLoaded: number;
20
+ numberOfSources: number;
21
+ };
22
+ /**
23
+ * Get image scene abort progress
24
+ */
25
+ get abortProgress(): {
26
+ sourcesAborted: number;
27
+ numberOfSources: number;
28
+ };
29
+ /**
30
+ * Start loading all image sources
31
+ */
32
+ load(): void;
33
+ /**
34
+ * Abort loading all image sources
35
+ */
36
+ abort(): void;
13
37
  destroy(): void;
14
38
  /**
15
39
  * Add image source
@@ -23,19 +47,6 @@ export default class ImageScene {
23
47
  label: string;
24
48
  imageSource: import("../../../config/typedef.js").ImageSource;
25
49
  }): void;
26
- /**
27
- * Start loading all image sources
28
- */
29
- load(): void;
30
- /**
31
- * Get image scene loading progress
32
- */
33
- get progress(): {
34
- totalBytesLoaded: number;
35
- totalSize: number;
36
- sourcesLoaded: number;
37
- numberOfSources: number;
38
- };
39
50
  /**
40
51
  * Get an image loader
41
52
  *
@@ -7,12 +7,14 @@ import { LoadingStateMachine } from '../../../state/machines.js';
7
7
  import {
8
8
  STATE_INITIAL,
9
9
  STATE_LOADING,
10
- STATE_UNLOADING,
11
10
  STATE_LOADED,
12
- STATE_CANCELLED,
11
+ STATE_ABORTING,
12
+ STATE_ABORTED,
13
13
  STATE_ERROR,
14
14
  LOAD,
15
- LOADED
15
+ LOADED,
16
+ ABORT,
17
+ ABORTED
16
18
  } from '../../../state/machines.js';
17
19
 
18
20
  import ImageLoader from './ImageLoader.svelte.js';
@@ -31,10 +33,9 @@ import ImageLoader from './ImageLoader.svelte.js';
31
33
  export default class ImageScene {
32
34
  #state = new LoadingStateMachine();
33
35
 
34
- // @note this exported state is set by $effect's
36
+ // @note this exported state is set by onenter
35
37
  state = $state(STATE_INITIAL);
36
38
 
37
- // @note this exported state is set by $effect's
38
39
  loaded = $derived.by(() => {
39
40
  return this.state === STATE_LOADED;
40
41
  });
@@ -74,6 +75,27 @@ export default class ImageScene {
74
75
  };
75
76
  });
76
77
 
78
+ #abortProgress = $derived.by(() => {
79
+ let sourcesAborted = 0;
80
+ const sources = this.#imageSources;
81
+ const numberOfSources = sources.length;
82
+
83
+ for (let j = 0; j < numberOfSources; j++) {
84
+ const source = sources[j];
85
+ const { imageLoader } = source;
86
+ const loaderState = imageLoader.state;
87
+
88
+ if (loaderState === STATE_ABORTED || loaderState === STATE_ERROR) {
89
+ sourcesAborted++;
90
+ }
91
+ } // end for
92
+
93
+ return {
94
+ sourcesAborted,
95
+ numberOfSources
96
+ };
97
+ });
98
+
77
99
  #sourcesLoaded = $derived( this.#progress.sourcesLoaded );
78
100
  #numberOfSources = $derived( this.#progress.numberOfSources );
79
101
 
@@ -92,55 +114,72 @@ export default class ImageScene {
92
114
  }
93
115
  } );
94
116
 
95
- state.onenter = ( state ) => {
96
- // console.log('onenter', state );
97
-
98
- switch (state) {
99
- case STATE_LOADING:
100
- {
101
- // console.log('ImageScene:loading');
102
- this.#startLoading();
103
- }
104
- break;
105
-
106
- case STATE_UNLOADING:
107
- {
108
- // console.log('ImageScene:unloading');
109
- // this.#startUnLoading();
110
- }
111
- break;
112
-
113
- case STATE_LOADED:
114
- {
115
- // console.log('ImageScene:loaded');
116
- // TODO
117
- // this.#abortLoading = null;
118
- }
119
- break;
120
-
121
- case STATE_CANCELLED:
122
- {
123
- // console.log('ImageScene:cancelled');
124
- // TODO
125
- }
126
- break;
127
-
128
- case STATE_ERROR:
129
- {
130
- console.log('ImageScene:error', state);
131
- }
132
- break;
133
- } // end switch
134
-
135
- this.state = state;
117
+ $effect(() => {
118
+ if (state.current === STATE_ABORTING) {
119
+ const { sourcesAborted, numberOfSources } = this.#abortProgress;
120
+
121
+ if (sourcesAborted === numberOfSources) {
122
+ // console.debug(`ImageScene: ${numberOfSources} sources aborted`);
123
+ this.#state.send(ABORTED);
124
+ }
125
+ }
126
+ });
127
+
128
+ state.onenter = ( currentState ) => {
129
+ // console.log('onenter', currentState );
130
+
131
+ if(currentState === STATE_LOADING )
132
+ {
133
+ // console.log('ImageScene:loading');
134
+ this.#startLoading();
135
+ }
136
+ else if(currentState === STATE_ABORTING )
137
+ {
138
+ // console.log('ImageScene:aborting');
139
+ this.#startAbort();
140
+ }
141
+
142
+ this.state = currentState;
136
143
  };
137
144
  }
138
145
 
146
+ /* ==== Common loader interface */
147
+
148
+ /**
149
+ * Get image scene loading progress
150
+ */
151
+ get progress() {
152
+ return this.#progress;
153
+ }
154
+
155
+ /**
156
+ * Get image scene abort progress
157
+ */
158
+ get abortProgress() {
159
+ return this.#abortProgress;
160
+ }
161
+
162
+ /**
163
+ * Start loading all image sources
164
+ */
165
+ load() {
166
+ this.#state.send(LOAD);
167
+ }
168
+
169
+ /**
170
+ * Abort loading all image sources
171
+ */
172
+ abort() {
173
+ this.#state.send(ABORT);
174
+ }
175
+
139
176
  destroy() {
140
177
  // TODO: disconnect all image sources?
141
178
  // TODO: Unload ImageLoaders?
142
179
  }
143
180
 
181
+ /* ==== Source definitions */
182
+
144
183
  /**
145
184
  * Add image source
146
185
  * - Uses an ImageLoader instance to load image data from network
@@ -159,42 +198,7 @@ export default class ImageScene {
159
198
  this.#imageSources.push({ label, imageLoader });
160
199
  }
161
200
 
162
- /**
163
- * Start loading all image sources
164
- */
165
- load() {
166
- this.#state.send(LOAD);
167
- }
168
-
169
- async #startLoading() {
170
- for (const { imageLoader } of this.#imageSources) {
171
- imageLoader.load();
172
- }
173
- }
174
-
175
- /**
176
- * Get Image source
177
- *
178
- * @param {string} label
179
- *
180
- * @returns {ImageSceneSource}
181
- */
182
- #getImageSceneSource(label) {
183
- for (const source of this.#imageSources) {
184
- if (label === source.label) {
185
- return source;
186
- }
187
- }
188
-
189
- throw new Error(`Source [${label}] has not been defined`);
190
- }
191
-
192
- /**
193
- * Get image scene loading progress
194
- */
195
- get progress() {
196
- return this.#progress;
197
- }
201
+ /* ==== Resource access */
198
202
 
199
203
  /**
200
204
  * Get an image loader
@@ -236,4 +240,35 @@ export default class ImageScene {
236
240
 
237
241
  return source.imageLoader.getObjectURL();
238
242
  }
239
- }
243
+
244
+ async #startLoading() {
245
+ for (const { imageLoader } of this.#imageSources) {
246
+ imageLoader.load();
247
+ }
248
+ }
249
+
250
+ #startAbort() {
251
+ for (const { imageLoader } of this.#imageSources) {
252
+ imageLoader.abort();
253
+ }
254
+ }
255
+
256
+ /* ==== Internals */
257
+
258
+ /**
259
+ * Get Image source
260
+ *
261
+ * @param {string} label
262
+ *
263
+ * @returns {ImageSceneSource}
264
+ */
265
+ #getImageSceneSource(label) {
266
+ for (const source of this.#imageSources) {
267
+ if (label === source.label) {
268
+ return source;
269
+ }
270
+ }
271
+
272
+ throw new Error(`Source [${label}] has not been defined`);
273
+ }
274
+ } // end class
@@ -45,9 +45,9 @@ export default class NetworkLoader {
45
45
  /**
46
46
  * Abort the current loading operation
47
47
  * - Only works when in STATE_LOADING
48
- * - Aborts network requests and transitions to STATE_CANCELLED
48
+ * - Aborts network requests and transitions to STATE_ABORTING
49
49
  */
50
- doAbort(): void;
50
+ abort(): void;
51
51
  /**
52
52
  * Get network data size in bytes
53
53
  * - Info comes from the content length response header
@@ -7,14 +7,14 @@ import {
7
7
  STATE_LOADING,
8
8
  STATE_UNLOADING,
9
9
  STATE_LOADED,
10
- STATE_CANCELLED,
11
- STATE_ERROR,
10
+ STATE_ABORTING,
12
11
  LOAD,
13
12
  ERROR,
14
13
  LOADED,
15
14
  UNLOAD,
16
15
  INITIAL,
17
- CANCEL
16
+ ABORT,
17
+ ABORTED
18
18
  } from '../../state/machines.js';
19
19
 
20
20
  import * as expect from '../../util/expect.js';
@@ -90,10 +90,6 @@ export default class NetworkLoader {
90
90
  const state = this._state;
91
91
  // const progress = this.progress;
92
92
 
93
- //
94
- // ISSUE: $effect is not triggered by this._state changes,
95
- // using onenter instead
96
- //
97
93
  this._state.onenter = () => {
98
94
  switch (state.current) {
99
95
  case STATE_LOADING:
@@ -127,21 +123,23 @@ export default class NetworkLoader {
127
123
  }
128
124
  break;
129
125
 
130
- case STATE_CANCELLED:
126
+ case STATE_ABORTING:
131
127
  {
132
- // console.log('NetworkLoader:cancelled');
128
+ // console.log('NetworkLoader:aborting');
133
129
  if (this._abortLoading) {
134
130
  this._abortLoading();
135
131
  this._abortLoading = null;
136
132
  }
133
+ // Transition to aborted state after abort completes
134
+ this._state.send(ABORTED);
137
135
  }
138
136
  break;
139
137
 
140
- case STATE_ERROR:
141
- {
142
- console.log('NetworkLoader:error', state.error);
143
- }
144
- break;
138
+ // case STATE_ERROR:
139
+ // {
140
+ // console.log('NetworkLoader:error', state.error);
141
+ // }
142
+ // break;
145
143
  } // end switch
146
144
  };
147
145
  }
@@ -164,10 +162,10 @@ export default class NetworkLoader {
164
162
  /**
165
163
  * Abort the current loading operation
166
164
  * - Only works when in STATE_LOADING
167
- * - Aborts network requests and transitions to STATE_CANCELLED
165
+ * - Aborts network requests and transitions to STATE_ABORTING
168
166
  */
169
- doAbort() {
170
- this._state.send(CANCEL);
167
+ abort() {
168
+ this._state.send(ABORT);
171
169
  }
172
170
 
173
171
  /**
@@ -1,4 +1,4 @@
1
- /** @typedef {import('./typedef.js').StateTransitionMetadata} StateTransitionMetadata */
1
+ /** @typedef {import('./typedef.js').TransitionData} TransitionData */
2
2
  /** @typedef {import('./typedef.js').OnEnterCallback} OnEnterCallback */
3
3
  /** @typedef {import('./typedef.js').OnExitCallback} OnExitCallback */
4
4
  /**
@@ -50,7 +50,7 @@ export default class FiniteStateMachine extends EventEmitter {
50
50
  get current(): any;
51
51
  #private;
52
52
  }
53
- export type StateTransitionMetadata = import("./typedef.js").StateTransitionMetadata;
53
+ export type TransitionData = import("./typedef.js").TransitionData;
54
54
  export type OnEnterCallback = import("./typedef.js").OnEnterCallback;
55
55
  export type OnExitCallback = import("./typedef.js").OnExitCallback;
56
56
  import EventEmitter from '../../../generic/events/classes/EventEmitter.js';