audio-channel-queue 1.8.0 → 1.10.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/info.js CHANGED
@@ -3,14 +3,73 @@
3
3
  * @fileoverview Audio information and progress tracking functions for the audio-channel-queue package
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.offAudioResume = exports.offAudioPause = exports.onAudioResume = exports.onAudioPause = exports.onAudioComplete = exports.onAudioStart = exports.offQueueChange = exports.onQueueChange = exports.offAudioProgress = exports.onAudioProgress = exports.getQueueSnapshot = exports.getAllChannelsInfo = exports.getCurrentAudioInfo = exports.audioChannels = void 0;
6
+ exports.offAudioResume = exports.offAudioPause = exports.onAudioResume = exports.onAudioPause = exports.onAudioComplete = exports.onAudioStart = exports.offQueueChange = exports.onQueueChange = exports.onAudioProgress = exports.getQueueSnapshot = exports.getAllChannelsInfo = exports.getCurrentAudioInfo = exports.audioChannels = void 0;
7
+ exports.offAudioProgress = offAudioProgress;
8
+ const types_1 = require("./types");
7
9
  const utils_1 = require("./utils");
8
10
  const events_1 = require("./events");
9
11
  /**
10
12
  * Global array to store audio channels with their queues and callback management
11
13
  * Each channel maintains its own audio queue and event callback sets
14
+ *
15
+ * Note: While you can inspect this array for debugging, direct modification is discouraged.
16
+ * Use the provided API functions for safe channel management.
12
17
  */
13
- exports.audioChannels = [];
18
+ exports.audioChannels = new Proxy([], {
19
+ deleteProperty(target, prop) {
20
+ if (typeof prop === 'string' && !isNaN(Number(prop))) {
21
+ // eslint-disable-next-line no-console
22
+ console.warn('Warning: Direct deletion from audioChannels detected. ' +
23
+ 'Consider using stopAllAudioInChannel() for proper cleanup.');
24
+ }
25
+ delete target[prop];
26
+ return true;
27
+ },
28
+ get(target, prop) {
29
+ const value = target[prop];
30
+ // Return channel objects with warnings on modification attempts
31
+ if (typeof value === 'object' &&
32
+ value !== null &&
33
+ typeof prop === 'string' &&
34
+ !isNaN(Number(prop))) {
35
+ return new Proxy(value, {
36
+ set(channelTarget, channelProp, channelValue) {
37
+ // Allow internal modifications but warn about direct property changes
38
+ if (typeof channelProp === 'string' &&
39
+ !['queue', 'volume', 'isPaused', 'isLocked', 'volumeConfig'].includes(channelProp)) {
40
+ // eslint-disable-next-line no-console
41
+ console.warn(`Warning: Direct modification of channel.${channelProp} detected. ` +
42
+ 'Use API functions for safer channel management.');
43
+ }
44
+ const key = typeof channelProp === 'symbol' ? channelProp.toString() : channelProp;
45
+ channelTarget[key] = channelValue;
46
+ return true;
47
+ }
48
+ });
49
+ }
50
+ return value;
51
+ },
52
+ set(target, prop, value) {
53
+ // Allow normal array operations
54
+ const key = typeof prop === 'symbol' ? prop.toString() : prop;
55
+ target[key] = value;
56
+ return true;
57
+ }
58
+ });
59
+ /**
60
+ * Validates a channel number against MAX_CHANNELS limit
61
+ * @param channelNumber - The channel number to validate
62
+ * @throws Error if the channel number is invalid
63
+ * @internal
64
+ */
65
+ const validateChannelNumber = (channelNumber) => {
66
+ if (channelNumber < 0) {
67
+ throw new Error('Channel number must be non-negative');
68
+ }
69
+ if (channelNumber >= types_1.MAX_CHANNELS) {
70
+ throw new Error(`Channel number ${channelNumber} exceeds maximum allowed channels (${types_1.MAX_CHANNELS})`);
71
+ }
72
+ };
14
73
  /**
15
74
  * Gets current audio information for a specific channel
16
75
  * @param channelNumber - The channel number (defaults to 0)
@@ -56,18 +115,19 @@ const getAllChannelsInfo = () => {
56
115
  exports.getAllChannelsInfo = getAllChannelsInfo;
57
116
  /**
58
117
  * Gets a complete snapshot of the queue state for a specific channel
59
- * @param channelNumber - The channel number
118
+ * @param channelNumber - The channel number (defaults to 0)
60
119
  * @returns QueueSnapshot object or null if channel doesn't exist
61
120
  * @example
62
121
  * ```typescript
63
- * const snapshot = getQueueSnapshot(0);
122
+ * const snapshot = getQueueSnapshot();
64
123
  * if (snapshot) {
65
124
  * console.log(`Queue has ${snapshot.totalItems} items`);
66
125
  * console.log(`Currently playing: ${snapshot.items[0]?.fileName}`);
67
126
  * }
127
+ * const channelSnapshot = getQueueSnapshot(2);
68
128
  * ```
69
129
  */
