audio-channel-queue 1.4.0 → 1.6.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/src/info.ts ADDED
@@ -0,0 +1,380 @@
1
+ /**
2
+ * @fileoverview Audio information and progress tracking functions for the audio-channel-queue package
3
+ */
4
+
5
+ import {
6
+ AudioInfo,
7
+ QueueSnapshot,
8
+ ProgressCallback,
9
+ QueueChangeCallback,
10
+ AudioStartCallback,
11
+ AudioCompleteCallback,
12
+ AudioPauseCallback,
13
+ AudioResumeCallback,
14
+ ExtendedAudioQueueChannel
15
+ } from './types';
16
+ import { getAudioInfoFromElement, createQueueSnapshot } from './utils';
17
+ import { setupProgressTracking, cleanupProgressTracking } from './events';
18
+
19
+ /**
20
+ * Global array of extended audio queue channels
21
+ */
22
+ export const audioChannels: ExtendedAudioQueueChannel[] = [];
23
+
24
+ /**
25
+ * Gets current audio information for a specific channel
26
+ * @param channelNumber - The channel number (defaults to 0)
27
+ * @returns AudioInfo object or null if no audio is playing
28
+ * @example
29
+ * ```typescript
30
+ * const info = getCurrentAudioInfo(0);
31
+ * if (info) {
32
+ * console.log(`Currently playing: ${info.fileName}`);
33
+ * console.log(`Progress: ${(info.progress * 100).toFixed(1)}%`);
34
+ * }
35
+ * ```
36
+ */
37
+ export const getCurrentAudioInfo = (channelNumber: number = 0): AudioInfo | null => {
38
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
39
+ if (!channel || channel.queue.length === 0) {
40
+ return null;
41
+ }
42
+
43
+ const currentAudio: HTMLAudioElement = channel.queue[0];
44
+ return getAudioInfoFromElement(currentAudio, channelNumber, audioChannels);
45
+ };
46
+
47
+ /**
48
+ * Gets audio information for all channels
49
+ * @returns Array of AudioInfo objects (null for channels with no audio)
50
+ * @example
51
+ * ```typescript
52
+ * const allInfo = getAllChannelsInfo();
53
+ * allInfo.forEach((info, channel) => {
54
+ * if (info) {
55
+ * console.log(`Channel ${channel}: ${info.fileName}`);
56
+ * }
57
+ * });
58
+ * ```
59
+ */
60
+ export const getAllChannelsInfo = (): (AudioInfo | null)[] => {
61
+ const allChannelsInfo: (AudioInfo | null)[] = [];
62
+
63
+ for (let i = 0; i < audioChannels.length; i++) {
64
+ allChannelsInfo.push(getCurrentAudioInfo(i));
65
+ }
66
+
67
+ return allChannelsInfo;
68
+ };
69
+
70
+ /**
71
+ * Gets a complete snapshot of the queue state for a specific channel
72
+ * @param channelNumber - The channel number
73
+ * @returns QueueSnapshot object or null if channel doesn't exist
74
+ * @example
75
+ * ```typescript
76
+ * const snapshot = getQueueSnapshot(0);
77
+ * if (snapshot) {
78
+ * console.log(`Queue has ${snapshot.totalItems} items`);
79
+ * console.log(`Currently playing: ${snapshot.items[0]?.fileName}`);
80
+ * }
81
+ * ```
82
+ */
83
+ export const getQueueSnapshot = (channelNumber: number): QueueSnapshot | null => {
84
+ return createQueueSnapshot(channelNumber, audioChannels);
85
+ };
86
+
87
+ /**
88
+ * Subscribes to real-time progress updates for a specific channel
89
+ * @param channelNumber - The channel number
90
+ * @param callback - Function to call with audio info updates
91
+ * @example
92
+ * ```typescript
93
+ * onAudioProgress(0, (info) => {
94
+ * updateProgressBar(info.progress);
95
+ * updateTimeDisplay(info.currentTime, info.duration);
96
+ * });
97
+ * ```
98
+ */
99
+ export const onAudioProgress = (channelNumber: number, callback: ProgressCallback): void => {
100
+ if (!audioChannels[channelNumber]) {
101
+ audioChannels[channelNumber] = {
102
+ audioCompleteCallbacks: new Set(),
103
+ audioPauseCallbacks: new Set(),
104
+ audioResumeCallbacks: new Set(),
105
+ audioStartCallbacks: new Set(),
106
+ isPaused: false,
107
+ progressCallbacks: new Map(),
108
+ queue: [],
109
+ queueChangeCallbacks: new Set(),
110
+ volume: 1.0
111
+ };
112
+ }
113
+
114
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
115
+ if (!channel.progressCallbacks) {
116
+ channel.progressCallbacks = new Map();
117
+ }
118
+
119
+ // Add callback for current audio if exists
120
+ if (channel.queue.length > 0) {
121
+ const currentAudio: HTMLAudioElement = channel.queue[0];
122
+ if (!channel.progressCallbacks.has(currentAudio)) {
123
+ channel.progressCallbacks.set(currentAudio, new Set());
124
+ }
125
+ channel.progressCallbacks.get(currentAudio)!.add(callback);
126
+
127
+ // Set up tracking if not already done
128
+ setupProgressTracking(currentAudio, channelNumber, audioChannels);
129
+ }
130
+
131
+ // Store callback for future audio elements in this channel
132
+ if (!channel.progressCallbacks.has(null as any)) {
133
+ channel.progressCallbacks.set(null as any, new Set());
134
+ }
135
+ channel.progressCallbacks.get(null as any)!.add(callback);
136
+ };
137
+
138
+ /**
139
+ * Removes progress listeners for a specific channel
140
+ * @param channelNumber - The channel number
141
+ * @example
142
+ * ```typescript
143
+ * offAudioProgress(0); // Stop receiving progress updates for channel 0
144
+ * ```
145
+ */
146
+ export const offAudioProgress = (channelNumber: number): void => {
147
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
148
+ if (!channel || !channel.progressCallbacks) return;
149
+
150
+ // Clean up event listeners for current audio if exists
151
+ if (channel.queue.length > 0) {
152
+ const currentAudio: HTMLAudioElement = channel.queue[0];
153
+ cleanupProgressTracking(currentAudio, channelNumber, audioChannels);
154
+ }
155
+
156
+ // Clear all callbacks for this channel
157
+ channel.progressCallbacks.clear();
158
+ };
159
+
160
+ /**
161
+ * Subscribes to queue change events for a specific channel
162
+ * @param channelNumber - The channel number to monitor
163
+ * @param callback - Function to call when queue changes
164
+ * @example
165
+ * ```typescript
166
+ * onQueueChange(0, (snapshot) => {
167
+ * updateQueueDisplay(snapshot.items);
168
+ * updateQueueCount(snapshot.totalItems);
169
+ * });
170
+ * ```
171
+ */
172
+ export const onQueueChange = (channelNumber: number, callback: QueueChangeCallback): void => {
173
+ if (!audioChannels[channelNumber]) {
174
+ audioChannels[channelNumber] = {
175
+ audioCompleteCallbacks: new Set(),
176
+ audioPauseCallbacks: new Set(),
177
+ audioResumeCallbacks: new Set(),
178
+ audioStartCallbacks: new Set(),
179
+ isPaused: false,
180
+ progressCallbacks: new Map(),
181
+ queue: [],
182
+ queueChangeCallbacks: new Set(),
183
+ volume: 1.0
184
+ };
185
+ }
186
+
187
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
188
+ if (!channel.queueChangeCallbacks) {
189
+ channel.queueChangeCallbacks = new Set();
190
+ }
191
+
192
+ channel.queueChangeCallbacks.add(callback);
193
+ };
194
+
195
+ /**
196
+ * Removes queue change listeners for a specific channel
197
+ * @param channelNumber - The channel number
198
+ * @example
199
+ * ```typescript
200
+ * offQueueChange(0); // Stop receiving queue change notifications for channel 0
201
+ * ```
202
+ */
203
+ export const offQueueChange = (channelNumber: number): void => {
204
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
205
+ if (!channel || !channel.queueChangeCallbacks) return;
206
+
207
+ channel.queueChangeCallbacks.clear();
208
+ };
209
+
210
+ /**
211
+ * Subscribes to audio start events for a specific channel
212
+ * @param channelNumber - The channel number to monitor
213
+ * @param callback - Function to call when audio starts playing
214
+ * @example
215
+ * ```typescript
216
+ * onAudioStart(0, (info) => {
217
+ * showNowPlaying(info.fileName);
218
+ * setTotalDuration(info.duration);
219
+ * });
220
+ * ```
221
+ */
222
+ export const onAudioStart = (channelNumber: number, callback: AudioStartCallback): void => {
223
+ if (!audioChannels[channelNumber]) {
224
+ audioChannels[channelNumber] = {
225
+ audioCompleteCallbacks: new Set(),
226
+ audioPauseCallbacks: new Set(),
227
+ audioResumeCallbacks: new Set(),
228
+ audioStartCallbacks: new Set(),
229
+ isPaused: false,
230
+ progressCallbacks: new Map(),
231
+ queue: [],
232
+ queueChangeCallbacks: new Set(),
233
+ volume: 1.0
234
+ };
235
+ }
236
+
237
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
238
+ if (!channel.audioStartCallbacks) {
239
+ channel.audioStartCallbacks = new Set();
240
+ }
241
+
242
+ channel.audioStartCallbacks.add(callback);
243
+ };
244
+
245
+ /**
246
+ * Subscribes to audio complete events for a specific channel
247
+ * @param channelNumber - The channel number to monitor
248
+ * @param callback - Function to call when audio completes
249
+ * @example
250
+ * ```typescript
251
+ * onAudioComplete(0, (info) => {
252
+ * logPlaybackComplete(info.fileName);
253
+ * if (info.remainingInQueue === 0) {
254
+ * showQueueComplete();
255
+ * }
256
+ * });
257
+ * ```
258
+ */
259
+ export const onAudioComplete = (channelNumber: number, callback: AudioCompleteCallback): void => {
260
+ if (!audioChannels[channelNumber]) {
261
+ audioChannels[channelNumber] = {
262
+ audioCompleteCallbacks: new Set(),
263
+ audioPauseCallbacks: new Set(),
264
+ audioResumeCallbacks: new Set(),
265
+ audioStartCallbacks: new Set(),
266
+ isPaused: false,
267
+ progressCallbacks: new Map(),
268
+ queue: [],
269
+ queueChangeCallbacks: new Set(),
270
+ volume: 1.0
271
+ };
272
+ }
273
+
274
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
275
+ if (!channel.audioCompleteCallbacks) {
276
+ channel.audioCompleteCallbacks = new Set();
277
+ }
278
+
279
+ channel.audioCompleteCallbacks.add(callback);
280
+ };
281
+
282
+ /**
283
+ * Subscribes to audio pause events for a specific channel
284
+ * @param channelNumber - The channel number to monitor
285
+ * @param callback - Function to call when audio is paused
286
+ * @example
287
+ * ```typescript
288
+ * onAudioPause(0, (channelNumber, info) => {
289
+ * showPauseIndicator();
290
+ * logPauseEvent(info.fileName, info.currentTime);
291
+ * });
292
+ * ```
293
+ */
294
+ export const onAudioPause = (channelNumber: number, callback: AudioPauseCallback): void => {
295
+ if (!audioChannels[channelNumber]) {
296
+ audioChannels[channelNumber] = {
297
+ audioCompleteCallbacks: new Set(),
298
+ audioPauseCallbacks: new Set(),
299
+ audioResumeCallbacks: new Set(),
300
+ audioStartCallbacks: new Set(),
301
+ isPaused: false,
302
+ progressCallbacks: new Map(),
303
+ queue: [],
304
+ queueChangeCallbacks: new Set(),
305
+ volume: 1.0
306
+ };
307
+ }
308
+
309
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
310
+ if (!channel.audioPauseCallbacks) {
311
+ channel.audioPauseCallbacks = new Set();
312
+ }
313
+
314
+ channel.audioPauseCallbacks.add(callback);
315
+ };
316
+
317
+ /**
318
+ * Subscribes to audio resume events for a specific channel
319
+ * @param channelNumber - The channel number to monitor
320
+ * @param callback - Function to call when audio is resumed
321
+ * @example
322
+ * ```typescript
323
+ * onAudioResume(0, (channelNumber, info) => {
324
+ * hidePauseIndicator();
325
+ * logResumeEvent(info.fileName, info.currentTime);
326
+ * });
327
+ * ```
328
+ */
329
+ export const onAudioResume = (channelNumber: number, callback: AudioResumeCallback): void => {
330
+ if (!audioChannels[channelNumber]) {
331
+ audioChannels[channelNumber] = {
332
+ audioCompleteCallbacks: new Set(),
333
+ audioPauseCallbacks: new Set(),
334
+ audioResumeCallbacks: new Set(),
335
+ audioStartCallbacks: new Set(),
336
+ isPaused: false,
337
+ progressCallbacks: new Map(),
338
+ queue: [],
339
+ queueChangeCallbacks: new Set(),
340
+ volume: 1.0
341
+ };
342
+ }
343
+
344
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
345
+ if (!channel.audioResumeCallbacks) {
346
+ channel.audioResumeCallbacks = new Set();
347
+ }
348
+
349
+ channel.audioResumeCallbacks.add(callback);
350
+ };
351
+
352
+ /**
353
+ * Removes pause event listeners for a specific channel
354
+ * @param channelNumber - The channel number
355
+ * @example
356
+ * ```typescript
357
+ * offAudioPause(0); // Stop receiving pause notifications for channel 0
358
+ * ```
359
+ */
360
+ export const offAudioPause = (channelNumber: number): void => {
361
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
362
+ if (!channel || !channel.audioPauseCallbacks) return;
363
+
364
+ channel.audioPauseCallbacks.clear();
365
+ };
366
+
367
+ /**
368
+ * Removes resume event listeners for a specific channel
369
+ * @param channelNumber - The channel number
370
+ * @example
371
+ * ```typescript
372
+ * offAudioResume(0); // Stop receiving resume notifications for channel 0
373
+ * ```
374
+ */
375
+ export const offAudioResume = (channelNumber: number): void => {
376
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
377
+ if (!channel || !channel.audioResumeCallbacks) return;
378
+
379
+ channel.audioResumeCallbacks.clear();
380
+ };
package/src/pause.ts ADDED
@@ -0,0 +1,190 @@
1
+ /**
2
+ * @fileoverview Pause and resume management functions for the audio-channel-queue package
3
+ */
4
+
5
+ import { ExtendedAudioQueueChannel, AudioInfo } from './types';
6
+ import { audioChannels } from './info';
7
+ import { getAudioInfoFromElement } from './utils';
8
+ import { emitAudioPause, emitAudioResume } from './events';
9
+
10
+ /**
11
+ * Pauses the currently playing audio in a specific channel
12
+ * @param channelNumber - The channel number to pause (defaults to 0)
13
+ * @returns Promise that resolves when the audio is paused
14
+ * @example
15
+ * ```typescript
16
+ * await pauseChannel(0); // Pause audio in channel 0
17
+ * await pauseChannel(); // Pause audio in default channel
18
+ * ```
19
+ */
20
+ export const pauseChannel = async (channelNumber: number = 0): Promise<void> => {
21
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
22
+
23
+ if (channel && channel.queue.length > 0) {
24
+ const currentAudio: HTMLAudioElement = channel.queue[0];
25
+
26
+ if (!currentAudio.paused && !currentAudio.ended) {
27
+ currentAudio.pause();
28
+ channel.isPaused = true;
29
+
30
+ const audioInfo: AudioInfo | null = getAudioInfoFromElement(currentAudio, channelNumber, audioChannels);
31
+ if (audioInfo) {
32
+ emitAudioPause(channelNumber, audioInfo, audioChannels);
33
+ }
34
+ }
35
+ }
36
+ };
37
+
38
+ /**
39
+ * Resumes the currently paused audio in a specific channel
40
+ * @param channelNumber - The channel number to resume (defaults to 0)
41
+ * @returns Promise that resolves when the audio starts playing
42
+ * @example
43
+ * ```typescript
44
+ * await resumeChannel(0); // Resume audio in channel 0
45
+ * await resumeChannel(); // Resume audio in default channel
46
+ * ```
47
+ */
48
+ export const resumeChannel = async (channelNumber: number = 0): Promise<void> => {
49
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
50
+
51
+ if (channel && channel.queue.length > 0) {
52
+ const currentAudio: HTMLAudioElement = channel.queue[0];
53
+
54
+ // Only resume if both the channel is marked as paused AND the audio element is actually paused
55
+ if (channel.isPaused && currentAudio.paused && !currentAudio.ended) {
56
+ await currentAudio.play();
57
+ channel.isPaused = false;
58
+
59
+ const audioInfo: AudioInfo | null = getAudioInfoFromElement(currentAudio, channelNumber, audioChannels);
60
+ if (audioInfo) {
61
+ emitAudioResume(channelNumber, audioInfo, audioChannels);
62
+ }
63
+ }
64
+ }
65
+ };
66
+
67
+ /**
68
+ * Toggles pause/resume state for a specific channel
69
+ * @param channelNumber - The channel number to toggle (defaults to 0)
70
+ * @returns Promise that resolves when the toggle is complete
71
+ * @example
72
+ * ```typescript
73
+ * await togglePauseChannel(0); // Toggle pause state for channel 0
74
+ * ```
75
+ */
76
+ export const togglePauseChannel = async (channelNumber: number = 0): Promise<void> => {
77
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
78
+
79
+ if (channel && channel.queue.length > 0) {
80
+ const currentAudio: HTMLAudioElement = channel.queue[0];
81
+
82
+ if (currentAudio.paused) {
83
+ await resumeChannel(channelNumber);
84
+ } else {
85
+ await pauseChannel(channelNumber);
86
+ }
87
+ }
88
+ };
89
+
90
+ /**
91
+ * Pauses all currently playing audio across all channels
92
+ * @returns Promise that resolves when all audio is paused
93
+ * @example
94
+ * ```typescript
95
+ * await pauseAllChannels(); // Pause everything
96
+ * ```
97
+ */
98
+ export const pauseAllChannels = async (): Promise<void> => {
99
+ const pausePromises: Promise<void>[] = [];
100
+
101
+ audioChannels.forEach((_channel: ExtendedAudioQueueChannel, index: number) => {
102
+ pausePromises.push(pauseChannel(index));
103
+ });
104
+
105
+ await Promise.all(pausePromises);
106
+ };
107
+
108
+ /**
109
+ * Resumes all currently paused audio across all channels
110
+ * @returns Promise that resolves when all audio is resumed
111
+ * @example
112
+ * ```typescript
113
+ * await resumeAllChannels(); // Resume everything that was paused
114
+ * ```
115
+ */
116
+ export const resumeAllChannels = async (): Promise<void> => {
117
+ const resumePromises: Promise<void>[] = [];
118
+
119
+ audioChannels.forEach((_channel: ExtendedAudioQueueChannel, index: number) => {
120
+ resumePromises.push(resumeChannel(index));
121
+ });
122
+
123
+ await Promise.all(resumePromises);
124
+ };
125
+
126
+ /**
127
+ * Checks if a specific channel is currently paused
128
+ * @param channelNumber - The channel number to check (defaults to 0)
129
+ * @returns True if the channel is paused, false otherwise
130
+ * @example
131
+ * ```typescript
132
+ * const isPaused = isChannelPaused(0);
133
+ * console.log(`Channel 0 is ${isPaused ? 'paused' : 'playing'}`);
134
+ * ```
135
+ */
136
+ export const isChannelPaused = (channelNumber: number = 0): boolean => {
137
+ const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
138
+ return channel?.isPaused || false;
139
+ };
140
+
141
+ /**
142
+ * Gets the pause state of all channels
143
+ * @returns Array of boolean values indicating pause state for each channel
144
+ * @example
145
+ * ```typescript
146
+ * const pauseStates = getAllChannelsPauseState();
147
+ * pauseStates.forEach((isPaused, index) => {
148
+ * console.log(`Channel ${index}: ${isPaused ? 'paused' : 'playing'}`);
149
+ * });
150
+ * ```
151
+ */
152
+ export const getAllChannelsPauseState = (): boolean[] => {
153
+ return audioChannels.map((channel: ExtendedAudioQueueChannel) =>
154
+ channel?.isPaused || false
155
+ );
156
+ };
157
+
158
+ /**
159
+ * Toggles pause/resume state for all channels globally
160
+ * If any channels are currently playing, all channels will be paused
161
+ * If all channels are paused, all channels will be resumed
162
+ * @returns Promise that resolves when the toggle is complete
163
+ * @example
164
+ * ```typescript
165
+ * await togglePauseAllChannels(); // Pause all if any are playing, resume all if all are paused
166
+ * ```
167
+ */
168
+ export const togglePauseAllChannels = async (): Promise<void> => {
169
+ let hasPlayingChannel: boolean = false;
170
+
171
+ // Check if any channel is currently playing
172
+ for (let i = 0; i < audioChannels.length; i++) {
173
+ const channel: ExtendedAudioQueueChannel = audioChannels[i];
174
+ if (channel && channel.queue.length > 0) {
175
+ const currentAudio: HTMLAudioElement = channel.queue[0];
176
+ if (!currentAudio.paused && !currentAudio.ended) {
177
+ hasPlayingChannel = true;
178
+ break;
179
+ }
180
+ }
181
+ }
182
+
183
+ // If any channel is playing, pause all channels
184
+ // If no channels are playing, resume all channels
185
+ if (hasPlayingChannel) {
186
+ await pauseAllChannels();
187
+ } else {
188
+ await resumeAllChannels();
189
+ }
190
+ };