audio-channel-queue 1.11.0 → 1.12.1-beta.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 +48 -25
- package/dist/core.js +6 -3
- package/dist/errors.d.ts +4 -4
- package/dist/errors.js +12 -51
- package/dist/index.d.ts +4 -3
- package/dist/index.js +19 -5
- package/dist/info.d.ts +20 -15
- package/dist/info.js +20 -15
- package/dist/types.d.ts +103 -24
- package/dist/types.js +17 -4
- package/dist/volume.d.ts +15 -14
- package/dist/volume.js +131 -33
- package/dist/web-audio.d.ts +156 -0
- package/dist/web-audio.js +327 -0
- package/package.json +1 -1
- package/src/core.ts +14 -4
- package/src/errors.ts +15 -64
- package/src/index.ts +31 -6
- package/src/info.ts +20 -15
- package/src/types.ts +107 -24
- package/src/volume.ts +158 -36
- package/src/web-audio.ts +331 -0
package/dist/types.d.ts
CHANGED
|
@@ -24,15 +24,15 @@ export interface AudioQueueChannel {
|
|
|
24
24
|
* Volume ducking configuration for channels
|
|
25
25
|
*/
|
|
26
26
|
export interface VolumeConfig {
|
|
27
|
+
/** Duration in milliseconds for volume duck transition (defaults to 250ms) */
|
|
28
|
+
duckTransitionDuration?: number;
|
|
29
|
+
/** Volume level for all other channels when priority channel is active (0-1) */
|
|
30
|
+
duckingVolume: number;
|
|
27
31
|
/** The channel number that should have priority */
|
|
28
32
|
priorityChannel: number;
|
|
29
33
|
/** Volume level for the priority channel (0-1) */
|
|
30
34
|
priorityVolume: number;
|
|
31
|
-
/**
|
|
32
|
-
duckingVolume: number;
|
|
33
|
-
/** Duration in milliseconds for volume duck transition (defaults to 250ms) */
|
|
34
|
-
duckTransitionDuration?: number;
|
|
35
|
-
/** Duration in milliseconds for volume restore transition (defaults to 500ms) */
|
|
35
|
+
/** Duration in milliseconds for volume restore transition (defaults to 250ms) */
|
|
36
36
|
restoreTransitionDuration?: number;
|
|
37
37
|
/** Easing function for volume transitions (defaults to 'ease-out') */
|
|
38
38
|
transitionEasing?: EasingType;
|
|
@@ -47,8 +47,6 @@ export interface AudioQueueOptions {
|
|
|
47
47
|
loop?: boolean;
|
|
48
48
|
/** Maximum number of items allowed in the queue (defaults to unlimited) */
|
|
49
49
|
maxQueueSize?: number;
|
|
50
|
-
/** @deprecated Use addToFront instead. Legacy support for priority queuing */
|
|
51
|
-
priority?: boolean;
|
|
52
50
|
/** Volume level for this specific audio (0-1) */
|
|
53
51
|
volume?: number;
|
|
54
52
|
}
|
|
@@ -192,67 +190,148 @@ export type AudioPauseCallback = (channelNumber: number, audioInfo: AudioInfo) =
|
|
|
192
190
|
*/
|
|
193
191
|
export type AudioResumeCallback = (channelNumber: number, audioInfo: AudioInfo) => void;
|
|
194
192
|
/**
|
|
195
|
-
*
|
|
193
|
+
* Types of audio errors that can occur during playback
|
|
194
|
+
*/
|
|
195
|
+
export declare enum AudioErrorType {
|
|
196
|
+
Abort = "abort",
|
|
197
|
+
Decode = "decode",
|
|
198
|
+
Network = "network",
|
|
199
|
+
Permission = "permission",
|
|
200
|
+
Timeout = "timeout",
|
|
201
|
+
Unknown = "unknown",
|
|
202
|
+
Unsupported = "unsupported"
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Information about an audio error that occurred during playback or loading
|
|
196
206
|
*/
|
|
197
207
|
export interface AudioErrorInfo {
|
|
208
|
+
/** Channel number where the error occurred */
|
|
198
209
|
channelNumber: number;
|
|
199
|
-
|
|
200
|
-
fileName: string;
|
|
210
|
+
/** The actual error object that was thrown */
|
|
201
211
|
error: Error;
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
212
|
+
/** Categorized type of error for handling different scenarios */
|
|
213
|
+
errorType: AudioErrorType;
|
|
214
|
+
/** Extracted filename from the source URL */
|
|
215
|
+
fileName: string;
|
|
216
|
+
/** Number of audio files remaining in the queue after this error */
|
|
205
217
|
remainingInQueue: number;
|
|
218
|
+
/** Current retry attempt number (if retrying is enabled) */
|
|
219
|
+
retryAttempt?: number;
|
|
220
|
+
/** Audio file source URL that failed */
|
|
221
|
+
src: string;
|
|
222
|
+
/** Unix timestamp when the error occurred */
|
|
223
|
+
timestamp: number;
|
|
206
224
|
}
|
|
207
225
|
/**
|
|
208
226
|
* Configuration for automatic retry behavior when audio fails to load or play
|
|
209
227
|
*/
|
|
210
228
|
export interface RetryConfig {
|
|
211
|
-
|
|
212
|
-
maxRetries: number;
|
|
229
|
+
/** Initial delay in milliseconds before first retry attempt */
|
|
213
230
|
baseDelay: number;
|
|
231
|
+
/** Whether automatic retries are enabled for this channel */
|
|
232
|
+
enabled: boolean;
|
|
233
|
+
/** Whether to use exponential backoff (doubling delay each retry) */
|
|
214
234
|
exponentialBackoff: boolean;
|
|
215
|
-
|
|
216
|
-
fallbackUrls
|
|
235
|
+
/** Alternative URLs to try if the primary source fails */
|
|
236
|
+
fallbackUrls: string[];
|
|
237
|
+
/** Maximum number of retry attempts before giving up */
|
|
238
|
+
maxRetries: number;
|
|
239
|
+
/** Whether to skip to next track in queue if all retries fail */
|
|
217
240
|
skipOnFailure: boolean;
|
|
241
|
+
/** Timeout in milliseconds for each individual retry attempt */
|
|
242
|
+
timeoutMs: number;
|
|
218
243
|
}
|
|
219
244
|
/**
|
|
220
|
-
* Configuration options for error recovery mechanisms
|
|
245
|
+
* Configuration options for error recovery mechanisms across the audio system
|
|
221
246
|
*/
|
|
222
247
|
export interface ErrorRecoveryOptions {
|
|
248
|
+
/** Whether to automatically retry failed audio loads/plays */
|
|
223
249
|
autoRetry: boolean;
|
|
224
|
-
|
|
250
|
+
/** Whether to automatically skip to next track when current fails */
|
|
251
|
+
fallbackToNextTrack: boolean;
|
|
252
|
+
/** Whether to send error data to analytics systems */
|
|
225
253
|
logErrorsToAnalytics: boolean;
|
|
254
|
+
/** Whether to maintain queue integrity when errors occur */
|
|
226
255
|
preserveQueueOnError: boolean;
|
|
227
|
-
|
|
256
|
+
/** Whether to display user-visible error feedback */
|
|
257
|
+
showUserFeedback: boolean;
|
|
228
258
|
}
|
|
229
259
|
/**
|
|
230
260
|
* Callback function type for audio error events
|
|
231
261
|
*/
|
|
232
262
|
export type AudioErrorCallback = (errorInfo: AudioErrorInfo) => void;
|
|
233
263
|
/**
|
|
234
|
-
*
|
|
264
|
+
* Web Audio API configuration options
|
|
265
|
+
*/
|
|
266
|
+
export interface WebAudioConfig {
|
|
267
|
+
/** Whether to automatically use Web Audio API on iOS devices */
|
|
268
|
+
autoDetectIOS: boolean;
|
|
269
|
+
/** Whether Web Audio API support is enabled */
|
|
270
|
+
enabled: boolean;
|
|
271
|
+
/** Whether to force Web Audio API usage on all devices */
|
|
272
|
+
forceWebAudio: boolean;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Web Audio API support information
|
|
276
|
+
*/
|
|
277
|
+
export interface WebAudioSupport {
|
|
278
|
+
/** Whether Web Audio API is available in the current environment */
|
|
279
|
+
available: boolean;
|
|
280
|
+
/** Whether the current device is iOS */
|
|
281
|
+
isIOS: boolean;
|
|
282
|
+
/** Whether Web Audio API is currently being used */
|
|
283
|
+
usingWebAudio: boolean;
|
|
284
|
+
/** Reason for current Web Audio API usage state */
|
|
285
|
+
reason: string;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Web Audio API node set for audio element control
|
|
289
|
+
*/
|
|
290
|
+
export interface WebAudioNodeSet {
|
|
291
|
+
/** Gain node for volume control */
|
|
292
|
+
gainNode: GainNode;
|
|
293
|
+
/** Media element source node */
|
|
294
|
+
sourceNode: MediaElementAudioSourceNode;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Extended audio channel with comprehensive queue management, callback support, and state tracking
|
|
235
298
|
*/
|
|
236
299
|
export interface ExtendedAudioQueueChannel {
|
|
300
|
+
/** Set of callbacks triggered when audio completes playback */
|
|
237
301
|
audioCompleteCallbacks: Set<AudioCompleteCallback>;
|
|
302
|
+
/** Set of callbacks triggered when audio errors occur */
|
|
238
303
|
audioErrorCallbacks: Set<AudioErrorCallback>;
|
|
304
|
+
/** Set of callbacks triggered when audio is paused */
|
|
239
305
|
audioPauseCallbacks: Set<AudioPauseCallback>;
|
|
306
|
+
/** Set of callbacks triggered when audio is resumed */
|
|
240
307
|
audioResumeCallbacks: Set<AudioResumeCallback>;
|
|
308
|
+
/** Set of callbacks triggered when audio starts playing */
|
|
241
309
|
audioStartCallbacks: Set<AudioStartCallback>;
|
|
310
|
+
/** Current fade state if pause/resume with fade is active */
|
|
242
311
|
fadeState?: ChannelFadeState;
|
|
312
|
+
/** Whether the channel is currently paused */
|
|
243
313
|
isPaused: boolean;
|
|
244
314
|
/** Active operation lock to prevent race conditions */
|
|
245
315
|
isLocked?: boolean;
|
|
246
316
|
/** Maximum allowed queue size for this channel */
|
|
247
317
|
maxQueueSize?: number;
|
|
318
|
+
/** Map of progress callbacks keyed by audio element or global symbol */
|
|
248
319
|
progressCallbacks: Map<HTMLAudioElement | typeof GLOBAL_PROGRESS_KEY, Set<ProgressCallback>>;
|
|
320
|
+
/** Array of HTMLAudioElement objects in the queue */
|
|
249
321
|
queue: HTMLAudioElement[];
|
|
322
|
+
/** Set of callbacks triggered when the queue changes */
|
|
250
323
|
queueChangeCallbacks: Set<QueueChangeCallback>;
|
|
324
|
+
/** Retry configuration for failed audio loads/plays */
|
|
251
325
|
retryConfig?: RetryConfig;
|
|
326
|
+
/** Current volume level for the channel (0-1) */
|
|
252
327
|
volume: number;
|
|
328
|
+
/** Web Audio API context for this channel */
|
|
329
|
+
webAudioContext?: AudioContext;
|
|
330
|
+
/** Map of Web Audio API nodes for each audio element */
|
|
331
|
+
webAudioNodes?: Map<HTMLAudioElement, WebAudioNodeSet>;
|
|
253
332
|
}
|
|
254
333
|
/**
|
|
255
|
-
* Easing function types for volume transitions
|
|
334
|
+
* Easing function types for smooth volume transitions and animations
|
|
256
335
|
*/
|
|
257
336
|
export declare enum EasingType {
|
|
258
337
|
Linear = "linear",
|
|
@@ -261,7 +340,7 @@ export declare enum EasingType {
|
|
|
261
340
|
EaseInOut = "ease-in-out"
|
|
262
341
|
}
|
|
263
342
|
/**
|
|
264
|
-
*
|
|
343
|
+
* Predefined fade types for pause/resume operations with different transition characteristics
|
|
265
344
|
*/
|
|
266
345
|
export declare enum FadeType {
|
|
267
346
|
Linear = "linear",
|
|
@@ -269,7 +348,7 @@ export declare enum FadeType {
|
|
|
269
348
|
Dramatic = "dramatic"
|
|
270
349
|
}
|
|
271
350
|
/**
|
|
272
|
-
* Timer types for volume transitions to ensure proper cleanup
|
|
351
|
+
* Timer implementation types used for volume transitions to ensure proper cleanup
|
|
273
352
|
*/
|
|
274
353
|
export declare enum TimerType {
|
|
275
354
|
RequestAnimationFrame = "raf",
|
package/dist/types.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @fileoverview Type definitions for the audio-channel-queue package
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.TimerType = exports.FadeType = exports.EasingType = exports.GLOBAL_PROGRESS_KEY = exports.MAX_CHANNELS = void 0;
|
|
6
|
+
exports.TimerType = exports.FadeType = exports.EasingType = exports.AudioErrorType = exports.GLOBAL_PROGRESS_KEY = exports.MAX_CHANNELS = void 0;
|
|
7
7
|
/**
|
|
8
8
|
* Maximum number of audio channels allowed to prevent memory exhaustion
|
|
9
9
|
*/
|
|
@@ -14,7 +14,20 @@ exports.MAX_CHANNELS = 64;
|
|
|
14
14
|
*/
|
|
15
15
|
exports.GLOBAL_PROGRESS_KEY = Symbol('global-progress-callbacks');
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
17
|
+
* Types of audio errors that can occur during playback
|
|
18
|
+
*/
|
|
19
|
+
var AudioErrorType;
|
|
20
|
+
(function (AudioErrorType) {
|
|
21
|
+
AudioErrorType["Abort"] = "abort";
|
|
22
|
+
AudioErrorType["Decode"] = "decode";
|
|
23
|
+
AudioErrorType["Network"] = "network";
|
|
24
|
+
AudioErrorType["Permission"] = "permission";
|
|
25
|
+
AudioErrorType["Timeout"] = "timeout";
|
|
26
|
+
AudioErrorType["Unknown"] = "unknown";
|
|
27
|
+
AudioErrorType["Unsupported"] = "unsupported";
|
|
28
|
+
})(AudioErrorType || (exports.AudioErrorType = AudioErrorType = {}));
|
|
29
|
+
/**
|
|
30
|
+
* Easing function types for smooth volume transitions and animations
|
|
18
31
|
*/
|
|
19
32
|
var EasingType;
|
|
20
33
|
(function (EasingType) {
|
|
@@ -24,7 +37,7 @@ var EasingType;
|
|
|
24
37
|
EasingType["EaseInOut"] = "ease-in-out";
|
|
25
38
|
})(EasingType || (exports.EasingType = EasingType = {}));
|
|
26
39
|
/**
|
|
27
|
-
*
|
|
40
|
+
* Predefined fade types for pause/resume operations with different transition characteristics
|
|
28
41
|
*/
|
|
29
42
|
var FadeType;
|
|
30
43
|
(function (FadeType) {
|
|
@@ -33,7 +46,7 @@ var FadeType;
|
|
|
33
46
|
FadeType["Dramatic"] = "dramatic";
|
|
34
47
|
})(FadeType || (exports.FadeType = FadeType = {}));
|
|
35
48
|
/**
|
|
36
|
-
* Timer types for volume transitions to ensure proper cleanup
|
|
49
|
+
* Timer implementation types used for volume transitions to ensure proper cleanup
|
|
37
50
|
*/
|
|
38
51
|
var TimerType;
|
|
39
52
|
(function (TimerType) {
|
package/dist/volume.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export declare const getFadeConfig: (fadeType: FadeType) => FadeConfig;
|
|
|
28
28
|
export declare const transitionVolume: (channelNumber: number, targetVolume: number, duration?: number, easing?: EasingType) => Promise<void>;
|
|
29
29
|
/**
|
|
30
30
|
* Sets the volume for a specific channel with optional smooth transition
|
|
31
|
+
* Automatically uses Web Audio API on iOS devices for enhanced volume control
|
|
31
32
|
* @param channelNumber - The channel number to set volume for
|
|
32
33
|
* @param volume - Volume level (0-1)
|
|
33
34
|
* @param transitionDuration - Optional transition duration in milliseconds
|
|
@@ -103,20 +104,6 @@ export declare const clearVolumeDucking: () => void;
|
|
|
103
104
|
* @internal
|
|
104
105
|
*/
|
|
105
106
|
export declare const applyVolumeDucking: (activeChannelNumber: number) => Promise<void>;
|
|
106
|
-
/**
|
|
107
|
-
* Fades the volume for a specific channel over time (alias for transitionVolume with improved naming)
|
|
108
|
-
* @param channelNumber - The channel number to fade
|
|
109
|
-
* @param targetVolume - Target volume level (0-1)
|
|
110
|
-
* @param duration - Fade duration in milliseconds (defaults to 250)
|
|
111
|
-
* @param easing - Easing function type (defaults to 'ease-out')
|
|
112
|
-
* @returns Promise that resolves when fade completes
|
|
113
|
-
* @example
|
|
114
|
-
* ```typescript
|
|
115
|
-
* await fadeVolume(0, 0, 800, 'ease-in'); // Fade out over 800ms
|
|
116
|
-
* await fadeVolume(0, 1, 600, 'ease-out'); // Fade in over 600ms
|
|
117
|
-
* ```
|
|
118
|
-
*/
|
|
119
|
-
export declare const fadeVolume: (channelNumber: number, targetVolume: number, duration?: number, easing?: EasingType) => Promise<void>;
|
|
120
107
|
/**
|
|
121
108
|
* Restores normal volume levels when priority channel queue becomes empty
|
|
122
109
|
* @param stoppedChannelNumber - The channel that just stopped playing
|
|
@@ -134,3 +121,17 @@ export declare const cancelVolumeTransition: (channelNumber: number) => void;
|
|
|
134
121
|
* @internal
|
|
135
122
|
*/
|
|
136
123
|
export declare const cancelAllVolumeTransitions: () => void;
|
|
124
|
+
/**
|
|
125
|
+
* Initializes Web Audio API nodes for a new audio element
|
|
126
|
+
* @param audio - The audio element to initialize nodes for
|
|
127
|
+
* @param channelNumber - The channel number this audio belongs to
|
|
128
|
+
* @internal
|
|
129
|
+
*/
|
|
130
|
+
export declare const initializeWebAudioForAudio: (audio: HTMLAudioElement, channelNumber: number) => Promise<void>;
|
|
131
|
+
/**
|
|
132
|
+
* Cleans up Web Audio API nodes for an audio element
|
|
133
|
+
* @param audio - The audio element to clean up nodes for
|
|
134
|
+
* @param channelNumber - The channel number this audio belongs to
|
|
135
|
+
* @internal
|
|
136
|
+
*/
|
|
137
|
+
export declare const cleanupWebAudioForAudio: (audio: HTMLAudioElement, channelNumber: number) => void;
|
package/dist/volume.js
CHANGED
|
@@ -12,9 +12,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
12
12
|
});
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.
|
|
15
|
+
exports.cleanupWebAudioForAudio = exports.initializeWebAudioForAudio = exports.cancelAllVolumeTransitions = exports.cancelVolumeTransition = exports.restoreVolumeLevels = exports.applyVolumeDucking = exports.clearVolumeDucking = exports.setVolumeDucking = exports.setAllChannelsVolume = exports.getAllChannelsVolume = exports.getChannelVolume = exports.setChannelVolume = exports.transitionVolume = exports.getFadeConfig = void 0;
|
|
16
16
|
const types_1 = require("./types");
|
|
17
17
|
const info_1 = require("./info");
|
|
18
|
+
const web_audio_1 = require("./web-audio");
|
|
18
19
|
// Store active volume transitions to handle interruptions
|
|
19
20
|
const activeTransitions = new Map();
|
|
20
21
|
// Track which timer type was used for each channel
|
|
@@ -119,7 +120,7 @@ const transitionVolume = (channelNumber_1, targetVolume_1, ...args_1) => __await
|
|
|
119
120
|
const startTime = performance.now();
|
|
120
121
|
const easingFn = easingFunctions[easing];
|
|
121
122
|
return new Promise((resolve) => {
|
|
122
|
-
const updateVolume = () => {
|
|
123
|
+
const updateVolume = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
123
124
|
const elapsed = performance.now() - startTime;
|
|
124
125
|
const progress = Math.min(elapsed / duration, 1);
|
|
125
126
|
const easedProgress = easingFn(progress);
|
|
@@ -128,7 +129,7 @@ const transitionVolume = (channelNumber_1, targetVolume_1, ...args_1) => __await
|
|
|
128
129
|
// Apply volume to both channel config and current audio
|
|
129
130
|
channel.volume = clampedVolume;
|
|
130
131
|
if (channel.queue.length > 0) {
|
|
131
|
-
channel.queue[0]
|
|
132
|
+
yield setVolumeForAudio(channel.queue[0], clampedVolume, channelNumber);
|
|
132
133
|
}
|
|
133
134
|
if (progress >= 1) {
|
|
134
135
|
// Transition complete
|
|
@@ -139,24 +140,25 @@ const transitionVolume = (channelNumber_1, targetVolume_1, ...args_1) => __await
|
|
|
139
140
|
else {
|
|
140
141
|
// Use requestAnimationFrame in browser, setTimeout in tests
|
|
141
142
|
if (typeof requestAnimationFrame !== 'undefined') {
|
|
142
|
-
const rafId = requestAnimationFrame(updateVolume);
|
|
143
|
+
const rafId = requestAnimationFrame(() => updateVolume());
|
|
143
144
|
activeTransitions.set(channelNumber, rafId);
|
|
144
145
|
timerTypes.set(channelNumber, types_1.TimerType.RequestAnimationFrame);
|
|
145
146
|
}
|
|
146
147
|
else {
|
|
147
148
|
// In test environment, use shorter intervals
|
|
148
|
-
const timeoutId = setTimeout(updateVolume, 1);
|
|
149
|
+
const timeoutId = setTimeout(() => updateVolume(), 1);
|
|
149
150
|
activeTransitions.set(channelNumber, timeoutId);
|
|
150
151
|
timerTypes.set(channelNumber, types_1.TimerType.Timeout);
|
|
151
152
|
}
|
|
152
153
|
}
|
|
153
|
-
};
|
|
154
|
+
});
|
|
154
155
|
updateVolume();
|
|
155
156
|
});
|
|
156
157
|
});
|
|
157
158
|
exports.transitionVolume = transitionVolume;
|
|
158
159
|
/**
|
|
159
160
|
* Sets the volume for a specific channel with optional smooth transition
|
|
161
|
+
* Automatically uses Web Audio API on iOS devices for enhanced volume control
|
|
160
162
|
* @param channelNumber - The channel number to set volume for
|
|
161
163
|
* @param volume - Volume level (0-1)
|
|
162
164
|
* @param transitionDuration - Optional transition duration in milliseconds
|
|
@@ -192,17 +194,21 @@ const setChannelVolume = (channelNumber, volume, transitionDuration, easing) =>
|
|
|
192
194
|
};
|
|
193
195
|
return;
|
|
194
196
|
}
|
|
197
|
+
const channel = info_1.audioChannels[channelNumber];
|
|
198
|
+
// Initialize Web Audio API if needed and supported
|
|
199
|
+
if ((0, web_audio_1.shouldUseWebAudio)() && !channel.webAudioContext) {
|
|
200
|
+
yield initializeWebAudioForChannel(channelNumber);
|
|
201
|
+
}
|
|
195
202
|
if (transitionDuration && transitionDuration > 0) {
|
|
196
203
|
// Smooth transition
|
|
197
204
|
yield (0, exports.transitionVolume)(channelNumber, clampedVolume, transitionDuration, easing);
|
|
198
205
|
}
|
|
199
206
|
else {
|
|
200
207
|
// Instant change (backward compatibility)
|
|
201
|
-
|
|
202
|
-
const channel = info_1.audioChannels[channelNumber];
|
|
208
|
+
channel.volume = clampedVolume;
|
|
203
209
|
if (channel.queue.length > 0) {
|
|
204
210
|
const currentAudio = channel.queue[0];
|
|
205
|
-
currentAudio
|
|
211
|
+
yield setVolumeForAudio(currentAudio, clampedVolume, channelNumber);
|
|
206
212
|
}
|
|
207
213
|
}
|
|
208
214
|
});
|
|
@@ -333,36 +339,19 @@ const applyVolumeDucking = (activeChannelNumber) => __awaiter(void 0, void 0, vo
|
|
|
333
339
|
// This is the priority channel - set to priority volume
|
|
334
340
|
// Only change audio volume, preserve channel.volume as desired volume
|
|
335
341
|
const currentAudio = channel.queue[0];
|
|
336
|
-
transitionPromises.push(transitionAudioVolume(currentAudio, config.priorityVolume, duration, easing));
|
|
342
|
+
transitionPromises.push(transitionAudioVolume(currentAudio, config.priorityVolume, duration, easing, channelNumber));
|
|
337
343
|
}
|
|
338
344
|
else {
|
|
339
345
|
// This is a background channel - duck it
|
|
340
346
|
// Only change audio volume, preserve channel.volume as desired volume
|
|
341
347
|
const currentAudio = channel.queue[0];
|
|
342
|
-
transitionPromises.push(transitionAudioVolume(currentAudio, config.duckingVolume, duration, easing));
|
|
348
|
+
transitionPromises.push(transitionAudioVolume(currentAudio, config.duckingVolume, duration, easing, channelNumber));
|
|
343
349
|
}
|
|
344
350
|
});
|
|
345
351
|
// Wait for all transitions to complete
|
|
346
352
|
yield Promise.all(transitionPromises);
|
|
347
353
|
});
|
|
348
354
|
exports.applyVolumeDucking = applyVolumeDucking;
|
|
349
|
-
/**
|
|
350
|
-
* Fades the volume for a specific channel over time (alias for transitionVolume with improved naming)
|
|
351
|
-
* @param channelNumber - The channel number to fade
|
|
352
|
-
* @param targetVolume - Target volume level (0-1)
|
|
353
|
-
* @param duration - Fade duration in milliseconds (defaults to 250)
|
|
354
|
-
* @param easing - Easing function type (defaults to 'ease-out')
|
|
355
|
-
* @returns Promise that resolves when fade completes
|
|
356
|
-
* @example
|
|
357
|
-
* ```typescript
|
|
358
|
-
* await fadeVolume(0, 0, 800, 'ease-in'); // Fade out over 800ms
|
|
359
|
-
* await fadeVolume(0, 1, 600, 'ease-out'); // Fade in over 600ms
|
|
360
|
-
* ```
|
|
361
|
-
*/
|
|
362
|
-
const fadeVolume = (channelNumber_1, targetVolume_1, ...args_1) => __awaiter(void 0, [channelNumber_1, targetVolume_1, ...args_1], void 0, function* (channelNumber, targetVolume, duration = 250, easing = types_1.EasingType.EaseOut) {
|
|
363
|
-
return (0, exports.transitionVolume)(channelNumber, targetVolume, duration, easing);
|
|
364
|
-
});
|
|
365
|
-
exports.fadeVolume = fadeVolume;
|
|
366
355
|
/**
|
|
367
356
|
* Restores normal volume levels when priority channel queue becomes empty
|
|
368
357
|
* @param stoppedChannelNumber - The channel that just stopped playing
|
|
@@ -388,12 +377,12 @@ const restoreVolumeLevels = (stoppedChannelNumber) => __awaiter(void 0, void 0,
|
|
|
388
377
|
return;
|
|
389
378
|
}
|
|
390
379
|
// Restore this channel to its desired volume
|
|
391
|
-
const duration = (_a = config.restoreTransitionDuration) !== null && _a !== void 0 ? _a :
|
|
380
|
+
const duration = (_a = config.restoreTransitionDuration) !== null && _a !== void 0 ? _a : 250;
|
|
392
381
|
const easing = (_b = config.transitionEasing) !== null && _b !== void 0 ? _b : types_1.EasingType.EaseOut;
|
|
393
382
|
const targetVolume = (_c = channel.volume) !== null && _c !== void 0 ? _c : 1.0;
|
|
394
383
|
// Only transition the audio element volume, keep channel.volume as the desired volume
|
|
395
384
|
const currentAudio = channel.queue[0];
|
|
396
|
-
transitionPromises.push(transitionAudioVolume(currentAudio, targetVolume, duration, easing));
|
|
385
|
+
transitionPromises.push(transitionAudioVolume(currentAudio, targetVolume, duration, easing, channelNumber));
|
|
397
386
|
});
|
|
398
387
|
// Wait for all transitions to complete
|
|
399
388
|
yield Promise.all(transitionPromises);
|
|
@@ -402,14 +391,31 @@ exports.restoreVolumeLevels = restoreVolumeLevels;
|
|
|
402
391
|
/**
|
|
403
392
|
* Transitions only the audio element volume without affecting channel.volume
|
|
404
393
|
* This is used for ducking/restoration where channel.volume represents desired volume
|
|
394
|
+
* Uses Web Audio API when available for enhanced volume control
|
|
405
395
|
* @param audio - The audio element to transition
|
|
406
396
|
* @param targetVolume - Target volume level (0-1)
|
|
407
397
|
* @param duration - Transition duration in milliseconds
|
|
408
398
|
* @param easing - Easing function type
|
|
399
|
+
* @param channelNumber - The channel number this audio belongs to (for Web Audio API)
|
|
409
400
|
* @returns Promise that resolves when transition completes
|
|
410
401
|
* @internal
|
|
411
402
|
*/
|
|
412
|
-
const transitionAudioVolume = (audio_1, targetVolume_1, ...args_1) => __awaiter(void 0, [audio_1, targetVolume_1, ...args_1], void 0, function* (audio, targetVolume, duration = 250, easing = types_1.EasingType.EaseOut) {
|
|
403
|
+
const transitionAudioVolume = (audio_1, targetVolume_1, ...args_1) => __awaiter(void 0, [audio_1, targetVolume_1, ...args_1], void 0, function* (audio, targetVolume, duration = 250, easing = types_1.EasingType.EaseOut, channelNumber) {
|
|
404
|
+
// Try to use Web Audio API if available and channel number is provided
|
|
405
|
+
if (channelNumber !== undefined) {
|
|
406
|
+
const channel = info_1.audioChannels[channelNumber];
|
|
407
|
+
if ((channel === null || channel === void 0 ? void 0 : channel.webAudioContext) && channel.webAudioNodes) {
|
|
408
|
+
const nodes = channel.webAudioNodes.get(audio);
|
|
409
|
+
if (nodes) {
|
|
410
|
+
// Use Web Audio API for smooth transitions
|
|
411
|
+
(0, web_audio_1.setWebAudioVolume)(nodes.gainNode, targetVolume, duration);
|
|
412
|
+
// Also update the audio element's volume property for consistency
|
|
413
|
+
audio.volume = targetVolume;
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// Fallback to standard HTMLAudioElement volume control with manual transition
|
|
413
419
|
const startVolume = audio.volume;
|
|
414
420
|
const volumeDelta = targetVolume - startVolume;
|
|
415
421
|
// If no change needed, resolve immediately
|
|
@@ -418,7 +424,7 @@ const transitionAudioVolume = (audio_1, targetVolume_1, ...args_1) => __awaiter(
|
|
|
418
424
|
}
|
|
419
425
|
// Handle zero or negative duration - instant change
|
|
420
426
|
if (duration <= 0) {
|
|
421
|
-
audio.volume =
|
|
427
|
+
audio.volume = targetVolume;
|
|
422
428
|
return Promise.resolve();
|
|
423
429
|
}
|
|
424
430
|
const startTime = performance.now();
|
|
@@ -441,7 +447,8 @@ const transitionAudioVolume = (audio_1, targetVolume_1, ...args_1) => __awaiter(
|
|
|
441
447
|
requestAnimationFrame(updateVolume);
|
|
442
448
|
}
|
|
443
449
|
else {
|
|
444
|
-
|
|
450
|
+
// In test environment, use longer intervals to prevent stack overflow
|
|
451
|
+
setTimeout(updateVolume, 16);
|
|
445
452
|
}
|
|
446
453
|
}
|
|
447
454
|
};
|
|
@@ -484,3 +491,94 @@ const cancelAllVolumeTransitions = () => {
|
|
|
484
491
|
});
|
|
485
492
|
};
|
|
486
493
|
exports.cancelAllVolumeTransitions = cancelAllVolumeTransitions;
|
|
494
|
+
/**
|
|
495
|
+
* Initializes Web Audio API for a specific channel
|
|
496
|
+
* @param channelNumber - The channel number to initialize Web Audio for
|
|
497
|
+
* @internal
|
|
498
|
+
*/
|
|
499
|
+
const initializeWebAudioForChannel = (channelNumber) => __awaiter(void 0, void 0, void 0, function* () {
|
|
500
|
+
const channel = info_1.audioChannels[channelNumber];
|
|
501
|
+
if (!channel || channel.webAudioContext)
|
|
502
|
+
return;
|
|
503
|
+
const audioContext = (0, web_audio_1.getAudioContext)();
|
|
504
|
+
if (!audioContext) {
|
|
505
|
+
throw new Error('AudioContext creation failed');
|
|
506
|
+
}
|
|
507
|
+
// Resume audio context if needed (for autoplay policy)
|
|
508
|
+
yield (0, web_audio_1.resumeAudioContext)(audioContext);
|
|
509
|
+
channel.webAudioContext = audioContext;
|
|
510
|
+
channel.webAudioNodes = new Map();
|
|
511
|
+
// Initialize Web Audio nodes for existing audio elements
|
|
512
|
+
for (const audio of channel.queue) {
|
|
513
|
+
const nodes = (0, web_audio_1.createWebAudioNodes)(audio, audioContext);
|
|
514
|
+
if (!nodes) {
|
|
515
|
+
throw new Error('Node creation failed');
|
|
516
|
+
}
|
|
517
|
+
channel.webAudioNodes.set(audio, nodes);
|
|
518
|
+
// Set initial volume to match channel volume
|
|
519
|
+
nodes.gainNode.gain.value = channel.volume;
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
/**
|
|
523
|
+
* Sets volume for an audio element using the appropriate method (Web Audio API or standard)
|
|
524
|
+
* @param audio - The audio element to set volume for
|
|
525
|
+
* @param volume - Volume level (0-1)
|
|
526
|
+
* @param channelNumber - The channel number this audio belongs to
|
|
527
|
+
* @param transitionDuration - Optional transition duration in milliseconds
|
|
528
|
+
* @internal
|
|
529
|
+
*/
|
|
530
|
+
const setVolumeForAudio = (audio, volume, channelNumber, transitionDuration) => __awaiter(void 0, void 0, void 0, function* () {
|
|
531
|
+
const channel = info_1.audioChannels[channelNumber];
|
|
532
|
+
// Use Web Audio API if available and initialized
|
|
533
|
+
if ((channel === null || channel === void 0 ? void 0 : channel.webAudioContext) && channel.webAudioNodes) {
|
|
534
|
+
const nodes = channel.webAudioNodes.get(audio);
|
|
535
|
+
if (nodes) {
|
|
536
|
+
(0, web_audio_1.setWebAudioVolume)(nodes.gainNode, volume, transitionDuration);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
// Fallback to standard HTMLAudioElement volume control
|
|
541
|
+
audio.volume = volume;
|
|
542
|
+
});
|
|
543
|
+
/**
|
|
544
|
+
* Initializes Web Audio API nodes for a new audio element
|
|
545
|
+
* @param audio - The audio element to initialize nodes for
|
|
546
|
+
* @param channelNumber - The channel number this audio belongs to
|
|
547
|
+
* @internal
|
|
548
|
+
*/
|
|
549
|
+
const initializeWebAudioForAudio = (audio, channelNumber) => __awaiter(void 0, void 0, void 0, function* () {
|
|
550
|
+
const channel = info_1.audioChannels[channelNumber];
|
|
551
|
+
if (!channel)
|
|
552
|
+
return;
|
|
553
|
+
// Initialize Web Audio API for the channel if needed
|
|
554
|
+
if ((0, web_audio_1.shouldUseWebAudio)() && !channel.webAudioContext) {
|
|
555
|
+
yield initializeWebAudioForChannel(channelNumber);
|
|
556
|
+
}
|
|
557
|
+
// Create nodes for this specific audio element
|
|
558
|
+
if (channel.webAudioContext && channel.webAudioNodes && !channel.webAudioNodes.has(audio)) {
|
|
559
|
+
const nodes = (0, web_audio_1.createWebAudioNodes)(audio, channel.webAudioContext);
|
|
560
|
+
if (nodes) {
|
|
561
|
+
channel.webAudioNodes.set(audio, nodes);
|
|
562
|
+
// Set initial volume to match channel volume
|
|
563
|
+
nodes.gainNode.gain.value = channel.volume;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
exports.initializeWebAudioForAudio = initializeWebAudioForAudio;
|
|
568
|
+
/**
|
|
569
|
+
* Cleans up Web Audio API nodes for an audio element
|
|
570
|
+
* @param audio - The audio element to clean up nodes for
|
|
571
|
+
* @param channelNumber - The channel number this audio belongs to
|
|
572
|
+
* @internal
|
|
573
|
+
*/
|
|
574
|
+
const cleanupWebAudioForAudio = (audio, channelNumber) => {
|
|
575
|
+
const channel = info_1.audioChannels[channelNumber];
|
|
576
|
+
if (!(channel === null || channel === void 0 ? void 0 : channel.webAudioNodes))
|
|
577
|
+
return;
|
|
578
|
+
const nodes = channel.webAudioNodes.get(audio);
|
|
579
|
+
if (nodes) {
|
|
580
|
+
(0, web_audio_1.cleanupWebAudioNodes)(nodes);
|
|
581
|
+
channel.webAudioNodes.delete(audio);
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
exports.cleanupWebAudioForAudio = cleanupWebAudioForAudio;
|