audio-channel-queue 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core.js +53 -34
- package/dist/errors.d.ts +137 -0
- package/dist/errors.js +461 -0
- package/dist/index.d.ts +9 -25
- package/dist/index.js +23 -33
- package/dist/info.d.ts +2 -1
- package/dist/info.js +8 -1
- package/dist/pause.js +1 -1
- package/dist/types.d.ts +52 -19
- package/dist/volume.js +4 -1
- package/package.json +1 -1
- package/src/core.ts +55 -35
- package/src/errors.ts +480 -0
- package/src/index.ts +71 -84
- package/src/info.ts +8 -1
- package/src/pause.ts +189 -189
- package/src/types.ts +55 -18
- package/src/volume.ts +330 -327
package/src/volume.ts
CHANGED
|
@@ -1,328 +1,331 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Volume management functions for the audio-channel-queue package
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { ExtendedAudioQueueChannel, VolumeConfig } from './types';
|
|
6
|
-
import { audioChannels } from './info';
|
|
7
|
-
|
|
8
|
-
// Store active volume transitions to handle interruptions
|
|
9
|
-
const activeTransitions = new Map<number, number>();
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Easing functions for smooth volume transitions
|
|
13
|
-
*/
|
|
14
|
-
const easingFunctions = {
|
|
15
|
-
linear: (t: number): number => t,
|
|
16
|
-
'ease-in': (t: number): number => t * t,
|
|
17
|
-
'ease-out': (t: number): number => t * (2 - t),
|
|
18
|
-
'ease-in-out': (t: number): number => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Smoothly transitions volume for a specific channel over time
|
|
23
|
-
* @param channelNumber - The channel number to transition
|
|
24
|
-
* @param targetVolume - Target volume level (0-1)
|
|
25
|
-
* @param duration - Transition duration in milliseconds
|
|
26
|
-
* @param easing - Easing function type
|
|
27
|
-
* @returns Promise that resolves when transition completes
|
|
28
|
-
* @example
|
|
29
|
-
* ```typescript
|
|
30
|
-
* await transitionVolume(0, 0.2, 500, 'ease-out'); // Duck to 20% over 500ms
|
|
31
|
-
* ```
|
|
32
|
-
*/
|
|
33
|
-
export const transitionVolume = async (
|
|
34
|
-
channelNumber: number,
|
|
35
|
-
targetVolume: number,
|
|
36
|
-
duration: number = 250,
|
|
37
|
-
easing: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out' = 'ease-out'
|
|
38
|
-
): Promise<void> => {
|
|
39
|
-
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
40
|
-
if (!channel || channel.queue.length === 0) return;
|
|
41
|
-
|
|
42
|
-
const currentAudio: HTMLAudioElement = channel.queue[0];
|
|
43
|
-
const startVolume: number = currentAudio.volume;
|
|
44
|
-
const volumeDelta: number = targetVolume - startVolume;
|
|
45
|
-
|
|
46
|
-
// Cancel any existing transition for this channel
|
|
47
|
-
if (activeTransitions.has(channelNumber)) {
|
|
48
|
-
clearTimeout(activeTransitions.get(channelNumber));
|
|
49
|
-
activeTransitions.delete(channelNumber);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// If no change needed, resolve immediately
|
|
53
|
-
if (Math.abs(volumeDelta) < 0.001) {
|
|
54
|
-
channel.volume = targetVolume;
|
|
55
|
-
return Promise.resolve();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Handle zero duration - instant change
|
|
59
|
-
if (duration
|
|
60
|
-
channel.volume = targetVolume;
|
|
61
|
-
if (channel.queue.length > 0) {
|
|
62
|
-
channel.queue[0].volume = targetVolume;
|
|
63
|
-
}
|
|
64
|
-
return Promise.resolve();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const startTime: number = performance.now();
|
|
68
|
-
const easingFn = easingFunctions[easing];
|
|
69
|
-
|
|
70
|
-
return new Promise<void>((resolve) => {
|
|
71
|
-
const updateVolume = (): void => {
|
|
72
|
-
const elapsed: number = performance.now() - startTime;
|
|
73
|
-
const progress: number = Math.min(elapsed / duration, 1);
|
|
74
|
-
const easedProgress: number = easingFn(progress);
|
|
75
|
-
|
|
76
|
-
const currentVolume: number = startVolume + (volumeDelta * easedProgress);
|
|
77
|
-
const clampedVolume: number = Math.max(0, Math.min(1, currentVolume));
|
|
78
|
-
|
|
79
|
-
// Apply volume to both channel config and current audio
|
|
80
|
-
channel.volume = clampedVolume;
|
|
81
|
-
if (channel.queue.length > 0) {
|
|
82
|
-
channel.queue[0].volume = clampedVolume;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (progress >= 1) {
|
|
86
|
-
// Transition complete
|
|
87
|
-
activeTransitions.delete(channelNumber);
|
|
88
|
-
resolve();
|
|
89
|
-
} else {
|
|
90
|
-
// Use requestAnimationFrame in browser, setTimeout in tests
|
|
91
|
-
if (typeof requestAnimationFrame !== 'undefined') {
|
|
92
|
-
const rafId = requestAnimationFrame(updateVolume);
|
|
93
|
-
activeTransitions.set(channelNumber, rafId as unknown as number);
|
|
94
|
-
} else {
|
|
95
|
-
// In test environment, use shorter intervals
|
|
96
|
-
const timeoutId = setTimeout(updateVolume, 1);
|
|
97
|
-
activeTransitions.set(channelNumber, timeoutId as unknown as number);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
updateVolume();
|
|
103
|
-
});
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Sets the volume for a specific channel with optional smooth transition
|
|
108
|
-
* @param channelNumber - The channel number to set volume for
|
|
109
|
-
* @param volume - Volume level (0-1)
|
|
110
|
-
* @param transitionDuration - Optional transition duration in milliseconds
|
|
111
|
-
* @param easing - Optional easing function
|
|
112
|
-
* @example
|
|
113
|
-
* ```typescript
|
|
114
|
-
* setChannelVolume(0, 0.5); // Set channel 0 to 50%
|
|
115
|
-
* setChannelVolume(0, 0.5, 300, 'ease-out'); // Smooth transition over 300ms
|
|
116
|
-
* ```
|
|
117
|
-
*/
|
|
118
|
-
export const setChannelVolume = async (
|
|
119
|
-
channelNumber: number,
|
|
120
|
-
volume: number,
|
|
121
|
-
transitionDuration?: number,
|
|
122
|
-
easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out'
|
|
123
|
-
): Promise<void> => {
|
|
124
|
-
const clampedVolume: number = Math.max(0, Math.min(1, volume));
|
|
125
|
-
|
|
126
|
-
if (!audioChannels[channelNumber]) {
|
|
127
|
-
audioChannels[channelNumber] = {
|
|
128
|
-
audioCompleteCallbacks: new Set(),
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
currentAudio
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
*
|
|
158
|
-
* @
|
|
159
|
-
* @
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* const
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
*
|
|
174
|
-
* @
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* volumes
|
|
178
|
-
*
|
|
179
|
-
* });
|
|
180
|
-
*
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
*
|
|
191
|
-
* @
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
* @
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Volume management functions for the audio-channel-queue package
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ExtendedAudioQueueChannel, VolumeConfig } from './types';
|
|
6
|
+
import { audioChannels } from './info';
|
|
7
|
+
|
|
8
|
+
// Store active volume transitions to handle interruptions
|
|
9
|
+
const activeTransitions = new Map<number, number>();
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Easing functions for smooth volume transitions
|
|
13
|
+
*/
|
|
14
|
+
const easingFunctions = {
|
|
15
|
+
linear: (t: number): number => t,
|
|
16
|
+
'ease-in': (t: number): number => t * t,
|
|
17
|
+
'ease-out': (t: number): number => t * (2 - t),
|
|
18
|
+
'ease-in-out': (t: number): number => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Smoothly transitions volume for a specific channel over time
|
|
23
|
+
* @param channelNumber - The channel number to transition
|
|
24
|
+
* @param targetVolume - Target volume level (0-1)
|
|
25
|
+
* @param duration - Transition duration in milliseconds
|
|
26
|
+
* @param easing - Easing function type
|
|
27
|
+
* @returns Promise that resolves when transition completes
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* await transitionVolume(0, 0.2, 500, 'ease-out'); // Duck to 20% over 500ms
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export const transitionVolume = async (
|
|
34
|
+
channelNumber: number,
|
|
35
|
+
targetVolume: number,
|
|
36
|
+
duration: number = 250,
|
|
37
|
+
easing: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out' = 'ease-out'
|
|
38
|
+
): Promise<void> => {
|
|
39
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
40
|
+
if (!channel || channel.queue.length === 0) return;
|
|
41
|
+
|
|
42
|
+
const currentAudio: HTMLAudioElement = channel.queue[0];
|
|
43
|
+
const startVolume: number = currentAudio.volume;
|
|
44
|
+
const volumeDelta: number = targetVolume - startVolume;
|
|
45
|
+
|
|
46
|
+
// Cancel any existing transition for this channel
|
|
47
|
+
if (activeTransitions.has(channelNumber)) {
|
|
48
|
+
clearTimeout(activeTransitions.get(channelNumber));
|
|
49
|
+
activeTransitions.delete(channelNumber);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// If no change needed, resolve immediately
|
|
53
|
+
if (Math.abs(volumeDelta) < 0.001) {
|
|
54
|
+
channel.volume = targetVolume;
|
|
55
|
+
return Promise.resolve();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Handle zero duration - instant change
|
|
59
|
+
if (duration === 0) {
|
|
60
|
+
channel.volume = targetVolume;
|
|
61
|
+
if (channel.queue.length > 0) {
|
|
62
|
+
channel.queue[0].volume = targetVolume;
|
|
63
|
+
}
|
|
64
|
+
return Promise.resolve();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const startTime: number = performance.now();
|
|
68
|
+
const easingFn = easingFunctions[easing];
|
|
69
|
+
|
|
70
|
+
return new Promise<void>((resolve) => {
|
|
71
|
+
const updateVolume = (): void => {
|
|
72
|
+
const elapsed: number = performance.now() - startTime;
|
|
73
|
+
const progress: number = Math.min(elapsed / duration, 1);
|
|
74
|
+
const easedProgress: number = easingFn(progress);
|
|
75
|
+
|
|
76
|
+
const currentVolume: number = startVolume + (volumeDelta * easedProgress);
|
|
77
|
+
const clampedVolume: number = Math.max(0, Math.min(1, currentVolume));
|
|
78
|
+
|
|
79
|
+
// Apply volume to both channel config and current audio
|
|
80
|
+
channel.volume = clampedVolume;
|
|
81
|
+
if (channel.queue.length > 0) {
|
|
82
|
+
channel.queue[0].volume = clampedVolume;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (progress >= 1) {
|
|
86
|
+
// Transition complete
|
|
87
|
+
activeTransitions.delete(channelNumber);
|
|
88
|
+
resolve();
|
|
89
|
+
} else {
|
|
90
|
+
// Use requestAnimationFrame in browser, setTimeout in tests
|
|
91
|
+
if (typeof requestAnimationFrame !== 'undefined') {
|
|
92
|
+
const rafId = requestAnimationFrame(updateVolume);
|
|
93
|
+
activeTransitions.set(channelNumber, rafId as unknown as number);
|
|
94
|
+
} else {
|
|
95
|
+
// In test environment, use shorter intervals
|
|
96
|
+
const timeoutId = setTimeout(updateVolume, 1);
|
|
97
|
+
activeTransitions.set(channelNumber, timeoutId as unknown as number);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
updateVolume();
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Sets the volume for a specific channel with optional smooth transition
|
|
108
|
+
* @param channelNumber - The channel number to set volume for
|
|
109
|
+
* @param volume - Volume level (0-1)
|
|
110
|
+
* @param transitionDuration - Optional transition duration in milliseconds
|
|
111
|
+
* @param easing - Optional easing function
|
|
112
|
+
* @example
|
|
113
|
+
* ```typescript
|
|
114
|
+
* setChannelVolume(0, 0.5); // Set channel 0 to 50%
|
|
115
|
+
* setChannelVolume(0, 0.5, 300, 'ease-out'); // Smooth transition over 300ms
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export const setChannelVolume = async (
|
|
119
|
+
channelNumber: number,
|
|
120
|
+
volume: number,
|
|
121
|
+
transitionDuration?: number,
|
|
122
|
+
easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out'
|
|
123
|
+
): Promise<void> => {
|
|
124
|
+
const clampedVolume: number = Math.max(0, Math.min(1, volume));
|
|
125
|
+
|
|
126
|
+
if (!audioChannels[channelNumber]) {
|
|
127
|
+
audioChannels[channelNumber] = {
|
|
128
|
+
audioCompleteCallbacks: new Set(),
|
|
129
|
+
audioErrorCallbacks: new Set(),
|
|
130
|
+
audioPauseCallbacks: new Set(),
|
|
131
|
+
audioResumeCallbacks: new Set(),
|
|
132
|
+
audioStartCallbacks: new Set(),
|
|
133
|
+
isPaused: false,
|
|
134
|
+
progressCallbacks: new Map(),
|
|
135
|
+
queue: [],
|
|
136
|
+
queueChangeCallbacks: new Set(),
|
|
137
|
+
volume: clampedVolume
|
|
138
|
+
};
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (transitionDuration && transitionDuration > 0) {
|
|
143
|
+
// Smooth transition
|
|
144
|
+
await transitionVolume(channelNumber, clampedVolume, transitionDuration, easing);
|
|
145
|
+
} else {
|
|
146
|
+
// Instant change (backward compatibility)
|
|
147
|
+
audioChannels[channelNumber].volume = clampedVolume;
|
|
148
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
149
|
+
if (channel.queue.length > 0) {
|
|
150
|
+
const currentAudio: HTMLAudioElement = channel.queue[0];
|
|
151
|
+
currentAudio.volume = clampedVolume;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Gets the current volume for a specific channel
|
|
158
|
+
* @param channelNumber - The channel number to get volume for (defaults to 0)
|
|
159
|
+
* @returns Current volume level (0-1) or 1.0 if channel doesn't exist
|
|
160
|
+
* @example
|
|
161
|
+
* ```typescript
|
|
162
|
+
* const volume = getChannelVolume(0);
|
|
163
|
+
* const defaultChannelVolume = getChannelVolume(); // Gets channel 0
|
|
164
|
+
* console.log(`Channel 0 volume: ${volume * 100}%`);
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
export const getChannelVolume = (channelNumber: number = 0): number => {
|
|
168
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
169
|
+
return channel?.volume || 1.0;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Gets the volume levels for all channels
|
|
174
|
+
* @returns Array of volume levels (0-1) for each channel
|
|
175
|
+
* @example
|
|
176
|
+
* ```typescript
|
|
177
|
+
* const volumes = getAllChannelsVolume();
|
|
178
|
+
* volumes.forEach((volume, index) => {
|
|
179
|
+
* console.log(`Channel ${index}: ${volume * 100}%`);
|
|
180
|
+
* });
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
export const getAllChannelsVolume = (): number[] => {
|
|
184
|
+
return audioChannels.map((channel: ExtendedAudioQueueChannel) =>
|
|
185
|
+
channel?.volume || 1.0
|
|
186
|
+
);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Sets volume for all channels to the same level
|
|
191
|
+
* @param volume - Volume level (0-1) to apply to all channels
|
|
192
|
+
* @example
|
|
193
|
+
* ```typescript
|
|
194
|
+
* await setAllChannelsVolume(0.6); // Set all channels to 60% volume
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
export const setAllChannelsVolume = async (volume: number): Promise<void> => {
|
|
198
|
+
const promises: Promise<void>[] = [];
|
|
199
|
+
audioChannels.forEach((_channel: ExtendedAudioQueueChannel, index: number) => {
|
|
200
|
+
promises.push(setChannelVolume(index, volume));
|
|
201
|
+
});
|
|
202
|
+
await Promise.all(promises);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Configures volume ducking for channels. When the priority channel plays audio,
|
|
207
|
+
* all other channels will be automatically reduced to the ducking volume level
|
|
208
|
+
* @param config - Volume ducking configuration
|
|
209
|
+
* @example
|
|
210
|
+
* ```typescript
|
|
211
|
+
* // When channel 1 plays, reduce all other channels to 20% volume
|
|
212
|
+
* setVolumeDucking({
|
|
213
|
+
* priorityChannel: 1,
|
|
214
|
+
* priorityVolume: 1.0,
|
|
215
|
+
* duckingVolume: 0.2
|
|
216
|
+
* });
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
export const setVolumeDucking = (config: VolumeConfig): void => {
|
|
220
|
+
// First, ensure we have enough channels for the priority channel
|
|
221
|
+
while (audioChannels.length <= config.priorityChannel) {
|
|
222
|
+
audioChannels.push({
|
|
223
|
+
audioCompleteCallbacks: new Set(),
|
|
224
|
+
audioErrorCallbacks: new Set(),
|
|
225
|
+
audioPauseCallbacks: new Set(),
|
|
226
|
+
audioResumeCallbacks: new Set(),
|
|
227
|
+
audioStartCallbacks: new Set(),
|
|
228
|
+
isPaused: false,
|
|
229
|
+
progressCallbacks: new Map(),
|
|
230
|
+
queue: [],
|
|
231
|
+
queueChangeCallbacks: new Set(),
|
|
232
|
+
volume: 1.0
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Apply the config to all existing channels
|
|
237
|
+
audioChannels.forEach((channel: ExtendedAudioQueueChannel, index: number) => {
|
|
238
|
+
if (!audioChannels[index]) {
|
|
239
|
+
audioChannels[index] = {
|
|
240
|
+
audioCompleteCallbacks: new Set(),
|
|
241
|
+
audioErrorCallbacks: new Set(),
|
|
242
|
+
audioPauseCallbacks: new Set(),
|
|
243
|
+
audioResumeCallbacks: new Set(),
|
|
244
|
+
audioStartCallbacks: new Set(),
|
|
245
|
+
isPaused: false,
|
|
246
|
+
progressCallbacks: new Map(),
|
|
247
|
+
queue: [],
|
|
248
|
+
queueChangeCallbacks: new Set(),
|
|
249
|
+
volume: 1.0
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
audioChannels[index].volumeConfig = config;
|
|
253
|
+
});
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Removes volume ducking configuration from all channels
|
|
258
|
+
* @example
|
|
259
|
+
* ```typescript
|
|
260
|
+
* clearVolumeDucking(); // Remove all volume ducking effects
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
export const clearVolumeDucking = (): void => {
|
|
264
|
+
audioChannels.forEach((channel: ExtendedAudioQueueChannel) => {
|
|
265
|
+
if (channel) {
|
|
266
|
+
delete channel.volumeConfig;
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Applies volume ducking effects based on current playback state with smooth transitions
|
|
273
|
+
* @param activeChannelNumber - The channel that just started playing
|
|
274
|
+
* @internal
|
|
275
|
+
*/
|
|
276
|
+
export const applyVolumeDucking = async (activeChannelNumber: number): Promise<void> => {
|
|
277
|
+
const transitionPromises: Promise<void>[] = [];
|
|
278
|
+
|
|
279
|
+
audioChannels.forEach((channel: ExtendedAudioQueueChannel, channelNumber: number) => {
|
|
280
|
+
if (channel?.volumeConfig) {
|
|
281
|
+
const config: VolumeConfig = channel.volumeConfig;
|
|
282
|
+
|
|
283
|
+
if (activeChannelNumber === config.priorityChannel) {
|
|
284
|
+
const duration = config.duckTransitionDuration || 250;
|
|
285
|
+
const easing = config.transitionEasing || 'ease-out';
|
|
286
|
+
|
|
287
|
+
// Priority channel is active, duck other channels
|
|
288
|
+
if (channelNumber === config.priorityChannel) {
|
|
289
|
+
transitionPromises.push(
|
|
290
|
+
transitionVolume(channelNumber, config.priorityVolume, duration, easing)
|
|
291
|
+
);
|
|
292
|
+
} else {
|
|
293
|
+
transitionPromises.push(
|
|
294
|
+
transitionVolume(channelNumber, config.duckingVolume, duration, easing)
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Wait for all transitions to complete
|
|
302
|
+
await Promise.all(transitionPromises);
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Restores normal volume levels when priority channel stops with smooth transitions
|
|
307
|
+
* @param stoppedChannelNumber - The channel that just stopped playing
|
|
308
|
+
* @internal
|
|
309
|
+
*/
|
|
310
|
+
export const restoreVolumeLevels = async (stoppedChannelNumber: number): Promise<void> => {
|
|
311
|
+
const transitionPromises: Promise<void>[] = [];
|
|
312
|
+
|
|
313
|
+
audioChannels.forEach((channel: ExtendedAudioQueueChannel, channelNumber: number) => {
|
|
314
|
+
if (channel?.volumeConfig) {
|
|
315
|
+
const config: VolumeConfig = channel.volumeConfig;
|
|
316
|
+
|
|
317
|
+
if (stoppedChannelNumber === config.priorityChannel) {
|
|
318
|
+
const duration = config.restoreTransitionDuration || 500;
|
|
319
|
+
const easing = config.transitionEasing || 'ease-out';
|
|
320
|
+
|
|
321
|
+
// Priority channel stopped, restore normal volumes
|
|
322
|
+
transitionPromises.push(
|
|
323
|
+
transitionVolume(channelNumber, channel.volume || 1.0, duration, easing)
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Wait for all transitions to complete
|
|
330
|
+
await Promise.all(transitionPromises);
|
|
328
331
|
};
|