audioq 2.0.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.
@@ -0,0 +1,138 @@
1
+ /**
2
+ * @fileoverview Error handling, retry logic, and recovery mechanisms for the audioq package
3
+ */
4
+ import { AudioErrorInfo, AudioErrorCallback, AudioErrorType, RetryConfig, ErrorRecoveryOptions, ExtendedAudioQueueChannel } from './types';
5
+ /**
6
+ * Subscribes to audio error events for a specific channel
7
+ * @param channelNumber - The channel number to listen to (defaults to 0)
8
+ * @param callback - Function to call when an audio error occurs
9
+ * @throws Error if the channel number exceeds the maximum allowed channels
10
+ * @example
11
+ * ```typescript
12
+ * onAudioError(0, (errorInfo) => {
13
+ * console.log(`Audio error: ${errorInfo.error.message}`);
14
+ * console.log(`Error type: ${errorInfo.errorType}`);
15
+ * });
16
+ * ```
17
+ */
18
+ export declare const onAudioError: (channelNumber: number | undefined, callback: AudioErrorCallback) => void;
19
+ /**
20
+ * Unsubscribes from audio error events for a specific channel
21
+ * @param channelNumber - The channel number to stop listening to (defaults to 0)
22
+ * @param callback - The specific callback to remove (optional - if not provided, removes all)
23
+ * @example
24
+ * ```typescript
25
+ * offAudioError(0); // Remove all error callbacks for channel 0
26
+ * offAudioError(0, specificCallback); // Remove specific callback
27
+ * ```
28
+ */
29
+ export declare const offAudioError: (channelNumber?: number, callback?: AudioErrorCallback) => void;
30
+ /**
31
+ * Sets the global retry configuration for audio loading failures
32
+ * @param config - Retry configuration options
33
+ * @example
34
+ * ```typescript
35
+ * setRetryConfig({
36
+ * enabled: true,
37
+ * maxRetries: 5,
38
+ * baseDelay: 1000,
39
+ * exponentialBackoff: true,
40
+ * timeoutMs: 15000,
41
+ * fallbackUrls: ['https://cdn.backup.com/audio/'],
42
+ * skipOnFailure: true
43
+ * });
44
+ * ```
45
+ */
46
+ export declare const setRetryConfig: (config: Partial<RetryConfig>) => void;
47
+ /**
48
+ * Gets the current global retry configuration
49
+ * @returns Current retry configuration
50
+ * @example
51
+ * ```typescript
52
+ * const config = getRetryConfig();
53
+ * console.log(`Max retries: ${config.maxRetries}`);
54
+ * ```
55
+ */
56
+ export declare const getRetryConfig: () => RetryConfig;
57
+ /**
58
+ * Sets the global error recovery configuration
59
+ * @param options - Error recovery options
60
+ * @example
61
+ * ```typescript
62
+ * setErrorRecovery({
63
+ * autoRetry: true,
64
+ * fallbackToNextTrack: true,
65
+ * logErrorsToAnalytics: true,
66
+ * preserveQueueOnError: true,
67
+ * showUserFeedback: true
68
+ * });
69
+ * ```
70
+ */
71
+ export declare const setErrorRecovery: (options: Partial<ErrorRecoveryOptions>) => void;
72
+ /**
73
+ * Gets the current global error recovery configuration
74
+ * @returns Current error recovery configuration
75
+ * @example
76
+ * ```typescript
77
+ * const recovery = getErrorRecovery();
78
+ * console.log(`Auto retry enabled: ${recovery.autoRetry}`);
79
+ * ```
80
+ */
81
+ export declare const getErrorRecovery: () => ErrorRecoveryOptions;
82
+ /**
83
+ * Manually retries loading failed audio for a specific channel
84
+ * @param channelNumber - The channel number to retry (defaults to 0)
85
+ * @returns Promise that resolves to true if retry was successful, false otherwise
86
+ * @example
87
+ * ```typescript
88
+ * const success = await retryFailedAudio(0);
89
+ * if (success) {
90
+ * console.log('Audio retry successful');
91
+ * } else {
92
+ * console.log('Audio retry failed');
93
+ * }
94
+ * ```
95
+ */
96
+ export declare const retryFailedAudio: (channelNumber?: number) => Promise<boolean>;
97
+ /**
98
+ * Emits an audio error event to all registered listeners for a specific channel
99
+ * @param channelNumber - The channel number where the error occurred
100
+ * @param errorInfo - Information about the error
101
+ * @param audioChannels - Array of audio channels
102
+ * @internal
103
+ */
104
+ export declare const emitAudioError: (channelNumber: number, errorInfo: AudioErrorInfo, audioChannels: ExtendedAudioQueueChannel[]) => void;
105
+ /**
106
+ * Determines the error type based on the error object and context
107
+ * @param error - The error that occurred
108
+ * @param audio - The audio element that failed
109
+ * @returns The categorized error type
110
+ * @internal
111
+ */
112
+ export declare const categorizeError: (error: Error, audio: HTMLAudioElement) => AudioErrorType;
113
+ /**
114
+ * Sets up comprehensive error handling for an audio element
115
+ * @param audio - The audio element to set up error handling for
116
+ * @param channelNumber - The channel number this audio belongs to
117
+ * @param originalUrl - The original URL that was requested
118
+ * @param onError - Callback for when an error occurs
119
+ * @internal
120
+ */
121
+ export declare const setupAudioErrorHandling: (audio: HTMLAudioElement, channelNumber: number, originalUrl: string, onError?: (error: Error) => Promise<void>) => void;
122
+ /**
123
+ * Handles audio errors with retry logic and recovery mechanisms
124
+ * @param audio - The audio element that failed
125
+ * @param channelNumber - The channel number
126
+ * @param originalUrl - The original URL that was requested
127
+ * @param error - The error that occurred
128
+ * @internal
129
+ */
130
+ export declare const handleAudioError: (audio: HTMLAudioElement, channelNumber: number, originalUrl: string, error: Error) => Promise<void>;
131
+ /**
132
+ * Creates a timeout-protected audio element with comprehensive error handling
133
+ * @param url - The audio URL to load
134
+ * @param channelNumber - The channel number this audio belongs to
135
+ * @returns Promise that resolves to the configured audio element
136
+ * @internal
137
+ */
138
+ export declare const createProtectedAudioElement: (url: string, channelNumber: number) => Promise<HTMLAudioElement>;
package/dist/errors.js ADDED
@@ -0,0 +1,441 @@
1
+ "use strict";
2
+ /**
3
+ * @fileoverview Error handling, retry logic, and recovery mechanisms for the audioq package
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || function (mod) {
22
+ if (mod && mod.__esModule) return mod;
23
+ var result = {};
24
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
25
+ __setModuleDefault(result, mod);
26
+ return result;
27
+ };
28
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
29
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
30
+ return new (P || (P = Promise))(function (resolve, reject) {
31
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
32
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
33
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
34
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
35
+ });
36
+ };
37
+ Object.defineProperty(exports, "__esModule", { value: true });
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");
40
+ const info_1 = require("./info");
41
+ const utils_1 = require("./utils");
42
+ let globalRetryConfig = {
43
+ baseDelay: 1000,
44
+ enabled: true,
45
+ exponentialBackoff: true,
46
+ fallbackUrls: [],
47
+ maxRetries: 3,
48
+ skipOnFailure: false,
49
+ timeoutMs: 10000
50
+ };
51
+ let globalErrorRecovery = {
52
+ autoRetry: true,
53
+ fallbackToNextTrack: true,
54
+ logErrorsToAnalytics: false,
55
+ preserveQueueOnError: true,
56
+ showUserFeedback: false
57
+ };
58
+ const retryAttempts = new WeakMap();
59
+ /**
60
+ * Subscribes to audio error events for a specific channel
61
+ * @param channelNumber - The channel number to listen to (defaults to 0)
62
+ * @param callback - Function to call when an audio error occurs
63
+ * @throws Error if the channel number exceeds the maximum allowed channels
64
+ * @example
65
+ * ```typescript
66
+ * onAudioError(0, (errorInfo) => {
67
+ * console.log(`Audio error: ${errorInfo.error.message}`);
68
+ * console.log(`Error type: ${errorInfo.errorType}`);
69
+ * });
70
+ * ```
71
+ */
72
+ const onAudioError = (channelNumber = 0, callback) => {
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)
81
+ while (info_1.audioChannels.length <= channelNumber) {
82
+ info_1.audioChannels.push({
83
+ audioCompleteCallbacks: new Set(),
84
+ audioErrorCallbacks: new Set(),
85
+ audioPauseCallbacks: new Set(),
86
+ audioResumeCallbacks: new Set(),
87
+ audioStartCallbacks: new Set(),
88
+ isPaused: false,
89
+ progressCallbacks: new Map(),
90
+ queue: [],
91
+ queueChangeCallbacks: new Set(),
92
+ volume: 1.0
93
+ });
94
+ }
95
+ const channel = info_1.audioChannels[channelNumber];
96
+ if (!channel.audioErrorCallbacks) {
97
+ channel.audioErrorCallbacks = new Set();
98
+ }
99
+ channel.audioErrorCallbacks.add(callback);
100
+ };
101
+ exports.onAudioError = onAudioError;
102
+ /**
103
+ * Unsubscribes from audio error events for a specific channel
104
+ * @param channelNumber - The channel number to stop listening to (defaults to 0)
105
+ * @param callback - The specific callback to remove (optional - if not provided, removes all)
106
+ * @example
107
+ * ```typescript
108
+ * offAudioError(0); // Remove all error callbacks for channel 0
109
+ * offAudioError(0, specificCallback); // Remove specific callback
110
+ * ```
111
+ */
112
+ const offAudioError = (channelNumber = 0, callback) => {
113
+ const channel = info_1.audioChannels[channelNumber];
114
+ if (!(channel === null || channel === void 0 ? void 0 : channel.audioErrorCallbacks))
115
+ return;
116
+ if (callback) {
117
+ channel.audioErrorCallbacks.delete(callback);
118
+ }
119
+ else {
120
+ channel.audioErrorCallbacks.clear();
121
+ }
122
+ };
123
+ exports.offAudioError = offAudioError;
124
+ /**
125
+ * Sets the global retry configuration for audio loading failures
126
+ * @param config - Retry configuration options
127
+ * @example
128
+ * ```typescript
129
+ * setRetryConfig({
130
+ * enabled: true,
131
+ * maxRetries: 5,
132
+ * baseDelay: 1000,
133
+ * exponentialBackoff: true,
134
+ * timeoutMs: 15000,
135
+ * fallbackUrls: ['https://cdn.backup.com/audio/'],
136
+ * skipOnFailure: true
137
+ * });
138
+ * ```
139
+ */
140
+ const setRetryConfig = (config) => {
141
+ globalRetryConfig = Object.assign(Object.assign({}, globalRetryConfig), config);
142
+ };
143
+ exports.setRetryConfig = setRetryConfig;
144
+ /**
145
+ * Gets the current global retry configuration
146
+ * @returns Current retry configuration
147
+ * @example
148
+ * ```typescript
149
+ * const config = getRetryConfig();
150
+ * console.log(`Max retries: ${config.maxRetries}`);
151
+ * ```
152
+ */
153
+ const getRetryConfig = () => {
154
+ return Object.assign({}, globalRetryConfig);
155
+ };
156
+ exports.getRetryConfig = getRetryConfig;
157
+ /**
158
+ * Sets the global error recovery configuration
159
+ * @param options - Error recovery options
160
+ * @example
161
+ * ```typescript
162
+ * setErrorRecovery({
163
+ * autoRetry: true,
164
+ * fallbackToNextTrack: true,
165
+ * logErrorsToAnalytics: true,
166
+ * preserveQueueOnError: true,
167
+ * showUserFeedback: true
168
+ * });
169
+ * ```
170
+ */
171
+ const setErrorRecovery = (options) => {
172
+ globalErrorRecovery = Object.assign(Object.assign({}, globalErrorRecovery), options);
173
+ };
174
+ exports.setErrorRecovery = setErrorRecovery;
175
+ /**
176
+ * Gets the current global error recovery configuration
177
+ * @returns Current error recovery configuration
178
+ * @example
179
+ * ```typescript
180
+ * const recovery = getErrorRecovery();
181
+ * console.log(`Auto retry enabled: ${recovery.autoRetry}`);
182
+ * ```
183
+ */
184
+ const getErrorRecovery = () => {
185
+ return Object.assign({}, globalErrorRecovery);
186
+ };
187
+ exports.getErrorRecovery = getErrorRecovery;
188
+ /**
189
+ * Manually retries loading failed audio for a specific channel
190
+ * @param channelNumber - The channel number to retry (defaults to 0)
191
+ * @returns Promise that resolves to true if retry was successful, false otherwise
192
+ * @example
193
+ * ```typescript
194
+ * const success = await retryFailedAudio(0);
195
+ * if (success) {
196
+ * console.log('Audio retry successful');
197
+ * } else {
198
+ * console.log('Audio retry failed');
199
+ * }
200
+ * ```
201
+ */
202
+ const retryFailedAudio = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (channelNumber = 0) {
203
+ var _a;
204
+ const channel = info_1.audioChannels[channelNumber];
205
+ if (!channel || channel.queue.length === 0)
206
+ return false;
207
+ const currentAudio = channel.queue[0];
208
+ const currentAttempts = (_a = retryAttempts.get(currentAudio)) !== null && _a !== void 0 ? _a : 0;
209
+ if (currentAttempts >= globalRetryConfig.maxRetries) {
210
+ return false;
211
+ }
212
+ try {
213
+ // Reset the audio element
214
+ currentAudio.currentTime = 0;
215
+ yield currentAudio.play();
216
+ // Reset retry counter on successful play
217
+ retryAttempts.delete(currentAudio);
218
+ return true;
219
+ }
220
+ catch (error) {
221
+ // eslint-disable-next-line no-console
222
+ console.error(`Error in retryFailedAudio: ${error}`);
223
+ // Increment retry counter
224
+ retryAttempts.set(currentAudio, currentAttempts + 1);
225
+ return false;
226
+ }
227
+ });
228
+ exports.retryFailedAudio = retryFailedAudio;
229
+ /**
230
+ * Emits an audio error event to all registered listeners for a specific channel
231
+ * @param channelNumber - The channel number where the error occurred
232
+ * @param errorInfo - Information about the error
233
+ * @param audioChannels - Array of audio channels
234
+ * @internal
235
+ */
236
+ const emitAudioError = (channelNumber, errorInfo, audioChannels) => {
237
+ const channel = audioChannels[channelNumber];
238
+ if (!(channel === null || channel === void 0 ? void 0 : channel.audioErrorCallbacks))
239
+ return;
240
+ // Log to analytics if enabled
241
+ if (globalErrorRecovery.logErrorsToAnalytics) {
242
+ // eslint-disable-next-line no-console
243
+ console.error('Audio Error Analytics:', errorInfo);
244
+ }
245
+ channel.audioErrorCallbacks.forEach((callback) => {
246
+ try {
247
+ callback(errorInfo);
248
+ }
249
+ catch (error) {
250
+ // eslint-disable-next-line no-console
251
+ console.error('Error in audio error callback:', error);
252
+ }
253
+ });
254
+ };
255
+ exports.emitAudioError = emitAudioError;
256
+ /**
257
+ * Determines the error type based on the error object and context
258
+ * @param error - The error that occurred
259
+ * @param audio - The audio element that failed
260
+ * @returns The categorized error type
261
+ * @internal
262
+ */
263
+ const categorizeError = (error, audio) => {
264
+ const errorMessage = error.message.toLowerCase();
265
+ if (errorMessage.includes('network') || errorMessage.includes('fetch')) {
266
+ return types_1.AudioErrorType.Network;
267
+ }
268
+ // Check for unsupported format first (more specific than decode)
269
+ if (errorMessage.includes('not supported') ||
270
+ errorMessage.includes('unsupported') ||
271
+ errorMessage.includes('format not supported')) {
272
+ return types_1.AudioErrorType.Unsupported;
273
+ }
274
+ if (errorMessage.includes('decode') || errorMessage.includes('format')) {
275
+ return types_1.AudioErrorType.Decode;
276
+ }
277
+ if (errorMessage.includes('permission') || errorMessage.includes('blocked')) {
278
+ return types_1.AudioErrorType.Permission;
279
+ }
280
+ if (errorMessage.includes('abort')) {
281
+ return types_1.AudioErrorType.Abort;
282
+ }
283
+ if (errorMessage.includes('timeout')) {
284
+ return types_1.AudioErrorType.Timeout;
285
+ }
286
+ // Check audio element network state for more context
287
+ if (audio.networkState === HTMLMediaElement.NETWORK_NO_SOURCE) {
288
+ return types_1.AudioErrorType.Network;
289
+ }
290
+ if (audio.networkState === HTMLMediaElement.NETWORK_LOADING) {
291
+ return types_1.AudioErrorType.Timeout;
292
+ }
293
+ return types_1.AudioErrorType.Unknown;
294
+ };
295
+ exports.categorizeError = categorizeError;
296
+ /**
297
+ * Sets up comprehensive error handling for an audio element
298
+ * @param audio - The audio element to set up error handling for
299
+ * @param channelNumber - The channel number this audio belongs to
300
+ * @param originalUrl - The original URL that was requested
301
+ * @param onError - Callback for when an error occurs
302
+ * @internal
303
+ */
304
+ const setupAudioErrorHandling = (audio, channelNumber, originalUrl, onError) => {
305
+ const channel = info_1.audioChannels[channelNumber];
306
+ if (!channel)
307
+ return;
308
+ // Handle various error events
309
+ const handleError = (_event) => {
310
+ var _a;
311
+ const error = new Error(`Audio loading failed: ${((_a = audio.error) === null || _a === void 0 ? void 0 : _a.message) || 'Unknown error'}`);
312
+ (0, exports.handleAudioError)(audio, channelNumber, originalUrl, error);
313
+ };
314
+ const handleAbort = () => {
315
+ const error = new Error('Audio loading was aborted');
316
+ (0, exports.handleAudioError)(audio, channelNumber, originalUrl, error);
317
+ };
318
+ const handleStall = () => {
319
+ const error = new Error('Audio loading stalled');
320
+ (0, exports.handleAudioError)(audio, channelNumber, originalUrl, error);
321
+ };
322
+ // Add event listeners
323
+ audio.addEventListener('error', handleError);
324
+ audio.addEventListener('abort', handleAbort);
325
+ audio.addEventListener('stalled', handleStall);
326
+ // Custom play error handling
327
+ if (onError) {
328
+ const originalPlay = audio.play.bind(audio);
329
+ const wrappedPlay = () => __awaiter(void 0, void 0, void 0, function* () {
330
+ try {
331
+ yield originalPlay();
332
+ }
333
+ catch (error) {
334
+ yield onError(error);
335
+ throw error;
336
+ }
337
+ });
338
+ audio.play = wrappedPlay;
339
+ }
340
+ };
341
+ exports.setupAudioErrorHandling = setupAudioErrorHandling;
342
+ /**
343
+ * Handles audio errors with retry logic and recovery mechanisms
344
+ * @param audio - The audio element that failed
345
+ * @param channelNumber - The channel number
346
+ * @param originalUrl - The original URL that was requested
347
+ * @param error - The error that occurred
348
+ * @internal
349
+ */
350
+ const handleAudioError = (audio, channelNumber, originalUrl, error) => __awaiter(void 0, void 0, void 0, function* () {
351
+ var _a, _b;
352
+ const channel = info_1.audioChannels[channelNumber];
353
+ if (!channel)
354
+ return;
355
+ const currentAttempts = (_a = retryAttempts.get(audio)) !== null && _a !== void 0 ? _a : 0;
356
+ const retryConfig = (_b = channel.retryConfig) !== null && _b !== void 0 ? _b : globalRetryConfig;
357
+ const errorInfo = {
358
+ channelNumber,
359
+ error,
360
+ errorType: (0, exports.categorizeError)(error, audio),
361
+ fileName: (0, utils_1.extractFileName)(originalUrl),
362
+ remainingInQueue: channel.queue.length - 1,
363
+ retryAttempt: currentAttempts,
364
+ src: originalUrl,
365
+ timestamp: Date.now()
366
+ };
367
+ // Emit error event
368
+ (0, exports.emitAudioError)(channelNumber, errorInfo, info_1.audioChannels);
369
+ // Attempt retry if enabled and within limits
370
+ if (retryConfig.enabled &&
371
+ currentAttempts < retryConfig.maxRetries &&
372
+ globalErrorRecovery.autoRetry) {
373
+ const delay = retryConfig.exponentialBackoff
374
+ ? retryConfig.baseDelay * Math.pow(2, currentAttempts)
375
+ : retryConfig.baseDelay;
376
+ retryAttempts.set(audio, currentAttempts + 1);
377
+ const retryFunction = () => __awaiter(void 0, void 0, void 0, function* () {
378
+ try {
379
+ // Try fallback URLs if available
380
+ if (retryConfig.fallbackUrls && retryConfig.fallbackUrls.length > 0) {
381
+ const fallbackIndex = currentAttempts % retryConfig.fallbackUrls.length;
382
+ const fallbackUrl = retryConfig.fallbackUrls[fallbackIndex] + (0, utils_1.extractFileName)(originalUrl);
383
+ audio.src = fallbackUrl;
384
+ }
385
+ yield audio.load();
386
+ yield audio.play();
387
+ // Reset retry counter on success
388
+ retryAttempts.delete(audio);
389
+ }
390
+ catch (retryError) {
391
+ yield (0, exports.handleAudioError)(audio, channelNumber, originalUrl, retryError);
392
+ }
393
+ });
394
+ setTimeout(retryFunction, delay);
395
+ }
396
+ else {
397
+ // Max retries reached or retry disabled
398
+ if (retryConfig.skipOnFailure || globalErrorRecovery.fallbackToNextTrack) {
399
+ // Skip to next track in queue
400
+ channel.queue.shift();
401
+ // Import and use playAudioQueue to continue with next track
402
+ const { playAudioQueue } = yield Promise.resolve().then(() => __importStar(require('./core')));
403
+ // eslint-disable-next-line no-console
404
+ playAudioQueue(channelNumber).catch(console.error);
405
+ }
406
+ else if (!globalErrorRecovery.preserveQueueOnError) {
407
+ // Clear the entire queue on failure
408
+ channel.queue = [];
409
+ }
410
+ }
411
+ });
412
+ exports.handleAudioError = handleAudioError;
413
+ /**
414
+ * Creates a timeout-protected audio element with comprehensive error handling
415
+ * @param url - The audio URL to load
416
+ * @param channelNumber - The channel number this audio belongs to
417
+ * @returns Promise that resolves to the configured audio element
418
+ * @internal
419
+ */
420
+ const createProtectedAudioElement = (url, channelNumber) => __awaiter(void 0, void 0, void 0, function* () {
421
+ const audio = new Audio();
422
+ return new Promise((resolve, reject) => {
423
+ const handleSuccess = () => {
424
+ resolve(audio);
425
+ };
426
+ const handleError = (error) => {
427
+ reject(error);
428
+ };
429
+ // Set up error handling
430
+ (0, exports.setupAudioErrorHandling)(audio, channelNumber, url, (error) => __awaiter(void 0, void 0, void 0, function* () {
431
+ handleError(error);
432
+ }));
433
+ // Set up success handlers
434
+ audio.addEventListener('canplay', handleSuccess, { once: true });
435
+ audio.addEventListener('loadedmetadata', handleSuccess, { once: true });
436
+ // Start loading
437
+ audio.src = url;
438
+ audio.load();
439
+ });
440
+ });
441
+ exports.createProtectedAudioElement = createProtectedAudioElement;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * @fileoverview Event handling and emission for the audioq package
3
+ */
4
+ import { AudioStartInfo, AudioCompleteInfo, ExtendedAudioQueueChannel, AudioInfo } from './types';
5
+ /**
6
+ * Emits a queue change event to all registered listeners for a specific channel
7
+ * @param channelNumber - The channel number that experienced a queue change
8
+ * @param audioChannels - Array of audio channels
9
+ * @example
10
+ * ```typescript
11
+ * emitQueueChange(0, audioChannels); // Notifies all queue change listeners
12
+ * ```
13
+ */
14
+ export declare const emitQueueChange: (channelNumber: number, audioChannels: ExtendedAudioQueueChannel[]) => void;
15
+ /**
16
+ * Emits an audio start event to all registered listeners for a specific channel
17
+ * @param channelNumber - The channel number where audio started
18
+ * @param audioInfo - Information about the audio that started
19
+ * @param audioChannels - Array of audio channels
20
+ * @example
21
+ * ```typescript
22
+ * emitAudioStart(0, { src: 'song.mp3', fileName: 'song.mp3', duration: 180000, channelNumber: 0 }, audioChannels);
23
+ * ```
24
+ */
25
+ export declare const emitAudioStart: (channelNumber: number, audioInfo: AudioStartInfo, audioChannels: ExtendedAudioQueueChannel[]) => void;
26
+ /**
27
+ * Emits an audio complete event to all registered listeners for a specific channel
28
+ * @param channelNumber - The channel number where audio completed
29
+ * @param audioInfo - Information about the audio that completed
30
+ * @param audioChannels - Array of audio channels
31
+ * @example
32
+ * ```typescript
33
+ * emitAudioComplete(0, { src: 'song.mp3', fileName: 'song.mp3', channelNumber: 0, remainingInQueue: 2 }, audioChannels);
34
+ * ```
35
+ */
36
+ export declare const emitAudioComplete: (channelNumber: number, audioInfo: AudioCompleteInfo, audioChannels: ExtendedAudioQueueChannel[]) => void;
37
+ /**
38
+ * Emits an audio pause event to all registered listeners for a specific channel
39
+ * @param channelNumber - The channel number where audio was paused
40
+ * @param audioInfo - Information about the audio that was paused
41
+ * @param audioChannels - Array of audio channels
42
+ * @example
43
+ * ```typescript
44
+ * emitAudioPause(0, audioInfo, audioChannels);
45
+ * ```
46
+ */
47
+ export declare const emitAudioPause: (channelNumber: number, audioInfo: AudioInfo, audioChannels: ExtendedAudioQueueChannel[]) => void;
48
+ /**
49
+ * Emits an audio resume event to all registered listeners for a specific channel
50
+ * @param channelNumber - The channel number where audio was resumed
51
+ * @param audioInfo - Information about the audio that was resumed
52
+ * @param audioChannels - Array of audio channels
53
+ * @example
54
+ * ```typescript
55
+ * emitAudioResume(0, audioInfo, audioChannels);
56
+ * ```
57
+ */
58
+ export declare const emitAudioResume: (channelNumber: number, audioInfo: AudioInfo, audioChannels: ExtendedAudioQueueChannel[]) => void;
59
+ /**
60
+ * Sets up comprehensive progress tracking for an audio element
61
+ * @param audio - The HTML audio element to track
62
+ * @param channelNumber - The channel number this audio belongs to
63
+ * @param audioChannels - Array of audio channels
64
+ * @example
65
+ * ```typescript
66
+ * const audioElement = new Audio('song.mp3');
67
+ * setupProgressTracking(audioElement, 0, audioChannels);
68
+ * ```
69
+ */
70
+ export declare const setupProgressTracking: (audio: HTMLAudioElement, channelNumber: number, audioChannels: ExtendedAudioQueueChannel[]) => void;
71
+ /**
72
+ * Cleans up progress tracking for an audio element to prevent memory leaks
73
+ * @param audio - The HTML audio element to clean up
74
+ * @param channelNumber - The channel number this audio belongs to
75
+ * @param audioChannels - Array of audio channels
76
+ * @example
77
+ * ```typescript
78
+ * cleanupProgressTracking(audioElement, 0, audioChannels);
79
+ * ```
80
+ */
81
+ export declare const cleanupProgressTracking: (audio: HTMLAudioElement, channelNumber: number, audioChannels: ExtendedAudioQueueChannel[]) => void;