audio-channel-queue 1.7.0 → 1.9.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/volume.ts CHANGED
@@ -2,20 +2,55 @@
2
2
  * @fileoverview Volume management functions for the audio-channel-queue package
3
3
  */
4
4
 
5
- import { ExtendedAudioQueueChannel, VolumeConfig } from './types';
5
+ import { ExtendedAudioQueueChannel, VolumeConfig, FadeType, FadeConfig, EasingType } from './types';
6
6
  import { audioChannels } from './info';
7
7
 
8
8
  // Store active volume transitions to handle interruptions
9
- const activeTransitions = new Map<number, number>();
9
+ const activeTransitions: Map<number, number> = new Map();
10
+
11
+ /**
12
+ * Predefined fade configurations for different transition types
13
+ */
14
+ const fadeConfigs: Record<FadeType, FadeConfig> = {
15
+ [FadeType.Dramatic]: {
16
+ duration: 800,
17
+ pauseCurve: EasingType.EaseIn,
18
+ resumeCurve: EasingType.EaseOut
19
+ },
20
+ [FadeType.Gentle]: {
21
+ duration: 800,
22
+ pauseCurve: EasingType.EaseOut,
23
+ resumeCurve: EasingType.EaseIn
24
+ },
25
+ [FadeType.Linear]: {
26
+ duration: 800,
27
+ pauseCurve: EasingType.Linear,
28
+ resumeCurve: EasingType.Linear
29
+ }
30
+ };
31
+
32
+ /**
33
+ * Gets the fade configuration for a specific fade type
34
+ * @param fadeType - The fade type to get configuration for
35
+ * @returns Fade configuration object
36
+ * @example
37
+ * ```typescript
38
+ * const config = getFadeConfig('gentle');
39
+ * console.log(`Gentle fade duration: ${config.duration}ms`);
40
+ * ```
41
+ */
42
+ export const getFadeConfig = (fadeType: FadeType): FadeConfig => {
43
+ return { ...fadeConfigs[fadeType] };
44
+ };
10
45
 
11
46
  /**
12
47
  * Easing functions for smooth volume transitions
13
48
  */
14
- const easingFunctions = {
15
- linear: (t: number): number => t,
16
- 'ease-in': (t: number): number => t * t,
17
- 'ease-out': (t: number): number => t * (2 - t),
18
- 'ease-in-out': (t: number): number => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
49
+ const easingFunctions: Record<EasingType, (t: number) => number> = {
50
+ [EasingType.Linear]: (t: number): number => t,
51
+ [EasingType.EaseIn]: (t: number): number => t * t,
52
+ [EasingType.EaseOut]: (t: number): number => t * (2 - t),
53
+ [EasingType.EaseInOut]: (t: number): number => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t)
19
54
  };
20
55
 
21
56
  /**
@@ -34,7 +69,7 @@ export const transitionVolume = async (
34
69
  channelNumber: number,
35
70
  targetVolume: number,
36
71
  duration: number = 250,
37
- easing: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out' = 'ease-out'
72
+ easing: EasingType = EasingType.EaseOut
38
73
  ): Promise<void> => {
39
74
  const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
40
75
  if (!channel || channel.queue.length === 0) return;
@@ -42,10 +77,17 @@ export const transitionVolume = async (
42
77
  const currentAudio: HTMLAudioElement = channel.queue[0];
43
78
  const startVolume: number = currentAudio.volume;
44
79
  const volumeDelta: number = targetVolume - startVolume;
45
-
80
+
46
81
  // Cancel any existing transition for this channel
47
82
  if (activeTransitions.has(channelNumber)) {
48
- clearTimeout(activeTransitions.get(channelNumber));
83
+ const transitionId = activeTransitions.get(channelNumber);
84
+ if (transitionId) {
85
+ // Handle both requestAnimationFrame and setTimeout IDs
86
+ if (typeof cancelAnimationFrame !== 'undefined') {
87
+ cancelAnimationFrame(transitionId);
88
+ }
89
+ clearTimeout(transitionId);
90
+ }
49
91
  activeTransitions.delete(channelNumber);
50
92
  }
51
93
 
@@ -72,10 +114,10 @@ export const transitionVolume = async (
72
114
  const elapsed: number = performance.now() - startTime;
73
115
  const progress: number = Math.min(elapsed / duration, 1);
74
116
  const easedProgress: number = easingFn(progress);
75
-
76
- const currentVolume: number = startVolume + (volumeDelta * easedProgress);
117
+
118
+ const currentVolume: number = startVolume + volumeDelta * easedProgress;
77
119
  const clampedVolume: number = Math.max(0, Math.min(1, currentVolume));
78
-
120
+
79
121
  // Apply volume to both channel config and current audio
80
122
  channel.volume = clampedVolume;
81
123
  if (channel.queue.length > 0) {
@@ -116,13 +158,13 @@ export const transitionVolume = async (
116
158
  * ```
117
159
  */
