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.
- package/LICENSE +21 -0
- package/README.md +486 -0
- package/dist/core.d.ts +129 -0
- package/dist/core.js +591 -0
- package/dist/errors.d.ts +138 -0
- package/dist/errors.js +441 -0
- package/dist/events.d.ts +81 -0
- package/dist/events.js +217 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +119 -0
- package/dist/info.d.ts +224 -0
- package/dist/info.js +529 -0
- package/dist/pause.d.ts +170 -0
- package/dist/pause.js +467 -0
- package/dist/queue-manipulation.d.ts +104 -0
- package/dist/queue-manipulation.js +319 -0
- package/dist/types.d.ts +382 -0
- package/dist/types.js +55 -0
- package/dist/utils.d.ts +83 -0
- package/dist/utils.js +215 -0
- package/dist/volume.d.ts +162 -0
- package/dist/volume.js +644 -0
- package/dist/web-audio.d.ts +156 -0
- package/dist/web-audio.js +327 -0
- package/package.json +63 -0
- package/src/core.ts +698 -0
- package/src/errors.ts +467 -0
- package/src/events.ts +252 -0
- package/src/index.ts +162 -0
- package/src/info.ts +590 -0
- package/src/pause.ts +523 -0
- package/src/queue-manipulation.ts +378 -0
- package/src/types.ts +415 -0
- package/src/utils.ts +235 -0
- package/src/volume.ts +735 -0
- package/src/web-audio.ts +331 -0
package/dist/errors.d.ts
ADDED
|
@@ -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;
|
package/dist/events.d.ts
ADDED
|
@@ -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;
|