audioq 2.0.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/LICENSE +21 -0
- package/README.md +486 -0
- package/dist/core.d.ts +129 -0
- package/dist/core.js +591 -0
- package/dist/errors.d.ts +138 -0
- package/dist/errors.js +441 -0
- package/dist/events.d.ts +81 -0
- package/dist/events.js +217 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +119 -0
- package/dist/info.d.ts +224 -0
- package/dist/info.js +529 -0
- package/dist/pause.d.ts +170 -0
- package/dist/pause.js +467 -0
- package/dist/queue-manipulation.d.ts +104 -0
- package/dist/queue-manipulation.js +319 -0
- package/dist/types.d.ts +382 -0
- package/dist/types.js +55 -0
- package/dist/utils.d.ts +83 -0
- package/dist/utils.js +215 -0
- package/dist/volume.d.ts +162 -0
- package/dist/volume.js +644 -0
- package/dist/web-audio.d.ts +156 -0
- package/dist/web-audio.js +327 -0
- package/package.json +63 -0
- package/src/core.ts +698 -0
- package/src/errors.ts +467 -0
- package/src/events.ts +252 -0
- package/src/index.ts +162 -0
- package/src/info.ts +590 -0
- package/src/pause.ts +523 -0
- package/src/queue-manipulation.ts +378 -0
- package/src/types.ts +415 -0
- package/src/utils.ts +235 -0
- package/src/volume.ts +735 -0
- package/src/web-audio.ts +331 -0
package/src/info.ts
ADDED
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Audio information and progress tracking functions for the audioq package
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
AudioInfo,
|
|
7
|
+
QueueSnapshot,
|
|
8
|
+
ProgressCallback,
|
|
9
|
+
QueueChangeCallback,
|
|
10
|
+
AudioStartCallback,
|
|
11
|
+
AudioCompleteCallback,
|
|
12
|
+
AudioPauseCallback,
|
|
13
|
+
AudioResumeCallback,
|
|
14
|
+
ExtendedAudioQueueChannel,
|
|
15
|
+
GLOBAL_PROGRESS_KEY,
|
|
16
|
+
MAX_CHANNELS
|
|
17
|
+
} from './types';
|
|
18
|
+
import { getAudioInfoFromElement, createQueueSnapshot } from './utils';
|
|
19
|
+
import { setupProgressTracking, cleanupProgressTracking } from './events';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Gets the current list of whitelisted channel properties
|
|
23
|
+
* This is automatically derived from the ExtendedAudioQueueChannel interface
|
|
24
|
+
* @returns Array of whitelisted property names
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export const getWhitelistedChannelProperties = (): string[] => {
|
|
28
|
+
// Create a sample channel object to extract property names
|
|
29
|
+
const sampleChannel: ExtendedAudioQueueChannel = {
|
|
30
|
+
audioCompleteCallbacks: new Set(),
|
|
31
|
+
audioErrorCallbacks: new Set(),
|
|
32
|
+
audioPauseCallbacks: new Set(),
|
|
33
|
+
audioResumeCallbacks: new Set(),
|
|
34
|
+
audioStartCallbacks: new Set(),
|
|
35
|
+
isPaused: false,
|
|
36
|
+
progressCallbacks: new Map(),
|
|
37
|
+
queue: [],
|
|
38
|
+
queueChangeCallbacks: new Set(),
|
|
39
|
+
volume: 1.0
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Get all property names from the interface (including optional ones)
|
|
43
|
+
const propertyNames = [
|
|
44
|
+
...Object.getOwnPropertyNames(sampleChannel),
|
|
45
|
+
// Add optional properties that might not be present on the sample
|
|
46
|
+
'fadeState',
|
|
47
|
+
'isLocked',
|
|
48
|
+
'maxQueueSize',
|
|
49
|
+
'retryConfig',
|
|
50
|
+
'volumeConfig', // Legacy property that might still be used
|
|
51
|
+
'webAudioContext', // Web Audio API context
|
|
52
|
+
'webAudioNodes' // Web Audio API nodes map
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
return [...new Set(propertyNames)]; // Remove duplicates
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Returns the list of non-whitelisted properties found on a specific channel
|
|
60
|
+
* These are properties that will trigger warnings when modified directly
|
|
61
|
+
* @param channelNumber - The channel number to inspect (defaults to 0)
|
|
62
|
+
* @returns Array of property names that are not in the whitelist, or empty array if channel doesn't exist
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* // Add some custom property to a channel
|
|
66
|
+
* (audioChannels[0] as any).customProperty = 'test';
|
|
67
|
+
*
|
|
68
|
+
* const nonWhitelisted = getNonWhitelistedChannelProperties(0);
|
|
69
|
+
* console.log(nonWhitelisted); // ['customProperty']
|
|
70
|
+
* ```
|
|
71
|
+
* @internal
|
|
72
|
+
*/
|
|
73
|
+
export const getNonWhitelistedChannelProperties = (channelNumber: number = 0): string[] => {
|
|
74
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
75
|
+
if (!channel) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const whitelistedProperties: string[] = getWhitelistedChannelProperties();
|
|
80
|
+
const allChannelProperties: string[] = Object.getOwnPropertyNames(channel);
|
|
81
|
+
|
|
82
|
+
// Filter out properties that are in the whitelist
|
|
83
|
+
const nonWhitelistedProperties: string[] = allChannelProperties.filter(
|
|
84
|
+
(property: string) => !whitelistedProperties.includes(property)
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return nonWhitelistedProperties;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Global array to store audio channels with their queues and callback management
|
|
92
|
+
* Each channel maintains its own audio queue and event callback sets
|
|
93
|
+
*
|
|
94
|
+
* Note: While you can inspect this array for debugging, direct modification is discouraged.
|
|
95
|
+
* Use the provided API functions for safe channel management.
|
|
96
|
+
*/
|
|
97
|
+
export const audioChannels: ExtendedAudioQueueChannel[] = new Proxy(
|
|
98
|
+
[] as ExtendedAudioQueueChannel[],
|
|
99
|
+
{
|
|
100
|
+
deleteProperty(target: ExtendedAudioQueueChannel[], prop: string | symbol): boolean {
|
|
101
|
+
if (typeof prop === 'string' && !isNaN(Number(prop))) {
|
|
102
|
+
// eslint-disable-next-line no-console
|
|
103
|
+
console.warn(
|
|
104
|
+
'Warning: Direct deletion from audioChannels detected. ' +
|
|
105
|
+
'Consider using stopAllAudioInChannel() for proper cleanup.'
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
delete (target as unknown as Record<string, unknown>)[prop as string];
|
|
109
|
+
return true;
|
|
110
|
+
},
|
|
111
|
+
get(target: ExtendedAudioQueueChannel[], prop: string | symbol): unknown {
|
|
112
|
+
const value = (target as unknown as Record<string, unknown>)[prop as string];
|
|
113
|
+
|
|
114
|
+
// Return channel objects with warnings on modification attempts
|
|
115
|
+
if (
|
|
116
|
+
typeof value === 'object' &&
|
|
117
|
+
value !== null &&
|
|
118
|
+
typeof prop === 'string' &&
|
|
119
|
+
!isNaN(Number(prop))
|
|
120
|
+
) {
|
|
121
|
+
return new Proxy(value as ExtendedAudioQueueChannel, {
|
|
122
|
+
set(
|
|
123
|
+
channelTarget: ExtendedAudioQueueChannel,
|
|
124
|
+
channelProp: string | symbol,
|
|
125
|
+
channelValue: unknown
|
|
126
|
+
): boolean {
|
|
127
|
+
// Allow internal modifications but warn about direct property changes
|
|
128
|
+
// Use the automatically-derived whitelist from the interface
|
|
129
|
+
const whitelistedProperties = getWhitelistedChannelProperties();
|
|
130
|
+
|
|
131
|
+
if (typeof channelProp === 'string' && !whitelistedProperties.includes(channelProp)) {
|
|
132
|
+
// eslint-disable-next-line no-console
|
|
133
|
+
console.warn(
|
|
134
|
+
`Warning: Direct modification of channel.${channelProp} detected. ` +
|
|
135
|
+
'Use API functions for safer channel management.'
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
const key = typeof channelProp === 'symbol' ? channelProp.toString() : channelProp;
|
|
139
|
+
(channelTarget as unknown as Record<string, unknown>)[key] = channelValue;
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return value;
|
|
146
|
+
},
|
|
147
|
+
set(target: ExtendedAudioQueueChannel[], prop: string | symbol, value: unknown): boolean {
|
|
148
|
+
// Allow normal array operations
|
|
149
|
+
const key = typeof prop === 'symbol' ? prop.toString() : prop;
|
|
150
|
+
(target as unknown as Record<string, unknown>)[key] = value;
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Validates a channel number against MAX_CHANNELS limit
|
|
158
|
+
* @param channelNumber - The channel number to validate
|
|
159
|
+
* @throws Error if the channel number is invalid
|
|
160
|
+
* @internal
|
|
161
|
+
*/
|
|
162
|
+
const validateChannelNumber = (channelNumber: number): void => {
|
|
163
|
+
if (channelNumber < 0) {
|
|
164
|
+
throw new Error('Channel number must be non-negative');
|
|
165
|
+
}
|
|
166
|
+
if (channelNumber >= MAX_CHANNELS) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`Channel number ${channelNumber} exceeds maximum allowed channels (${MAX_CHANNELS})`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Gets current audio information for a specific channel
|
|
175
|
+
* @param channelNumber - The channel number (defaults to 0)
|
|
176
|
+
* @returns AudioInfo object or null if no audio is playing
|
|
177
|
+
* @example
|
|
178
|
+
* ```typescript
|
|
179
|
+
* const info = getCurrentAudioInfo(0);
|
|
180
|
+
* if (info) {
|
|
181
|
+
* console.log(`Currently playing: ${info.fileName}`);
|
|
182
|
+
* console.log(`Progress: ${(info.progress * 100).toFixed(1)}%`);
|
|
183
|
+
* }
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
export const getCurrentAudioInfo = (channelNumber: number = 0): AudioInfo | null => {
|
|
187
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
188
|
+
if (!channel || channel.queue.length === 0) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const currentAudio: HTMLAudioElement = channel.queue[0];
|
|
193
|
+
return getAudioInfoFromElement(currentAudio, channelNumber, audioChannels);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Gets audio information for all channels
|
|
198
|
+
* @returns Array of AudioInfo objects (null for channels with no audio)
|
|
199
|
+
* @example
|
|
200
|
+
* ```typescript
|
|
201
|
+
* const allInfo = getAllChannelsInfo();
|
|
202
|
+
* allInfo.forEach((info, channel) => {
|
|
203
|
+
* if (info) {
|
|
204
|
+
* console.log(`Channel ${channel}: ${info.fileName}`);
|
|
205
|
+
* }
|
|
206
|
+
* });
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
export const getAllChannelsInfo = (): (AudioInfo | null)[] => {
|
|
210
|
+
const allChannelsInfo: (AudioInfo | null)[] = [];
|
|
211
|
+
|
|
212
|
+
for (let i: number = 0; i < audioChannels.length; i++) {
|
|
213
|
+
allChannelsInfo.push(getCurrentAudioInfo(i));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return allChannelsInfo;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Gets a complete snapshot of the queue state for a specific channel
|
|
221
|
+
* @param channelNumber - The channel number (defaults to 0)
|
|
222
|
+
* @returns QueueSnapshot object or null if channel doesn't exist
|
|
223
|
+
* @example
|
|
224
|
+
* ```typescript
|
|
225
|
+
* const snapshot = getQueueSnapshot();
|
|
226
|
+
* if (snapshot) {
|
|
227
|
+
* console.log(`Queue has ${snapshot.totalItems} items`);
|
|
228
|
+
* console.log(`Currently playing: ${snapshot.items[0]?.fileName}`);
|
|
229
|
+
* }
|
|
230
|
+
* const channelSnapshot = getQueueSnapshot(2);
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
export const getQueueSnapshot = (channelNumber: number = 0): QueueSnapshot | null => {
|
|
234
|
+
return createQueueSnapshot(channelNumber, audioChannels);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Subscribes to real-time progress updates for a specific channel
|
|
239
|
+
* @param channelNumber - The channel number
|
|
240
|
+
* @param callback - Function to call with audio info updates
|
|
241
|
+
* @throws Error if the channel number exceeds the maximum allowed channels
|
|
242
|
+
* @example
|
|
243
|
+
* ```typescript
|
|
244
|
+
* onAudioProgress(0, (info) => {
|
|
245
|
+
* updateProgressBar(info.progress);
|
|
246
|
+
* updateTimeDisplay(info.currentTime, info.duration);
|
|
247
|
+
* });
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
export const onAudioProgress = (channelNumber: number, callback: ProgressCallback): void => {
|
|
251
|
+
validateChannelNumber(channelNumber);
|
|
252
|
+
|
|
253
|
+
if (!audioChannels[channelNumber]) {
|
|
254
|
+
audioChannels[channelNumber] = {
|
|
255
|
+
audioCompleteCallbacks: new Set(),
|
|
256
|
+
audioErrorCallbacks: new Set(),
|
|
257
|
+
audioPauseCallbacks: new Set(),
|
|
258
|
+
audioResumeCallbacks: new Set(),
|
|
259
|
+
audioStartCallbacks: new Set(),
|
|
260
|
+
isPaused: false,
|
|
261
|
+
progressCallbacks: new Map(),
|
|
262
|
+
queue: [],
|
|
263
|
+
queueChangeCallbacks: new Set(),
|
|
264
|
+
volume: 1.0
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
269
|
+
if (!channel.progressCallbacks) {
|
|
270
|
+
channel.progressCallbacks = new Map();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Add callback for current audio if exists
|
|
274
|
+
if (channel.queue.length > 0) {
|
|
275
|
+
const currentAudio: HTMLAudioElement = channel.queue[0];
|
|
276
|
+
if (!channel.progressCallbacks.has(currentAudio)) {
|
|
277
|
+
channel.progressCallbacks.set(currentAudio, new Set());
|
|
278
|
+
}
|
|
279
|
+
channel.progressCallbacks.get(currentAudio)!.add(callback);
|
|
280
|
+
|
|
281
|
+
// Set up tracking if not already done
|
|
282
|
+
setupProgressTracking(currentAudio, channelNumber, audioChannels);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Store callback for future audio elements in this channel
|
|
286
|
+
if (!channel.progressCallbacks.has(GLOBAL_PROGRESS_KEY)) {
|
|
287
|
+
channel.progressCallbacks.set(GLOBAL_PROGRESS_KEY, new Set());
|
|
288
|
+
}
|
|
289
|
+
channel.progressCallbacks.get(GLOBAL_PROGRESS_KEY)!.add(callback);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Removes progress listeners for a specific channel
|
|
294
|
+
* @param channelNumber - The channel number (defaults to 0)
|
|
295
|
+
* @example
|
|
296
|
+
* ```typescript
|
|
297
|
+
* offAudioProgress();
|
|
298
|
+
* offAudioProgress(1); // Stop receiving progress updates for channel 1
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
export function offAudioProgress(channelNumber: number = 0): void {
|
|
302
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
303
|
+
if (!channel?.progressCallbacks) return;
|
|
304
|
+
|
|
305
|
+
// Clean up event listeners for current audio if exists
|
|
306
|
+
if (channel.queue.length > 0) {
|
|
307
|
+
const currentAudio: HTMLAudioElement = channel.queue[0];
|
|
308
|
+
cleanupProgressTracking(currentAudio, channelNumber, audioChannels);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Clear all callbacks for this channel
|
|
312
|
+
channel.progressCallbacks.clear();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Subscribes to queue change events for a specific channel
|
|
317
|
+
* @param channelNumber - The channel number to monitor
|
|
318
|
+
* @param callback - Function to call when queue changes
|
|
319
|
+
* @throws Error if the channel number exceeds the maximum allowed channels
|
|
320
|
+
* @example
|
|
321
|
+
* ```typescript
|
|
322
|
+
* onQueueChange(0, (snapshot) => {
|
|
323
|
+
* updateQueueDisplay(snapshot.items);
|
|
324
|
+
* updateQueueCount(snapshot.totalItems);
|
|
325
|
+
* });
|
|
326
|
+
* ```
|
|
327
|
+
*/
|
|
328
|
+
export const onQueueChange = (channelNumber: number, callback: QueueChangeCallback): void => {
|
|
329
|
+
validateChannelNumber(channelNumber);
|
|
330
|
+
|
|
331
|
+
if (!audioChannels[channelNumber]) {
|
|
332
|
+
audioChannels[channelNumber] = {
|
|
333
|
+
audioCompleteCallbacks: new Set(),
|
|
334
|
+
audioErrorCallbacks: new Set(),
|
|
335
|
+
audioPauseCallbacks: new Set(),
|
|
336
|
+
audioResumeCallbacks: new Set(),
|
|
337
|
+
audioStartCallbacks: new Set(),
|
|
338
|
+
isPaused: false,
|
|
339
|
+
progressCallbacks: new Map(),
|
|
340
|
+
queue: [],
|
|
341
|
+
queueChangeCallbacks: new Set(),
|
|
342
|
+
volume: 1.0
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
347
|
+
if (!channel.queueChangeCallbacks) {
|
|
348
|
+
channel.queueChangeCallbacks = new Set();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
channel.queueChangeCallbacks.add(callback);
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Removes queue change listeners for a specific channel
|
|
356
|
+
* @param channelNumber - The channel number (defaults to 0)
|
|
357
|
+
* @example
|
|
358
|
+
* ```typescript
|
|
359
|
+
* offQueueChange(); // Stop receiving queue change notifications for default channel (0)
|
|
360
|
+
* offQueueChange(1); // Stop receiving queue change notifications for channel 1
|
|
361
|
+
* ```
|
|
362
|
+
*/
|
|
363
|
+
export const offQueueChange = (channelNumber: number = 0): void => {
|
|
364
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
365
|
+
if (!channel?.queueChangeCallbacks) return;
|
|
366
|
+
|
|
367
|
+
channel.queueChangeCallbacks.clear();
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Subscribes to audio start events for a specific channel
|
|
372
|
+
* @param channelNumber - The channel number to monitor
|
|
373
|
+
* @param callback - Function to call when audio starts playing
|
|
374
|
+
* @throws Error if the channel number exceeds the maximum allowed channels
|
|
375
|
+
* @example
|
|
376
|
+
* ```typescript
|
|
377
|
+
* onAudioStart(0, (info) => {
|
|
378
|
+
* showNowPlaying(info.fileName);
|
|
379
|
+
* setTotalDuration(info.duration);
|
|
380
|
+
* });
|
|
381
|
+
* ```
|
|
382
|
+
*/
|
|
383
|
+
export const onAudioStart = (channelNumber: number, callback: AudioStartCallback): void => {
|
|
384
|
+
validateChannelNumber(channelNumber);
|
|
385
|
+
|
|
386
|
+
if (!audioChannels[channelNumber]) {
|
|
387
|
+
audioChannels[channelNumber] = {
|
|
388
|
+
audioCompleteCallbacks: new Set(),
|
|
389
|
+
audioErrorCallbacks: new Set(),
|
|
390
|
+
audioPauseCallbacks: new Set(),
|
|
391
|
+
audioResumeCallbacks: new Set(),
|
|
392
|
+
audioStartCallbacks: new Set(),
|
|
393
|
+
isPaused: false,
|
|
394
|
+
progressCallbacks: new Map(),
|
|
395
|
+
queue: [],
|
|
396
|
+
queueChangeCallbacks: new Set(),
|
|
397
|
+
volume: 1.0
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
402
|
+
if (!channel.audioStartCallbacks) {
|
|
403
|
+
channel.audioStartCallbacks = new Set();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
channel.audioStartCallbacks.add(callback);
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Subscribes to audio complete events for a specific channel
|
|
411
|
+
* @param channelNumber - The channel number to monitor
|
|
412
|
+
* @param callback - Function to call when audio completes
|
|
413
|
+
* @throws Error if the channel number exceeds the maximum allowed channels
|
|
414
|
+
* @example
|
|
415
|
+
* ```typescript
|
|
416
|
+
* onAudioComplete(0, (info) => {
|
|
417
|
+
* logPlaybackComplete(info.fileName);
|
|
418
|
+
* if (info.remainingInQueue === 0) {
|
|
419
|
+
* showQueueComplete();
|
|
420
|
+
* }
|
|
421
|
+
* });
|
|
422
|
+
* ```
|
|
423
|
+
*/
|
|
424
|
+
export const onAudioComplete = (channelNumber: number, callback: AudioCompleteCallback): void => {
|
|
425
|
+
validateChannelNumber(channelNumber);
|
|
426
|
+
|
|
427
|
+
if (!audioChannels[channelNumber]) {
|
|
428
|
+
audioChannels[channelNumber] = {
|
|
429
|
+
audioCompleteCallbacks: new Set(),
|
|
430
|
+
audioErrorCallbacks: new Set(),
|
|
431
|
+
audioPauseCallbacks: new Set(),
|
|
432
|
+
audioResumeCallbacks: new Set(),
|
|
433
|
+
audioStartCallbacks: new Set(),
|
|
434
|
+
isPaused: false,
|
|
435
|
+
progressCallbacks: new Map(),
|
|
436
|
+
queue: [],
|
|
437
|
+
queueChangeCallbacks: new Set(),
|
|
438
|
+
volume: 1.0
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
443
|
+
if (!channel.audioCompleteCallbacks) {
|
|
444
|
+
channel.audioCompleteCallbacks = new Set();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
channel.audioCompleteCallbacks.add(callback);
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Subscribes to audio pause events for a specific channel
|
|
452
|
+
* @param channelNumber - The channel number to monitor
|
|
453
|
+
* @param callback - Function to call when audio is paused
|
|
454
|
+
* @throws Error if the channel number exceeds the maximum allowed channels
|
|
455
|
+
* @example
|
|
456
|
+
* ```typescript
|
|
457
|
+
* onAudioPause(0, (channelNumber, info) => {
|
|
458
|
+
* showPauseIndicator();
|
|
459
|
+
* logPauseEvent(info.fileName, info.currentTime);
|
|
460
|
+
* });
|
|
461
|
+
* ```
|
|
462
|
+
*/
|
|
463
|
+
export const onAudioPause = (channelNumber: number, callback: AudioPauseCallback): void => {
|
|
464
|
+
validateChannelNumber(channelNumber);
|
|
465
|
+
|
|
466
|
+
if (!audioChannels[channelNumber]) {
|
|
467
|
+
audioChannels[channelNumber] = {
|
|
468
|
+
audioCompleteCallbacks: new Set(),
|
|
469
|
+
audioErrorCallbacks: new Set(),
|
|
470
|
+
audioPauseCallbacks: new Set(),
|
|
471
|
+
audioResumeCallbacks: new Set(),
|
|
472
|
+
audioStartCallbacks: new Set(),
|
|
473
|
+
isPaused: false,
|
|
474
|
+
progressCallbacks: new Map(),
|
|
475
|
+
queue: [],
|
|
476
|
+
queueChangeCallbacks: new Set(),
|
|
477
|
+
volume: 1.0
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
482
|
+
if (!channel.audioPauseCallbacks) {
|
|
483
|
+
channel.audioPauseCallbacks = new Set();
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
channel.audioPauseCallbacks.add(callback);
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Subscribes to audio resume events for a specific channel
|
|
491
|
+
* @param channelNumber - The channel number to monitor
|
|
492
|
+
* @param callback - Function to call when audio is resumed
|
|
493
|
+
* @throws Error if the channel number exceeds the maximum allowed channels
|
|
494
|
+
* @example
|
|
495
|
+
* ```typescript
|
|
496
|
+
* onAudioResume(0, (channelNumber, info) => {
|
|
497
|
+
* hidePauseIndicator();
|
|
498
|
+
* logResumeEvent(info.fileName, info.currentTime);
|
|
499
|
+
* });
|
|
500
|
+
* ```
|
|
501
|
+
*/
|
|
502
|
+
export const onAudioResume = (channelNumber: number, callback: AudioResumeCallback): void => {
|
|
503
|
+
validateChannelNumber(channelNumber);
|
|
504
|
+
|
|
505
|
+
if (!audioChannels[channelNumber]) {
|
|
506
|
+
audioChannels[channelNumber] = {
|
|
507
|
+
audioCompleteCallbacks: new Set(),
|
|
508
|
+
audioErrorCallbacks: new Set(),
|
|
509
|
+
audioPauseCallbacks: new Set(),
|
|
510
|
+
audioResumeCallbacks: new Set(),
|
|
511
|
+
audioStartCallbacks: new Set(),
|
|
512
|
+
isPaused: false,
|
|
513
|
+
progressCallbacks: new Map(),
|
|
514
|
+
queue: [],
|
|
515
|
+
queueChangeCallbacks: new Set(),
|
|
516
|
+
volume: 1.0
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
521
|
+
if (!channel.audioResumeCallbacks) {
|
|
522
|
+
channel.audioResumeCallbacks = new Set();
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
channel.audioResumeCallbacks.add(callback);
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Removes pause event listeners for a specific channel
|
|
530
|
+
* @param channelNumber - The channel number (defaults to 0)
|
|
531
|
+
* @example
|
|
532
|
+
* ```typescript
|
|
533
|
+
* offAudioPause(); // Stop receiving pause notifications for default channel (0)
|
|
534
|
+
* offAudioPause(1); // Stop receiving pause notifications for channel 1
|
|
535
|
+
* ```
|
|
536
|
+
*/
|
|
537
|
+
export const offAudioPause = (channelNumber: number = 0): void => {
|
|
538
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
539
|
+
if (!channel?.audioPauseCallbacks) return;
|
|
540
|
+
|
|
541
|
+
channel.audioPauseCallbacks.clear();
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Removes resume event listeners for a specific channel
|
|
546
|
+
* @param channelNumber - The channel number (defaults to 0)
|
|
547
|
+
* @example
|
|
548
|
+
* ```typescript
|
|
549
|
+
* offAudioResume(); // Stop receiving resume notifications for default channel (0)
|
|
550
|
+
* offAudioResume(1); // Stop receiving resume notifications for channel 1
|
|
551
|
+
* ```
|
|
552
|
+
*/
|
|
553
|
+
export const offAudioResume = (channelNumber: number = 0): void => {
|
|
554
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
555
|
+
if (!channel?.audioResumeCallbacks) return;
|
|
556
|
+
|
|
557
|
+
channel.audioResumeCallbacks.clear();
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Removes audio start event listeners for a specific channel
|
|
562
|
+
* @param channelNumber - The channel number (defaults to 0)
|
|
563
|
+
* @example
|
|
564
|
+
* ```typescript
|
|
565
|
+
* offAudioStart(); // Stop receiving start notifications for default channel (0)
|
|
566
|
+
* offAudioStart(1); // Stop receiving start notifications for channel 1
|
|
567
|
+
* ```
|
|
568
|
+
*/
|
|
569
|
+
export const offAudioStart = (channelNumber: number = 0): void => {
|
|
570
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
571
|
+
if (!channel?.audioStartCallbacks) return;
|
|
572
|
+
|
|
573
|
+
channel.audioStartCallbacks.clear();
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Removes audio complete event listeners for a specific channel
|
|
578
|
+
* @param channelNumber - The channel number (defaults to 0)
|
|
579
|
+
* @example
|
|
580
|
+
* ```typescript
|
|
581
|
+
* offAudioComplete(); // Stop receiving completion notifications for default channel (0)
|
|
582
|
+
* offAudioComplete(1); // Stop receiving completion notifications for channel 1
|
|
583
|
+
* ```
|
|
584
|
+
*/
|
|
585
|
+
export const offAudioComplete = (channelNumber: number = 0): void => {
|
|
586
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
587
|
+
if (!channel?.audioCompleteCallbacks) return;
|
|
588
|
+
|
|
589
|
+
channel.audioCompleteCallbacks.clear();
|
|
590
|
+
};
|