70
- const getQueueSnapshot = (channelNumber) => {
130
+ const getQueueSnapshot = (channelNumber = 0) => {
71
131
  return (0, utils_1.createQueueSnapshot)(channelNumber, exports.audioChannels);
72
132
  };
73
133
  exports.getQueueSnapshot = getQueueSnapshot;
@@ -75,6 +135,7 @@ exports.getQueueSnapshot = getQueueSnapshot;
75
135
  * Subscribes to real-time progress updates for a specific channel
76
136
  * @param channelNumber - The channel number
77
137
  * @param callback - Function to call with audio info updates
138
+ * @throws Error if the channel number exceeds the maximum allowed channels
78
139
  * @example
79
140
  * ```typescript
80
141
  * onAudioProgress(0, (info) => {
@@ -84,6 +145,7 @@ exports.getQueueSnapshot = getQueueSnapshot;
84
145
  * ```
85
146
  */
86
147
  const onAudioProgress = (channelNumber, callback) => {
148
+ validateChannelNumber(channelNumber);
87
149
  if (!exports.audioChannels[channelNumber]) {
88
150
  exports.audioChannels[channelNumber] = {
89
151
  audioCompleteCallbacks: new Set(),
@@ -113,23 +175,24 @@ const onAudioProgress = (channelNumber, callback) => {
113
175
  (0, events_1.setupProgressTracking)(currentAudio, channelNumber, exports.audioChannels);
114
176
  }
115
177
  // Store callback for future audio elements in this channel
116
- if (!channel.progressCallbacks.has(null)) {
117
- channel.progressCallbacks.set(null, new Set());
178
+ if (!channel.progressCallbacks.has(types_1.GLOBAL_PROGRESS_KEY)) {
179
+ channel.progressCallbacks.set(types_1.GLOBAL_PROGRESS_KEY, new Set());
118
180
  }
119
- channel.progressCallbacks.get(null).add(callback);
181
+ channel.progressCallbacks.get(types_1.GLOBAL_PROGRESS_KEY).add(callback);
120
182
  };
121
183
  exports.onAudioProgress = onAudioProgress;
122
184
  /**
123
185
  * Removes progress listeners for a specific channel
124
- * @param channelNumber - The channel number
186
+ * @param channelNumber - The channel number (defaults to 0)
125
187
  * @example
126
188
  * ```typescript
127
- * offAudioProgress(0); // Stop receiving progress updates for channel 0
189
+ * offAudioProgress();
190
+ * offAudioProgress(1); // Stop receiving progress updates for channel 1
128
191
  * ```
129
192
  */
130
- const offAudioProgress = (channelNumber) => {
193
+ function offAudioProgress(channelNumber = 0) {
131
194
  const channel = exports.audioChannels[channelNumber];
132
- if (!channel || !channel.progressCallbacks)
195
+ if (!(channel === null || channel === void 0 ? void 0 : channel.progressCallbacks))
133
196
  return;
134
197
  // Clean up event listeners for current audio if exists
135
198
  if (channel.queue.length > 0) {
@@ -138,12 +201,12 @@ const offAudioProgress = (channelNumber) => {
138
201
  }
139
202
  // Clear all callbacks for this channel
140
203
  channel.progressCallbacks.clear();
141
- };
142
- exports.offAudioProgress = offAudioProgress;
204
+ }
143
205
  /**
144
206
  * Subscribes to queue change events for a specific channel
145
207
  * @param channelNumber - The channel number to monitor
146
208
  * @param callback - Function to call when queue changes
209
+ * @throws Error if the channel number exceeds the maximum allowed channels
147
210
  * @example
148
211
  * ```typescript
149
212
  * onQueueChange(0, (snapshot) => {
@@ -153,6 +216,7 @@ exports.offAudioProgress = offAudioProgress;
153
216
  * ```
154
217
  */
155
218
  const onQueueChange = (channelNumber, callback) => {
219
+ validateChannelNumber(channelNumber);
156
220
  if (!exports.audioChannels[channelNumber]) {
157
221
  exports.audioChannels[channelNumber] = {
158
222
  audioCompleteCallbacks: new Set(),
@@ -184,7 +248,7 @@ exports.onQueueChange = onQueueChange;
184
248
  */
185
249
  const offQueueChange = (channelNumber) => {
186
250
  const channel = exports.audioChannels[channelNumber];
187
- if (!channel || !channel.queueChangeCallbacks)
251
+ if (!(channel === null || channel === void 0 ? void 0 : channel.queueChangeCallbacks))
188
252
  return;
189
253
  channel.queueChangeCallbacks.clear();
190
254
  };
@@ -193,6 +257,7 @@ exports.offQueueChange = offQueueChange;
193
257
  * Subscribes to audio start events for a specific channel
194
258
  * @param channelNumber - The channel number to monitor
195
259
  * @param callback - Function to call when audio starts playing
260
+ * @throws Error if the channel number exceeds the maximum allowed channels
196
261
  * @example
197
262
  * ```typescript
198
263
  * onAudioStart(0, (info) => {
@@ -202,6 +267,7 @@ exports.offQueueChange = offQueueChange;
202
267
  * ```
203
268
  */
204
269
  const onAudioStart = (channelNumber, callback) => {
270
+ validateChannelNumber(channelNumber);
205
271
  if (!exports.audioChannels[channelNumber]) {
206
272
  exports.audioChannels[channelNumber] = {
207
273
  audioCompleteCallbacks: new Set(),
@@ -227,6 +293,7 @@ exports.onAudioStart = onAudioStart;
227
293
  * Subscribes to audio complete events for a specific channel
228
294
  * @param channelNumber - The channel number to monitor
229
295
  * @param callback - Function to call when audio completes
296
+ * @throws Error if the channel number exceeds the maximum allowed channels
230
297
  * @example
231
298
  * ```typescript
232
299
  * onAudioComplete(0, (info) => {
@@ -238,6 +305,7 @@ exports.onAudioStart = onAudioStart;
238
305
  * ```
239
306
  */
240
307
  const onAudioComplete = (channelNumber, callback) => {
308
+ validateChannelNumber(channelNumber);
241
309
  if (!exports.audioChannels[channelNumber]) {
242
310
  exports.audioChannels[channelNumber] = {
243
311
  audioCompleteCallbacks: new Set(),
@@ -263,6 +331,7 @@ exports.onAudioComplete = onAudioComplete;
263
331
  * Subscribes to audio pause events for a specific channel
264
332
  * @param channelNumber - The channel number to monitor
265
333
  * @param callback - Function to call when audio is paused
334
+ * @throws Error if the channel number exceeds the maximum allowed channels
266
335
  * @example
267
336
  * ```typescript
268
337
  * onAudioPause(0, (channelNumber, info) => {
@@ -272,6 +341,7 @@ exports.onAudioComplete = onAudioComplete;
272
341
  * ```
273
342
  */
274
343
  const onAudioPause = (channelNumber, callback) => {
344
+ validateChannelNumber(channelNumber);
275
345
  if (!exports.audioChannels[channelNumber]) {
276
346
  exports.audioChannels[channelNumber] = {
277
347
  audioCompleteCallbacks: new Set(),
@@ -297,6 +367,7 @@ exports.onAudioPause = onAudioPause;
297
367
  * Subscribes to audio resume events for a specific channel
298
368
  * @param channelNumber - The channel number to monitor
299
369
  * @param callback - Function to call when audio is resumed
370
+ * @throws Error if the channel number exceeds the maximum allowed channels
300
371
  * @example
301
372
  * ```typescript
302
373
  * onAudioResume(0, (channelNumber, info) => {
@@ -306,6 +377,7 @@ exports.onAudioPause = onAudioPause;
306
377
  * ```
307
378
  */
308
379
  const onAudioResume = (channelNumber, callback) => {
380
+ validateChannelNumber(channelNumber);
309
381
  if (!exports.audioChannels[channelNumber]) {
310
382
  exports.audioChannels[channelNumber] = {
311
383
  audioCompleteCallbacks: new Set(),
@@ -337,7 +409,7 @@ exports.onAudioResume = onAudioResume;
337
409
  */
338
410
  const offAudioPause = (channelNumber) => {
339
411
  const channel = exports.audioChannels[channelNumber];
340
- if (!channel || !channel.audioPauseCallbacks)
412
+ if (!(channel === null || channel === void 0 ? void 0 : channel.audioPauseCallbacks))
341
413
  return;
342
414
  channel.audioPauseCallbacks.clear();
343
415
  };
@@ -352,7 +424,7 @@ exports.offAudioPause = offAudioPause;
352
424
  */
353
425
  const offAudioResume = (channelNumber) => {
354
426
  const channel = exports.audioChannels[channelNumber];
355
- if (!channel || !channel.audioResumeCallbacks)
427
+ if (!(channel === null || channel === void 0 ? void 0 : channel.audioResumeCallbacks))
356
428
  return;
357
429
  channel.audioResumeCallbacks.clear();
358
430
  };
package/dist/pause.d.ts CHANGED
@@ -6,72 +6,84 @@ import { FadeType } from './types';
6
6
  * Pauses the currently playing audio in a specific channel with smooth volume fade
7
7
  * @param fadeType - Type of fade transition to apply
8
8
  * @param channelNumber - The channel number to pause (defaults to 0)
9
+ * @param duration - Optional custom fade duration in milliseconds (uses fadeType default if not provided)
9
10
  * @returns Promise that resolves when the pause and fade are complete
10
11
  * @example
11
12
  * ```typescript
12
13
  * await pauseWithFade(FadeType.Gentle, 0); // Pause with gentle fade out over 800ms
13
- * await pauseWithFade(FadeType.Dramatic, 1); // Pause with dramatic fade out over 800ms
14
- * await pauseWithFade(FadeType.Linear, 2); // Linear pause with 800ms fade
14
+ * await pauseWithFade(FadeType.Dramatic, 1, 1500); // Pause with dramatic fade out over 1.5s
15
+ * await pauseWithFade(FadeType.Linear, 2, 500); // Linear pause with custom 500ms fade
15
16
  * ```
16
17
  */
17
- export declare const pauseWithFade: (fadeType?: FadeType, channelNumber?: number) => Promise<void>;
18
+ export declare const pauseWithFade: (fadeType?: FadeType, channelNumber?: number, duration?: number) => Promise<void>;
18
19
  /**
19
20
  * Resumes the currently paused audio in a specific channel with smooth volume fade
20
21
  * Uses the complementary fade curve automatically based on the pause fade type, or allows override
21
22
  * @param fadeType - Optional fade type to override the stored fade type from pause
22
23
  * @param channelNumber - The channel number to resume (defaults to 0)
24
+ * @param duration - Optional custom fade duration in milliseconds (uses stored or fadeType default if not provided)
23
25
  * @returns Promise that resolves when the resume and fade are complete
24
26
  * @example
25
27
  * ```typescript
26
28
  * await resumeWithFade(); // Resume with automatically paired fade curve from pause
27
29
  * await resumeWithFade(FadeType.Dramatic, 0); // Override with dramatic fade
28
- * await resumeWithFade(FadeType.Linear); // Override with linear fade on default channel
30
+ * await resumeWithFade(FadeType.Linear, 0, 1000); // Override with linear fade over 1 second
29
31
  * ```
30
32
  */
31
- export declare const resumeWithFade: (fadeType?: FadeType, channelNumber?: number) => Promise<void>;
33
+ export declare const resumeWithFade: (fadeType?: FadeType, channelNumber?: number, duration?: number) => Promise<void>;
32
34
  /**
33
35
  * Toggles pause/resume state for a specific channel with integrated fade
34
36
  * @param fadeType - Type of fade transition to apply when pausing
35
37
  * @param channelNumber - The channel number to toggle (defaults to 0)
38
+ * @param duration - Optional custom fade duration in milliseconds (uses fadeType default if not provided)
36
39
  * @returns Promise that resolves when the toggle and fade are complete
37
40
  * @example
38
41
  * ```typescript
39
42
  * await togglePauseWithFade(FadeType.Gentle, 0); // Toggle with gentle fade
43
+ * await togglePauseWithFade(FadeType.Dramatic, 0, 500); // Toggle with custom 500ms fade
40
44
  * ```
41
45
  */
42
- export declare const togglePauseWithFade: (fadeType?: FadeType, channelNumber?: number) => Promise<void>;
46
+ export declare const togglePauseWithFade: (fadeType?: FadeType, channelNumber?: number, duration?: number) => Promise<void>;
43
47
  /**
44
48
  * Pauses all currently playing audio across all channels with smooth volume fade
45
49
  * @param fadeType - Type of fade transition to apply to all channels
50
+ * @param duration - Optional custom fade duration in milliseconds (uses fadeType default if not provided)
46
51
  * @returns Promise that resolves when all channels are paused and faded
47
52
  * @example
48
53
  * ```typescript
49
- * await pauseAllWithFade('dramatic'); // Pause everything with dramatic fade
54
+ * await pauseAllWithFade(FadeType.Dramatic); // Pause everything with dramatic fade
55
+ * await pauseAllWithFade(FadeType.Gentle, 1200); // Pause all channels with custom 1.2s fade
50
56
  * ```
51
57
  */
52
- export declare const pauseAllWithFade: (fadeType?: FadeType) => Promise<void>;
58
+ export declare const pauseAllWithFade: (fadeType?: FadeType, duration?: number) => Promise<void>;
53
59
  /**
54
60
  * Resumes all currently paused audio across all channels with smooth volume fade
55
- * Uses automatically paired fade curves based on each channel's pause fade type
61
+ * Uses automatically paired fade curves based on each channel's pause fade type, or allows override
62
+ * @param fadeType - Optional fade type to override stored fade types for all channels
63
+ * @param duration - Optional custom fade duration in milliseconds (uses stored or fadeType default if not provided)
56
64
  * @returns Promise that resolves when all channels are resumed and faded
57
65
  * @example
58
66
  * ```typescript
59
67
  * await resumeAllWithFade(); // Resume everything with paired fade curves
68
+ * await resumeAllWithFade(FadeType.Gentle, 800); // Override all channels with gentle fade over 800ms
69
+ * await resumeAllWithFade(undefined, 600); // Use stored fade types with custom 600ms duration
60
70
  * ```
61
71
  */
62
- export declare const resumeAllWithFade: () => Promise<void>;
72
+ export declare const resumeAllWithFade: (fadeType?: FadeType, duration?: number) => Promise<void>;
63
73
  /**
64
74
  * Toggles pause/resume state for all channels with integrated fade
65
75
  * If any channels are playing, all will be paused with fade
66
76
  * If all channels are paused, all will be resumed with fade
67
77
  * @param fadeType - Type of fade transition to apply when pausing
78
+ * @param duration - Optional custom fade duration in milliseconds (uses fadeType default if not provided)
68
79
  * @returns Promise that resolves when all toggles and fades are complete
69
80
  * @example
70
81
  * ```typescript
71
- * await togglePauseAllWithFade('gentle'); // Global toggle with gentle fade
82
+ * await togglePauseAllWithFade(FadeType.Gentle); // Global toggle with gentle fade
83
+ * await togglePauseAllWithFade(FadeType.Dramatic, 600); // Global toggle with custom 600ms fade
72
84
  * ```
73
85
  */
74
- export declare const togglePauseAllWithFade: (fadeType?: FadeType) => Promise<void>;
86
+ export declare const togglePauseAllWithFade: (fadeType?: FadeType, duration?: number) => Promise<void>;
75
87
  /**
76
88
  * Pauses the currently playing audio in a specific channel
77
89
  * @param channelNumber - The channel number to pause (defaults to 0)
package/dist/pause.js CHANGED
@@ -18,22 +18,15 @@ const info_1 = require("./info");
18
18
  const utils_1 = require("./utils");
19
19
  const events_1 = require("./events");
20
20
  const volume_1 = require("./volume");
21
- /**
22
- * Predefined fade configurations for different transition types
23
- */
24
- const FADE_CONFIGS = {
25
- [types_1.FadeType.Linear]: { duration: 800, pauseCurve: types_1.EasingType.Linear, resumeCurve: types_1.EasingType.Linear },
26
- [types_1.FadeType.Gentle]: { duration: 800, pauseCurve: types_1.EasingType.EaseOut, resumeCurve: types_1.EasingType.EaseIn },
27
- [types_1.FadeType.Dramatic]: { duration: 800, pauseCurve: types_1.EasingType.EaseIn, resumeCurve: types_1.EasingType.EaseOut }
28
- };
29
21
  /**
30
22
  * Gets the current volume for a channel, accounting for synchronous state
31
23
  * @param channelNumber - The channel number
32
24
  * @returns Current volume level (0-1)
33
25
  */
34
26
  const getChannelVolumeSync = (channelNumber) => {
27
+ var _a;
35
28
  const channel = info_1.audioChannels[channelNumber];
36
- return (channel === null || channel === void 0 ? void 0 : channel.volume) || 1.0;
29
+ return (_a = channel === null || channel === void 0 ? void 0 : channel.volume) !== null && _a !== void 0 ? _a : 1.0;
37
30
  };
38
31
  /**
39
32
  * Sets the channel volume synchronously in internal state
@@ -53,15 +46,17 @@ const setChannelVolumeSync = (channelNumber, volume) => {
53
46
  * Pauses the currently playing audio in a specific channel with smooth volume fade
54
47
  * @param fadeType - Type of fade transition to apply
55
48
  * @param channelNumber - The channel number to pause (defaults to 0)
49
+ * @param duration - Optional custom fade duration in milliseconds (uses fadeType default if not provided)
56
50
  * @returns Promise that resolves when the pause and fade are complete
57
51
  * @example
58
52
  * ```typescript
59
53
  * await pauseWithFade(FadeType.Gentle, 0); // Pause with gentle fade out over 800ms
60
- * await pauseWithFade(FadeType.Dramatic, 1); // Pause with dramatic fade out over 800ms
61
- * await pauseWithFade(FadeType.Linear, 2); // Linear pause with 800ms fade
54
+ * await pauseWithFade(FadeType.Dramatic, 1, 1500); // Pause with dramatic fade out over 1.5s
55
+ * await pauseWithFade(FadeType.Linear, 2, 500); // Linear pause with custom 500ms fade
62
56
  * ```
63
57
  */
64
- const pauseWithFade = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (fadeType = types_1.FadeType.Gentle, channelNumber = 0) {
58
+ const pauseWithFade = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (fadeType = types_1.FadeType.Gentle, channelNumber = 0, duration) {
59
+ var _a, _b, _c;
65
60
  const channel = info_1.audioChannels[channelNumber];
66
61
  if (!channel || channel.queue.length === 0)
67
62
  return;
@@ -69,24 +64,49 @@ const pauseWithFade = (...args_1) => __awaiter(void 0, [...args_1], void 0, func
69
64
  // Don't pause if already paused or ended
70
65
  if (currentAudio.paused || currentAudio.ended)
71
66
  return;
72
- const config = FADE_CONFIGS[fadeType];
73
- const originalVolume = getChannelVolumeSync(channelNumber);
74
- // Store fade state for resumeWithFade to use
67
+ const config = (0, volume_1.getFadeConfig)(fadeType);
68
+ const effectiveDuration = duration !== null && duration !== void 0 ? duration : config.duration;
69
+ // Race condition fix: Use existing fadeState originalVolume if already transitioning,
70
+ // otherwise capture current volume
71
+ let originalVolume;
72
+ if ((_a = channel.fadeState) === null || _a === void 0 ? void 0 : _a.isTransitioning) {
73
+ // We're already in any kind of transition (pause or resume), preserve original volume
74
+ originalVolume = channel.fadeState.originalVolume;
75
+ }
76
+ else {
77
+ // First fade or no transition in progress, capture current volume
78
+ // But ensure we don't capture a volume of 0 during a transition
79
+ const currentVolume = getChannelVolumeSync(channelNumber);
80
+ originalVolume = currentVolume > 0 ? currentVolume : ((_c = (_b = channel.fadeState) === null || _b === void 0 ? void 0 : _b.originalVolume) !== null && _c !== void 0 ? _c : 1.0);
81
+ }
82
+ // Store fade state for resumeWithFade to use (including custom duration)
75
83
  channel.fadeState = {
76
- originalVolume,
84
+ customDuration: duration,
77
85
  fadeType,
78
- isPaused: true
86
+ isPaused: true,
87
+ isTransitioning: true,
88
+ originalVolume
79
89
  };
80
- if (config.duration === 0) {
90
+ if (effectiveDuration === 0) {
81
91
  // Instant pause
82
92
  yield (0, exports.pauseChannel)(channelNumber);
93
+ // Reset volume to original for resume (synchronously to avoid state issues)
94
+ setChannelVolumeSync(channelNumber, originalVolume);
95
+ // Mark transition as complete for instant pause
96
+ if (channel.fadeState) {
97
+ channel.fadeState.isTransitioning = false;
98
+ }
83
99
  return;
84
100
  }
85
101
  // Fade to 0 with pause curve, then pause
86
- yield (0, volume_1.transitionVolume)(channelNumber, 0, config.duration, config.pauseCurve);
102
+ yield (0, volume_1.transitionVolume)(channelNumber, 0, effectiveDuration, config.pauseCurve);
87
103
  yield (0, exports.pauseChannel)(channelNumber);
88
104
  // Reset volume to original for resume (synchronously to avoid state issues)
89
105
  setChannelVolumeSync(channelNumber, originalVolume);
106
+ // Mark transition as complete
107
+ if (channel.fadeState) {
108
+ channel.fadeState.isTransitioning = false;
109
+ }
90
110
  });
91
111
  exports.pauseWithFade = pauseWithFade;
92
112
  /**
@@ -94,93 +114,122 @@ exports.pauseWithFade = pauseWithFade;
94
114
  * Uses the complementary fade curve automatically based on the pause fade type, or allows override
95
115
  * @param fadeType - Optional fade type to override the stored fade type from pause
96
116
  * @param channelNumber - The channel number to resume (defaults to 0)
117
+ * @param duration - Optional custom fade duration in milliseconds (uses stored or fadeType default if not provided)
97
118
  * @returns Promise that resolves when the resume and fade are complete
98
119
  * @example
99
120
  * ```typescript
100
121
  * await resumeWithFade(); // Resume with automatically paired fade curve from pause
101
122
  * await resumeWithFade(FadeType.Dramatic, 0); // Override with dramatic fade
102
- * await resumeWithFade(FadeType.Linear); // Override with linear fade on default channel
123
+ * await resumeWithFade(FadeType.Linear, 0, 1000); // Override with linear fade over 1 second
103
124
  * ```
104
125
  */
105
- const resumeWithFade = (fadeType_1, ...args_1) => __awaiter(void 0, [fadeType_1, ...args_1], void 0, function* (fadeType, channelNumber = 0) {
126
+ const resumeWithFade = (fadeType_1, ...args_1) => __awaiter(void 0, [fadeType_1, ...args_1], void 0, function* (fadeType, channelNumber = 0, duration) {
106
127
  const channel = info_1.audioChannels[channelNumber];
107
128
  if (!channel || channel.queue.length === 0)
108
129
  return;
109
130
  const fadeState = channel.fadeState;
110
- if (!fadeState || !fadeState.isPaused) {
131
+ if (!(fadeState === null || fadeState === void 0 ? void 0 : fadeState.isPaused)) {
111
132
  // Fall back to regular resume if no fade state
112
133
  yield (0, exports.resumeChannel)(channelNumber);
113
134
  return;
114
135
  }
115
136
  // Use provided fadeType or fall back to stored fadeType from pause
116
- const effectiveFadeType = fadeType || fadeState.fadeType;
117
- const config = FADE_CONFIGS[effectiveFadeType];
118
- if (config.duration === 0) {
137
+ const effectiveFadeType = fadeType !== null && fadeType !== void 0 ? fadeType : fadeState.fadeType;
138
+ const config = (0, volume_1.getFadeConfig)(effectiveFadeType);
139
+ // Determine effective duration: custom parameter > stored custom > fadeType default
140
+ let effectiveDuration;
141
+ if (duration !== undefined) {
142
+ effectiveDuration = duration;
143
+ }
144
+ else if (fadeState.customDuration !== undefined) {
145
+ effectiveDuration = fadeState.customDuration;
146
+ }
147
+ else {
148
+ effectiveDuration = config.duration;
149
+ }
150
+ if (effectiveDuration === 0) {
119
151
  // Instant resume
152
+ const targetVolume = fadeState.originalVolume > 0 ? fadeState.originalVolume : 1.0;
153
+ setChannelVolumeSync(channelNumber, targetVolume);
120
154
  yield (0, exports.resumeChannel)(channelNumber);
121
155
  fadeState.isPaused = false;
156
+ fadeState.isTransitioning = false;
122
157
  return;
123
158
  }
159
+ // Race condition fix: Ensure we have a valid original volume to restore to
160
+ const targetVolume = fadeState.originalVolume > 0 ? fadeState.originalVolume : 1.0;
161
+ // Mark as transitioning to prevent volume capture during rapid toggles
162
+ fadeState.isTransitioning = true;
124
163
  // Set volume to 0, resume, then fade to original with resume curve
125
164
  setChannelVolumeSync(channelNumber, 0);
126
165
  yield (0, exports.resumeChannel)(channelNumber);
127
- yield (0, volume_1.transitionVolume)(channelNumber, fadeState.originalVolume, config.duration, config.resumeCurve);
166
+ // Use the stored original volume, not current volume, to prevent race conditions
167
+ yield (0, volume_1.transitionVolume)(channelNumber, targetVolume, effectiveDuration, config.resumeCurve);
128
168
  fadeState.isPaused = false;
169
+ fadeState.isTransitioning = false;
129
170
  });
130
171
  exports.resumeWithFade = resumeWithFade;
131
172
  /**
132
173
  * Toggles pause/resume state for a specific channel with integrated fade
133
174
  * @param fadeType - Type of fade transition to apply when pausing
134
175
  * @param channelNumber - The channel number to toggle (defaults to 0)
176
+ * @param duration - Optional custom fade duration in milliseconds (uses fadeType default if not provided)
135
177
  * @returns Promise that resolves when the toggle and fade are complete
136
178
  * @example
137
179
  * ```typescript
138
180
  * await togglePauseWithFade(FadeType.Gentle, 0); // Toggle with gentle fade
181
+ * await togglePauseWithFade(FadeType.Dramatic, 0, 500); // Toggle with custom 500ms fade
139
182
  * ```
140
183
  */
141
- const togglePauseWithFade = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (fadeType = types_1.FadeType.Gentle, channelNumber = 0) {
184
+ const togglePauseWithFade = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (fadeType = types_1.FadeType.Gentle, channelNumber = 0, duration) {
142
185
  const channel = info_1.audioChannels[channelNumber];
143
186
  if (!channel || channel.queue.length === 0)
144
187
  return;
145
188
  const currentAudio = channel.queue[0];
146
189
  if (currentAudio.paused) {
147
- yield (0, exports.resumeWithFade)(undefined, channelNumber);
190
+ yield (0, exports.resumeWithFade)(undefined, channelNumber, duration);
148
191
  }
149
192
  else {
150
- yield (0, exports.pauseWithFade)(fadeType, channelNumber);
193
+ yield (0, exports.pauseWithFade)(fadeType, channelNumber, duration);
151
194
  }
152
195
  });
153
196
  exports.togglePauseWithFade = togglePauseWithFade;
154
197
  /**
155
198
  * Pauses all currently playing audio across all channels with smooth volume fade
156
199
  * @param fadeType - Type of fade transition to apply to all channels
200
+ * @param duration - Optional custom fade duration in milliseconds (uses fadeType default if not provided)
157
201
  * @returns Promise that resolves when all channels are paused and faded
158
202
  * @example
159
203
  * ```typescript
160
- * await pauseAllWithFade('dramatic'); // Pause everything with dramatic fade
204
+ * await pauseAllWithFade(FadeType.Dramatic); // Pause everything with dramatic fade
205
+ * await pauseAllWithFade(FadeType.Gentle, 1200); // Pause all channels with custom 1.2s fade
161
206
  * ```
162
207
  */
163
- const pauseAllWithFade = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (fadeType = types_1.FadeType.Gentle) {
208
+ const pauseAllWithFade = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (fadeType = types_1.FadeType.Gentle, duration) {
164
209
  const pausePromises = [];
165
210
  info_1.audioChannels.forEach((_channel, index) => {
166
- pausePromises.push((0, exports.pauseWithFade)(fadeType, index));
211
+ pausePromises.push((0, exports.pauseWithFade)(fadeType, index, duration));
167
212
  });
168
213
  yield Promise.all(pausePromises);
169
214
  });
170
215
  exports.pauseAllWithFade = pauseAllWithFade;
171
216
  /**
172
217
  * Resumes all currently paused audio across all channels with smooth volume fade
173
- * Uses automatically paired fade curves based on each channel's pause fade type
218
+ * Uses automatically paired fade curves based on each channel's pause fade type, or allows override
219
+ * @param fadeType - Optional fade type to override stored fade types for all channels
220
+ * @param duration - Optional custom fade duration in milliseconds (uses stored or fadeType default if not provided)
174
221
  * @returns Promise that resolves when all channels are resumed and faded
175
222
  * @example
176
223
  * ```typescript
177
224
  * await resumeAllWithFade(); // Resume everything with paired fade curves
225
+ * await resumeAllWithFade(FadeType.Gentle, 800); // Override all channels with gentle fade over 800ms
226
+ * await resumeAllWithFade(undefined, 600); // Use stored fade types with custom 600ms duration
178
227
  * ```
179
228
  */
180
- const resumeAllWithFade = () => __awaiter(void 0, void 0, void 0, function* () {
229
+ const resumeAllWithFade = (fadeType, duration) => __awaiter(void 0, void 0, void 0, function* () {
181
230
  const resumePromises = [];
182
231
  info_1.audioChannels.forEach((_channel, index) => {
183
- resumePromises.push((0, exports.resumeWithFade)(undefined, index));
232
+ resumePromises.push((0, exports.resumeWithFade)(fadeType, index, duration));
184
233
  });
185
234
  yield Promise.all(resumePromises);
186
235
  });
@@ -190,13 +239,15 @@ exports.resumeAllWithFade = resumeAllWithFade;
190
239
  * If any channels are playing, all will be paused with fade
191
240
  * If all channels are paused, all will be resumed with fade
192
241
  * @param fadeType - Type of fade transition to apply when pausing
242
+ * @param duration - Optional custom fade duration in milliseconds (uses fadeType default if not provided)
193
243
  * @returns Promise that resolves when all toggles and fades are complete
194
244
  * @example
195
245
  * ```typescript
196
- * await togglePauseAllWithFade('gentle'); // Global toggle with gentle fade
246
+ * await togglePauseAllWithFade(FadeType.Gentle); // Global toggle with gentle fade
247
+ * await togglePauseAllWithFade(FadeType.Dramatic, 600); // Global toggle with custom 600ms fade
197
248
  * ```
198
249
  */
199
- const togglePauseAllWithFade = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (fadeType = types_1.FadeType.Gentle) {
250
+ const togglePauseAllWithFade = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (fadeType = types_1.FadeType.Gentle, duration) {
200
251
  let hasPlayingChannel = false;
201
252
  // Check if any channel is currently playing
202
253
  for (let i = 0; i < info_1.audioChannels.length; i++) {
@@ -212,10 +263,10 @@ const togglePauseAllWithFade = (...args_1) => __awaiter(void 0, [...args_1], voi
212
263
  // If any channel is playing, pause all with fade
213
264
  // If no channels are playing, resume all with fade
214
265
  if (hasPlayingChannel) {
215
- yield (0, exports.pauseAllWithFade)(fadeType);
266
+ yield (0, exports.pauseAllWithFade)(fadeType, duration);
216
267
  }
217
268
  else {
218
- yield (0, exports.resumeAllWithFade)();
269
+ yield (0, exports.resumeAllWithFade)(fadeType, duration);
219
270
  }
220
271
  });
221
272
  exports.togglePauseAllWithFade = togglePauseAllWithFade;
@@ -335,8 +386,9 @@ exports.resumeAllChannels = resumeAllChannels;
335
386
  * ```
336
387
  */
337
388
  const isChannelPaused = (channelNumber = 0) => {
389
+ var _a;
338
390
  const channel = info_1.audioChannels[channelNumber];
339
- return (channel === null || channel === void 0 ? void 0 : channel.isPaused) || false;
391
+ return (_a = channel === null || channel === void 0 ? void 0 : channel.isPaused) !== null && _a !== void 0 ? _a : false;
340
392
  };
341
393
  exports.isChannelPaused = isChannelPaused;
342
394
  /**
@@ -351,7 +403,7 @@ exports.isChannelPaused = isChannelPaused;
351
403
  * ```
352
404
  */
353
405
  const getAllChannelsPauseState = () => {
354
- return info_1.audioChannels.map((channel) => (channel === null || channel === void 0 ? void 0 : channel.isPaused) || false);
406
+ return info_1.audioChannels.map((channel) => { var _a; return (_a = channel === null || channel === void 0 ? void 0 : channel.isPaused) !== null && _a !== void 0 ? _a : false; });
355
407
  };
356
408
  exports.getAllChannelsPauseState = getAllChannelsPauseState;
357
409
  /**