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/src/pause.ts
ADDED
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pause and resume management functions for the audioq package
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
ExtendedAudioQueueChannel,
|
|
7
|
+
AudioInfo,
|
|
8
|
+
FadeType,
|
|
9
|
+
FadeConfig,
|
|
10
|
+
ChannelFadeState
|
|
11
|
+
} from './types';
|
|
12
|
+
import { audioChannels } from './info';
|
|
13
|
+
import { getAudioInfoFromElement } from './utils';
|
|
14
|
+
import { emitAudioPause, emitAudioResume } from './events';
|
|
15
|
+
import { transitionVolume, getFadeConfig } from './volume';
|
|
16
|
+
import { setWebAudioVolume } from './web-audio';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Gets the current volume for a channel, accounting for synchronous state
|
|
20
|
+
* @param channelNumber - The channel number
|
|
21
|
+
* @returns Current volume level (0-1)
|
|
22
|
+
*/
|
|
23
|
+
const getChannelVolumeSync = (channelNumber: number): number => {
|
|
24
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
25
|
+
return channel?.volume ?? 1.0;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Sets the channel volume synchronously in internal state
|
|
30
|
+
* @param channelNumber - The channel number
|
|
31
|
+
* @param volume - Volume level (0-1)
|
|
32
|
+
*/
|
|
33
|
+
const setChannelVolumeSync = (channelNumber: number, volume: number): void => {
|
|
34
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
35
|
+
if (channel) {
|
|
36
|
+
channel.volume = volume;
|
|
37
|
+
if (channel.queue.length > 0) {
|
|
38
|
+
const audio = channel.queue[0];
|
|
39
|
+
if (channel.webAudioNodes) {
|
|
40
|
+
const nodes = channel.webAudioNodes.get(audio);
|
|
41
|
+
if (nodes) {
|
|
42
|
+
// Update the gain node when Web Audio is active
|
|
43
|
+
setWebAudioVolume(nodes.gainNode, volume);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
audio.volume = volume;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Pauses the currently playing audio in a specific channel with smooth volume fade
|
|
53
|
+
* @param fadeType - Type of fade transition to apply
|
|
54
|
+
* @param channelNumber - The channel number to pause (defaults to 0)
|
|
55
|
+
* @param duration - Optional custom fade duration in milliseconds (uses fadeType default if not provided)
|
|
56
|
+
* @returns Promise that resolves when the pause and fade are complete
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* await pauseWithFade(FadeType.Gentle, 0); // Pause with gentle fade out over 800ms
|
|
60
|
+
* await pauseWithFade(FadeType.Dramatic, 1, 1500); // Pause with dramatic fade out over 1.5s
|
|
61
|
+
* await pauseWithFade(FadeType.Linear, 2, 500); // Linear pause with custom 500ms fade
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export const pauseWithFade = async (
|
|
65
|
+
fadeType: FadeType = FadeType.Gentle,
|
|
66
|
+
channelNumber: number = 0,
|
|
67
|
+
duration?: number
|
|
68
|
+
): Promise<void> => {
|
|
69
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
70
|
+
|
|
71
|
+
if (!channel || channel.queue.length === 0) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const currentAudio: HTMLAudioElement = channel.queue[0];
|
|
76
|
+
|
|
77
|
+
// Don't pause if already paused or ended
|
|
78
|
+
if (currentAudio.paused || currentAudio.ended) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const config: FadeConfig = getFadeConfig(fadeType);
|
|
83
|
+
const effectiveDuration: number = duration ?? config.duration;
|
|
84
|
+
|
|
85
|
+
// Race condition fix: Use existing fadeState originalVolume if already transitioning,
|
|
86
|
+
// otherwise capture current volume
|
|
87
|
+
let originalVolume: number;
|
|
88
|
+
if (channel.fadeState?.isTransitioning) {
|
|
89
|
+
// We're already in any kind of transition (pause or resume), preserve original volume
|
|
90
|
+
originalVolume = channel.fadeState.originalVolume;
|
|
91
|
+
} else {
|
|
92
|
+
// First fade or no transition in progress, capture current volume
|
|
93
|
+
// But ensure we don't capture a volume of 0 during a transition
|
|
94
|
+
const currentVolume = getChannelVolumeSync(channelNumber);
|
|
95
|
+
originalVolume = currentVolume > 0 ? currentVolume : (channel.fadeState?.originalVolume ?? 1.0);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Store fade state for resumeWithFade to use (including custom duration)
|
|
99
|
+
channel.fadeState = {
|
|
100
|
+
customDuration: duration,
|
|
101
|
+
fadeType,
|
|
102
|
+
isPaused: true,
|
|
103
|
+
isTransitioning: true,
|
|
104
|
+
originalVolume
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
if (effectiveDuration === 0) {
|
|
108
|
+
// Instant pause
|
|
109
|
+
await pauseChannel(channelNumber);
|
|
110
|
+
// Reset volume to original for resume (synchronously to avoid state issues)
|
|
111
|
+
setChannelVolumeSync(channelNumber, originalVolume);
|
|
112
|
+
// Mark transition as complete for instant pause
|
|
113
|
+
if (channel.fadeState) {
|
|
114
|
+
channel.fadeState.isTransitioning = false;
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Fade to 0 with pause curve, then pause
|
|
120
|
+
await transitionVolume(channelNumber, 0, effectiveDuration, config.pauseCurve);
|
|
121
|
+
// Pause the audio
|
|
122
|
+
await pauseChannel(channelNumber);
|
|
123
|
+
|
|
124
|
+
// Restore channel.volume for resume, but DON'T restore gain node to prevent blip
|
|
125
|
+
// The gain node will be restored during the resume fade
|
|
126
|
+
channel.volume = originalVolume;
|
|
127
|
+
|
|
128
|
+
// Mark transition as complete
|
|
129
|
+
if (channel.fadeState) {
|
|
130
|
+
channel.fadeState.isTransitioning = false;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Resumes the currently paused audio in a specific channel with smooth volume fade
|
|
136
|
+
* Uses the complementary fade curve automatically based on the pause fade type, or allows override
|
|
137
|
+
* @param fadeType - Optional fade type to override the stored fade type from pause
|
|
138
|
+
* @param channelNumber - The channel number to resume (defaults to 0)
|
|
139
|
+
* @param duration - Optional custom fade duration in milliseconds (uses stored or fadeType default if not provided)
|
|
140
|
+
* @returns Promise that resolves when the resume and fade are complete
|
|
141
|
+
* @example
|
|
142
|
+
* ```typescript
|
|
143
|
+
* await resumeWithFade(); // Resume with automatically paired fade curve from pause
|
|
144
|
+
* await resumeWithFade(FadeType.Dramatic, 0); // Override with dramatic fade
|
|
145
|
+
* await resumeWithFade(FadeType.Linear, 0, 1000); // Override with linear fade over 1 second
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export const resumeWithFade = async (
|
|
149
|
+
fadeType?: FadeType,
|
|
150
|
+
channelNumber: number = 0,
|
|
151
|
+
duration?: number
|
|
152
|
+
): Promise<void> => {
|
|
153
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
154
|
+
|
|
155
|
+
if (!channel || channel.queue.length === 0) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const audio = channel.queue[0];
|
|
159
|
+
const fadeState: ChannelFadeState | undefined = channel.fadeState;
|
|
160
|
+
|
|
161
|
+
if (!fadeState?.isPaused) {
|
|
162
|
+
// Fall back to regular resume if no fade state
|
|
163
|
+
await resumeChannel(channelNumber);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Use provided fadeType or fall back to stored fadeType from pause
|
|
168
|
+
const effectiveFadeType: FadeType = fadeType ?? fadeState.fadeType;
|
|
169
|
+
const config: FadeConfig = getFadeConfig(effectiveFadeType);
|
|
170
|
+
|
|
171
|
+
// Determine effective duration: custom parameter > stored custom > fadeType default
|
|
172
|
+
let effectiveDuration: number;
|
|
173
|
+
if (duration !== undefined) {
|
|
174
|
+
effectiveDuration = duration;
|
|
175
|
+
} else if (fadeState.customDuration !== undefined) {
|
|
176
|
+
effectiveDuration = fadeState.customDuration;
|
|
177
|
+
} else {
|
|
178
|
+
effectiveDuration = config.duration;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (effectiveDuration === 0) {
|
|
182
|
+
// Instant resume
|
|
183
|
+
const targetVolume = fadeState.originalVolume > 0 ? fadeState.originalVolume : 1.0;
|
|
184
|
+
|
|
185
|
+
setChannelVolumeSync(channelNumber, targetVolume);
|
|
186
|
+
await resumeChannel(channelNumber);
|
|
187
|
+
|
|
188
|
+
fadeState.isPaused = false;
|
|
189
|
+
fadeState.isTransitioning = false;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Race condition fix: Ensure we have a valid original volume to restore to
|
|
194
|
+
const targetVolume = fadeState.originalVolume > 0 ? fadeState.originalVolume : 1.0;
|
|
195
|
+
|
|
196
|
+
// Mark as transitioning to prevent volume capture during rapid toggles
|
|
197
|
+
fadeState.isTransitioning = true;
|
|
198
|
+
|
|
199
|
+
// Ensure gain node is at 0 before resuming (should already be from pause)
|
|
200
|
+
// Don't touch audio.volume when Web Audio is active - iOS may reset it
|
|
201
|
+
// Don't touch channel.volume - it should stay at originalVolume
|
|
202
|
+
if (channel.webAudioNodes) {
|
|
203
|
+
const nodes = channel.webAudioNodes.get(audio);
|
|
204
|
+
if (nodes) {
|
|
205
|
+
setWebAudioVolume(nodes.gainNode, 0);
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
// Fallback for non-Web Audio: set audio.volume directly
|
|
209
|
+
audio.volume = 0;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await resumeChannel(channelNumber);
|
|
213
|
+
|
|
214
|
+
// Use the stored original volume, not current volume, to prevent race conditions
|
|
215
|
+
await transitionVolume(channelNumber, targetVolume, effectiveDuration, config.resumeCurve);
|
|
216
|
+
fadeState.isPaused = false;
|
|
217
|
+
fadeState.isTransitioning = false;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Toggles pause/resume state for a specific channel with integrated fade
|
|
222
|
+
* @param fadeType - Type of fade transition to apply when pausing
|
|
223
|
+
* @param channelNumber - The channel number to toggle (defaults to 0)
|
|
224
|
+
* @param duration - Optional custom fade duration in milliseconds (uses fadeType default if not provided)
|
|
225
|
+
* @returns Promise that resolves when the toggle and fade are complete
|
|
226
|
+
* @example
|
|
227
|
+
* ```typescript
|
|
228
|
+
* await togglePauseWithFade(FadeType.Gentle, 0); // Toggle with gentle fade
|
|
229
|
+
* await togglePauseWithFade(FadeType.Dramatic, 0, 500); // Toggle with custom 500ms fade
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
export const togglePauseWithFade = async (
|
|
233
|
+
fadeType: FadeType = FadeType.Gentle,
|
|
234
|
+
channelNumber: number = 0,
|
|
235
|
+
duration?: number
|
|
236
|
+
): Promise<void> => {
|
|
237
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
238
|
+
|
|
239
|
+
if (!channel || channel.queue.length === 0) return;
|
|
240
|
+
|
|
241
|
+
const currentAudio: HTMLAudioElement = channel.queue[0];
|
|
242
|
+
|
|
243
|
+
if (currentAudio.paused) {
|
|
244
|
+
await resumeWithFade(undefined, channelNumber, duration);
|
|
245
|
+
} else {
|
|
246
|
+
await pauseWithFade(fadeType, channelNumber, duration);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Pauses all currently playing audio across all channels with smooth volume fade
|
|
252
|
+
* @param fadeType - Type of fade transition to apply to all channels
|
|
253
|
+
* @param duration - Optional custom fade duration in milliseconds (uses fadeType default if not provided)
|
|
254
|
+
* @returns Promise that resolves when all channels are paused and faded
|
|
255
|
+
* @example
|
|
256
|
+
* ```typescript
|
|
257
|
+
* await pauseAllWithFade(FadeType.Dramatic); // Pause everything with dramatic fade
|
|
258
|
+
* await pauseAllWithFade(FadeType.Gentle, 1200); // Pause all channels with custom 1.2s fade
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
export const pauseAllWithFade = async (
|
|
262
|
+
fadeType: FadeType = FadeType.Gentle,
|
|
263
|
+
duration?: number
|
|
264
|
+
): Promise<void> => {
|
|
265
|
+
const pausePromises: Promise<void>[] = [];
|
|
266
|
+
|
|
267
|
+
audioChannels.forEach((_channel: ExtendedAudioQueueChannel, index: number) => {
|
|
268
|
+
pausePromises.push(pauseWithFade(fadeType, index, duration));
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
await Promise.all(pausePromises);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Resumes all currently paused audio across all channels with smooth volume fade
|
|
276
|
+
* Uses automatically paired fade curves based on each channel's pause fade type, or allows override
|
|
277
|
+
* @param fadeType - Optional fade type to override stored fade types for all channels
|
|
278
|
+
* @param duration - Optional custom fade duration in milliseconds (uses stored or fadeType default if not provided)
|
|
279
|
+
* @returns Promise that resolves when all channels are resumed and faded
|
|
280
|
+
* @example
|
|
281
|
+
* ```typescript
|
|
282
|
+
* await resumeAllWithFade(); // Resume everything with paired fade curves
|
|
283
|
+
* await resumeAllWithFade(FadeType.Gentle, 800); // Override all channels with gentle fade over 800ms
|
|
284
|
+
* await resumeAllWithFade(undefined, 600); // Use stored fade types with custom 600ms duration
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
export const resumeAllWithFade = async (fadeType?: FadeType, duration?: number): Promise<void> => {
|
|
288
|
+
const resumePromises: Promise<void>[] = [];
|
|
289
|
+
|
|
290
|
+
audioChannels.forEach((_channel: ExtendedAudioQueueChannel, index: number) => {
|
|
291
|
+
resumePromises.push(resumeWithFade(fadeType, index, duration));
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
await Promise.all(resumePromises);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Toggles pause/resume state for all channels with integrated fade
|
|
299
|
+
* If any channels are playing, all will be paused with fade
|
|
300
|
+
* If all channels are paused, all will be resumed with fade
|
|
301
|
+
* @param fadeType - Type of fade transition to apply when pausing
|
|
302
|
+
* @param duration - Optional custom fade duration in milliseconds (uses fadeType default if not provided)
|
|
303
|
+
* @returns Promise that resolves when all toggles and fades are complete
|
|
304
|
+
* @example
|
|
305
|
+
* ```typescript
|
|
306
|
+
* await togglePauseAllWithFade(FadeType.Gentle); // Global toggle with gentle fade
|
|
307
|
+
* await togglePauseAllWithFade(FadeType.Dramatic, 600); // Global toggle with custom 600ms fade
|
|
308
|
+
* ```
|
|
309
|
+
*/
|
|
310
|
+
export const togglePauseAllWithFade = async (
|
|
311
|
+
fadeType: FadeType = FadeType.Gentle,
|
|
312
|
+
duration?: number
|
|
313
|
+
): Promise<void> => {
|
|
314
|
+
let hasPlayingChannel: boolean = false;
|
|
315
|
+
|
|
316
|
+
// Check if any channel is currently playing
|
|
317
|
+
for (let i: number = 0; i < audioChannels.length; i++) {
|
|
318
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[i];
|
|
319
|
+
if (channel && channel.queue.length > 0) {
|
|
320
|
+
const currentAudio: HTMLAudioElement = channel.queue[0];
|
|
321
|
+
if (!currentAudio.paused && !currentAudio.ended) {
|
|
322
|
+
hasPlayingChannel = true;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// If any channel is playing, pause all with fade
|
|
329
|
+
// If no channels are playing, resume all with fade
|
|
330
|
+
if (hasPlayingChannel) {
|
|
331
|
+
await pauseAllWithFade(fadeType, duration);
|
|
332
|
+
} else {
|
|
333
|
+
await resumeAllWithFade(fadeType, duration);
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Pauses the currently playing audio in a specific channel
|
|
339
|
+
* @param channelNumber - The channel number to pause (defaults to 0)
|
|
340
|
+
* @returns Promise that resolves when the audio is paused
|
|
341
|
+
* @example
|
|
342
|
+
* ```typescript
|
|
343
|
+
* await pauseChannel(0); // Pause audio in channel 0
|
|
344
|
+
* await pauseChannel(); // Pause audio in default channel
|
|
345
|
+
* ```
|
|
346
|
+
*/
|
|
347
|
+
export const pauseChannel = async (channelNumber: number = 0): Promise<void> => {
|
|
348
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
349
|
+
|
|
350
|
+
if (channel && channel.queue.length > 0) {
|
|
351
|
+
const currentAudio: HTMLAudioElement = channel.queue[0];
|
|
352
|
+
|
|
353
|
+
if (!currentAudio.paused && !currentAudio.ended) {
|
|
354
|
+
currentAudio.pause();
|
|
355
|
+
channel.isPaused = true;
|
|
356
|
+
|
|
357
|
+
const audioInfo: AudioInfo | null = getAudioInfoFromElement(
|
|
358
|
+
currentAudio,
|
|
359
|
+
channelNumber,
|
|
360
|
+
audioChannels
|
|
361
|
+
);
|
|
362
|
+
if (audioInfo) {
|
|
363
|
+
emitAudioPause(channelNumber, audioInfo, audioChannels);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Resumes the currently paused audio in a specific channel
|
|
371
|
+
* @param channelNumber - The channel number to resume (defaults to 0)
|
|
372
|
+
* @returns Promise that resolves when the audio starts playing
|
|
373
|
+
* @example
|
|
374
|
+
* ```typescript
|
|
375
|
+
* await resumeChannel(0); // Resume audio in channel 0
|
|
376
|
+
* await resumeChannel(); // Resume audio in default channel
|
|
377
|
+
* ```
|
|
378
|
+
*/
|
|
379
|
+
export const resumeChannel = async (channelNumber: number = 0): Promise<void> => {
|
|
380
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
381
|
+
|
|
382
|
+
if (channel && channel.queue.length > 0) {
|
|
383
|
+
const currentAudio: HTMLAudioElement = channel.queue[0];
|
|
384
|
+
|
|
385
|
+
// Only resume if both the channel is marked as paused AND the audio element is actually paused AND not ended
|
|
386
|
+
if (channel.isPaused && currentAudio.paused && !currentAudio.ended) {
|
|
387
|
+
await currentAudio.play();
|
|
388
|
+
channel.isPaused = false;
|
|
389
|
+
|
|
390
|
+
const audioInfo: AudioInfo | null = getAudioInfoFromElement(
|
|
391
|
+
currentAudio,
|
|
392
|
+
channelNumber,
|
|
393
|
+
audioChannels
|
|
394
|
+
);
|
|
395
|
+
if (audioInfo) {
|
|
396
|
+
emitAudioResume(channelNumber, audioInfo, audioChannels);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Toggles pause/resume state for a specific channel
|
|
404
|
+
* @param channelNumber - The channel number to toggle (defaults to 0)
|
|
405
|
+
* @returns Promise that resolves when the toggle is complete
|
|
406
|
+
* @example
|
|
407
|
+
* ```typescript
|
|
408
|
+
* await togglePauseChannel(0); // Toggle pause state for channel 0
|
|
409
|
+
* ```
|
|
410
|
+
*/
|
|
411
|
+
export const togglePauseChannel = async (channelNumber: number = 0): Promise<void> => {
|
|
412
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
413
|
+
|
|
414
|
+
if (channel && channel.queue.length > 0) {
|
|
415
|
+
const currentAudio: HTMLAudioElement = channel.queue[0];
|
|
416
|
+
|
|
417
|
+
if (currentAudio.paused) {
|
|
418
|
+
await resumeChannel(channelNumber);
|
|
419
|
+
} else {
|
|
420
|
+
await pauseChannel(channelNumber);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Pauses all currently playing audio across all channels
|
|
427
|
+
* @returns Promise that resolves when all audio is paused
|
|
428
|
+
* @example
|
|
429
|
+
* ```typescript
|
|
430
|
+
* await pauseAllChannels(); // Pause everything
|
|
431
|
+
* ```
|
|
432
|
+
*/
|
|
433
|
+
export const pauseAllChannels = async (): Promise<void> => {
|
|
434
|
+
const pausePromises: Promise<void>[] = [];
|
|
435
|
+
|
|
436
|
+
audioChannels.forEach((_channel: ExtendedAudioQueueChannel, index: number) => {
|
|
437
|
+
pausePromises.push(pauseChannel(index));
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
await Promise.all(pausePromises);
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Resumes all currently paused audio across all channels
|
|
445
|
+
* @returns Promise that resolves when all audio is resumed
|
|
446
|
+
* @example
|
|
447
|
+
* ```typescript
|
|
448
|
+
* await resumeAllChannels(); // Resume everything that was paused
|
|
449
|
+
* ```
|
|
450
|
+
*/
|
|
451
|
+
export const resumeAllChannels = async (): Promise<void> => {
|
|
452
|
+
const resumePromises: Promise<void>[] = [];
|
|
453
|
+
|
|
454
|
+
audioChannels.forEach((_channel: ExtendedAudioQueueChannel, index: number) => {
|
|
455
|
+
resumePromises.push(resumeChannel(index));
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
await Promise.all(resumePromises);
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Checks if a specific channel is currently paused
|
|
463
|
+
* @param channelNumber - The channel number to check (defaults to 0)
|
|
464
|
+
* @returns True if the channel is paused, false otherwise
|
|
465
|
+
* @example
|
|
466
|
+
* ```typescript
|
|
467
|
+
* const isPaused = isChannelPaused(0);
|
|
468
|
+
* console.log(`Channel 0 is ${isPaused ? 'paused' : 'playing'}`);
|
|
469
|
+
* ```
|
|
470
|
+
*/
|
|
471
|
+
export const isChannelPaused = (channelNumber: number = 0): boolean => {
|
|
472
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
473
|
+
return channel?.isPaused ?? false;
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Gets the pause state of all channels
|
|
478
|
+
* @returns Array of boolean values indicating pause state for each channel
|
|
479
|
+
* @example
|
|
480
|
+
* ```typescript
|
|
481
|
+
* const pauseStates = getAllChannelsPauseState();
|
|
482
|
+
* pauseStates.forEach((isPaused, index) => {
|
|
483
|
+
* console.log(`Channel ${index}: ${isPaused ? 'paused' : 'playing'}`);
|
|
484
|
+
* });
|
|
485
|
+
* ```
|
|
486
|
+
*/
|
|
487
|
+
export const getAllChannelsPauseState = (): boolean[] => {
|
|
488
|
+
return audioChannels.map((channel: ExtendedAudioQueueChannel) => channel?.isPaused ?? false);
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Toggles pause/resume state for all channels globally
|
|
493
|
+
* If any channels are currently playing, all channels will be paused
|
|
494
|
+
* If all channels are paused, all channels will be resumed
|
|
495
|
+
* @returns Promise that resolves when the toggle is complete
|
|
496
|
+
* @example
|
|
497
|
+
* ```typescript
|
|
498
|
+
* await togglePauseAllChannels(); // Pause all if any are playing, resume all if all are paused
|
|
499
|
+
* ```
|
|
500
|
+
*/
|
|
501
|
+
export const togglePauseAllChannels = async (): Promise<void> => {
|
|
502
|
+
let hasPlayingChannel: boolean = false;
|
|
503
|
+
|
|
504
|
+
// Check if any channel is currently playing
|
|
505
|
+
for (let i: number = 0; i < audioChannels.length; i++) {
|
|
506
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[i];
|
|
507
|
+
if (channel && channel.queue.length > 0) {
|
|
508
|
+
const currentAudio: HTMLAudioElement = channel.queue[0];
|
|
509
|
+
if (!currentAudio.paused && !currentAudio.ended) {
|
|
510
|
+
hasPlayingChannel = true;
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// If any channel is playing, pause all channels
|
|
517
|
+
// If no channels are playing, resume all channels
|
|
518
|
+
if (hasPlayingChannel) {
|
|
519
|
+
await pauseAllChannels();
|
|
520
|
+
} else {
|
|
521
|
+
await resumeAllChannels();
|
|
522
|
+
}
|
|
523
|
+
};
|