audio-channel-queue 1.10.0 → 1.12.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/index.ts CHANGED
@@ -60,17 +60,16 @@ export {
60
60
 
61
61
  // Volume control and ducking functions
62
62
  export {
63
+ cancelAllVolumeTransitions,
64
+ cancelVolumeTransition,
63
65
  clearVolumeDucking,
64
- fadeVolume,
65
66
  getAllChannelsVolume,
66
67
  getChannelVolume,
67
68
  getFadeConfig,
68
69
  setAllChannelsVolume,
69
70
  setChannelVolume,
70
71
  setVolumeDucking,
71
- transitionVolume,
72
- cancelVolumeTransition,
73
- cancelAllVolumeTransitions
72
+ transitionVolume
74
73
  } from './volume';
75
74
 
76
75
  // Audio information and progress tracking functions
@@ -78,9 +77,11 @@ export {
78
77
  getAllChannelsInfo,
79
78
  getCurrentAudioInfo,
80
79
  getQueueSnapshot,
80
+ offAudioComplete,
81
81
  offAudioPause,
82
82
  offAudioProgress,
83
83
  offAudioResume,
84
+ offAudioStart,
84
85
  offQueueChange,
85
86
  onAudioComplete,
86
87
  onAudioPause,
@@ -130,4 +131,11 @@ export type {
130
131
  } from './types';
131
132
 
132
133
  // Enums and constants
133
- export { EasingType, FadeType, MAX_CHANNELS, TimerType, GLOBAL_PROGRESS_KEY } from './types';
134
+ export {
135
+ AudioErrorType,
136
+ EasingType,
137
+ FadeType,
138
+ MAX_CHANNELS,
139
+ TimerType,
140
+ GLOBAL_PROGRESS_KEY
141
+ } from './types';
package/src/info.ts CHANGED
@@ -18,6 +18,73 @@ import {
18
18
  import { getAudioInfoFromElement, createQueueSnapshot } from './utils';
19
19
  import { setupProgressTracking, cleanupProgressTracking } from './events';
20
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
+ ];
52
+
53
+ return [...new Set(propertyNames)]; // Remove duplicates
54
+ };
55
+
56
+ /**
57
+ * Returns the list of non-whitelisted properties found on a specific channel
58
+ * These are properties that will trigger warnings when modified directly
59
+ * @param channelNumber - The channel number to inspect (defaults to 0)
60
+ * @returns Array of property names that are not in the whitelist, or empty array if channel doesn't exist
61
+ * @example
62
+ * ```typescript
63
+ * // Add some custom property to a channel
64
+ * (audioChannels[0] as any).customProperty = 'test';
65
+ *
66
+ * const nonWhitelisted = getNonWhitelistedChannelProperties(0);
67
+ * console.log(nonWhitelisted); // ['customProperty']
68
+ * ```
69
+ * @internal
70
+ */
71
+ export const getNonWhitelistedChannelProperties = (channelNumber: number = 0): string[] => {
72
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
73
+ if (!channel) {
74
+ return [];
75
+ }
76
+
77
+ const whitelistedProperties: string[] = getWhitelistedChannelProperties();
78
+ const allChannelProperties: string[] = Object.getOwnPropertyNames(channel);
79
+
80
+ // Filter out properties that are in the whitelist
81
+ const nonWhitelistedProperties: string[] = allChannelProperties.filter(
82
+ (property: string) => !whitelistedProperties.includes(property)
83
+ );
84
+
85
+ return nonWhitelistedProperties;
86
+ };
87
+
21
88
  /**
22
89
  * Global array to store audio channels with their queues and callback management
23
90
  * Each channel maintains its own audio queue and event callback sets
@@ -56,10 +123,10 @@ export const audioChannels: ExtendedAudioQueueChannel[] = new Proxy(
56
123
  channelValue: unknown
57
124
  ): boolean {
58
125
  // Allow internal modifications but warn about direct property changes
59
- if (
60
- typeof channelProp === 'string' &&
61
- !['queue', 'volume', 'isPaused', 'isLocked', 'volumeConfig'].includes(channelProp)
62
- ) {
126
+ // Use the automatically-derived whitelist from the interface
127
+ const whitelistedProperties = getWhitelistedChannelProperties();
128
+
129
+ if (typeof channelProp === 'string' && !whitelistedProperties.includes(channelProp)) {
63
130
  // eslint-disable-next-line no-console
64
131
  console.warn(
65
132
  `Warning: Direct modification of channel.${channelProp} detected. ` +
@@ -284,13 +351,14 @@ export const onQueueChange = (channelNumber: number, callback: QueueChangeCallba
284
351
 
285
352
  /**
286
353
  * Removes queue change listeners for a specific channel
287
- * @param channelNumber - The channel number
354
+ * @param channelNumber - The channel number (defaults to 0)
288
355
  * @example
289
356
  * ```typescript
290
- * offQueueChange(0); // Stop receiving queue change notifications for channel 0
357
+ * offQueueChange(); // Stop receiving queue change notifications for default channel (0)
358
+ * offQueueChange(1); // Stop receiving queue change notifications for channel 1
291
359
  * ```
292
360
  */
293
- export const offQueueChange = (channelNumber: number): void => {
361
+ export const offQueueChange = (channelNumber: number = 0): void => {
294
362
  const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
295
363
  if (!channel?.queueChangeCallbacks) return;
296
364
 
@@ -457,13 +525,14 @@ export const onAudioResume = (channelNumber: number, callback: AudioResumeCallba
457
525
 
458
526
  /**
459
527
  * Removes pause event listeners for a specific channel
460
- * @param channelNumber - The channel number
528
+ * @param channelNumber - The channel number (defaults to 0)
461
529
  * @example
462
530
  * ```typescript
463
- * offAudioPause(0); // Stop receiving pause notifications for channel 0
531
+ * offAudioPause(); // Stop receiving pause notifications for default channel (0)
532
+ * offAudioPause(1); // Stop receiving pause notifications for channel 1
464
533
  * ```
465
534
  */
466
- export const offAudioPause = (channelNumber: number): void => {
535
+ export const offAudioPause = (channelNumber: number = 0): void => {
467
536
  const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
468
537
  if (!channel?.audioPauseCallbacks) return;
469
538
 
@@ -472,15 +541,48 @@ export const offAudioPause = (channelNumber: number): void => {
472
541
 
473
542
  /**
474
543
  * Removes resume event listeners for a specific channel
475
- * @param channelNumber - The channel number
544
+ * @param channelNumber - The channel number (defaults to 0)
476
545
  * @example
477
546
  * ```typescript
478
- * offAudioResume(0); // Stop receiving resume notifications for channel 0
547
+ * offAudioResume(); // Stop receiving resume notifications for default channel (0)
548
+ * offAudioResume(1); // Stop receiving resume notifications for channel 1
479
549
  * ```
480
550
  */
481
- export const offAudioResume = (channelNumber: number): void => {
551
+ export const offAudioResume = (channelNumber: number = 0): void => {
482
552
  const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
483
553
  if (!channel?.audioResumeCallbacks) return;
484
554
 
485
555
  channel.audioResumeCallbacks.clear();
486
556
  };
557
+
558
+ /**
559
+ * Removes audio start event listeners for a specific channel
560
+ * @param channelNumber - The channel number (defaults to 0)
561
+ * @example
562
+ * ```typescript
563
+ * offAudioStart(); // Stop receiving start notifications for default channel (0)
564
+ * offAudioStart(1); // Stop receiving start notifications for channel 1
565
+ * ```
566
+ */
567
+ export const offAudioStart = (channelNumber: number = 0): void => {
568
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
569
+ if (!channel?.audioStartCallbacks) return;
570
+
571
+ channel.audioStartCallbacks.clear();
572
+ };
573
+
574
+ /**
575
+ * Removes audio complete event listeners for a specific channel
576
+ * @param channelNumber - The channel number (defaults to 0)
577
+ * @example
578
+ * ```typescript
579
+ * offAudioComplete(); // Stop receiving completion notifications for default channel (0)
580
+ * offAudioComplete(1); // Stop receiving completion notifications for channel 1
581
+ * ```
582
+ */
583
+ export const offAudioComplete = (channelNumber: number = 0): void => {
584
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
585
+ if (!channel?.audioCompleteCallbacks) return;
586
+
587
+ channel.audioCompleteCallbacks.clear();
588
+ };
package/src/types.ts CHANGED
@@ -29,15 +29,15 @@ export interface AudioQueueChannel {
29
29
  * Volume ducking configuration for channels
30
30
  */
31
31
  export interface VolumeConfig {
32
+ /** Duration in milliseconds for volume duck transition (defaults to 250ms) */
33
+ duckTransitionDuration?: number;
34
+ /** Volume level for all other channels when priority channel is active (0-1) */
35
+ duckingVolume: number;
32
36
  /** The channel number that should have priority */
33
37
  priorityChannel: number;
34
38
  /** Volume level for the priority channel (0-1) */
35
39
  priorityVolume: number;
36
- /** Volume level for all other channels when priority channel is active (0-1) */
37
- duckingVolume: number;
38
- /** Duration in milliseconds for volume duck transition (defaults to 250ms) */
39
- duckTransitionDuration?: number;
40
- /** Duration in milliseconds for volume restore transition (defaults to 500ms) */
40
+ /** Duration in milliseconds for volume restore transition (defaults to 250ms) */
41
41
  restoreTransitionDuration?: number;
42
42
  /** Easing function for volume transitions (defaults to 'ease-out') */
43
43
  transitionEasing?: EasingType;
@@ -53,8 +53,6 @@ export interface AudioQueueOptions {
53
53
  loop?: boolean;
54
54
  /** Maximum number of items allowed in the queue (defaults to unlimited) */
55
55
  maxQueueSize?: number;
56
- /** @deprecated Use addToFront instead. Legacy support for priority queuing */
57
- priority?: boolean;
58
56
  /** Volume level for this specific audio (0-1) */
59
57
  volume?: number;
60
58
  }
@@ -212,41 +210,74 @@ export type AudioPauseCallback = (channelNumber: number, audioInfo: AudioInfo) =
212
210
  export type AudioResumeCallback = (channelNumber: number, audioInfo: AudioInfo) => void;
213
211
 
214
212
  /**
215
- * Information about an audio error that occurred
213
+ * Types of audio errors that can occur during playback
214
+ */
215
+ export enum AudioErrorType {
216
+ Abort = 'abort',
217
+ Decode = 'decode',
218
+ Network = 'network',
219
+ Permission = 'permission',
220
+ Timeout = 'timeout',
221
+ Unknown = 'unknown',
222
+ Unsupported = 'unsupported'
223
+ }
224
+
225
+ /**
226
+ * Information about an audio error that occurred during playback or loading
216
227
  */
217
228
  export interface AudioErrorInfo {
229
+ /** Channel number where the error occurred */
218
230
  channelNumber: number;
219
- src: string;
220
- fileName: string;
231
+ /** The actual error object that was thrown */
221
232
  error: Error;
222
- errorType: 'network' | 'decode' | 'unsupported' | 'permission' | 'abort' | 'timeout' | 'unknown';
223
- timestamp: number;
224
- retryAttempt?: number;
233
+ /** Categorized type of error for handling different scenarios */
234
+ errorType: AudioErrorType;
235
+ /** Extracted filename from the source URL */
236
+ fileName: string;
237
+ /** Number of audio files remaining in the queue after this error */
225
238
  remainingInQueue: number;
239
+ /** Current retry attempt number (if retrying is enabled) */
240
+ retryAttempt?: number;
241
+ /** Audio file source URL that failed */
242
+ src: string;
243
+ /** Unix timestamp when the error occurred */
244
+ timestamp: number;
226
245
  }
227
246
 
228
247
  /**
229
248
  * Configuration for automatic retry behavior when audio fails to load or play
230
249
  */
231
250
  export interface RetryConfig {
232
- enabled: boolean;
233
- maxRetries: number;
251
+ /** Initial delay in milliseconds before first retry attempt */
234
252
  baseDelay: number;
253
+ /** Whether automatic retries are enabled for this channel */
254
+ enabled: boolean;
255
+ /** Whether to use exponential backoff (doubling delay each retry) */
235
256
  exponentialBackoff: boolean;
236
- timeoutMs: number;
237
- fallbackUrls?: string[];
257
+ /** Alternative URLs to try if the primary source fails */
258
+ fallbackUrls: string[];
259
+ /** Maximum number of retry attempts before giving up */
260
+ maxRetries: number;
261
+ /** Whether to skip to next track in queue if all retries fail */
238
262
  skipOnFailure: boolean;
263
+ /** Timeout in milliseconds for each individual retry attempt */
264
+ timeoutMs: number;
239
265
  }
240
266
 
241
267
  /**
242
- * Configuration options for error recovery mechanisms
268
+ * Configuration options for error recovery mechanisms across the audio system
243
269
  */
244
270
  export interface ErrorRecoveryOptions {
271
+ /** Whether to automatically retry failed audio loads/plays */
245
272
  autoRetry: boolean;
246
- showUserFeedback: boolean;
273
+ /** Whether to automatically skip to next track when current fails */
274
+ fallbackToNextTrack: boolean;
275
+ /** Whether to send error data to analytics systems */
247
276
  logErrorsToAnalytics: boolean;
277
+ /** Whether to maintain queue integrity when errors occur */
248
278
  preserveQueueOnError: boolean;
249
- fallbackToNextTrack: boolean;
279
+ /** Whether to display user-visible error feedback */
280
+ showUserFeedback: boolean;
250
281
  }
251
282
 
252
283
  /**
@@ -255,29 +286,41 @@ export interface ErrorRecoveryOptions {
255
286
  export type AudioErrorCallback = (errorInfo: AudioErrorInfo) => void;
256
287
 
257
288
  /**
258
- * Extended audio channel with queue management and callback support
289
+ * Extended audio channel with comprehensive queue management, callback support, and state tracking
259
290
  */
260
291
  export interface ExtendedAudioQueueChannel {
292
+ /** Set of callbacks triggered when audio completes playback */
261
293
  audioCompleteCallbacks: Set<AudioCompleteCallback>;
294
+ /** Set of callbacks triggered when audio errors occur */
262
295
  audioErrorCallbacks: Set<AudioErrorCallback>;
296
+ /** Set of callbacks triggered when audio is paused */
263
297
  audioPauseCallbacks: Set<AudioPauseCallback>;
298
+ /** Set of callbacks triggered when audio is resumed */
264
299
  audioResumeCallbacks: Set<AudioResumeCallback>;
300
+ /** Set of callbacks triggered when audio starts playing */
265
301
  audioStartCallbacks: Set<AudioStartCallback>;
302
+ /** Current fade state if pause/resume with fade is active */
266
303
  fadeState?: ChannelFadeState;
304
+ /** Whether the channel is currently paused */
267
305
  isPaused: boolean;
268
306
  /** Active operation lock to prevent race conditions */
269
307
  isLocked?: boolean;
270
308
  /** Maximum allowed queue size for this channel */
271
309
  maxQueueSize?: number;
310
+ /** Map of progress callbacks keyed by audio element or global symbol */
272
311
  progressCallbacks: Map<HTMLAudioElement | typeof GLOBAL_PROGRESS_KEY, Set<ProgressCallback>>;
312
+ /** Array of HTMLAudioElement objects in the queue */
273
313
  queue: HTMLAudioElement[];
314
+ /** Set of callbacks triggered when the queue changes */
274
315
  queueChangeCallbacks: Set<QueueChangeCallback>;
316
+ /** Retry configuration for failed audio loads/plays */
275
317
  retryConfig?: RetryConfig;
318
+ /** Current volume level for the channel (0-1) */
276
319
  volume: number;
277
320
  }
278
321
 
279
322
  /**
280
- * Easing function types for volume transitions
323
+ * Easing function types for smooth volume transitions and animations
281
324
  */
282
325
  export enum EasingType {
283
326
  Linear = 'linear',
@@ -287,7 +330,7 @@ export enum EasingType {
287
330
  }
288
331
 
289
332
  /**
290
- * Fade type for pause/resume operations with integrated volume transitions
333
+ * Predefined fade types for pause/resume operations with different transition characteristics
291
334
  */
292
335
  export enum FadeType {
293
336
  Linear = 'linear',
@@ -296,7 +339,7 @@ export enum FadeType {
296
339
  }
297
340
 
298
341
  /**
299
- * Timer types for volume transitions to ensure proper cleanup
342
+ * Timer implementation types used for volume transitions to ensure proper cleanup
300
343
  */
301
344
  export enum TimerType {
302
345
  RequestAnimationFrame = 'raf',
package/src/volume.ts CHANGED
@@ -380,28 +380,6 @@ export const applyVolumeDucking = async (activeChannelNumber: number): Promise<v
380
380
  await Promise.all(transitionPromises);
381
381
  };
382
382
 
383
- /**
384
- * Fades the volume for a specific channel over time (alias for transitionVolume with improved naming)
385
- * @param channelNumber - The channel number to fade
386
- * @param targetVolume - Target volume level (0-1)
387
- * @param duration - Fade duration in milliseconds (defaults to 250)
388
- * @param easing - Easing function type (defaults to 'ease-out')
389
- * @returns Promise that resolves when fade completes
390
- * @example
391
- * ```typescript
392
- * await fadeVolume(0, 0, 800, 'ease-in'); // Fade out over 800ms
393
- * await fadeVolume(0, 1, 600, 'ease-out'); // Fade in over 600ms
394
- * ```
395
- */
396
- export const fadeVolume = async (
397
- channelNumber: number,
398
- targetVolume: number,
399
- duration: number = 250,
400
- easing: EasingType = EasingType.EaseOut
401
- ): Promise<void> => {
402
- return transitionVolume(channelNumber, targetVolume, duration, easing);
403
- };
404
-
405
383
  /**
406
384
  * Restores normal volume levels when priority channel queue becomes empty
407
385
  * @param stoppedChannelNumber - The channel that just stopped playing
@@ -430,7 +408,7 @@ export const restoreVolumeLevels = async (stoppedChannelNumber: number): Promise
430
408
  }
431
409
 
432
410
  // Restore this channel to its desired volume
433
- const duration = config.restoreTransitionDuration ?? 500;
411
+ const duration = config.restoreTransitionDuration ?? 250;
434
412
  const easing = config.transitionEasing ?? EasingType.EaseOut;
435
413
  const targetVolume = channel.volume ?? 1.0;
436
414