audio-channel-queue 1.9.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/README.md +197 -313
- package/dist/core.d.ts +59 -1
- package/dist/core.js +331 -40
- package/dist/errors.d.ts +1 -0
- package/dist/errors.js +10 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +21 -2
- package/dist/info.d.ts +17 -6
- package/dist/info.js +81 -10
- package/dist/pause.js +1 -1
- package/dist/queue-manipulation.d.ts +104 -0
- package/dist/queue-manipulation.js +319 -0
- package/dist/types.d.ts +46 -8
- package/dist/types.js +13 -1
- package/dist/utils.d.ts +25 -0
- package/dist/utils.js +98 -10
- package/dist/volume.d.ts +14 -1
- package/dist/volume.js +173 -54
- package/package.json +12 -2
- package/src/core.ts +389 -50
- package/src/errors.ts +14 -2
- package/src/index.ts +26 -5
- package/src/info.ts +107 -9
- package/src/pause.ts +4 -6
- package/src/queue-manipulation.ts +378 -0
- package/src/types.ts +51 -9
- package/src/utils.ts +110 -9
- package/src/volume.ts +214 -62
package/dist/core.d.ts
CHANGED
|
@@ -1,19 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Core queue management functions for the audio-channel-queue package
|
|
3
3
|
*/
|
|
4
|
-
import { AudioQueueOptions } from './types';
|
|
4
|
+
import { AudioQueueOptions, QueueConfig } from './types';
|
|
5
|
+
/**
|
|
6
|
+
* Sets the global queue configuration
|
|
7
|
+
* @param config - Queue configuration options
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* setQueueConfig({
|
|
11
|
+
* defaultMaxQueueSize: 50,
|
|
12
|
+
* dropOldestWhenFull: true,
|
|
13
|
+
* showQueueWarnings: true
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare const setQueueConfig: (config: Partial<QueueConfig>) => void;
|
|
18
|
+
/**
|
|
19
|
+
* Gets the current global queue configuration
|
|
20
|
+
* @returns Current queue configuration
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const config = getQueueConfig();
|
|
24
|
+
* console.log(`Default max queue size: ${config.defaultMaxQueueSize}`);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare const getQueueConfig: () => QueueConfig;
|
|
28
|
+
/**
|
|
29
|
+
* Sets the maximum queue size for a specific channel
|
|
30
|
+
* @param channelNumber - The channel number to configure
|
|
31
|
+
* @param maxSize - Maximum queue size (undefined for unlimited)
|
|
32
|
+
* @throws Error if the channel number exceeds the maximum allowed channels
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* setChannelQueueLimit(0, 25); // Limit channel 0 to 25 items
|
|
36
|
+
* setChannelQueueLimit(1, undefined); // Remove limit for channel 1
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare const setChannelQueueLimit: (channelNumber: number, maxSize?: number) => void;
|
|
5
40
|
/**
|
|
6
41
|
* Queues an audio file to a specific channel and starts playing if it's the first in queue
|
|
7
42
|
* @param audioUrl - The URL of the audio file to queue
|
|
8
43
|
* @param channelNumber - The channel number to queue the audio to (defaults to 0)
|
|
9
44
|
* @param options - Optional configuration for the audio file
|
|
10
45
|
* @returns Promise that resolves when the audio is queued and starts playing (if first in queue)
|
|
46
|
+
* @throws Error if the audio URL is invalid or potentially malicious
|
|
47
|
+
* @throws Error if the channel number exceeds the maximum allowed channels
|
|
48
|
+
* @throws Error if the queue size limit would be exceeded
|
|
11
49
|
* @example
|
|
12
50
|
* ```typescript
|
|
13
51
|
* await queueAudio('https://example.com/song.mp3', 0);
|
|
14
52
|
* await queueAudio('./sounds/notification.wav'); // Uses default channel 0
|
|
15
53
|
* await queueAudio('./music/loop.mp3', 1, { loop: true }); // Loop the audio
|
|
16
54
|
* await queueAudio('./urgent.wav', 0, { addToFront: true }); // Add to front of queue
|
|
55
|
+
* await queueAudio('./limited.mp3', 0, { maxQueueSize: 10 }); // Limit this queue to 10 items
|
|
17
56
|
* ```
|
|
18
57
|
*/
|
|
19
58
|
export declare const queueAudio: (audioUrl: string, channelNumber?: number, options?: AudioQueueOptions) => Promise<void>;
|
|
@@ -69,3 +108,22 @@ export declare const stopAllAudioInChannel: (channelNumber?: number) => Promise<
|
|
|
69
108
|
* ```
|
|
70
109
|
*/
|
|
71
110
|
export declare const stopAllAudio: () => Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Completely destroys a channel and cleans up all associated resources
|
|
113
|
+
* This stops all audio, cancels transitions, clears callbacks, and removes the channel
|
|
114
|
+
* @param channelNumber - The channel number to destroy (defaults to 0)
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* await destroyChannel(1); // Completely removes channel 1 and cleans up resources
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export declare const destroyChannel: (channelNumber?: number) => Promise<void>;
|
|
121
|
+
/**
|
|
122
|
+
* Destroys all channels and cleans up all resources
|
|
123
|
+
* This is useful for complete cleanup when the audio system is no longer needed
|
|
124
|
+
* @example
|
|
125
|
+
* ```typescript
|
|
126
|
+
* await destroyAllChannels(); // Complete cleanup - removes all channels
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export declare const destroyAllChannels: () => Promise<void>;
|
package/dist/core.js
CHANGED
|
@@ -12,27 +12,213 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
12
12
|
});
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.stopAllAudio = exports.stopAllAudioInChannel = exports.stopCurrentAudioInChannel = exports.playAudioQueue = exports.queueAudioPriority = exports.queueAudio = void 0;
|
|
15
|
+
exports.destroyAllChannels = exports.destroyChannel = exports.stopAllAudio = exports.stopAllAudioInChannel = exports.stopCurrentAudioInChannel = exports.playAudioQueue = exports.queueAudioPriority = exports.queueAudio = exports.setChannelQueueLimit = exports.getQueueConfig = exports.setQueueConfig = void 0;
|
|
16
|
+
const types_1 = require("./types");
|
|
16
17
|
const info_1 = require("./info");
|
|
17
18
|
const utils_1 = require("./utils");
|
|
18
19
|
const events_1 = require("./events");
|
|
19
20
|
const volume_1 = require("./volume");
|
|
20
21
|
const errors_1 = require("./errors");
|
|
22
|
+
/**
|
|
23
|
+
* Global queue configuration
|
|
24
|
+
*/
|
|
25
|
+
let globalQueueConfig = {
|
|
26
|
+
defaultMaxQueueSize: undefined, // unlimited by default
|
|
27
|
+
dropOldestWhenFull: false,
|
|
28
|
+
showQueueWarnings: true
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Operation lock timeout in milliseconds
|
|
32
|
+
*/
|
|
33
|
+
const OPERATION_LOCK_TIMEOUT = 100;
|
|
34
|
+
/**
|
|
35
|
+
* Acquires an operation lock for a channel to prevent race conditions
|
|
36
|
+
* @param channelNumber - The channel number to lock
|
|
37
|
+
* @param operationName - Name of the operation for debugging
|
|
38
|
+
* @returns Promise that resolves when lock is acquired
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
const acquireChannelLock = (channelNumber, operationName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
42
|
+
const channel = info_1.audioChannels[channelNumber];
|
|
43
|
+
if (!channel)
|
|
44
|
+
return;
|
|
45
|
+
const startTime = Date.now();
|
|
46
|
+
// Wait for any existing lock to be released
|
|
47
|
+
while (channel.isLocked) {
|
|
48
|
+
// Prevent infinite waiting with timeout
|
|
49
|
+
if (Date.now() - startTime > OPERATION_LOCK_TIMEOUT) {
|
|
50
|
+
// eslint-disable-next-line no-console
|
|
51
|
+
console.warn(`Operation lock timeout for channel ${channelNumber} during ${operationName}. ` +
|
|
52
|
+
`Forcibly acquiring lock.`);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
// Small delay to prevent tight polling
|
|
56
|
+
yield new Promise((resolve) => setTimeout(resolve, 10));
|
|
57
|
+
}
|
|
58
|
+
channel.isLocked = true;
|
|
59
|
+
});
|
|
60
|
+
/**
|
|
61
|
+
* Releases an operation lock for a channel
|
|
62
|
+
* @param channelNumber - The channel number to unlock
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
const releaseChannelLock = (channelNumber) => {
|
|
66
|
+
const channel = info_1.audioChannels[channelNumber];
|
|
67
|
+
if (channel) {
|
|
68
|
+
channel.isLocked = false;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Executes an operation with channel lock protection
|
|
73
|
+
* @param channelNumber - The channel number to operate on
|
|
74
|
+
* @param operationName - Name of the operation for debugging
|
|
75
|
+
* @param operation - The operation to execute
|
|
76
|
+
* @returns Promise that resolves with the operation result
|
|
77
|
+
* @internal
|
|
78
|
+
*/
|
|
79
|
+
const withChannelLock = (channelNumber, operationName, operation) => __awaiter(void 0, void 0, void 0, function* () {
|
|
80
|
+
try {
|
|
81
|
+
yield acquireChannelLock(channelNumber, operationName);
|
|
82
|
+
return yield operation();
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
releaseChannelLock(channelNumber);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
/**
|
|
89
|
+
* Sets the global queue configuration
|
|
90
|
+
* @param config - Queue configuration options
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* setQueueConfig({
|
|
94
|
+
* defaultMaxQueueSize: 50,
|
|
95
|
+
* dropOldestWhenFull: true,
|
|
96
|
+
* showQueueWarnings: true
|
|
97
|
+
* });
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
const setQueueConfig = (config) => {
|
|
101
|
+
globalQueueConfig = Object.assign(Object.assign({}, globalQueueConfig), config);
|
|
102
|
+
};
|
|
103
|
+
exports.setQueueConfig = setQueueConfig;
|
|
104
|
+
/**
|
|
105
|
+
* Gets the current global queue configuration
|
|
106
|
+
* @returns Current queue configuration
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* const config = getQueueConfig();
|
|
110
|
+
* console.log(`Default max queue size: ${config.defaultMaxQueueSize}`);
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
const getQueueConfig = () => {
|
|
114
|
+
return Object.assign({}, globalQueueConfig);
|
|
115
|
+
};
|
|
116
|
+
exports.getQueueConfig = getQueueConfig;
|
|
117
|
+
/**
|
|
118
|
+
* Sets the maximum queue size for a specific channel
|
|
119
|
+
* @param channelNumber - The channel number to configure
|
|
120
|
+
* @param maxSize - Maximum queue size (undefined for unlimited)
|
|
121
|
+
* @throws Error if the channel number exceeds the maximum allowed channels
|
|
122
|
+
* @example
|
|
123
|
+
* ```typescript
|
|
124
|
+
* setChannelQueueLimit(0, 25); // Limit channel 0 to 25 items
|
|
125
|
+
* setChannelQueueLimit(1, undefined); // Remove limit for channel 1
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
const setChannelQueueLimit = (channelNumber, maxSize) => {
|
|
129
|
+
// Validate channel number limits BEFORE creating any channels
|
|
130
|
+
if (channelNumber < 0) {
|
|
131
|
+
throw new Error('Channel number must be non-negative');
|
|
132
|
+
}
|
|
133
|
+
if (channelNumber >= types_1.MAX_CHANNELS) {
|
|
134
|
+
throw new Error(`Channel number ${channelNumber} exceeds maximum allowed channels (${types_1.MAX_CHANNELS})`);
|
|
135
|
+
}
|
|
136
|
+
// Ensure channel exists (now safe because we validated the limit above)
|
|
137
|
+
while (info_1.audioChannels.length <= channelNumber) {
|
|
138
|
+
info_1.audioChannels.push({
|
|
139
|
+
audioCompleteCallbacks: new Set(),
|
|
140
|
+
audioErrorCallbacks: new Set(),
|
|
141
|
+
audioPauseCallbacks: new Set(),
|
|
142
|
+
audioResumeCallbacks: new Set(),
|
|
143
|
+
audioStartCallbacks: new Set(),
|
|
144
|
+
isPaused: false,
|
|
145
|
+
progressCallbacks: new Map(),
|
|
146
|
+
queue: [],
|
|
147
|
+
queueChangeCallbacks: new Set(),
|
|
148
|
+
volume: 1.0
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const channel = info_1.audioChannels[channelNumber];
|
|
152
|
+
channel.maxQueueSize = maxSize;
|
|
153
|
+
};
|
|
154
|
+
exports.setChannelQueueLimit = setChannelQueueLimit;
|
|
155
|
+
/**
|
|
156
|
+
* Checks if adding an item to the queue would exceed limits and handles the situation
|
|
157
|
+
* @param channel - The channel to check
|
|
158
|
+
* @param channelNumber - The channel number for logging
|
|
159
|
+
* @param maxQueueSize - Override max queue size from options
|
|
160
|
+
* @returns true if the item can be added, false otherwise
|
|
161
|
+
* @internal
|
|
162
|
+
*/
|
|
163
|
+
const checkQueueLimit = (channel, channelNumber, maxQueueSize) => {
|
|
164
|
+
var _a;
|
|
165
|
+
// Determine the effective queue limit
|
|
166
|
+
const effectiveLimit = (_a = maxQueueSize !== null && maxQueueSize !== void 0 ? maxQueueSize : channel.maxQueueSize) !== null && _a !== void 0 ? _a : globalQueueConfig.defaultMaxQueueSize;
|
|
167
|
+
if (effectiveLimit === undefined) {
|
|
168
|
+
return true; // No limit set
|
|
169
|
+
}
|
|
170
|
+
if (channel.queue.length < effectiveLimit) {
|
|
171
|
+
return true; // Within limits
|
|
172
|
+
}
|
|
173
|
+
// Queue is at or over the limit
|
|
174
|
+
if (globalQueueConfig.showQueueWarnings) {
|
|
175
|
+
// eslint-disable-next-line no-console
|
|
176
|
+
console.warn(`Queue limit reached for channel ${channelNumber}. ` +
|
|
177
|
+
`Current size: ${channel.queue.length}, Limit: ${effectiveLimit}`);
|
|
178
|
+
}
|
|
179
|
+
if (globalQueueConfig.dropOldestWhenFull) {
|
|
180
|
+
// Remove oldest item (but not currently playing)
|
|
181
|
+
if (channel.queue.length > 1) {
|
|
182
|
+
const removedAudio = channel.queue.splice(1, 1)[0];
|
|
183
|
+
(0, events_1.cleanupProgressTracking)(removedAudio, channelNumber, info_1.audioChannels);
|
|
184
|
+
if (globalQueueConfig.showQueueWarnings) {
|
|
185
|
+
// eslint-disable-next-line no-console
|
|
186
|
+
console.info(`Dropped oldest queued item to make room for new audio`);
|
|
187
|
+
}
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Cannot add - queue is full and not dropping oldest
|
|
192
|
+
return false;
|
|
193
|
+
};
|
|
21
194
|
/**
|
|
22
195
|
* Queues an audio file to a specific channel and starts playing if it's the first in queue
|
|
23
196
|
* @param audioUrl - The URL of the audio file to queue
|
|
24
197
|
* @param channelNumber - The channel number to queue the audio to (defaults to 0)
|
|
25
198
|
* @param options - Optional configuration for the audio file
|
|
26
199
|
* @returns Promise that resolves when the audio is queued and starts playing (if first in queue)
|
|
200
|
+
* @throws Error if the audio URL is invalid or potentially malicious
|
|
201
|
+
* @throws Error if the channel number exceeds the maximum allowed channels
|
|
202
|
+
* @throws Error if the queue size limit would be exceeded
|
|
27
203
|
* @example
|
|
28
204
|
* ```typescript
|
|
29
205
|
* await queueAudio('https://example.com/song.mp3', 0);
|
|
30
206
|
* await queueAudio('./sounds/notification.wav'); // Uses default channel 0
|
|
31
207
|
* await queueAudio('./music/loop.mp3', 1, { loop: true }); // Loop the audio
|
|
32
208
|
* await queueAudio('./urgent.wav', 0, { addToFront: true }); // Add to front of queue
|
|
209
|
+
* await queueAudio('./limited.mp3', 0, { maxQueueSize: 10 }); // Limit this queue to 10 items
|
|
33
210
|
* ```
|
|
34
211
|
*/
|
|
35
212
|
const queueAudio = (audioUrl_1, ...args_1) => __awaiter(void 0, [audioUrl_1, ...args_1], void 0, function* (audioUrl, channelNumber = 0, options) {
|
|
213
|
+
// Validate the URL for security
|
|
214
|
+
const validatedUrl = (0, utils_1.validateAudioUrl)(audioUrl);
|
|
215
|
+
// Check channel number limits
|
|
216
|
+
if (channelNumber < 0) {
|
|
217
|
+
throw new Error('Channel number must be non-negative');
|
|
218
|
+
}
|
|
219
|
+
if (channelNumber >= types_1.MAX_CHANNELS) {
|
|
220
|
+
throw new Error(`Channel number ${channelNumber} exceeds maximum allowed channels (${types_1.MAX_CHANNELS})`);
|
|
221
|
+
}
|
|
36
222
|
// Ensure the channel exists
|
|
37
223
|
while (info_1.audioChannels.length <= channelNumber) {
|
|
38
224
|
info_1.audioChannels.push({
|
|
@@ -49,10 +235,14 @@ const queueAudio = (audioUrl_1, ...args_1) => __awaiter(void 0, [audioUrl_1, ...
|
|
|
49
235
|
});
|
|
50
236
|
}
|
|
51
237
|
const channel = info_1.audioChannels[channelNumber];
|
|
52
|
-
|
|
238
|
+
// Check queue size limits before creating audio element
|
|
239
|
+
if (!checkQueueLimit(channel, channelNumber, options === null || options === void 0 ? void 0 : options.maxQueueSize)) {
|
|
240
|
+
throw new Error(`Queue size limit exceeded for channel ${channelNumber}`);
|
|
241
|
+
}
|
|
242
|
+
const audio = new Audio(validatedUrl);
|
|
53
243
|
// Set up comprehensive error handling
|
|
54
|
-
(0, errors_1.setupAudioErrorHandling)(audio, channelNumber,
|
|
55
|
-
yield (0, errors_1.handleAudioError)(audio, channelNumber,
|
|
244
|
+
(0, errors_1.setupAudioErrorHandling)(audio, channelNumber, validatedUrl, (error) => __awaiter(void 0, void 0, void 0, function* () {
|
|
245
|
+
yield (0, errors_1.handleAudioError)(audio, channelNumber, validatedUrl, error);
|
|
56
246
|
}));
|
|
57
247
|
// Apply options if provided
|
|
58
248
|
if (options) {
|
|
@@ -65,6 +255,10 @@ const queueAudio = (audioUrl_1, ...args_1) => __awaiter(void 0, [audioUrl_1, ...
|
|
|
65
255
|
// Set channel volume to match the audio volume
|
|
66
256
|
channel.volume = clampedVolume;
|
|
67
257
|
}
|
|
258
|
+
// Set channel-specific queue limit if provided
|
|
259
|
+
if (typeof options.maxQueueSize === 'number') {
|
|
260
|
+
channel.maxQueueSize = options.maxQueueSize;
|
|
261
|
+
}
|
|
68
262
|
}
|
|
69
263
|
// Handle priority option (same as addToFront for backward compatibility)
|
|
70
264
|
const shouldAddToFront = (options === null || options === void 0 ? void 0 : options.addToFront) || (options === null || options === void 0 ? void 0 : options.priority);
|
|
@@ -88,7 +282,7 @@ const queueAudio = (audioUrl_1, ...args_1) => __awaiter(void 0, [audioUrl_1, ...
|
|
|
88
282
|
// Use setTimeout to ensure the queue change event is emitted first
|
|
89
283
|
setTimeout(() => {
|
|
90
284
|
(0, exports.playAudioQueue)(channelNumber).catch((error) => {
|
|
91
|
-
(0, errors_1.handleAudioError)(audio, channelNumber,
|
|
285
|
+
(0, errors_1.handleAudioError)(audio, channelNumber, validatedUrl, error);
|
|
92
286
|
});
|
|
93
287
|
}, 0);
|
|
94
288
|
}
|
|
@@ -167,16 +361,9 @@ const playAudioQueue = (channelNumber) => __awaiter(void 0, void 0, void 0, func
|
|
|
167
361
|
remainingInQueue: channel.queue.length - 1,
|
|
168
362
|
src: currentAudio.src
|
|
169
363
|
}, info_1.audioChannels);
|
|
170
|
-
// Restore volume levels when priority channel stops
|
|
171
|
-
yield (0, volume_1.restoreVolumeLevels)(channelNumber);
|
|
172
|
-
// Clean up event listeners
|
|
173
|
-
currentAudio.removeEventListener('loadedmetadata', handleLoadedMetadata);
|
|
174
|
-
currentAudio.removeEventListener('play', handlePlay);
|
|
175
|
-
currentAudio.removeEventListener('ended', handleEnded);
|
|
176
|
-
(0, events_1.cleanupProgressTracking)(currentAudio, channelNumber, info_1.audioChannels);
|
|
177
364
|
// Handle looping vs non-looping audio
|
|
178
365
|
if (currentAudio.loop) {
|
|
179
|
-
// For looping audio,
|
|
366
|
+
// For looping audio, keep in queue and try to restart playback
|
|
180
367
|
currentAudio.currentTime = 0;
|
|
181
368
|
try {
|
|
182
369
|
yield currentAudio.play();
|
|
@@ -188,9 +375,14 @@ const playAudioQueue = (channelNumber) => __awaiter(void 0, void 0, void 0, func
|
|
|
188
375
|
}
|
|
189
376
|
else {
|
|
190
377
|
// For non-looping audio, remove from queue and play next
|
|
378
|
+
currentAudio.pause();
|
|
379
|
+
(0, events_1.cleanupProgressTracking)(currentAudio, channelNumber, info_1.audioChannels);
|
|
191
380
|
channel.queue.shift();
|
|
192
|
-
|
|
193
|
-
|
|
381
|
+
channel.isPaused = false; // Reset pause state
|
|
382
|
+
// Restore volume levels AFTER removing audio from queue
|
|
383
|
+
yield (0, volume_1.restoreVolumeLevels)(channelNumber);
|
|
384
|
+
(0, events_1.emitQueueChange)(channelNumber, info_1.audioChannels);
|
|
385
|
+
// Play next audio immediately if there's more in queue
|
|
194
386
|
yield (0, exports.playAudioQueue)(channelNumber);
|
|
195
387
|
resolve();
|
|
196
388
|
}
|
|
@@ -230,16 +422,18 @@ const stopCurrentAudioInChannel = (...args_1) => __awaiter(void 0, [...args_1],
|
|
|
230
422
|
remainingInQueue: channel.queue.length - 1,
|
|
231
423
|
src: currentAudio.src
|
|
232
424
|
}, info_1.audioChannels);
|
|
233
|
-
// Restore volume levels when stopping
|
|
234
|
-
yield (0, volume_1.restoreVolumeLevels)(channelNumber);
|
|
235
425
|
currentAudio.pause();
|
|
236
426
|
(0, events_1.cleanupProgressTracking)(currentAudio, channelNumber, info_1.audioChannels);
|
|
237
427
|
channel.queue.shift();
|
|
238
428
|
channel.isPaused = false; // Reset pause state
|
|
429
|
+
// Restore volume levels AFTER removing from queue (so queue.length check works correctly)
|
|
430
|
+
yield (0, volume_1.restoreVolumeLevels)(channelNumber);
|
|
239
431
|
(0, events_1.emitQueueChange)(channelNumber, info_1.audioChannels);
|
|
240
|
-
// Start next audio
|
|
241
|
-
|
|
242
|
-
|
|
432
|
+
// Start next audio immediately if there's more in queue
|
|
433
|
+
if (channel.queue.length > 0) {
|
|
434
|
+
// eslint-disable-next-line no-console
|
|
435
|
+
(0, exports.playAudioQueue)(channelNumber).catch(console.error);
|
|
436
|
+
}
|
|
243
437
|
}
|
|
244
438
|
});
|
|
245
439
|
exports.stopCurrentAudioInChannel = stopCurrentAudioInChannel;
|
|
@@ -253,27 +447,29 @@ exports.stopCurrentAudioInChannel = stopCurrentAudioInChannel;
|
|
|
253
447
|
* ```
|
|
254
448
|
*/
|
|
255
449
|
const stopAllAudioInChannel = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (channelNumber = 0) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (channel
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
channelNumber,
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
450
|
+
return withChannelLock(channelNumber, 'stopAllAudioInChannel', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
451
|
+
const channel = info_1.audioChannels[channelNumber];
|
|
452
|
+
if (channel) {
|
|
453
|
+
if (channel.queue.length > 0) {
|
|
454
|
+
const currentAudio = channel.queue[0];
|
|
455
|
+
(0, events_1.emitAudioComplete)(channelNumber, {
|
|
456
|
+
channelNumber,
|
|
457
|
+
fileName: (0, utils_1.extractFileName)(currentAudio.src),
|
|
458
|
+
remainingInQueue: 0, // Will be 0 since we're clearing the queue
|
|
459
|
+
src: currentAudio.src
|
|
460
|
+
}, info_1.audioChannels);
|
|
461
|
+
// Restore volume levels when stopping
|
|
462
|
+
yield (0, volume_1.restoreVolumeLevels)(channelNumber);
|
|
463
|
+
currentAudio.pause();
|
|
464
|
+
(0, events_1.cleanupProgressTracking)(currentAudio, channelNumber, info_1.audioChannels);
|
|
465
|
+
}
|
|
466
|
+
// Clean up all progress tracking for this channel
|
|
467
|
+
channel.queue.forEach((audio) => (0, events_1.cleanupProgressTracking)(audio, channelNumber, info_1.audioChannels));
|
|
468
|
+
channel.queue = [];
|
|
469
|
+
channel.isPaused = false; // Reset pause state
|
|
470
|
+
(0, events_1.emitQueueChange)(channelNumber, info_1.audioChannels);
|
|
270
471
|
}
|
|
271
|
-
|
|
272
|
-
channel.queue.forEach((audio) => (0, events_1.cleanupProgressTracking)(audio, channelNumber, info_1.audioChannels));
|
|
273
|
-
channel.queue = [];
|
|
274
|
-
channel.isPaused = false; // Reset pause state
|
|
275
|
-
(0, events_1.emitQueueChange)(channelNumber, info_1.audioChannels);
|
|
276
|
-
}
|
|
472
|
+
}));
|
|
277
473
|
});
|
|
278
474
|
exports.stopAllAudioInChannel = stopAllAudioInChannel;
|
|
279
475
|
/**
|
|
@@ -291,3 +487,98 @@ const stopAllAudio = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
291
487
|
yield Promise.all(stopPromises);
|
|
292
488
|
});
|
|
293
489
|
exports.stopAllAudio = stopAllAudio;
|
|
490
|
+
/**
|
|
491
|
+
* Completely destroys a channel and cleans up all associated resources
|
|
492
|
+
* This stops all audio, cancels transitions, clears callbacks, and removes the channel
|
|
493
|
+
* @param channelNumber - The channel number to destroy (defaults to 0)
|
|
494
|
+
* @example
|
|
495
|
+
* ```typescript
|
|
496
|
+
* await destroyChannel(1); // Completely removes channel 1 and cleans up resources
|
|
497
|
+
* ```
|
|
498
|
+
*/
|
|
499
|
+
const destroyChannel = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (channelNumber = 0) {
|
|
500
|
+
const channel = info_1.audioChannels[channelNumber];
|
|
501
|
+
if (!channel)
|
|
502
|
+
return;
|
|
503
|
+
// Comprehensive cleanup of all audio elements in the queue
|
|
504
|
+
if (channel.queue && channel.queue.length > 0) {
|
|
505
|
+
channel.queue.forEach((audio) => {
|
|
506
|
+
// Properly clean up each audio element
|
|
507
|
+
const cleanAudio = audio;
|
|
508
|
+
cleanAudio.pause();
|
|
509
|
+
cleanAudio.currentTime = 0;
|
|
510
|
+
// Remove all event listeners if possible
|
|
511
|
+
if (cleanAudio.parentNode) {
|
|
512
|
+
cleanAudio.parentNode.removeChild(cleanAudio);
|
|
513
|
+
}
|
|
514
|
+
// Clean up audio attributes
|
|
515
|
+
cleanAudio.removeAttribute('src');
|
|
516
|
+
// Reset audio element state
|
|
517
|
+
if (cleanAudio.src) {
|
|
518
|
+
// Copy essential properties
|
|
519
|
+
cleanAudio.src = '';
|
|
520
|
+
try {
|
|
521
|
+
cleanAudio.load();
|
|
522
|
+
}
|
|
523
|
+
catch (_a) {
|
|
524
|
+
// Ignore load errors in tests (jsdom limitation)
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
// Stop all audio in the channel (this handles additional cleanup)
|
|
530
|
+
yield (0, exports.stopAllAudioInChannel)(channelNumber);
|
|
531
|
+
// Cancel any active volume transitions
|
|
532
|
+
(0, volume_1.cancelVolumeTransition)(channelNumber);
|
|
533
|
+
// Clear all callback sets completely
|
|
534
|
+
const callbackProperties = [
|
|
535
|
+
'audioCompleteCallbacks',
|
|
536
|
+
'audioErrorCallbacks',
|
|
537
|
+
'audioPauseCallbacks',
|
|
538
|
+
'audioResumeCallbacks',
|
|
539
|
+
'audioStartCallbacks',
|
|
540
|
+
'queueChangeCallbacks',
|
|
541
|
+
'progressCallbacks'
|
|
542
|
+
];
|
|
543
|
+
callbackProperties.forEach((prop) => {
|
|
544
|
+
if (channel[prop]) {
|
|
545
|
+
channel[prop].clear();
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
// Remove optional channel configuration
|
|
549
|
+
delete channel.fadeState;
|
|
550
|
+
delete channel.retryConfig;
|
|
551
|
+
// Reset required properties to clean state
|
|
552
|
+
channel.isPaused = false;
|
|
553
|
+
channel.volume = 1.0;
|
|
554
|
+
channel.queue = [];
|
|
555
|
+
// Remove the channel completely
|
|
556
|
+
delete info_1.audioChannels[channelNumber];
|
|
557
|
+
});
|
|
558
|
+
exports.destroyChannel = destroyChannel;
|
|
559
|
+
/**
|
|
560
|
+
* Destroys all channels and cleans up all resources
|
|
561
|
+
* This is useful for complete cleanup when the audio system is no longer needed
|
|
562
|
+
* @example
|
|
563
|
+
* ```typescript
|
|
564
|
+
* await destroyAllChannels(); // Complete cleanup - removes all channels
|
|
565
|
+
* ```
|
|
566
|
+
*/
|
|
567
|
+
const destroyAllChannels = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
568
|
+
const destroyPromises = [];
|
|
569
|
+
// Collect indices of existing channels
|
|
570
|
+
const channelIndices = [];
|
|
571
|
+
info_1.audioChannels.forEach((_channel, index) => {
|
|
572
|
+
if (info_1.audioChannels[index]) {
|
|
573
|
+
channelIndices.push(index);
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
// Destroy all channels in parallel
|
|
577
|
+
channelIndices.forEach((index) => {
|
|
578
|
+
destroyPromises.push((0, exports.destroyChannel)(index));
|
|
579
|
+
});
|
|
580
|
+
yield Promise.all(destroyPromises);
|
|
581
|
+
// Clear the entire array
|
|
582
|
+
info_1.audioChannels.length = 0;
|
|
583
|
+
});
|
|
584
|
+
exports.destroyAllChannels = destroyAllChannels;
|
package/dist/errors.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { AudioErrorInfo, AudioErrorCallback, RetryConfig, ErrorRecoveryOptions,
|
|
|
6
6
|
* Subscribes to audio error events for a specific channel
|
|
7
7
|
* @param channelNumber - The channel number to listen to (defaults to 0)
|
|
8
8
|
* @param callback - Function to call when an audio error occurs
|
|
9
|
+
* @throws Error if the channel number exceeds the maximum allowed channels
|
|
9
10
|
* @example
|
|
10
11
|
* ```typescript
|
|
11
12
|
* onAudioError(0, (errorInfo) => {
|
package/dist/errors.js
CHANGED
|
@@ -36,6 +36,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
36
36
|
};
|
|
37
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
38
|
exports.createProtectedAudioElement = exports.handleAudioError = exports.setupAudioErrorHandling = exports.categorizeError = exports.emitAudioError = exports.retryFailedAudio = exports.getErrorRecovery = exports.setErrorRecovery = exports.getRetryConfig = exports.setRetryConfig = exports.offAudioError = exports.onAudioError = void 0;
|
|
39
|
+
const types_1 = require("./types");
|
|
39
40
|
const info_1 = require("./info");
|
|
40
41
|
const utils_1 = require("./utils");
|
|
41
42
|
let globalRetryConfig = {
|
|
@@ -59,6 +60,7 @@ const loadTimeouts = new WeakMap();
|
|
|
59
60
|
* Subscribes to audio error events for a specific channel
|
|
60
61
|
* @param channelNumber - The channel number to listen to (defaults to 0)
|
|
61
62
|
* @param callback - Function to call when an audio error occurs
|
|
63
|
+
* @throws Error if the channel number exceeds the maximum allowed channels
|
|
62
64
|
* @example
|
|
63
65
|
* ```typescript
|
|
64
66
|
* onAudioError(0, (errorInfo) => {
|
|
@@ -68,7 +70,14 @@ const loadTimeouts = new WeakMap();
|
|
|
68
70
|
* ```
|
|
69
71
|
*/
|
|
70
72
|
const onAudioError = (channelNumber = 0, callback) => {
|
|
71
|
-
//
|
|
73
|
+
// Validate channel number limits BEFORE creating any channels
|
|
74
|
+
if (channelNumber < 0) {
|
|
75
|
+
throw new Error('Channel number must be non-negative');
|
|
76
|
+
}
|
|
77
|
+
if (channelNumber >= types_1.MAX_CHANNELS) {
|
|
78
|
+
throw new Error(`Channel number ${channelNumber} exceeds maximum allowed channels (${types_1.MAX_CHANNELS})`);
|
|
79
|
+
}
|
|
80
|
+
// Ensure channel exists (now safe because we validated the limit above)
|
|
72
81
|
while (info_1.audioChannels.length <= channelNumber) {
|
|
73
82
|
info_1.audioChannels.push({
|
|
74
83
|
audioCompleteCallbacks: new Set(),
|
package/dist/index.d.ts
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
* Exports all public functions and types for audio queue management, pause/resume controls,
|
|
4
4
|
* volume management with ducking, progress tracking, and comprehensive event system
|
|
5
5
|
*/
|
|
6
|
-
export { queueAudio, queueAudioPriority, stopCurrentAudioInChannel, stopAllAudioInChannel, stopAllAudio, playAudioQueue } from './core';
|
|
6
|
+
export { queueAudio, queueAudioPriority, stopCurrentAudioInChannel, stopAllAudioInChannel, stopAllAudio, playAudioQueue, destroyChannel, destroyAllChannels, setQueueConfig, getQueueConfig, setChannelQueueLimit } from './core';
|
|
7
|
+
export { clearQueueAfterCurrent, getQueueItemInfo, getQueueLength, removeQueuedItem, reorderQueue, swapQueueItems } from './queue-manipulation';
|
|
7
8
|
export { getErrorRecovery, getRetryConfig, offAudioError, onAudioError, retryFailedAudio, setErrorRecovery, setRetryConfig } from './errors';
|
|
8
9
|
export { getAllChannelsPauseState, isChannelPaused, pauseAllChannels, pauseAllWithFade, pauseChannel, pauseWithFade, resumeAllChannels, resumeAllWithFade, resumeChannel, resumeWithFade, togglePauseAllChannels, togglePauseAllWithFade, togglePauseChannel, togglePauseWithFade } from './pause';
|
|
9
|
-
export { clearVolumeDucking, fadeVolume, getAllChannelsVolume, getChannelVolume, getFadeConfig, setAllChannelsVolume, setChannelVolume, setVolumeDucking, transitionVolume } from './volume';
|
|
10
|
+
export { clearVolumeDucking, fadeVolume, getAllChannelsVolume, getChannelVolume, getFadeConfig, setAllChannelsVolume, setChannelVolume, setVolumeDucking, transitionVolume, cancelVolumeTransition, cancelAllVolumeTransitions } from './volume';
|
|
10
11
|
export { getAllChannelsInfo, getCurrentAudioInfo, getQueueSnapshot, offAudioPause, offAudioProgress, offAudioResume, offQueueChange, onAudioComplete, onAudioPause, onAudioProgress, onAudioResume, onAudioStart, onQueueChange } from './info';
|
|
11
12
|
export { audioChannels } from './info';
|
|
12
|
-
export { cleanWebpackFilename, createQueueSnapshot, extractFileName, getAudioInfoFromElement } from './utils';
|
|
13
|
-
export type { AudioCompleteCallback, AudioCompleteInfo, AudioErrorCallback, AudioErrorInfo, AudioInfo, AudioPauseCallback, AudioQueueOptions, AudioResumeCallback, AudioStartCallback, AudioStartInfo, ChannelFadeState, ErrorRecoveryOptions, ExtendedAudioQueueChannel, FadeConfig, ProgressCallback, QueueChangeCallback, QueueItem, QueueSnapshot, RetryConfig, VolumeConfig } from './types';
|
|
14
|
-
export { EasingType, FadeType, GLOBAL_PROGRESS_KEY } from './types';
|
|
13
|
+
export { cleanWebpackFilename, createQueueSnapshot, extractFileName, getAudioInfoFromElement, sanitizeForDisplay, validateAudioUrl } from './utils';
|
|
14
|
+
export type { AudioCompleteCallback, AudioCompleteInfo, AudioErrorCallback, AudioErrorInfo, AudioInfo, AudioPauseCallback, AudioQueueOptions, AudioResumeCallback, AudioStartCallback, AudioStartInfo, ChannelFadeState, ErrorRecoveryOptions, ExtendedAudioQueueChannel, FadeConfig, ProgressCallback, QueueChangeCallback, QueueItem, QueueManipulationResult, QueueSnapshot, RetryConfig, VolumeConfig, QueueConfig } from './types';
|
|
15
|
+
export { EasingType, FadeType, MAX_CHANNELS, TimerType, GLOBAL_PROGRESS_KEY } from './types';
|