audio-channel-queue 1.5.0 → 1.6.0

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/src/info.ts CHANGED
@@ -1,262 +1,380 @@
1
- /**
2
- * @fileoverview Audio information and progress tracking functions for the audio-channel-queue package
3
- */
4
-
5
- import {
6
- AudioInfo,
7
- QueueSnapshot,
8
- ProgressCallback,
9
- QueueChangeCallback,
10
- AudioStartCallback,
11
- AudioCompleteCallback,
12
- ExtendedAudioQueueChannel
13
- } from './types';
14
- import { getAudioInfoFromElement, createQueueSnapshot } from './utils';
15
- import { setupProgressTracking, cleanupProgressTracking } from './events';
16
-
17
- /**
18
- * Global array of extended audio queue channels
19
- */
20
- export const audioChannels: ExtendedAudioQueueChannel[] = [];
21
-
22
- /**
23
- * Gets current audio information for a specific channel
24
- * @param channelNumber - The channel number (defaults to 0)
25
- * @returns AudioInfo object or null if no audio is playing
26
- * @example
27
- * ```typescript
28
- * const info = getCurrentAudioInfo(0);
29
- * if (info) {
30
- * console.log(`Currently playing: ${info.fileName}`);
31
- * console.log(`Progress: ${(info.progress * 100).toFixed(1)}%`);
32
- * }
33
- * ```
34
- */
35
- export const getCurrentAudioInfo = (channelNumber: number = 0): AudioInfo | null => {
36
- const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
37
- if (!channel || channel.queue.length === 0) {
38
- return null;
39
- }
40
-
41
- const currentAudio: HTMLAudioElement = channel.queue[0];
42
- return getAudioInfoFromElement(currentAudio);
43
- };
44
-
45
- /**
46
- * Gets audio information for all channels
47
- * @returns Array of AudioInfo objects (null for channels with no audio)
48
- * @example
49
- * ```typescript
50
- * const allInfo = getAllChannelsInfo();
51
- * allInfo.forEach((info, channel) => {
52
- * if (info) {
53
- * console.log(`Channel ${channel}: ${info.fileName}`);
54
- * }
55
- * });
56
- * ```
57
- */
58
- export const getAllChannelsInfo = (): (AudioInfo | null)[] => {
59
- const allChannelsInfo: (AudioInfo | null)[] = [];
60
-
61
- for (let i = 0; i < audioChannels.length; i++) {
62
- allChannelsInfo.push(getCurrentAudioInfo(i));
63
- }
64
-
65
- return allChannelsInfo;
66
- };
67
-
68
- /**
69
- * Gets a complete snapshot of the queue state for a specific channel
70
- * @param channelNumber - The channel number
71
- * @returns QueueSnapshot object or null if channel doesn't exist
72
- * @example
73
- * ```typescript
74
- * const snapshot = getQueueSnapshot(0);
75
- * if (snapshot) {
76
- * console.log(`Queue has ${snapshot.totalItems} items`);
77
- * console.log(`Currently playing: ${snapshot.items[0]?.fileName}`);
78
- * }
79
- * ```
80
- */
81
- export const getQueueSnapshot = (channelNumber: number): QueueSnapshot | null => {
82
- return createQueueSnapshot(channelNumber, audioChannels);
83
- };
84
-
85
- /**
86
- * Subscribes to real-time progress updates for a specific channel
87
- * @param channelNumber - The channel number
88
- * @param callback - Function to call with audio info updates
89
- * @example
90
- * ```typescript
91
- * onAudioProgress(0, (info) => {
92
- * updateProgressBar(info.progress);
93
- * updateTimeDisplay(info.currentTime, info.duration);
94
- * });
95
- * ```
96
- */
97
- export const onAudioProgress = (channelNumber: number, callback: ProgressCallback): void => {
98
- if (!audioChannels[channelNumber]) {
99
- audioChannels[channelNumber] = {
100
- audioCompleteCallbacks: new Set(),
101
- audioStartCallbacks: new Set(),
102
- progressCallbacks: new Map(),
103
- queue: [],
104
- queueChangeCallbacks: new Set()
105
- };
106
- }
107
-
108
- const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
109
- if (!channel.progressCallbacks) {
110
- channel.progressCallbacks = new Map();
111
- }
112
-
113
- // Add callback for current audio if exists
114
- if (channel.queue.length > 0) {
115
- const currentAudio: HTMLAudioElement = channel.queue[0];
116
- if (!channel.progressCallbacks.has(currentAudio)) {
117
- channel.progressCallbacks.set(currentAudio, new Set());
118
- }
119
- channel.progressCallbacks.get(currentAudio)!.add(callback);
120
-
121
- // Set up tracking if not already done
122
- setupProgressTracking(currentAudio, channelNumber, audioChannels);
123
- }
124
-
125
- // Store callback for future audio elements in this channel
126
- if (!channel.progressCallbacks.has(null as any)) {
127
- channel.progressCallbacks.set(null as any, new Set());
128
- }
129
- channel.progressCallbacks.get(null as any)!.add(callback);
130
- };
131
-
132
- /**
133
- * Removes progress listeners for a specific channel
134
- * @param channelNumber - The channel number
135
- * @example
136
- * ```typescript
137
- * offAudioProgress(0); // Stop receiving progress updates for channel 0
138
- * ```
139
- */
140
- export const offAudioProgress = (channelNumber: number): void => {
141
- const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
142
- if (!channel || !channel.progressCallbacks) return;
143
-
144
- // Clean up event listeners for current audio if exists
145
- if (channel.queue.length > 0) {
146
- const currentAudio: HTMLAudioElement = channel.queue[0];
147
- cleanupProgressTracking(currentAudio, channelNumber, audioChannels);
148
- }
149
-
150
- // Clear all callbacks for this channel
151
- channel.progressCallbacks.clear();
152
- };
153
-
154
- /**
155
- * Subscribes to queue change events for a specific channel
156
- * @param channelNumber - The channel number to monitor
157
- * @param callback - Function to call when queue changes
158
- * @example
159
- * ```typescript
160
- * onQueueChange(0, (snapshot) => {
161
- * updateQueueDisplay(snapshot.items);
162
- * updateQueueCount(snapshot.totalItems);
163
- * });
164
- * ```
165
- */
166
- export const onQueueChange = (channelNumber: number, callback: QueueChangeCallback): void => {
167
- if (!audioChannels[channelNumber]) {
168
- audioChannels[channelNumber] = {
169
- audioCompleteCallbacks: new Set(),
170
- audioStartCallbacks: new Set(),
171
- progressCallbacks: new Map(),
172
- queue: [],
173
- queueChangeCallbacks: new Set()
174
- };
175
- }
176
-
177
- const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
178
- if (!channel.queueChangeCallbacks) {
179
- channel.queueChangeCallbacks = new Set();
180
- }
181
-
182
- channel.queueChangeCallbacks.add(callback);
183
- };
184
-
185
- /**
186
- * Removes queue change listeners for a specific channel
187
- * @param channelNumber - The channel number
188
- * @example
189
- * ```typescript
190
- * offQueueChange(0); // Stop receiving queue change notifications for channel 0
191
- * ```
192
- */
193
- export const offQueueChange = (channelNumber: number): void => {
194
- const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
195
- if (!channel || !channel.queueChangeCallbacks) return;
196
-
197
- channel.queueChangeCallbacks.clear();
198
- };
199
-
200
- /**
201
- * Subscribes to audio start events for a specific channel
202
- * @param channelNumber - The channel number to monitor
203
- * @param callback - Function to call when audio starts playing
204
- * @example
205
- * ```typescript
206
- * onAudioStart(0, (info) => {
207
- * showNowPlaying(info.fileName);
208
- * setTotalDuration(info.duration);
209
- * });
210
- * ```
211
- */
212
- export const onAudioStart = (channelNumber: number, callback: AudioStartCallback): void => {
213
- if (!audioChannels[channelNumber]) {
214
- audioChannels[channelNumber] = {
215
- audioCompleteCallbacks: new Set(),
216
- audioStartCallbacks: new Set(),
217
- progressCallbacks: new Map(),
218
- queue: [],
219
- queueChangeCallbacks: new Set()
220
- };
221
- }
222
-
223
- const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
224
- if (!channel.audioStartCallbacks) {
225
- channel.audioStartCallbacks = new Set();
226
- }
227
-
228
- channel.audioStartCallbacks.add(callback);
229
- };
230
-
231
- /**
232
- * Subscribes to audio complete events for a specific channel
233
- * @param channelNumber - The channel number to monitor
234
- * @param callback - Function to call when audio completes
235
- * @example
236
- * ```typescript
237
- * onAudioComplete(0, (info) => {
238
- * logPlaybackComplete(info.fileName);
239
- * if (info.remainingInQueue === 0) {
240
- * showQueueComplete();
241
- * }
242
- * });
243
- * ```
244
- */
245
- export const onAudioComplete = (channelNumber: number, callback: AudioCompleteCallback): void => {
246
- if (!audioChannels[channelNumber]) {
247
- audioChannels[channelNumber] = {
248
- audioCompleteCallbacks: new Set(),
249
- audioStartCallbacks: new Set(),
250
- progressCallbacks: new Map(),
251
- queue: [],
252
- queueChangeCallbacks: new Set()
253
- };
254
- }
255
-
256
- const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
257
- if (!channel.audioCompleteCallbacks) {
258
- channel.audioCompleteCallbacks = new Set();
259
- }
260
-
261
- channel.audioCompleteCallbacks.add(callback);
1
+ /**
2
+ * @fileoverview Audio information and progress tracking functions for the audio-channel-queue package
3
+ */
4
+
5
+ import {
6
+ AudioInfo,
7
+ QueueSnapshot,
8
+ ProgressCallback,
9
+ QueueChangeCallback,
10
+ AudioStartCallback,
11
+ AudioCompleteCallback,
12
+ AudioPauseCallback,
13
+ AudioResumeCallback,
14
+ ExtendedAudioQueueChannel
15
+ } from './types';
16
+ import { getAudioInfoFromElement, createQueueSnapshot } from './utils';
17
+ import { setupProgressTracking, cleanupProgressTracking } from './events';
18
+
19
+ /**
20
+ * Global array of extended audio queue channels
21
+ */
22
+ export const audioChannels: ExtendedAudioQueueChannel[] = [];
23
+
24
+ /**
25
+ * Gets current audio information for a specific channel
26
+ * @param channelNumber - The channel number (defaults to 0)
27
+ * @returns AudioInfo object or null if no audio is playing
28
+ * @example
29
+ * ```typescript
30
+ * const info = getCurrentAudioInfo(0);
31
+ * if (info) {
32
+ * console.log(`Currently playing: ${info.fileName}`);
33
+ * console.log(`Progress: ${(info.progress * 100).toFixed(1)}%`);
34
+ * }
35
+ * ```
36
+ */
37
+ export const getCurrentAudioInfo = (channelNumber: number = 0): AudioInfo | null => {
38
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
39
+ if (!channel || channel.queue.length === 0) {
40
+ return null;
41
+ }
42
+
43
+ const currentAudio: HTMLAudioElement = channel.queue[0];
44
+ return getAudioInfoFromElement(currentAudio, channelNumber, audioChannels);
45
+ };
46
+
47
+ /**
48
+ * Gets audio information for all channels
49
+ * @returns Array of AudioInfo objects (null for channels with no audio)
50
+ * @example
51
+ * ```typescript
52
+ * const allInfo = getAllChannelsInfo();
53
+ * allInfo.forEach((info, channel) => {
54
+ * if (info) {
55
+ * console.log(`Channel ${channel}: ${info.fileName}`);
56
+ * }
57
+ * });
58
+ * ```
59
+ */
60
+ export const getAllChannelsInfo = (): (AudioInfo | null)[] => {
61
+ const allChannelsInfo: (AudioInfo | null)[] = [];
62
+
63
+ for (let i = 0; i < audioChannels.length; i++) {
64
+ allChannelsInfo.push(getCurrentAudioInfo(i));
65
+ }
66
+
67
+ return allChannelsInfo;
68
+ };
69
+
70
+ /**
71
+ * Gets a complete snapshot of the queue state for a specific channel
72
+ * @param channelNumber - The channel number
73
+ * @returns QueueSnapshot object or null if channel doesn't exist
74
+ * @example
75
+ * ```typescript
76
+ * const snapshot = getQueueSnapshot(0);
77
+ * if (snapshot) {
78
+ * console.log(`Queue has ${snapshot.totalItems} items`);
79
+ * console.log(`Currently playing: ${snapshot.items[0]?.fileName}`);
80
+ * }
81
+ * ```
82
+ */
83
+ export const getQueueSnapshot = (channelNumber: number): QueueSnapshot | null => {
84
+ return createQueueSnapshot(channelNumber, audioChannels);
85
+ };
86
+
87
+ /**
88
+ * Subscribes to real-time progress updates for a specific channel
89
+ * @param channelNumber - The channel number
90
+ * @param callback - Function to call with audio info updates
91
+ * @example
92
+ * ```typescript
93
+ * onAudioProgress(0, (info) => {
94
+ * updateProgressBar(info.progress);
95
+ * updateTimeDisplay(info.currentTime, info.duration);
96
+ * });
97
+ * ```
98
+ */
99
+ export const onAudioProgress = (channelNumber: number, callback: ProgressCallback): void => {
100
+ if (!audioChannels[channelNumber]) {
101
+ audioChannels[channelNumber] = {
102
+ audioCompleteCallbacks: new Set(),
103
+ audioPauseCallbacks: new Set(),
104
+ audioResumeCallbacks: new Set(),
105
+ audioStartCallbacks: new Set(),
106
+ isPaused: false,
107
+ progressCallbacks: new Map(),
108
+ queue: [],
109
+ queueChangeCallbacks: new Set(),
110
+ volume: 1.0
111
+ };
112
+ }
113
+
114
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
115
+ if (!channel.progressCallbacks) {
116
+ channel.progressCallbacks = new Map();
117
+ }
118
+
119
+ // Add callback for current audio if exists
120
+ if (channel.queue.length > 0) {
121
+ const currentAudio: HTMLAudioElement = channel.queue[0];
122
+ if (!channel.progressCallbacks.has(currentAudio)) {
123
+ channel.progressCallbacks.set(currentAudio, new Set());
124
+ }
125
+ channel.progressCallbacks.get(currentAudio)!.add(callback);
126
+
127
+ // Set up tracking if not already done
128
+ setupProgressTracking(currentAudio, channelNumber, audioChannels);
129
+ }
130
+
131
+ // Store callback for future audio elements in this channel
132
+ if (!channel.progressCallbacks.has(null as any)) {
133
+ channel.progressCallbacks.set(null as any, new Set());
134
+ }
135
+ channel.progressCallbacks.get(null as any)!.add(callback);
136
+ };
137
+
138
+ /**
139
+ * Removes progress listeners for a specific channel
140
+ * @param channelNumber - The channel number
141
+ * @example
142
+ * ```typescript
143
+ * offAudioProgress(0); // Stop receiving progress updates for channel 0
144
+ * ```
145
+ */
146
+ export const offAudioProgress = (channelNumber: number): void => {
147
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
148
+ if (!channel || !channel.progressCallbacks) return;
149
+
150
+ // Clean up event listeners for current audio if exists
151
+ if (channel.queue.length > 0) {
152
+ const currentAudio: HTMLAudioElement = channel.queue[0];
153
+ cleanupProgressTracking(currentAudio, channelNumber, audioChannels);
154
+ }
155
+
156
+ // Clear all callbacks for this channel
157
+ channel.progressCallbacks.clear();
158
+ };
159
+
160
+ /**
161
+ * Subscribes to queue change events for a specific channel
162
+ * @param channelNumber - The channel number to monitor
163
+ * @param callback - Function to call when queue changes
164
+ * @example
165
+ * ```typescript
166
+ * onQueueChange(0, (snapshot) => {
167
+ * updateQueueDisplay(snapshot.items);
168
+ * updateQueueCount(snapshot.totalItems);
169
+ * });
170
+ * ```
171
+ */
172
+ export const onQueueChange = (channelNumber: number, callback: QueueChangeCallback): void => {
173
+ if (!audioChannels[channelNumber]) {
174
+ audioChannels[channelNumber] = {
175
+ audioCompleteCallbacks: new Set(),
176
+ audioPauseCallbacks: new Set(),
177
+ audioResumeCallbacks: new Set(),
178
+ audioStartCallbacks: new Set(),
179
+ isPaused: false,
180
+ progressCallbacks: new Map(),
181
+ queue: [],
182
+ queueChangeCallbacks: new Set(),
183
+ volume: 1.0
184
+ };
185
+ }
186
+
187
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
188
+ if (!channel.queueChangeCallbacks) {
189
+ channel.queueChangeCallbacks = new Set();
190
+ }
191
+
192
+ channel.queueChangeCallbacks.add(callback);
193
+ };
194
+
195
+ /**
196
+ * Removes queue change listeners for a specific channel
197
+ * @param channelNumber - The channel number
198
+ * @example
199
+ * ```typescript
200
+ * offQueueChange(0); // Stop receiving queue change notifications for channel 0
201
+ * ```
202
+ */
203
+ export const offQueueChange = (channelNumber: number): void => {
204
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
205
+ if (!channel || !channel.queueChangeCallbacks) return;
206
+
207
+ channel.queueChangeCallbacks.clear();
208
+ };
209
+
210
+ /**
211
+ * Subscribes to audio start events for a specific channel
212
+ * @param channelNumber - The channel number to monitor
213
+ * @param callback - Function to call when audio starts playing
214
+ * @example
215
+ * ```typescript
216
+ * onAudioStart(0, (info) => {
217
+ * showNowPlaying(info.fileName);
218
+ * setTotalDuration(info.duration);
219
+ * });
220
+ * ```
221
+ */
222
+ export const onAudioStart = (channelNumber: number, callback: AudioStartCallback): void => {
223
+ if (!audioChannels[channelNumber]) {
224
+ audioChannels[channelNumber] = {
225
+ audioCompleteCallbacks: new Set(),
226
+ audioPauseCallbacks: new Set(),
227
+ audioResumeCallbacks: new Set(),
228
+ audioStartCallbacks: new Set(),
229
+ isPaused: false,
230
+ progressCallbacks: new Map(),
231
+ queue: [],
232
+ queueChangeCallbacks: new Set(),
233
+ volume: 1.0
234
+ };
235
+ }
236
+
237
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
238
+ if (!channel.audioStartCallbacks) {
239
+ channel.audioStartCallbacks = new Set();
240
+ }
241
+
242
+ channel.audioStartCallbacks.add(callback);
243
+ };
244
+
245
+ /**
246
+ * Subscribes to audio complete events for a specific channel
247
+ * @param channelNumber - The channel number to monitor
248
+ * @param callback - Function to call when audio completes
249
+ * @example
250
+ * ```typescript
251
+ * onAudioComplete(0, (info) => {
252
+ * logPlaybackComplete(info.fileName);
253
+ * if (info.remainingInQueue === 0) {
254
+ * showQueueComplete();
255
+ * }
256
+ * });
257
+ * ```
258
+ */
259
+ export const onAudioComplete = (channelNumber: number, callback: AudioCompleteCallback): void => {
260
+ if (!audioChannels[channelNumber]) {
261
+ audioChannels[channelNumber] = {
262
+ audioCompleteCallbacks: new Set(),
263
+ audioPauseCallbacks: new Set(),
264
+ audioResumeCallbacks: new Set(),
265
+ audioStartCallbacks: new Set(),
266
+ isPaused: false,
267
+ progressCallbacks: new Map(),
268
+ queue: [],
269
+ queueChangeCallbacks: new Set(),
270
+ volume: 1.0
271
+ };
272
+ }
273
+
274
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
275
+ if (!channel.audioCompleteCallbacks) {
276
+ channel.audioCompleteCallbacks = new Set();
277
+ }
278
+
279
+ channel.audioCompleteCallbacks.add(callback);
280
+ };
281
+
282
+ /**
283
+ * Subscribes to audio pause events for a specific channel
284
+ * @param channelNumber - The channel number to monitor
285
+ * @param callback - Function to call when audio is paused
286
+ * @example
287
+ * ```typescript
288
+ * onAudioPause(0, (channelNumber, info) => {
289
+ * showPauseIndicator();
290
+ * logPauseEvent(info.fileName, info.currentTime);
291
+ * });
292
+ * ```
293
+ */
294
+ export const onAudioPause = (channelNumber: number, callback: AudioPauseCallback): void => {
295
+ if (!audioChannels[channelNumber]) {
296
+ audioChannels[channelNumber] = {
297
+ audioCompleteCallbacks: new Set(),
298
+ audioPauseCallbacks: new Set(),
299
+ audioResumeCallbacks: new Set(),
300
+ audioStartCallbacks: new Set(),
301
+ isPaused: false,
302
+ progressCallbacks: new Map(),
303
+ queue: [],
304
+ queueChangeCallbacks: new Set(),
305
+ volume: 1.0
306
+ };
307
+ }
308
+
309
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
310
+ if (!channel.audioPauseCallbacks) {
311
+ channel.audioPauseCallbacks = new Set();
312
+ }
313
+
314
+ channel.audioPauseCallbacks.add(callback);
315
+ };
316
+
317
+ /**
318
+ * Subscribes to audio resume events for a specific channel
319
+ * @param channelNumber - The channel number to monitor
320
+ * @param callback - Function to call when audio is resumed
321
+ * @example
322
+ * ```typescript
323
+ * onAudioResume(0, (channelNumber, info) => {
324
+ * hidePauseIndicator();
325
+ * logResumeEvent(info.fileName, info.currentTime);
326
+ * });
327
+ * ```
328
+ */
329
+ export const onAudioResume = (channelNumber: number, callback: AudioResumeCallback): void => {
330
+ if (!audioChannels[channelNumber]) {
331
+ audioChannels[channelNumber] = {
332
+ audioCompleteCallbacks: new Set(),
333
+ audioPauseCallbacks: new Set(),
334
+ audioResumeCallbacks: new Set(),
335
+ audioStartCallbacks: new Set(),
336
+ isPaused: false,
337
+ progressCallbacks: new Map(),
338
+ queue: [],
339
+ queueChangeCallbacks: new Set(),
340
+ volume: 1.0
341
+ };
342
+ }
343
+
344
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
345
+ if (!channel.audioResumeCallbacks) {
346
+ channel.audioResumeCallbacks = new Set();
347
+ }
348
+
349
+ channel.audioResumeCallbacks.add(callback);
350
+ };
351
+
352
+ /**
353
+ * Removes pause event listeners for a specific channel
354
+ * @param channelNumber - The channel number
355
+ * @example
356
+ * ```typescript
357
+ * offAudioPause(0); // Stop receiving pause notifications for channel 0
358
+ * ```
359
+ */
360
+ export const offAudioPause = (channelNumber: number): void => {
361
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
362
+ if (!channel || !channel.audioPauseCallbacks) return;
363
+
364
+ channel.audioPauseCallbacks.clear();
365
+ };
366
+
367
+ /**
368
+ * Removes resume event listeners for a specific channel
369
+ * @param channelNumber - The channel number
370
+ * @example
371
+ * ```typescript
372
+ * offAudioResume(0); // Stop receiving resume notifications for channel 0
373
+ * ```
374
+ */
375
+ export const offAudioResume = (channelNumber: number): void => {
376
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
377
+ if (!channel || !channel.audioResumeCallbacks) return;
378
+
379
+ channel.audioResumeCallbacks.clear();
262
380
  };