audio-channel-queue 1.11.0 → 1.12.1-beta.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/dist/types.d.ts CHANGED
@@ -24,15 +24,15 @@ export interface AudioQueueChannel {
24
24
  * Volume ducking configuration for channels
25
25
  */
26
26
  export interface VolumeConfig {
27
+ /** Duration in milliseconds for volume duck transition (defaults to 250ms) */
28
+ duckTransitionDuration?: number;
29
+ /** Volume level for all other channels when priority channel is active (0-1) */
30
+ duckingVolume: number;
27
31
  /** The channel number that should have priority */
28
32
  priorityChannel: number;
29
33
  /** Volume level for the priority channel (0-1) */
30
34
  priorityVolume: number;
31
- /** Volume level for all other channels when priority channel is active (0-1) */
32
- duckingVolume: number;
33
- /** Duration in milliseconds for volume duck transition (defaults to 250ms) */
34
- duckTransitionDuration?: number;
35
- /** Duration in milliseconds for volume restore transition (defaults to 500ms) */
35
+ /** Duration in milliseconds for volume restore transition (defaults to 250ms) */
36
36
  restoreTransitionDuration?: number;
37
37
  /** Easing function for volume transitions (defaults to 'ease-out') */
38
38
  transitionEasing?: EasingType;
@@ -47,8 +47,6 @@ export interface AudioQueueOptions {
47
47
  loop?: boolean;
48
48
  /** Maximum number of items allowed in the queue (defaults to unlimited) */
49
49
  maxQueueSize?: number;
50
- /** @deprecated Use addToFront instead. Legacy support for priority queuing */
51
- priority?: boolean;
52
50
  /** Volume level for this specific audio (0-1) */
53
51
  volume?: number;
54
52
  }
@@ -192,67 +190,148 @@ export type AudioPauseCallback = (channelNumber: number, audioInfo: AudioInfo) =
192
190
  */
193
191
  export type AudioResumeCallback = (channelNumber: number, audioInfo: AudioInfo) => void;
194
192
  /**
195
- * Information about an audio error that occurred
193
+ * Types of audio errors that can occur during playback
194
+ */
195
+ export declare enum AudioErrorType {
196
+ Abort = "abort",
197
+ Decode = "decode",
198
+ Network = "network",
199
+ Permission = "permission",
200
+ Timeout = "timeout",
201
+ Unknown = "unknown",
202
+ Unsupported = "unsupported"
203
+ }
204
+ /**
205
+ * Information about an audio error that occurred during playback or loading
196
206
  */
197
207
  export interface AudioErrorInfo {
208
+ /** Channel number where the error occurred */
198
209
  channelNumber: number;
199
- src: string;
200
- fileName: string;
210
+ /** The actual error object that was thrown */
201
211
  error: Error;
202
- errorType: 'network' | 'decode' | 'unsupported' | 'permission' | 'abort' | 'timeout' | 'unknown';
203
- timestamp: number;
204
- retryAttempt?: number;
212
+ /** Categorized type of error for handling different scenarios */
213
+ errorType: AudioErrorType;
214
+ /** Extracted filename from the source URL */
215
+ fileName: string;
216
+ /** Number of audio files remaining in the queue after this error */
205
217
  remainingInQueue: number;
218
+ /** Current retry attempt number (if retrying is enabled) */
219
+ retryAttempt?: number;
220
+ /** Audio file source URL that failed */
221
+ src: string;
222
+ /** Unix timestamp when the error occurred */
223
+ timestamp: number;
206
224
  }
207
225
  /**
208
226
  * Configuration for automatic retry behavior when audio fails to load or play
209
227
  */
210
228
  export interface RetryConfig {
211
- enabled: boolean;
212
- maxRetries: number;
229
+ /** Initial delay in milliseconds before first retry attempt */
213
230
  baseDelay: number;
231
+ /** Whether automatic retries are enabled for this channel */
232
+ enabled: boolean;
233
+ /** Whether to use exponential backoff (doubling delay each retry) */
214
234
  exponentialBackoff: boolean;
215
- timeoutMs: number;
216
- fallbackUrls?: string[];
235
+ /** Alternative URLs to try if the primary source fails */
236
+ fallbackUrls: string[];
237
+ /** Maximum number of retry attempts before giving up */
238
+ maxRetries: number;
239
+ /** Whether to skip to next track in queue if all retries fail */
217
240
  skipOnFailure: boolean;
241
+ /** Timeout in milliseconds for each individual retry attempt */
242
+ timeoutMs: number;
218
243
  }
219
244
  /**
220
- * Configuration options for error recovery mechanisms
245
+ * Configuration options for error recovery mechanisms across the audio system
221
246
  */
222
247
  export interface ErrorRecoveryOptions {
248
+ /** Whether to automatically retry failed audio loads/plays */
223
249
  autoRetry: boolean;
224
- showUserFeedback: boolean;
250
+ /** Whether to automatically skip to next track when current fails */
251
+ fallbackToNextTrack: boolean;
252
+ /** Whether to send error data to analytics systems */
225
253
  logErrorsToAnalytics: boolean;
254
+ /** Whether to maintain queue integrity when errors occur */
226
255
  preserveQueueOnError: boolean;
227
- fallbackToNextTrack: boolean;
256
+ /** Whether to display user-visible error feedback */
257
+ showUserFeedback: boolean;
228
258
  }
229
259
  /**
230
260
  * Callback function type for audio error events
231
261
  */
232
262
  export type AudioErrorCallback = (errorInfo: AudioErrorInfo) => void;
233
263
  /**
234
- * Extended audio channel with queue management and callback support
264
+ * Web Audio API configuration options
265
+ */
266
+ export interface WebAudioConfig {
267
+ /** Whether to automatically use Web Audio API on iOS devices */
268
+ autoDetectIOS: boolean;
269
+ /** Whether Web Audio API support is enabled */
270
+ enabled: boolean;
271
+ /** Whether to force Web Audio API usage on all devices */
272
+ forceWebAudio: boolean;
273
+ }
274
+ /**
275
+ * Web Audio API support information
276
+ */
277
+ export interface WebAudioSupport {
278
+ /** Whether Web Audio API is available in the current environment */
279
+ available: boolean;
280
+ /** Whether the current device is iOS */
281
+ isIOS: boolean;
282
+ /** Whether Web Audio API is currently being used */
283
+ usingWebAudio: boolean;
284
+ /** Reason for current Web Audio API usage state */
285
+ reason: string;
286
+ }
287
+ /**
288
+ * Web Audio API node set for audio element control
289
+ */
290
+ export interface WebAudioNodeSet {
291
+ /** Gain node for volume control */
292
+ gainNode: GainNode;
293
+ /** Media element source node */
294
+ sourceNode: MediaElementAudioSourceNode;
295
+ }
296
+ /**
297
+ * Extended audio channel with comprehensive queue management, callback support, and state tracking
235
298
  */
236
299
  export interface ExtendedAudioQueueChannel {
300
+ /** Set of callbacks triggered when audio completes playback */
237
301
  audioCompleteCallbacks: Set<AudioCompleteCallback>;
302
+ /** Set of callbacks triggered when audio errors occur */
238
303
  audioErrorCallbacks: Set<AudioErrorCallback>;
304
+ /** Set of callbacks triggered when audio is paused */
239
305
  audioPauseCallbacks: Set<AudioPauseCallback>;
306
+ /** Set of callbacks triggered when audio is resumed */
240
307
  audioResumeCallbacks: Set<AudioResumeCallback>;
308
+ /** Set of callbacks triggered when audio starts playing */
241
309
  audioStartCallbacks: Set<AudioStartCallback>;
310
+ /** Current fade state if pause/resume with fade is active */
242
311
  fadeState?: ChannelFadeState;
312
+ /** Whether the channel is currently paused */
243
313
  isPaused: boolean;
244
314
  /** Active operation lock to prevent race conditions */
245
315
  isLocked?: boolean;
246
316
  /** Maximum allowed queue size for this channel */
247
317
  maxQueueSize?: number;
318
+ /** Map of progress callbacks keyed by audio element or global symbol */
248
319
  progressCallbacks: Map<HTMLAudioElement | typeof GLOBAL_PROGRESS_KEY, Set<ProgressCallback>>;
320
+ /** Array of HTMLAudioElement objects in the queue */
249
321
  queue: HTMLAudioElement[];
322
+ /** Set of callbacks triggered when the queue changes */
250
323
  queueChangeCallbacks: Set<QueueChangeCallback>;
324
+ /** Retry configuration for failed audio loads/plays */
251
325
  retryConfig?: RetryConfig;
326
+ /** Current volume level for the channel (0-1) */
252
327
  volume: number;
328
+ /** Web Audio API context for this channel */
329
+ webAudioContext?: AudioContext;
330
+ /** Map of Web Audio API nodes for each audio element */
331
+ webAudioNodes?: Map<HTMLAudioElement, WebAudioNodeSet>;
253
332
  }
254
333
  /**
255
- * Easing function types for volume transitions
334
+ * Easing function types for smooth volume transitions and animations
256
335
  */
257
336
  export declare enum EasingType {
258
337
  Linear = "linear",
@@ -261,7 +340,7 @@ export declare enum EasingType {
261
340
  EaseInOut = "ease-in-out"
262
341
  }
263
342
  /**
264
- * Fade type for pause/resume operations with integrated volume transitions
343
+ * Predefined fade types for pause/resume operations with different transition characteristics
265
344
  */
266
345
  export declare enum FadeType {
267
346
  Linear = "linear",
@@ -269,7 +348,7 @@ export declare enum FadeType {
269
348
  Dramatic = "dramatic"
270
349
  }
271
350
  /**
272
- * Timer types for volume transitions to ensure proper cleanup
351
+ * Timer implementation types used for volume transitions to ensure proper cleanup
273
352
  */
274
353
  export declare enum TimerType {
275
354
  RequestAnimationFrame = "raf",
package/dist/types.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * @fileoverview Type definitions for the audio-channel-queue package
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.TimerType = exports.FadeType = exports.EasingType = exports.GLOBAL_PROGRESS_KEY = exports.MAX_CHANNELS = void 0;
6
+ exports.TimerType = exports.FadeType = exports.EasingType = exports.AudioErrorType = exports.GLOBAL_PROGRESS_KEY = exports.MAX_CHANNELS = void 0;
7
7
  /**
8
8
  * Maximum number of audio channels allowed to prevent memory exhaustion
9
9
  */
@@ -14,7 +14,20 @@ exports.MAX_CHANNELS = 64;
14
14
  */
15
15
  exports.GLOBAL_PROGRESS_KEY = Symbol('global-progress-callbacks');
16
16
  /**
17
- * Easing function types for volume transitions
17
+ * Types of audio errors that can occur during playback
18
+ */
19
+ var AudioErrorType;
20
+ (function (AudioErrorType) {
21
+ AudioErrorType["Abort"] = "abort";
22
+ AudioErrorType["Decode"] = "decode";
23
+ AudioErrorType["Network"] = "network";
24
+ AudioErrorType["Permission"] = "permission";
25
+ AudioErrorType["Timeout"] = "timeout";
26
+ AudioErrorType["Unknown"] = "unknown";
27
+ AudioErrorType["Unsupported"] = "unsupported";
28
+ })(AudioErrorType || (exports.AudioErrorType = AudioErrorType = {}));
29
+ /**
30
+ * Easing function types for smooth volume transitions and animations
18
31
  */
19
32
  var EasingType;
20
33
  (function (EasingType) {
@@ -24,7 +37,7 @@ var EasingType;
24
37
  EasingType["EaseInOut"] = "ease-in-out";
25
38
  })(EasingType || (exports.EasingType = EasingType = {}));
26
39
  /**
27
- * Fade type for pause/resume operations with integrated volume transitions
40
+ * Predefined fade types for pause/resume operations with different transition characteristics
28
41
  */
29
42
  var FadeType;
30
43
  (function (FadeType) {
@@ -33,7 +46,7 @@ var FadeType;
33
46
  FadeType["Dramatic"] = "dramatic";
34
47
  })(FadeType || (exports.FadeType = FadeType = {}));
35
48
  /**
36
- * Timer types for volume transitions to ensure proper cleanup
49
+ * Timer implementation types used for volume transitions to ensure proper cleanup
37
50
  */
38
51
  var TimerType;
39
52
  (function (TimerType) {
package/dist/volume.d.ts CHANGED
@@ -28,6 +28,7 @@ export declare const getFadeConfig: (fadeType: FadeType) => FadeConfig;
28
28
  export declare const transitionVolume: (channelNumber: number, targetVolume: number, duration?: number, easing?: EasingType) => Promise<void>;
29
29
  /**
30
30
  * Sets the volume for a specific channel with optional smooth transition
31
+ * Automatically uses Web Audio API on iOS devices for enhanced volume control
31
32
  * @param channelNumber - The channel number to set volume for
32
33
  * @param volume - Volume level (0-1)
33
34
  * @param transitionDuration - Optional transition duration in milliseconds
@@ -103,20 +104,6 @@ export declare const clearVolumeDucking: () => void;
103
104
  * @internal
104
105
  */
105
106
  export declare const applyVolumeDucking: (activeChannelNumber: number) => Promise<void>;
106
- /**
107
- * Fades the volume for a specific channel over time (alias for transitionVolume with improved naming)
108
- * @param channelNumber - The channel number to fade
109
- * @param targetVolume - Target volume level (0-1)
110
- * @param duration - Fade duration in milliseconds (defaults to 250)
111
- * @param easing - Easing function type (defaults to 'ease-out')
112
- * @returns Promise that resolves when fade completes
113
- * @example
114
- * ```typescript
115
- * await fadeVolume(0, 0, 800, 'ease-in'); // Fade out over 800ms
116
- * await fadeVolume(0, 1, 600, 'ease-out'); // Fade in over 600ms
117
- * ```
118
- */
119
- export declare const fadeVolume: (channelNumber: number, targetVolume: number, duration?: number, easing?: EasingType) => Promise<void>;
120
107
  /**
121
108
  * Restores normal volume levels when priority channel queue becomes empty
122
109
  * @param stoppedChannelNumber - The channel that just stopped playing
@@ -134,3 +121,17 @@ export declare const cancelVolumeTransition: (channelNumber: number) => void;
134
121
  * @internal
135
122
  */
136
123
  export declare const cancelAllVolumeTransitions: () => void;
124
+ /**
125
+ * Initializes Web Audio API nodes for a new audio element
126
+ * @param audio - The audio element to initialize nodes for
127
+ * @param channelNumber - The channel number this audio belongs to
128
+ * @internal
129
+ */
130
+ export declare const initializeWebAudioForAudio: (audio: HTMLAudioElement, channelNumber: number) => Promise<void>;
131
+ /**
132
+ * Cleans up Web Audio API nodes for an audio element
133
+ * @param audio - The audio element to clean up nodes for
134
+ * @param channelNumber - The channel number this audio belongs to
135
+ * @internal
136
+ */
137
+ export declare const cleanupWebAudioForAudio: (audio: HTMLAudioElement, channelNumber: number) => void;
package/dist/volume.js CHANGED
@@ -12,9 +12,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
12
12
  });
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.cancelAllVolumeTransitions = exports.cancelVolumeTransition = exports.restoreVolumeLevels = exports.fadeVolume = exports.applyVolumeDucking = exports.clearVolumeDucking = exports.setVolumeDucking = exports.setAllChannelsVolume = exports.getAllChannelsVolume = exports.getChannelVolume = exports.setChannelVolume = exports.transitionVolume = exports.getFadeConfig = void 0;
15
+ exports.cleanupWebAudioForAudio = exports.initializeWebAudioForAudio = exports.cancelAllVolumeTransitions = exports.cancelVolumeTransition = exports.restoreVolumeLevels = exports.applyVolumeDucking = exports.clearVolumeDucking = exports.setVolumeDucking = exports.setAllChannelsVolume = exports.getAllChannelsVolume = exports.getChannelVolume = exports.setChannelVolume = exports.transitionVolume = exports.getFadeConfig = void 0;
16
16
  const types_1 = require("./types");
17
17
  const info_1 = require("./info");
18
+ const web_audio_1 = require("./web-audio");
18
19
  // Store active volume transitions to handle interruptions
19
20
  const activeTransitions = new Map();
20
21
  // Track which timer type was used for each channel
@@ -119,7 +120,7 @@ const transitionVolume = (channelNumber_1, targetVolume_1, ...args_1) => __await
119
120
  const startTime = performance.now();
120
121
  const easingFn = easingFunctions[easing];
121
122
  return new Promise((resolve) => {
122
- const updateVolume = () => {
123
+ const updateVolume = () => __awaiter(void 0, void 0, void 0, function* () {
123
124
  const elapsed = performance.now() - startTime;
124
125
  const progress = Math.min(elapsed / duration, 1);
125
126
  const easedProgress = easingFn(progress);
@@ -128,7 +129,7 @@ const transitionVolume = (channelNumber_1, targetVolume_1, ...args_1) => __await
128
129
  // Apply volume to both channel config and current audio
129
130
  channel.volume = clampedVolume;
130
131
  if (channel.queue.length > 0) {
131
- channel.queue[0].volume = clampedVolume;
132
+ yield setVolumeForAudio(channel.queue[0], clampedVolume, channelNumber);
132
133
  }
133
134
  if (progress >= 1) {
134
135
  // Transition complete
@@ -139,24 +140,25 @@ const transitionVolume = (channelNumber_1, targetVolume_1, ...args_1) => __await
139
140
  else {
140
141
  // Use requestAnimationFrame in browser, setTimeout in tests
141
142
  if (typeof requestAnimationFrame !== 'undefined') {
142
- const rafId = requestAnimationFrame(updateVolume);
143
+ const rafId = requestAnimationFrame(() => updateVolume());
143
144
  activeTransitions.set(channelNumber, rafId);
144
145
  timerTypes.set(channelNumber, types_1.TimerType.RequestAnimationFrame);
145
146
  }
146
147
  else {
147
148
  // In test environment, use shorter intervals
148
- const timeoutId = setTimeout(updateVolume, 1);
149
+ const timeoutId = setTimeout(() => updateVolume(), 1);
149
150
  activeTransitions.set(channelNumber, timeoutId);
150
151
  timerTypes.set(channelNumber, types_1.TimerType.Timeout);
151
152
  }
152
153
  }
153
- };
154
+ });
154
155
  updateVolume();
155
156
  });
156
157
  });
157
158
  exports.transitionVolume = transitionVolume;
158
159
  /**
159
160
  * Sets the volume for a specific channel with optional smooth transition
161
+ * Automatically uses Web Audio API on iOS devices for enhanced volume control
160
162
  * @param channelNumber - The channel number to set volume for
161
163
  * @param volume - Volume level (0-1)
162
164
  * @param transitionDuration - Optional transition duration in milliseconds
@@ -192,17 +194,21 @@ const setChannelVolume = (channelNumber, volume, transitionDuration, easing) =>
192
194
  };
193
195
  return;
194
196
  }
197
+ const channel = info_1.audioChannels[channelNumber];
198
+ // Initialize Web Audio API if needed and supported
199
+ if ((0, web_audio_1.shouldUseWebAudio)() && !channel.webAudioContext) {
200
+ yield initializeWebAudioForChannel(channelNumber);
201
+ }
195
202
  if (transitionDuration && transitionDuration > 0) {
196
203
  // Smooth transition
197
204
  yield (0, exports.transitionVolume)(channelNumber, clampedVolume, transitionDuration, easing);
198
205
  }
199
206
  else {
200
207
  // Instant change (backward compatibility)
201
- info_1.audioChannels[channelNumber].volume = clampedVolume;
202
- const channel = info_1.audioChannels[channelNumber];
208
+ channel.volume = clampedVolume;
203
209
  if (channel.queue.length > 0) {
204
210
  const currentAudio = channel.queue[0];
205
- currentAudio.volume = clampedVolume;
211
+ yield setVolumeForAudio(currentAudio, clampedVolume, channelNumber);
206
212
  }
207
213
  }
208
214
  });
@@ -333,36 +339,19 @@ const applyVolumeDucking = (activeChannelNumber) => __awaiter(void 0, void 0, vo
333
339
  // This is the priority channel - set to priority volume
334
340
  // Only change audio volume, preserve channel.volume as desired volume
335
341
  const currentAudio = channel.queue[0];
336
- transitionPromises.push(transitionAudioVolume(currentAudio, config.priorityVolume, duration, easing));
342
+ transitionPromises.push(transitionAudioVolume(currentAudio, config.priorityVolume, duration, easing, channelNumber));
337
343
  }
338
344
  else {
339
345
  // This is a background channel - duck it
340
346
  // Only change audio volume, preserve channel.volume as desired volume
341
347
  const currentAudio = channel.queue[0];
342
- transitionPromises.push(transitionAudioVolume(currentAudio, config.duckingVolume, duration, easing));
348
+ transitionPromises.push(transitionAudioVolume(currentAudio, config.duckingVolume, duration, easing, channelNumber));
343
349
  }
344
350
  });
345
351
  // Wait for all transitions to complete
346
352
  yield Promise.all(transitionPromises);
347
353
  });
348
354
  exports.applyVolumeDucking = applyVolumeDucking;
349
- /**
350
- * Fades the volume for a specific channel over time (alias for transitionVolume with improved naming)
351
- * @param channelNumber - The channel number to fade
352
- * @param targetVolume - Target volume level (0-1)
353
- * @param duration - Fade duration in milliseconds (defaults to 250)
354
- * @param easing - Easing function type (defaults to 'ease-out')
355
- * @returns Promise that resolves when fade completes
356
- * @example
357
- * ```typescript
358
- * await fadeVolume(0, 0, 800, 'ease-in'); // Fade out over 800ms
359
- * await fadeVolume(0, 1, 600, 'ease-out'); // Fade in over 600ms
360
- * ```
361
- */
362
- const fadeVolume = (channelNumber_1, targetVolume_1, ...args_1) => __awaiter(void 0, [channelNumber_1, targetVolume_1, ...args_1], void 0, function* (channelNumber, targetVolume, duration = 250, easing = types_1.EasingType.EaseOut) {
363
- return (0, exports.transitionVolume)(channelNumber, targetVolume, duration, easing);
364
- });
365
- exports.fadeVolume = fadeVolume;
366
355
  /**
367
356
  * Restores normal volume levels when priority channel queue becomes empty
368
357
  * @param stoppedChannelNumber - The channel that just stopped playing
@@ -388,12 +377,12 @@ const restoreVolumeLevels = (stoppedChannelNumber) => __awaiter(void 0, void 0,
388
377
  return;
389
378
  }
390
379
  // Restore this channel to its desired volume
391
- const duration = (_a = config.restoreTransitionDuration) !== null && _a !== void 0 ? _a : 500;
380
+ const duration = (_a = config.restoreTransitionDuration) !== null && _a !== void 0 ? _a : 250;
392
381
  const easing = (_b = config.transitionEasing) !== null && _b !== void 0 ? _b : types_1.EasingType.EaseOut;
393
382
  const targetVolume = (_c = channel.volume) !== null && _c !== void 0 ? _c : 1.0;
394
383
  // Only transition the audio element volume, keep channel.volume as the desired volume
395
384
  const currentAudio = channel.queue[0];
396
- transitionPromises.push(transitionAudioVolume(currentAudio, targetVolume, duration, easing));
385
+ transitionPromises.push(transitionAudioVolume(currentAudio, targetVolume, duration, easing, channelNumber));
397
386
  });
398
387
  // Wait for all transitions to complete
399
388
  yield Promise.all(transitionPromises);
@@ -402,14 +391,31 @@ exports.restoreVolumeLevels = restoreVolumeLevels;
402
391
  /**
403
392
  * Transitions only the audio element volume without affecting channel.volume
404
393
  * This is used for ducking/restoration where channel.volume represents desired volume
394
+ * Uses Web Audio API when available for enhanced volume control
405
395
  * @param audio - The audio element to transition
406
396
  * @param targetVolume - Target volume level (0-1)
407
397
  * @param duration - Transition duration in milliseconds
408
398
  * @param easing - Easing function type
399
+ * @param channelNumber - The channel number this audio belongs to (for Web Audio API)
409
400
  * @returns Promise that resolves when transition completes
410
401
  * @internal
411
402
  */
412
- const transitionAudioVolume = (audio_1, targetVolume_1, ...args_1) => __awaiter(void 0, [audio_1, targetVolume_1, ...args_1], void 0, function* (audio, targetVolume, duration = 250, easing = types_1.EasingType.EaseOut) {
403
+ const transitionAudioVolume = (audio_1, targetVolume_1, ...args_1) => __awaiter(void 0, [audio_1, targetVolume_1, ...args_1], void 0, function* (audio, targetVolume, duration = 250, easing = types_1.EasingType.EaseOut, channelNumber) {
404
+ // Try to use Web Audio API if available and channel number is provided
405
+ if (channelNumber !== undefined) {
406
+ const channel = info_1.audioChannels[channelNumber];
407
+ if ((channel === null || channel === void 0 ? void 0 : channel.webAudioContext) && channel.webAudioNodes) {
408
+ const nodes = channel.webAudioNodes.get(audio);
409
+ if (nodes) {
410
+ // Use Web Audio API for smooth transitions
411
+ (0, web_audio_1.setWebAudioVolume)(nodes.gainNode, targetVolume, duration);
412
+ // Also update the audio element's volume property for consistency
413
+ audio.volume = targetVolume;
414
+ return;
415
+ }
416
+ }
417
+ }
418
+ // Fallback to standard HTMLAudioElement volume control with manual transition
413
419
  const startVolume = audio.volume;
414
420
  const volumeDelta = targetVolume - startVolume;
415
421
  // If no change needed, resolve immediately
@@ -418,7 +424,7 @@ const transitionAudioVolume = (audio_1, targetVolume_1, ...args_1) => __awaiter(
418
424
  }
419
425
  // Handle zero or negative duration - instant change
420
426
  if (duration <= 0) {
421
- audio.volume = Math.max(0, Math.min(1, targetVolume));
427
+ audio.volume = targetVolume;
422
428
  return Promise.resolve();
423
429
  }
424
430
  const startTime = performance.now();
@@ -441,7 +447,8 @@ const transitionAudioVolume = (audio_1, targetVolume_1, ...args_1) => __awaiter(
441
447
  requestAnimationFrame(updateVolume);
442
448
  }
443
449
  else {
444
- setTimeout(updateVolume, 1);
450
+ // In test environment, use longer intervals to prevent stack overflow
451
+ setTimeout(updateVolume, 16);
445
452
  }
446
453
  }
447
454
  };
@@ -484,3 +491,94 @@ const cancelAllVolumeTransitions = () => {
484
491
  });
485
492
  };
486
493
  exports.cancelAllVolumeTransitions = cancelAllVolumeTransitions;
494
+ /**
495
+ * Initializes Web Audio API for a specific channel
496
+ * @param channelNumber - The channel number to initialize Web Audio for
497
+ * @internal
498
+ */
499
+ const initializeWebAudioForChannel = (channelNumber) => __awaiter(void 0, void 0, void 0, function* () {
500
+ const channel = info_1.audioChannels[channelNumber];
501
+ if (!channel || channel.webAudioContext)
502
+ return;
503
+ const audioContext = (0, web_audio_1.getAudioContext)();
504
+ if (!audioContext) {
505
+ throw new Error('AudioContext creation failed');
506
+ }
507
+ // Resume audio context if needed (for autoplay policy)
508
+ yield (0, web_audio_1.resumeAudioContext)(audioContext);
509
+ channel.webAudioContext = audioContext;
510
+ channel.webAudioNodes = new Map();
511
+ // Initialize Web Audio nodes for existing audio elements
512
+ for (const audio of channel.queue) {
513
+ const nodes = (0, web_audio_1.createWebAudioNodes)(audio, audioContext);
514
+ if (!nodes) {
515
+ throw new Error('Node creation failed');
516
+ }
517
+ channel.webAudioNodes.set(audio, nodes);
518
+ // Set initial volume to match channel volume
519
+ nodes.gainNode.gain.value = channel.volume;
520
+ }
521
+ });
522
+ /**
523
+ * Sets volume for an audio element using the appropriate method (Web Audio API or standard)
524
+ * @param audio - The audio element to set volume for
525
+ * @param volume - Volume level (0-1)
526
+ * @param channelNumber - The channel number this audio belongs to
527
+ * @param transitionDuration - Optional transition duration in milliseconds
528
+ * @internal
529
+ */
530
+ const setVolumeForAudio = (audio, volume, channelNumber, transitionDuration) => __awaiter(void 0, void 0, void 0, function* () {
531
+ const channel = info_1.audioChannels[channelNumber];
532
+ // Use Web Audio API if available and initialized
533
+ if ((channel === null || channel === void 0 ? void 0 : channel.webAudioContext) && channel.webAudioNodes) {
534
+ const nodes = channel.webAudioNodes.get(audio);
535
+ if (nodes) {
536
+ (0, web_audio_1.setWebAudioVolume)(nodes.gainNode, volume, transitionDuration);
537
+ return;
538
+ }
539
+ }
540
+ // Fallback to standard HTMLAudioElement volume control
541
+ audio.volume = volume;
542
+ });
543
+ /**
544
+ * Initializes Web Audio API nodes for a new audio element
545
+ * @param audio - The audio element to initialize nodes for
546
+ * @param channelNumber - The channel number this audio belongs to
547
+ * @internal
548
+ */
549
+ const initializeWebAudioForAudio = (audio, channelNumber) => __awaiter(void 0, void 0, void 0, function* () {
550
+ const channel = info_1.audioChannels[channelNumber];
551
+ if (!channel)
552
+ return;
553
+ // Initialize Web Audio API for the channel if needed
554
+ if ((0, web_audio_1.shouldUseWebAudio)() && !channel.webAudioContext) {
555
+ yield initializeWebAudioForChannel(channelNumber);
556
+ }
557
+ // Create nodes for this specific audio element
558
+ if (channel.webAudioContext && channel.webAudioNodes && !channel.webAudioNodes.has(audio)) {
559
+ const nodes = (0, web_audio_1.createWebAudioNodes)(audio, channel.webAudioContext);
560
+ if (nodes) {
561
+ channel.webAudioNodes.set(audio, nodes);
562
+ // Set initial volume to match channel volume
563
+ nodes.gainNode.gain.value = channel.volume;
564
+ }
565
+ }
566
+ });
567
+ exports.initializeWebAudioForAudio = initializeWebAudioForAudio;
568
+ /**
569
+ * Cleans up Web Audio API nodes for an audio element
570
+ * @param audio - The audio element to clean up nodes for
571
+ * @param channelNumber - The channel number this audio belongs to
572
+ * @internal
573
+ */
574
+ const cleanupWebAudioForAudio = (audio, channelNumber) => {
575
+ const channel = info_1.audioChannels[channelNumber];
576
+ if (!(channel === null || channel === void 0 ? void 0 : channel.webAudioNodes))
577
+ return;
578
+ const nodes = channel.webAudioNodes.get(audio);
579
+ if (nodes) {
580
+ (0, web_audio_1.cleanupWebAudioNodes)(nodes);
581
+ channel.webAudioNodes.delete(audio);
582
+ }
583
+ };
584
+ exports.cleanupWebAudioForAudio = cleanupWebAudioForAudio;