118
160
  export const setChannelVolume = async (
119
- channelNumber: number,
161
+ channelNumber: number,
120
162
  volume: number,
121
163
  transitionDuration?: number,
122
- easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out'
164
+ easing?: EasingType
123
165
  ): Promise<void> => {
124
166
  const clampedVolume: number = Math.max(0, Math.min(1, volume));
125
-
167
+
126
168
  if (!audioChannels[channelNumber]) {
127
169
  audioChannels[channelNumber] = {
128
170
  audioCompleteCallbacks: new Set(),
@@ -166,7 +208,7 @@ export const setChannelVolume = async (
166
208
  */
167
209
  export const getChannelVolume = (channelNumber: number = 0): number => {
168
210
  const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
169
- return channel?.volume || 1.0;
211
+ return channel?.volume ?? 1.0;
170
212
  };
171
213
 
172
214
  /**
@@ -181,9 +223,7 @@ export const getChannelVolume = (channelNumber: number = 0): number => {
181
223
  * ```
182
224
  */
183
225
  export const getAllChannelsVolume = (): number[] => {
184
- return audioChannels.map((channel: ExtendedAudioQueueChannel) =>
185
- channel?.volume || 1.0
186
- );
226
+ return audioChannels.map((channel: ExtendedAudioQueueChannel) => channel?.volume ?? 1.0);
187
227
  };
188
228
 
189
229
  /**
@@ -279,11 +319,11 @@ export const applyVolumeDucking = async (activeChannelNumber: number): Promise<v
279
319
  audioChannels.forEach((channel: ExtendedAudioQueueChannel, channelNumber: number) => {
280
320
  if (channel?.volumeConfig) {
281
321
  const config: VolumeConfig = channel.volumeConfig;
282
-
322
+
283
323
  if (activeChannelNumber === config.priorityChannel) {
284
- const duration = config.duckTransitionDuration || 250;
285
- const easing = config.transitionEasing || 'ease-out';
286
-
324
+ const duration = config.duckTransitionDuration ?? 250;
325
+ const easing = config.transitionEasing ?? EasingType.EaseOut;
326
+
287
327
  // Priority channel is active, duck other channels
288
328
  if (channelNumber === config.priorityChannel) {
289
329
  transitionPromises.push(
@@ -302,6 +342,28 @@ export const applyVolumeDucking = async (activeChannelNumber: number): Promise<v
302
342
  await Promise.all(transitionPromises);
303
343
  };
304
344
 
345
+ /**
346
+ * Fades the volume for a specific channel over time (alias for transitionVolume with improved naming)
347
+ * @param channelNumber - The channel number to fade
348
+ * @param targetVolume - Target volume level (0-1)
349
+ * @param duration - Fade duration in milliseconds (defaults to 250)
350
+ * @param easing - Easing function type (defaults to 'ease-out')
351
+ * @returns Promise that resolves when fade completes
352
+ * @example
353
+ * ```typescript
354
+ * await fadeVolume(0, 0, 800, 'ease-in'); // Fade out over 800ms
355
+ * await fadeVolume(0, 1, 600, 'ease-out'); // Fade in over 600ms
356
+ * ```
357
+ */
358
+ export const fadeVolume = async (
359
+ channelNumber: number,
360
+ targetVolume: number,
361
+ duration: number = 250,
362
+ easing: EasingType = EasingType.EaseOut
363
+ ): Promise<void> => {
364
+ return transitionVolume(channelNumber, targetVolume, duration, easing);
365
+ };
366
+
305
367
  /**
306
368
  * Restores normal volume levels when priority channel stops with smooth transitions
307
369
  * @param stoppedChannelNumber - The channel that just stopped playing
@@ -313,14 +375,14 @@ export const restoreVolumeLevels = async (stoppedChannelNumber: number): Promise
313
375
  audioChannels.forEach((channel: ExtendedAudioQueueChannel, channelNumber: number) => {
314
376
  if (channel?.volumeConfig) {
315
377
  const config: VolumeConfig = channel.volumeConfig;
316
-
378
+
317
379
  if (stoppedChannelNumber === config.priorityChannel) {
318
- const duration = config.restoreTransitionDuration || 500;
319
- const easing = config.transitionEasing || 'ease-out';
320
-
380
+ const duration = config.restoreTransitionDuration ?? 500;
381
+ const easing = config.transitionEasing ?? EasingType.EaseOut;
382
+
321
383
  // Priority channel stopped, restore normal volumes
322
384
  transitionPromises.push(
323
- transitionVolume(channelNumber, channel.volume || 1.0, duration, easing)
385
+ transitionVolume(channelNumber, channel.volume ?? 1.0, duration, easing)
324
386
  );
325
387
  }
326
388
  }
@@ -328,4 +390,4 @@ export const restoreVolumeLevels = async (stoppedChannelNumber: number): Promise
328
390
 
329
391
  // Wait for all transitions to complete
330
392
  await Promise.all(transitionPromises);
331
- };
393
+ };