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
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Queue manipulation functions for the audioq package
|
|
3
|
+
* Provides advanced queue management including item removal, reordering, and clearing
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
ExtendedAudioQueueChannel,
|
|
8
|
+
QueueManipulationResult,
|
|
9
|
+
QueueSnapshot,
|
|
10
|
+
QueueItem
|
|
11
|
+
} from './types';
|
|
12
|
+
import { audioChannels } from './info';
|
|
13
|
+
import { emitQueueChange } from './events';
|
|
14
|
+
import { cleanupProgressTracking } from './events';
|
|
15
|
+
import { createQueueSnapshot, getAudioInfoFromElement } from './utils';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Removes a specific item from the queue by its slot number (0-based index)
|
|
19
|
+
* Cannot remove the currently playing item (index 0) - use stopCurrentAudioInChannel instead
|
|
20
|
+
* @param queuedSlotNumber - Zero-based index of the item to remove (must be > 0)
|
|
21
|
+
* @param channelNumber - The channel number (defaults to 0)
|
|
22
|
+
* @returns Promise resolving to operation result with success status and updated queue
|
|
23
|
+
* @throws Error if trying to remove currently playing item or invalid slot number
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* // Remove the second item in queue (index 1)
|
|
27
|
+
* const result = await removeQueuedItem(1, 0);
|
|
28
|
+
* if (result.success) {
|
|
29
|
+
* console.log(`Removed item, queue now has ${result.updatedQueue.totalItems} items`);
|
|
30
|
+
* }
|
|
31
|
+
*
|
|
32
|
+
* // Remove the third item from channel 1
|
|
33
|
+
* await removeQueuedItem(2, 1);
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export const removeQueuedItem = async (
|
|
37
|
+
queuedSlotNumber: number,
|
|
38
|
+
channelNumber: number = 0
|
|
39
|
+
): Promise<QueueManipulationResult> => {
|
|
40
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
41
|
+
|
|
42
|
+
if (!channel) {
|
|
43
|
+
return {
|
|
44
|
+
error: `Channel ${channelNumber} does not exist`,
|
|
45
|
+
success: false
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (queuedSlotNumber < 0 || queuedSlotNumber >= channel.queue.length) {
|
|
50
|
+
return {
|
|
51
|
+
error:
|
|
52
|
+
`Invalid slot number ${queuedSlotNumber}. Queue has ${channel.queue.length} items ` +
|
|
53
|
+
`(indices 0-${channel.queue.length - 1})`,
|
|
54
|
+
success: false
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (queuedSlotNumber === 0) {
|
|
59
|
+
return {
|
|
60
|
+
error:
|
|
61
|
+
'Cannot remove currently playing item (index 0). ' +
|
|
62
|
+
'Use stopCurrentAudioInChannel() instead',
|
|
63
|
+
success: false
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Remove the audio element from the queue
|
|
68
|
+
const removedAudio: HTMLAudioElement = channel.queue.splice(queuedSlotNumber, 1)[0];
|
|
69
|
+
|
|
70
|
+
// Clean up any progress tracking for the removed audio
|
|
71
|
+
cleanupProgressTracking(removedAudio, channelNumber, audioChannels);
|
|
72
|
+
|
|
73
|
+
// Emit queue change event
|
|
74
|
+
emitQueueChange(channelNumber, audioChannels);
|
|
75
|
+
|
|
76
|
+
const updatedQueue: QueueSnapshot | null = createQueueSnapshot(channelNumber, audioChannels);
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
success: true,
|
|
80
|
+
updatedQueue: updatedQueue ?? undefined
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Reorders a queue item by moving it from one position to another
|
|
86
|
+
* Cannot reorder the currently playing item (index 0)
|
|
87
|
+
* @param currentQueuedSlotNumber - Current zero-based index of the item to move (must be > 0)
|
|
88
|
+
* @param newQueuedSlotNumber - New zero-based index where the item should be placed (must be > 0)
|
|
89
|
+
* @param channelNumber - The channel number (defaults to 0)
|
|
90
|
+
* @returns Promise resolving to operation result with success status and updated queue
|
|
91
|
+
* @throws Error if trying to reorder currently playing item or invalid slot numbers
|
|
92
|
+
* @example
|
|
93
|
+
* ```typescript
|
|
94
|
+
* // Move item from position 2 to position 1 (make it play next)
|
|
95
|
+
* const result = await reorderQueue(2, 1, 0);
|
|
96
|
+
* if (result.success) {
|
|
97
|
+
* console.log('Item moved successfully');
|
|
98
|
+
* }
|
|
99
|
+
*
|
|
100
|
+
* // Move item from position 1 to end of queue
|
|
101
|
+
* await reorderQueue(1, 4, 0); // Assuming queue has 5+ items
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export const reorderQueue = async (
|
|
105
|
+
currentQueuedSlotNumber: number,
|
|
106
|
+
newQueuedSlotNumber: number,
|
|
107
|
+
channelNumber: number = 0
|
|
108
|
+
): Promise<QueueManipulationResult> => {
|
|
109
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
110
|
+
|
|
111
|
+
if (!channel) {
|
|
112
|
+
return {
|
|
113
|
+
error: `Channel ${channelNumber} does not exist`,
|
|
114
|
+
success: false
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (currentQueuedSlotNumber < 0 || currentQueuedSlotNumber >= channel.queue.length) {
|
|
119
|
+
return {
|
|
120
|
+
error:
|
|
121
|
+
`Invalid current slot number ${currentQueuedSlotNumber}. Queue has ` +
|
|
122
|
+
`${channel.queue.length} items (indices 0-${channel.queue.length - 1})`,
|
|
123
|
+
success: false
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (newQueuedSlotNumber < 0 || newQueuedSlotNumber >= channel.queue.length) {
|
|
128
|
+
return {
|
|
129
|
+
error:
|
|
130
|
+
`Invalid new slot number ${newQueuedSlotNumber}. Queue has ` +
|
|
131
|
+
`${channel.queue.length} items (indices 0-${channel.queue.length - 1})`,
|
|
132
|
+
success: false
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (currentQueuedSlotNumber === 0) {
|
|
137
|
+
return {
|
|
138
|
+
error:
|
|
139
|
+
'Cannot reorder currently playing item (index 0). ' + 'Stop current audio first if needed',
|
|
140
|
+
success: false
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (newQueuedSlotNumber === 0) {
|
|
145
|
+
return {
|
|
146
|
+
error:
|
|
147
|
+
'Cannot move item to currently playing position (index 0). ' +
|
|
148
|
+
'Use queueAudioPriority() to add items to front of queue',
|
|
149
|
+
success: false
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (currentQueuedSlotNumber === newQueuedSlotNumber) {
|
|
154
|
+
// No change needed, but return success
|
|
155
|
+
const updatedQueue: QueueSnapshot | null = createQueueSnapshot(channelNumber, audioChannels);
|
|
156
|
+
return {
|
|
157
|
+
success: true,
|
|
158
|
+
updatedQueue: updatedQueue ?? undefined
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Remove the item from its current position
|
|
163
|
+
const audioToMove: HTMLAudioElement = channel.queue.splice(currentQueuedSlotNumber, 1)[0];
|
|
164
|
+
|
|
165
|
+
// Insert it at the new position
|
|
166
|
+
channel.queue.splice(newQueuedSlotNumber, 0, audioToMove);
|
|
167
|
+
|
|
168
|
+
// Emit queue change event
|
|
169
|
+
emitQueueChange(channelNumber, audioChannels);
|
|
170
|
+
|
|
171
|
+
const updatedQueue: QueueSnapshot | null = createQueueSnapshot(channelNumber, audioChannels);
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
success: true,
|
|
175
|
+
updatedQueue: updatedQueue ?? undefined
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Clears all queued audio items after the currently playing item
|
|
181
|
+
* The current audio will continue playing but nothing will follow it
|
|
182
|
+
* @param channelNumber - The channel number (defaults to 0)
|
|
183
|
+
* @returns Promise resolving to operation result with success status and updated queue
|
|
184
|
+
* @example
|
|
185
|
+
* ```typescript
|
|
186
|
+
* // Let current song finish but clear everything after it
|
|
187
|
+
* const result = await clearQueueAfterCurrent(0);
|
|
188
|
+
* if (result.success) {
|
|
189
|
+
* console.log(`Cleared queue, current audio will be the last to play`);
|
|
190
|
+
* }
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
export const clearQueueAfterCurrent = async (
|
|
194
|
+
channelNumber: number = 0
|
|
195
|
+
): Promise<QueueManipulationResult> => {
|
|
196
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
197
|
+
|
|
198
|
+
if (!channel) {
|
|
199
|
+
// For empty/non-existent channels, we can consider this a successful no-op
|
|
200
|
+
// since there's nothing to clear anyway
|
|
201
|
+
return {
|
|
202
|
+
success: true,
|
|
203
|
+
updatedQueue: {
|
|
204
|
+
channelNumber,
|
|
205
|
+
currentIndex: -1,
|
|
206
|
+
isPaused: false,
|
|
207
|
+
items: [],
|
|
208
|
+
totalItems: 0,
|
|
209
|
+
volume: 1.0
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (channel.queue.length <= 1) {
|
|
215
|
+
// Nothing to clear - either empty queue or only current audio
|
|
216
|
+
const updatedQueue: QueueSnapshot | null = createQueueSnapshot(channelNumber, audioChannels);
|
|
217
|
+
return {
|
|
218
|
+
success: true,
|
|
219
|
+
updatedQueue: updatedQueue ?? undefined
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Clean up progress tracking for all items except the current one
|
|
224
|
+
for (let i: number = 1; i < channel.queue.length; i++) {
|
|
225
|
+
cleanupProgressTracking(channel.queue[i], channelNumber, audioChannels);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Keep only the currently playing audio (index 0)
|
|
229
|
+
channel.queue = channel.queue.slice(0, 1);
|
|
230
|
+
|
|
231
|
+
// Emit queue change event
|
|
232
|
+
emitQueueChange(channelNumber, audioChannels);
|
|
233
|
+
|
|
234
|
+
const updatedQueue: QueueSnapshot | null = createQueueSnapshot(channelNumber, audioChannels);
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
success: true,
|
|
238
|
+
updatedQueue: updatedQueue ?? undefined
|
|
239
|
+
};
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Gets information about a specific queue item by its slot number
|
|
244
|
+
* @param queueSlotNumber - Zero-based index of the queue item
|
|
245
|
+
* @param channelNumber - The channel number (defaults to 0)
|
|
246
|
+
* @returns QueueItem information or null if slot doesn't exist
|
|
247
|
+
* @example
|
|
248
|
+
* ```typescript
|
|
249
|
+
* const itemInfo = getQueueItemInfo(1, 0);
|
|
250
|
+
* if (itemInfo) {
|
|
251
|
+
* console.log(`Next to play: ${itemInfo.fileName}`);
|
|
252
|
+
* console.log(`Duration: ${itemInfo.duration}ms`);
|
|
253
|
+
* }
|
|
254
|
+
* ```
|
|
255
|
+
*/
|
|
256
|
+
export const getQueueItemInfo = (
|
|
257
|
+
queueSlotNumber: number,
|
|
258
|
+
channelNumber: number = 0
|
|
259
|
+
): QueueItem | null => {
|
|
260
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
261
|
+
|
|
262
|
+
if (!channel || queueSlotNumber < 0 || queueSlotNumber >= channel.queue.length) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const audio: HTMLAudioElement = channel.queue[queueSlotNumber];
|
|
267
|
+
const audioInfo = getAudioInfoFromElement(audio, channelNumber, audioChannels);
|
|
268
|
+
|
|
269
|
+
if (!audioInfo) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const { duration, fileName, isLooping, isPlaying, src, volume } = audioInfo;
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
duration,
|
|
277
|
+
fileName,
|
|
278
|
+
isCurrentlyPlaying: queueSlotNumber === 0 && isPlaying,
|
|
279
|
+
isLooping,
|
|
280
|
+
src,
|
|
281
|
+
volume
|
|
282
|
+
};
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Gets the current queue length for a specific channel
|
|
287
|
+
* @param channelNumber - The channel number (defaults to 0)
|
|
288
|
+
* @returns Number of items in the queue, or 0 if channel doesn't exist
|
|
289
|
+
* @example
|
|
290
|
+
* ```typescript
|
|
291
|
+
* const queueSize = getQueueLength(0);
|
|
292
|
+
* console.log(`Channel 0 has ${queueSize} items in queue`);
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
export const getQueueLength = (channelNumber: number = 0): number => {
|
|
296
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
297
|
+
return channel ? channel.queue.length : 0;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Swaps the positions of two queue items
|
|
302
|
+
* Cannot swap with the currently playing item (index 0)
|
|
303
|
+
* @param slotA - Zero-based index of first item to swap (must be > 0)
|
|
304
|
+
* @param slotB - Zero-based index of second item to swap (must be > 0)
|
|
305
|
+
* @param channelNumber - The channel number (defaults to 0)
|
|
306
|
+
* @returns Promise resolving to operation result with success status and updated queue
|
|
307
|
+
* @example
|
|
308
|
+
* ```typescript
|
|
309
|
+
* // Swap the second and third items in queue
|
|
310
|
+
* const result = await swapQueueItems(1, 2, 0);
|
|
311
|
+
* if (result.success) {
|
|
312
|
+
* console.log('Items swapped successfully');
|
|
313
|
+
* }
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
export const swapQueueItems = async (
|
|
317
|
+
slotA: number,
|
|
318
|
+
slotB: number,
|
|
319
|
+
channelNumber: number = 0
|
|
320
|
+
): Promise<QueueManipulationResult> => {
|
|
321
|
+
const channel: ExtendedAudioQueueChannel = audioChannels[channelNumber];
|
|
322
|
+
|
|
323
|
+
if (!channel) {
|
|
324
|
+
return {
|
|
325
|
+
error: `Channel ${channelNumber} does not exist`,
|
|
326
|
+
success: false
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (slotA < 0 || slotA >= channel.queue.length) {
|
|
331
|
+
return {
|
|
332
|
+
error:
|
|
333
|
+
`Invalid slot A ${slotA}. Queue has ${channel.queue.length} items ` +
|
|
334
|
+
`(indices 0-${channel.queue.length - 1})`,
|
|
335
|
+
success: false
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (slotB < 0 || slotB >= channel.queue.length) {
|
|
340
|
+
return {
|
|
341
|
+
error:
|
|
342
|
+
`Invalid slot B ${slotB}. Queue has ${channel.queue.length} items ` +
|
|
343
|
+
`(indices 0-${channel.queue.length - 1})`,
|
|
344
|
+
success: false
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (slotA === 0 || slotB === 0) {
|
|
349
|
+
return {
|
|
350
|
+
error: 'Cannot swap with currently playing item (index 0)',
|
|
351
|
+
success: false
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (slotA === slotB) {
|
|
356
|
+
// No change needed, but return success
|
|
357
|
+
const updatedQueue: QueueSnapshot | null = createQueueSnapshot(channelNumber, audioChannels);
|
|
358
|
+
return {
|
|
359
|
+
success: true,
|
|
360
|
+
updatedQueue: updatedQueue ?? undefined
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Swap the audio elements
|
|
365
|
+
const temp: HTMLAudioElement = channel.queue[slotA];
|
|
366
|
+
channel.queue[slotA] = channel.queue[slotB];
|
|
367
|
+
channel.queue[slotB] = temp;
|
|
368
|
+
|
|
369
|
+
// Emit queue change event
|
|
370
|
+
emitQueueChange(channelNumber, audioChannels);
|
|
371
|
+
|
|
372
|
+
const updatedQueue: QueueSnapshot | null = createQueueSnapshot(channelNumber, audioChannels);
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
success: true,
|
|
376
|
+
updatedQueue: updatedQueue ?? undefined
|
|
377
|
+
};
|
|
378
|
+
};
|