audio-channel-queue 1.12.1-beta.0 → 1.12.1-beta.3

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/README.md CHANGED
@@ -5,20 +5,20 @@ The purpose of this package is to help manage the playback of audio files.
5
5
 
6
6
  📚 [Docs](https://tonycarpenter21.github.io/audio-queue-docs/)
7
7
 
8
- ## 🌟 Key Features
8
+ ## Key Features
9
9
 
10
- - **Multi-channel queue management** - Independent audio queues for concurrent playback
11
- - **Pause/Resume functionality** - Full playback control for individual channels or all channels
12
- - **Volume control with ducking** - Dynamic volume management and automatic background audio reduction
13
- - **Loop support** - Seamless audio looping for background music and ambient sounds
14
- - **Priority queueing** - Add urgent audio to the front of any queue
15
- - **Real-time progress tracking** - Comprehensive playback monitoring and metadata
16
- - **Event-driven architecture** - Extensive callback system for UI integration
17
- - **TypeScript support** - Full type definitions and IntelliSense support
18
- - **Zero dependencies** - Lightweight and self-contained
19
- - **Backward compatible** - All existing APIs continue to work
10
+ - **Multi-channel queue management** - Independent audio queues for concurrent playback
11
+ - **Pause/Resume functionality** - Full playback control for individual channels or all channels
12
+ - **Volume control with ducking** - Dynamic volume management, global volume control, and automatic background audio reduction
13
+ - **Loop support** - Seamless audio looping for background music and ambient sounds
14
+ - **Priority queueing** - Add urgent audio to the front of any queue
15
+ - **Real-time progress tracking** - Comprehensive playback monitoring and metadata
16
+ - **Event-driven architecture** - Extensive callback system for UI integration
17
+ - **TypeScript support** - Full type definitions and IntelliSense support
18
+ - **Zero dependencies** - Lightweight and self-contained
19
+ - **Backward compatible** - All existing APIs continue to work
20
20
 
21
- This package offers TypeScript support 📘, boasts zero dependencies 🚫, and is released under the MIT license 📜. As an added bonus, it's NON-GMO 🌱 and 100% Free Range Organic 🐓.
21
+ This package offers TypeScript support, has zero dependencies, and is released under the MIT license.
22
22
 
23
23
  To preview this package and see how it works with visualized code examples, check out the demo that can be found here: [Audio Channel Queue Demo](https://tonycarpenter21.github.io/audio-queue-demo/). (A link to the demo repo can be found here: [Audio Channel Queue Demo Repo](https://github.com/tonycarpenter21/audio-queue-demo).)
24
24
 
@@ -32,18 +32,18 @@ Documentation can be found [here](https://tonycarpenter21.github.io/audio-queue-
32
32
 
33
33
  This package is designed for **browser environments** and uses the Web Audio API (`HTMLAudioElement`). It is **not** intended for Node.js server-side use.
34
34
 
35
- ### **Supported Browsers:**
35
+ ### **Supported Browsers:**
36
36
  - **Chrome 51+** (June 2016)
37
37
  - **Firefox 54+** (June 2017)
38
38
  - **Safari 10+** (September 2016)
39
39
  - **Edge 15+** (April 2017)
40
40
  - **Mobile browsers** with HTML5 audio support
41
41
 
42
- ### 🛠️ **Development Requirements:**
42
+ ### **Development Requirements:**
43
43
  - **Node.js 14+** (for building and testing only)
44
44
  - **TypeScript 4.5+** (included in devDependencies)
45
45
 
46
- ### ⚠️ **Not Supported:**
46
+ ### **Not Supported:**
47
47
  - Node.js server environments (no HTMLAudioElement)
48
48
  - Internet Explorer (lacks ES6 support)
49
49
  - Web Workers (no DOM access)
@@ -197,6 +197,47 @@ await setAllChannelsVolume(0.6); // Set all channels to 60% volume
197
197
  await setAllChannelsVolume(0.0); // Mute all channels
198
198
  ```
199
199
 
200
+ ### Global Volume Control
201
+ ```typescript
202
+ // Set a global volume multiplier that affects all channels while preserving their relative levels.
203
+ setGlobalVolume(volume);
204
+ await setGlobalVolume(0.5); // Set global volume to 50%
205
+ ```
206
+
207
+ ```typescript
208
+ // Get the current global volume level.
209
+ getGlobalVolume();
210
+ const globalVol = getGlobalVolume(); // Returns current global volume (0-1)
211
+ console.log(`Global volume: ${(getGlobalVolume() * 100).toFixed(0)}%`);
212
+ ```
213
+
214
+ **How Global Volume Works:**
215
+ The global volume acts as a **global volume multiplier** - it scales all channel volumes proportionally while preserving their individual settings. This is perfect for implementing a global volume slider in your UI.
216
+
217
+ ```typescript
218
+ // Example: Setting up different audio types with a global volume control
219
+ await setChannelVolume(0, 0.3); // SFX channel at 30%
220
+ await setChannelVolume(1, 0.7); // Music channel at 70%
221
+ await setChannelVolume(2, 0.5); // Voice channel at 50%
222
+
223
+ // User adjusts global volume slider to 50%
224
+ await setGlobalVolume(0.5);
225
+
226
+ // Actual playback volumes become:
227
+ // - SFX: 30% × 50% = 15%
228
+ // - Music: 70% × 50% = 35%
229
+ // - Voice: 50% × 50% = 25%
230
+
231
+ // Channel volumes remain at their original settings (0.3, 0.7, 0.5)
232
+ // So when global volume is increased, ratios are preserved
233
+ await setGlobalVolume(1.0); // Back to full volume
234
+ // - SFX: 30% × 100% = 30%
235
+ // - Music: 70% × 100% = 70%
236
+ // - Voice: 50% × 100% = 50%
237
+ ```
238
+
239
+ **Note:** `setAllChannelsVolume()` sets all channels to the **same** volume, while `setGlobalVolume()` **multiplies** all channels by the same amount, preserving their relative differences.
240
+
200
241
  ### Volume Ducking (Background Audio Reduction)
201
242
  ```typescript
202
243
  // Automatically reduce other channels' volume when priority audio plays.
package/dist/core.js CHANGED
@@ -318,9 +318,10 @@ const playAudioQueue = (channelNumber) => __awaiter(void 0, void 0, void 0, func
318
318
  if (!channel || channel.queue.length === 0)
319
319
  return;
320
320
  const currentAudio = channel.queue[0];
321
- // Apply channel volume if not already set
321
+ // Apply channel volume with global volume multiplier if not already set
322
322
  if (currentAudio.volume === 1.0 && channel.volume !== undefined) {
323
- currentAudio.volume = channel.volume;
323
+ const globalVolume = (0, volume_1.getGlobalVolume)();
324
+ currentAudio.volume = channel.volume * globalVolume;
324
325
  }
325
326
  (0, events_1.setupProgressTracking)(currentAudio, channelNumber, info_1.audioChannels);
326
327
  // Apply volume ducking when audio starts
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ export { queueAudio, queueAudioPriority, stopCurrentAudioInChannel, stopAllAudio
7
7
  export { clearQueueAfterCurrent, getQueueItemInfo, getQueueLength, removeQueuedItem, reorderQueue, swapQueueItems } from './queue-manipulation';
8
8
  export { getErrorRecovery, getRetryConfig, offAudioError, onAudioError, retryFailedAudio, setErrorRecovery, setRetryConfig } from './errors';
9
9
  export { getAllChannelsPauseState, isChannelPaused, pauseAllChannels, pauseAllWithFade, pauseChannel, pauseWithFade, resumeAllChannels, resumeAllWithFade, resumeChannel, resumeWithFade, togglePauseAllChannels, togglePauseAllWithFade, togglePauseChannel, togglePauseWithFade } from './pause';
10
- export { cancelAllVolumeTransitions, cancelVolumeTransition, clearVolumeDucking, getAllChannelsVolume, getChannelVolume, getFadeConfig, setAllChannelsVolume, setChannelVolume, setVolumeDucking, transitionVolume } from './volume';
10
+ export { cancelAllVolumeTransitions, cancelVolumeTransition, clearVolumeDucking, getAllChannelsVolume, getChannelVolume, getFadeConfig, getGlobalVolume, setAllChannelsVolume, setChannelVolume, setGlobalVolume, setVolumeDucking, transitionVolume } from './volume';
11
11
  export { cleanupWebAudioNodes, createWebAudioNodes, getAudioContext, getWebAudioConfig, getWebAudioSupport, getWebAudioVolume, isIOSDevice, isWebAudioSupported, resumeAudioContext, setWebAudioConfig, setWebAudioVolume, shouldUseWebAudio } from './web-audio';
12
12
  export { getAllChannelsInfo, getCurrentAudioInfo, getQueueSnapshot, offAudioComplete, offAudioPause, offAudioProgress, offAudioResume, offAudioStart, offQueueChange, onAudioComplete, onAudioPause, onAudioProgress, onAudioResume, onAudioStart, onQueueChange } from './info';
13
13
  export { audioChannels } from './info';
package/dist/index.js CHANGED
@@ -5,8 +5,8 @@
5
5
  * volume management with ducking, progress tracking, and comprehensive event system
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.createWebAudioNodes = exports.cleanupWebAudioNodes = exports.transitionVolume = exports.setVolumeDucking = exports.setChannelVolume = exports.setAllChannelsVolume = exports.getFadeConfig = exports.getChannelVolume = exports.getAllChannelsVolume = exports.clearVolumeDucking = exports.cancelVolumeTransition = exports.cancelAllVolumeTransitions = exports.togglePauseWithFade = exports.togglePauseChannel = exports.togglePauseAllWithFade = exports.togglePauseAllChannels = exports.resumeWithFade = exports.resumeChannel = exports.resumeAllWithFade = exports.resumeAllChannels = exports.pauseWithFade = exports.pauseChannel = exports.pauseAllWithFade = exports.pauseAllChannels = exports.isChannelPaused = exports.getAllChannelsPauseState = exports.setRetryConfig = exports.setErrorRecovery = exports.retryFailedAudio = exports.onAudioError = exports.offAudioError = exports.getRetryConfig = exports.getErrorRecovery = exports.swapQueueItems = exports.reorderQueue = exports.removeQueuedItem = exports.getQueueLength = exports.getQueueItemInfo = exports.clearQueueAfterCurrent = exports.setChannelQueueLimit = exports.getQueueConfig = exports.setQueueConfig = exports.destroyAllChannels = exports.destroyChannel = exports.playAudioQueue = exports.stopAllAudio = exports.stopAllAudioInChannel = exports.stopCurrentAudioInChannel = exports.queueAudioPriority = exports.queueAudio = void 0;
9
- exports.GLOBAL_PROGRESS_KEY = exports.TimerType = exports.MAX_CHANNELS = exports.FadeType = exports.EasingType = exports.AudioErrorType = exports.validateAudioUrl = exports.sanitizeForDisplay = exports.getAudioInfoFromElement = exports.extractFileName = exports.createQueueSnapshot = exports.cleanWebpackFilename = exports.audioChannels = exports.onQueueChange = exports.onAudioStart = exports.onAudioResume = exports.onAudioProgress = exports.onAudioPause = exports.onAudioComplete = exports.offQueueChange = exports.offAudioStart = exports.offAudioResume = exports.offAudioProgress = exports.offAudioPause = exports.offAudioComplete = exports.getQueueSnapshot = exports.getCurrentAudioInfo = exports.getAllChannelsInfo = exports.shouldUseWebAudio = exports.setWebAudioVolume = exports.setWebAudioConfig = exports.resumeAudioContext = exports.isWebAudioSupported = exports.isIOSDevice = exports.getWebAudioVolume = exports.getWebAudioSupport = exports.getWebAudioConfig = exports.getAudioContext = void 0;
8
+ exports.transitionVolume = exports.setVolumeDucking = exports.setGlobalVolume = exports.setChannelVolume = exports.setAllChannelsVolume = exports.getGlobalVolume = exports.getFadeConfig = exports.getChannelVolume = exports.getAllChannelsVolume = exports.clearVolumeDucking = exports.cancelVolumeTransition = exports.cancelAllVolumeTransitions = exports.togglePauseWithFade = exports.togglePauseChannel = exports.togglePauseAllWithFade = exports.togglePauseAllChannels = exports.resumeWithFade = exports.resumeChannel = exports.resumeAllWithFade = exports.resumeAllChannels = exports.pauseWithFade = exports.pauseChannel = exports.pauseAllWithFade = exports.pauseAllChannels = exports.isChannelPaused = exports.getAllChannelsPauseState = exports.setRetryConfig = exports.setErrorRecovery = exports.retryFailedAudio = exports.onAudioError = exports.offAudioError = exports.getRetryConfig = exports.getErrorRecovery = exports.swapQueueItems = exports.reorderQueue = exports.removeQueuedItem = exports.getQueueLength = exports.getQueueItemInfo = exports.clearQueueAfterCurrent = exports.setChannelQueueLimit = exports.getQueueConfig = exports.setQueueConfig = exports.destroyAllChannels = exports.destroyChannel = exports.playAudioQueue = exports.stopAllAudio = exports.stopAllAudioInChannel = exports.stopCurrentAudioInChannel = exports.queueAudioPriority = exports.queueAudio = void 0;
9
+ exports.GLOBAL_PROGRESS_KEY = exports.TimerType = exports.MAX_CHANNELS = exports.FadeType = exports.EasingType = exports.AudioErrorType = exports.validateAudioUrl = exports.sanitizeForDisplay = exports.getAudioInfoFromElement = exports.extractFileName = exports.createQueueSnapshot = exports.cleanWebpackFilename = exports.audioChannels = exports.onQueueChange = exports.onAudioStart = exports.onAudioResume = exports.onAudioProgress = exports.onAudioPause = exports.onAudioComplete = exports.offQueueChange = exports.offAudioStart = exports.offAudioResume = exports.offAudioProgress = exports.offAudioPause = exports.offAudioComplete = exports.getQueueSnapshot = exports.getCurrentAudioInfo = exports.getAllChannelsInfo = exports.shouldUseWebAudio = exports.setWebAudioVolume = exports.setWebAudioConfig = exports.resumeAudioContext = exports.isWebAudioSupported = exports.isIOSDevice = exports.getWebAudioVolume = exports.getWebAudioSupport = exports.getWebAudioConfig = exports.getAudioContext = exports.createWebAudioNodes = exports.cleanupWebAudioNodes = void 0;
10
10
  // Core queue management functions
11
11
  var core_1 = require("./core");
12
12
  Object.defineProperty(exports, "queueAudio", { enumerable: true, get: function () { return core_1.queueAudio; } });
@@ -61,8 +61,10 @@ Object.defineProperty(exports, "clearVolumeDucking", { enumerable: true, get: fu
61
61
  Object.defineProperty(exports, "getAllChannelsVolume", { enumerable: true, get: function () { return volume_1.getAllChannelsVolume; } });
62
62
  Object.defineProperty(exports, "getChannelVolume", { enumerable: true, get: function () { return volume_1.getChannelVolume; } });
63
63
  Object.defineProperty(exports, "getFadeConfig", { enumerable: true, get: function () { return volume_1.getFadeConfig; } });
64
+ Object.defineProperty(exports, "getGlobalVolume", { enumerable: true, get: function () { return volume_1.getGlobalVolume; } });
64
65
  Object.defineProperty(exports, "setAllChannelsVolume", { enumerable: true, get: function () { return volume_1.setAllChannelsVolume; } });
65
66
  Object.defineProperty(exports, "setChannelVolume", { enumerable: true, get: function () { return volume_1.setChannelVolume; } });
67
+ Object.defineProperty(exports, "setGlobalVolume", { enumerable: true, get: function () { return volume_1.setGlobalVolume; } });
66
68
  Object.defineProperty(exports, "setVolumeDucking", { enumerable: true, get: function () { return volume_1.setVolumeDucking; } });
67
69
  Object.defineProperty(exports, "transitionVolume", { enumerable: true, get: function () { return volume_1.transitionVolume; } });
68
70
  // Web Audio API support functions
package/dist/volume.d.ts CHANGED
@@ -74,6 +74,31 @@ export declare const getAllChannelsVolume: () => number[];
74
74
  * ```
75
75
  */
76
76
  export declare const setAllChannelsVolume: (volume: number) => Promise<void>;
77
+ /**
78
+ * Sets the global volume multiplier that affects all channels
79
+ * This acts as a global volume control - individual channel volumes are multiplied by this value
80
+ * @param volume - Global volume level (0-1, will be clamped to this range)
81
+ * @example
82
+ * ```typescript
83
+ * // Set channel-specific volumes
84
+ * await setChannelVolume(0, 0.8); // SFX at 80%
85
+ * await setChannelVolume(1, 0.6); // Music at 60%
86
+ *
87
+ * // Apply global volume of 50% - all channels play at half their set volume
88
+ * await setGlobalVolume(0.5); // SFX now plays at 40%, music at 30%
89
+ * ```
90
+ */
91
+ export declare const setGlobalVolume: (volume: number) => Promise<void>;
92
+ /**
93
+ * Gets the current global volume multiplier
94
+ * @returns Current global volume level (0-1), defaults to 1.0
95
+ * @example
96
+ * ```typescript
97
+ * const globalVol = getGlobalVolume();
98
+ * console.log(`Global volume is ${globalVol * 100}%`);
99
+ * ```
100
+ */
101
+ export declare const getGlobalVolume: () => number;
77
102
  /**
78
103
  * Configures volume ducking for channels. When the priority channel plays audio,
79
104
  * all other channels will be automatically reduced to the ducking volume level
package/dist/volume.js CHANGED
@@ -12,7 +12,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
12
12
  });
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
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;
15
+ exports.cleanupWebAudioForAudio = exports.initializeWebAudioForAudio = exports.cancelAllVolumeTransitions = exports.cancelVolumeTransition = exports.restoreVolumeLevels = exports.applyVolumeDucking = exports.clearVolumeDucking = exports.setVolumeDucking = exports.getGlobalVolume = exports.setGlobalVolume = 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
18
  const web_audio_1 = require("./web-audio");
@@ -20,6 +20,11 @@ const web_audio_1 = require("./web-audio");
20
20
  const activeTransitions = new Map();
21
21
  // Track which timer type was used for each channel
22
22
  const timerTypes = new Map();
23
+ /**
24
+ * Global volume multiplier that affects all channels
25
+ * Acts as a global volume control (0-1)
26
+ */
27
+ let globalVolume = 1.0;
23
28
  /**
24
29
  * Global volume ducking configuration
25
30
  * Stores the volume ducking settings that apply to all channels
@@ -261,6 +266,48 @@ const setAllChannelsVolume = (volume) => __awaiter(void 0, void 0, void 0, funct
261
266
  yield Promise.all(promises);
262
267
  });
263
268
  exports.setAllChannelsVolume = setAllChannelsVolume;
269
+ /**
270
+ * Sets the global volume multiplier that affects all channels
271
+ * This acts as a global volume control - individual channel volumes are multiplied by this value
272
+ * @param volume - Global volume level (0-1, will be clamped to this range)
273
+ * @example
274
+ * ```typescript
275
+ * // Set channel-specific volumes
276
+ * await setChannelVolume(0, 0.8); // SFX at 80%
277
+ * await setChannelVolume(1, 0.6); // Music at 60%
278
+ *
279
+ * // Apply global volume of 50% - all channels play at half their set volume
280
+ * await setGlobalVolume(0.5); // SFX now plays at 40%, music at 30%
281
+ * ```
282
+ */
283
+ const setGlobalVolume = (volume) => __awaiter(void 0, void 0, void 0, function* () {
284
+ // Clamp to valid range
285
+ globalVolume = Math.max(0, Math.min(1, volume));
286
+ // Update all currently playing audio to reflect the new global volume
287
+ // Note: setVolumeForAudio internally multiplies channel.volume by globalVolume
288
+ const updatePromises = [];
289
+ info_1.audioChannels.forEach((channel, channelNumber) => {
290
+ if (channel && channel.queue.length > 0) {
291
+ const currentAudio = channel.queue[0];
292
+ updatePromises.push(setVolumeForAudio(currentAudio, channel.volume, channelNumber));
293
+ }
294
+ });
295
+ yield Promise.all(updatePromises);
296
+ });
297
+ exports.setGlobalVolume = setGlobalVolume;
298
+ /**
299
+ * Gets the current global volume multiplier
300
+ * @returns Current global volume level (0-1), defaults to 1.0
301
+ * @example
302
+ * ```typescript
303
+ * const globalVol = getGlobalVolume();
304
+ * console.log(`Global volume is ${globalVol * 100}%`);
305
+ * ```
306
+ */
307
+ const getGlobalVolume = () => {
308
+ return globalVolume;
309
+ };
310
+ exports.getGlobalVolume = getGlobalVolume;
264
311
  /**
265
312
  * Configures volume ducking for channels. When the priority channel plays audio,
266
313
  * all other channels will be automatically reduced to the ducking volume level
@@ -401,6 +448,8 @@ exports.restoreVolumeLevels = restoreVolumeLevels;
401
448
  * @internal
402
449
  */
403
450
  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) {
451
+ // Apply global volume multiplier
452
+ const actualTargetVolume = targetVolume * globalVolume;
404
453
  // Try to use Web Audio API if available and channel number is provided
405
454
  if (channelNumber !== undefined) {
406
455
  const channel = info_1.audioChannels[channelNumber];
@@ -408,23 +457,23 @@ const transitionAudioVolume = (audio_1, targetVolume_1, ...args_1) => __awaiter(
408
457
  const nodes = channel.webAudioNodes.get(audio);
409
458
  if (nodes) {
410
459
  // Use Web Audio API for smooth transitions
411
- (0, web_audio_1.setWebAudioVolume)(nodes.gainNode, targetVolume, duration);
460
+ (0, web_audio_1.setWebAudioVolume)(nodes.gainNode, actualTargetVolume, duration);
412
461
  // Also update the audio element's volume property for consistency
413
- audio.volume = targetVolume;
462
+ audio.volume = actualTargetVolume;
414
463
  return;
415
464
  }
416
465
  }
417
466
  }
418
467
  // Fallback to standard HTMLAudioElement volume control with manual transition
419
468
  const startVolume = audio.volume;
420
- const volumeDelta = targetVolume - startVolume;
469
+ const volumeDelta = actualTargetVolume - startVolume;
421
470
  // If no change needed, resolve immediately
422
471
  if (Math.abs(volumeDelta) < 0.001) {
423
472
  return Promise.resolve();
424
473
  }
425
474
  // Handle zero or negative duration - instant change
426
475
  if (duration <= 0) {
427
- audio.volume = targetVolume;
476
+ audio.volume = actualTargetVolume;
428
477
  return Promise.resolve();
429
478
  }
430
479
  const startTime = performance.now();
@@ -522,23 +571,25 @@ const initializeWebAudioForChannel = (channelNumber) => __awaiter(void 0, void 0
522
571
  /**
523
572
  * Sets volume for an audio element using the appropriate method (Web Audio API or standard)
524
573
  * @param audio - The audio element to set volume for
525
- * @param volume - Volume level (0-1)
574
+ * @param volume - Channel volume level (0-1) - will be multiplied by global volume
526
575
  * @param channelNumber - The channel number this audio belongs to
527
576
  * @param transitionDuration - Optional transition duration in milliseconds
528
577
  * @internal
529
578
  */
530
579
  const setVolumeForAudio = (audio, volume, channelNumber, transitionDuration) => __awaiter(void 0, void 0, void 0, function* () {
531
580
  const channel = info_1.audioChannels[channelNumber];
581
+ // Apply global volume multiplier to the channel volume
582
+ const actualVolume = volume * globalVolume;
532
583
  // Use Web Audio API if available and initialized
533
584
  if ((channel === null || channel === void 0 ? void 0 : channel.webAudioContext) && channel.webAudioNodes) {
534
585
  const nodes = channel.webAudioNodes.get(audio);
535
586
  if (nodes) {
536
- (0, web_audio_1.setWebAudioVolume)(nodes.gainNode, volume, transitionDuration);
587
+ (0, web_audio_1.setWebAudioVolume)(nodes.gainNode, actualVolume, transitionDuration);
537
588
  return;
538
589
  }
539
590
  }
540
591
  // Fallback to standard HTMLAudioElement volume control
541
- audio.volume = volume;
592
+ audio.volume = actualVolume;
542
593
  });
543
594
  /**
544
595
  * Initializes Web Audio API nodes for a new audio element
@@ -559,8 +610,8 @@ const initializeWebAudioForAudio = (audio, channelNumber) => __awaiter(void 0, v
559
610
  const nodes = (0, web_audio_1.createWebAudioNodes)(audio, channel.webAudioContext);
560
611
  if (nodes) {
561
612
  channel.webAudioNodes.set(audio, nodes);
562
- // Set initial volume to match channel volume
563
- nodes.gainNode.gain.value = channel.volume;
613
+ // Set initial volume to match channel volume with global volume multiplier
614
+ nodes.gainNode.gain.value = channel.volume * globalVolume;
564
615
  }
565
616
  }
566
617
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "audio-channel-queue",
3
- "version": "1.12.1-beta.0",
3
+ "version": "1.12.1-beta.3",
4
4
  "description": "Allows you to queue audio files to different playback channels.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -10,7 +10,7 @@
10
10
  ],
11
11
  "scripts": {
12
12
  "build": "tsc",
13
- "prepare": "npm run build",
13
+ "prepare": "husky",
14
14
  "test": "jest",
15
15
  "test:watch": "jest --watch",
16
16
  "test:coverage": "jest --coverage",
@@ -26,6 +26,7 @@
26
26
  "publish:minor": "npm version minor && npm run publish:release",
27
27
  "publish:major": "npm version major && npm run publish:release",
28
28
  "publish:release": "npm run validate && npm run build && npm publish",
29
+ "publish:beta": "npm version prerelease --preid=beta && npm publish --tag beta",
29
30
  "publish:dry": "npm run validate && npm run build && npm publish --dry-run"
30
31
  },
31
32
  "repository": {
@@ -49,8 +50,10 @@
49
50
  },
50
51
  "devDependencies": {
51
52
  "@types/jest": "^29.5.13",
53
+ "husky": "^9.1.7",
52
54
  "jest": "^29.7.0",
53
55
  "jest-environment-jsdom": "^29.7.0",
56
+ "lint-staged": "^16.2.7",
54
57
  "rimraf": "^6.0.1",
55
58
  "ts-jest": "^29.2.5",
56
59
  "typescript": "^5.6.2",
package/src/core.ts CHANGED
@@ -14,10 +14,11 @@ import {
14
14
  } from './events';
15
15
  import {
16
16
  applyVolumeDucking,
17
- restoreVolumeLevels,
18
17
  cancelVolumeTransition,
18
+ cleanupWebAudioForAudio,
19
+ getGlobalVolume,
19
20
  initializeWebAudioForAudio,
20
- cleanupWebAudioForAudio
21
+ restoreVolumeLevels
21
22
  } from './volume';
22
23
  import { setupAudioErrorHandling, handleAudioError } from './errors';
23
24
 
@@ -372,9 +373,10 @@ export const playAudioQueue = async (channelNumber: number): Promise<void> => {
372
373
 
373
374
  const currentAudio: HTMLAudioElement = channel.queue[0];
374
375
 
375
- // Apply channel volume if not already set
376
+ // Apply channel volume with global volume multiplier if not already set
376
377
  if (currentAudio.volume === 1.0 && channel.volume !== undefined) {
377
- currentAudio.volume = channel.volume;
378
+ const globalVolume: number = getGlobalVolume();
379
+ currentAudio.volume = channel.volume * globalVolume;
378
380
  }
379
381
 
380
382
  setupProgressTracking(currentAudio, channelNumber, audioChannels);
package/src/index.ts CHANGED
@@ -66,8 +66,10 @@ export {
66
66
  getAllChannelsVolume,
67
67
  getChannelVolume,
68
68
  getFadeConfig,
69
+ getGlobalVolume,
69
70
  setAllChannelsVolume,
70
71
  setChannelVolume,
72
+ setGlobalVolume,
71
73
  setVolumeDucking,
72
74
  transitionVolume
73
75
  } from './volume';
package/src/volume.ts CHANGED
@@ -26,6 +26,12 @@ const activeTransitions: Map<number, number> = new Map();
26
26
  // Track which timer type was used for each channel
27
27
  const timerTypes: Map<number, TimerType> = new Map();
28
28
 
29
+ /**
30
+ * Global volume multiplier that affects all channels
31
+ * Acts as a global volume control (0-1)
32
+ */
33
+ let globalVolume: number = 1.0;
34
+
29
35
  /**
30
36
  * Global volume ducking configuration
31
37
  * Stores the volume ducking settings that apply to all channels
@@ -293,6 +299,50 @@ export const setAllChannelsVolume = async (volume: number): Promise<void> => {
293
299
  await Promise.all(promises);
294
300
  };
295
301
 
302
+ /**
303
+ * Sets the global volume multiplier that affects all channels
304
+ * This acts as a global volume control - individual channel volumes are multiplied by this value
305
+ * @param volume - Global volume level (0-1, will be clamped to this range)
306
+ * @example
307
+ * ```typescript
308
+ * // Set channel-specific volumes
309
+ * await setChannelVolume(0, 0.8); // SFX at 80%
310
+ * await setChannelVolume(1, 0.6); // Music at 60%
311
+ *
312
+ * // Apply global volume of 50% - all channels play at half their set volume
313
+ * await setGlobalVolume(0.5); // SFX now plays at 40%, music at 30%
314
+ * ```
315
+ */
316
+ export const setGlobalVolume = async (volume: number): Promise<void> => {
317
+ // Clamp to valid range
318
+ globalVolume = Math.max(0, Math.min(1, volume));
319
+
320
+ // Update all currently playing audio to reflect the new global volume
321
+ // Note: setVolumeForAudio internally multiplies channel.volume by globalVolume
322
+ const updatePromises: Promise<void>[] = [];
323
+ audioChannels.forEach((channel: ExtendedAudioQueueChannel, channelNumber: number) => {
324
+ if (channel && channel.queue.length > 0) {
325
+ const currentAudio: HTMLAudioElement = channel.queue[0];
326
+ updatePromises.push(setVolumeForAudio(currentAudio, channel.volume, channelNumber));
327
+ }
328
+ });
329
+
330
+ await Promise.all(updatePromises);
331
+ };
332
+
333
+ /**
334
+ * Gets the current global volume multiplier
335
+ * @returns Current global volume level (0-1), defaults to 1.0
336
+ * @example
337
+ * ```typescript
338
+ * const globalVol = getGlobalVolume();
339
+ * console.log(`Global volume is ${globalVol * 100}%`);
340
+ * ```
341
+ */
342
+ export const getGlobalVolume = (): number => {
343
+ return globalVolume;
344
+ };
345
+
296
346
  /**
297
347
  * Configures volume ducking for channels. When the priority channel plays audio,
298
348
  * all other channels will be automatically reduced to the ducking volume level
@@ -457,6 +507,9 @@ const transitionAudioVolume = async (
457
507
  easing: EasingType = EasingType.EaseOut,
458
508
  channelNumber?: number
459
509
  ): Promise<void> => {
510
+ // Apply global volume multiplier
511
+ const actualTargetVolume: number = targetVolume * globalVolume;
512
+
460
513
  // Try to use Web Audio API if available and channel number is provided
461
514
  if (channelNumber !== undefined) {
462
515
  const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
@@ -464,9 +517,9 @@ const transitionAudioVolume = async (
464
517
  const nodes = channel.webAudioNodes.get(audio);
465
518
  if (nodes) {
466
519
  // Use Web Audio API for smooth transitions
467
- setWebAudioVolume(nodes.gainNode, targetVolume, duration);
520
+ setWebAudioVolume(nodes.gainNode, actualTargetVolume, duration);
468
521
  // Also update the audio element's volume property for consistency
469
- audio.volume = targetVolume;
522
+ audio.volume = actualTargetVolume;
470
523
  return;
471
524
  }
472
525
  }
@@ -474,7 +527,7 @@ const transitionAudioVolume = async (
474
527
 
475
528
  // Fallback to standard HTMLAudioElement volume control with manual transition
476
529
  const startVolume: number = audio.volume;
477
- const volumeDelta: number = targetVolume - startVolume;
530
+ const volumeDelta: number = actualTargetVolume - startVolume;
478
531
 
479
532
  // If no change needed, resolve immediately
480
533
  if (Math.abs(volumeDelta) < 0.001) {
@@ -483,7 +536,7 @@ const transitionAudioVolume = async (
483
536
 
484
537
  // Handle zero or negative duration - instant change
485
538
  if (duration <= 0) {
486
- audio.volume = targetVolume;
539
+ audio.volume = actualTargetVolume;
487
540
  return Promise.resolve();
488
541
  }
489
542
 
@@ -594,7 +647,7 @@ const initializeWebAudioForChannel = async (channelNumber: number): Promise<void
594
647
  /**
595
648
  * Sets volume for an audio element using the appropriate method (Web Audio API or standard)
596
649
  * @param audio - The audio element to set volume for
597
- * @param volume - Volume level (0-1)
650
+ * @param volume - Channel volume level (0-1) - will be multiplied by global volume
598
651
  * @param channelNumber - The channel number this audio belongs to
599
652
  * @param transitionDuration - Optional transition duration in milliseconds
600
653
  * @internal
@@ -607,17 +660,20 @@ const setVolumeForAudio = async (
607
660
  ): Promise<void> => {
608
661
  const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
609
662
 
663
+ // Apply global volume multiplier to the channel volume
664
+ const actualVolume: number = volume * globalVolume;
665
+
610
666
  // Use Web Audio API if available and initialized
611
667
  if (channel?.webAudioContext && channel.webAudioNodes) {
612
668
  const nodes = channel.webAudioNodes.get(audio);
613
669
  if (nodes) {
614
- setWebAudioVolume(nodes.gainNode, volume, transitionDuration);
670
+ setWebAudioVolume(nodes.gainNode, actualVolume, transitionDuration);
615
671
  return;
616
672
  }
617
673
  }
618
674
 
619
675
  // Fallback to standard HTMLAudioElement volume control
620
- audio.volume = volume;
676
+ audio.volume = actualVolume;
621
677
  };
622
678
 
623
679
  /**
@@ -643,8 +699,8 @@ export const initializeWebAudioForAudio = async (
643
699
  const nodes = createWebAudioNodes(audio, channel.webAudioContext);
644
700
  if (nodes) {
645
701
  channel.webAudioNodes.set(audio, nodes);
646
- // Set initial volume to match channel volume
647
- nodes.gainNode.gain.value = channel.volume;
702
+ // Set initial volume to match channel volume with global volume multiplier
703
+ nodes.gainNode.gain.value = channel.volume * globalVolume;
648
704
  }
649
705
  }
650
706
  };