audio-channel-queue 1.5.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/README.md +323 -96
- package/dist/core.d.ts +27 -9
- package/dist/core.js +132 -29
- package/dist/errors.d.ts +137 -0
- package/dist/errors.js +461 -0
- package/dist/events.d.ts +23 -1
- package/dist/events.js +50 -2
- package/dist/index.d.ts +9 -19
- package/dist/index.js +45 -25
- package/dist/info.d.ts +47 -2
- package/dist/info.js +126 -7
- package/dist/pause.d.ts +87 -0
- package/dist/pause.js +186 -0
- package/dist/types.d.ts +113 -12
- package/dist/utils.d.ts +7 -1
- package/dist/utils.js +24 -4
- package/dist/volume.d.ts +98 -0
- package/dist/volume.js +305 -0
- package/package.json +4 -1
- package/src/core.ts +144 -27
- package/src/errors.ts +480 -0
- package/src/events.ts +242 -188
- package/src/index.ts +91 -66
- package/src/info.ts +386 -261
- package/src/pause.ts +190 -0
- package/src/types.ts +235 -126
- package/src/utils.ts +133 -108
- package/src/volume.ts +331 -0
package/dist/types.d.ts
CHANGED
|
@@ -11,6 +11,36 @@ export type AudioQueue = HTMLAudioElement[];
|
|
|
11
11
|
export type AudioQueueChannel = {
|
|
12
12
|
queue: AudioQueue;
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* Volume ducking configuration for channels
|
|
16
|
+
*/
|
|
17
|
+
export interface VolumeConfig {
|
|
18
|
+
/** The channel number that should have priority */
|
|
19
|
+
priorityChannel: number;
|
|
20
|
+
/** Volume level for the priority channel (0-1) */
|
|
21
|
+
priorityVolume: number;
|
|
22
|
+
/** Volume level for all other channels when priority channel is active (0-1) */
|
|
23
|
+
duckingVolume: number;
|
|
24
|
+
/** Duration in milliseconds for volume duck transition (defaults to 250ms) */
|
|
25
|
+
duckTransitionDuration?: number;
|
|
26
|
+
/** Duration in milliseconds for volume restore transition (defaults to 500ms) */
|
|
27
|
+
restoreTransitionDuration?: number;
|
|
28
|
+
/** Easing function for volume transitions (defaults to 'ease-out') */
|
|
29
|
+
transitionEasing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Audio file configuration for queueing
|
|
33
|
+
*/
|
|
34
|
+
export interface AudioQueueOptions {
|
|
35
|
+
/** Whether to add the audio to the front of the queue (defaults to false) */
|
|
36
|
+
addToFront?: boolean;
|
|
37
|
+
/** Whether the audio should loop when it finishes */
|
|
38
|
+
loop?: boolean;
|
|
39
|
+
/** Whether to add the audio with priority (same as addToFront) */
|
|
40
|
+
priority?: boolean;
|
|
41
|
+
/** Volume level for this specific audio file (0-1, defaults to channel volume) */
|
|
42
|
+
volume?: number;
|
|
43
|
+
}
|
|
14
44
|
/**
|
|
15
45
|
* Comprehensive audio information interface providing metadata about currently playing audio
|
|
16
46
|
*/
|
|
@@ -21,12 +51,20 @@ export interface AudioInfo {
|
|
|
21
51
|
duration: number;
|
|
22
52
|
/** Extracted filename from the source URL */
|
|
23
53
|
fileName: string;
|
|
54
|
+
/** Whether the audio is set to loop */
|
|
55
|
+
isLooping: boolean;
|
|
56
|
+
/** Whether the audio is currently paused */
|
|
57
|
+
isPaused: boolean;
|
|
24
58
|
/** Whether the audio is currently playing */
|
|
25
59
|
isPlaying: boolean;
|
|
26
60
|
/** Playback progress as a decimal (0-1) */
|
|
27
61
|
progress: number;
|
|
62
|
+
/** Number of audio files remaining in the queue after current */
|
|
63
|
+
remainingInQueue: number;
|
|
28
64
|
/** Audio file source URL */
|
|
29
65
|
src: string;
|
|
66
|
+
/** Current volume level (0-1) */
|
|
67
|
+
volume: number;
|
|
30
68
|
}
|
|
31
69
|
/**
|
|
32
70
|
* Information provided when an audio file completes playback
|
|
@@ -64,8 +102,12 @@ export interface QueueItem {
|
|
|
64
102
|
fileName: string;
|
|
65
103
|
/** Whether this item is currently playing */
|
|
66
104
|
isCurrentlyPlaying: boolean;
|
|
105
|
+
/** Whether this item is set to loop */
|
|
106
|
+
isLooping: boolean;
|
|
67
107
|
/** Audio file source URL */
|
|
68
108
|
src: string;
|
|
109
|
+
/** Volume level for this item (0-1) */
|
|
110
|
+
volume: number;
|
|
69
111
|
}
|
|
70
112
|
/**
|
|
71
113
|
* Complete snapshot of a queue's current state
|
|
@@ -75,10 +117,14 @@ export interface QueueSnapshot {
|
|
|
75
117
|
channelNumber: number;
|
|
76
118
|
/** Zero-based index of the currently playing item */
|
|
77
119
|
currentIndex: number;
|
|
120
|
+
/** Whether the current audio is paused */
|
|
121
|
+
isPaused: boolean;
|
|
78
122
|
/** Array of audio items in the queue with their metadata */
|
|
79
123
|
items: QueueItem[];
|
|
80
124
|
/** Total number of items in the queue */
|
|
81
125
|
totalItems: number;
|
|
126
|
+
/** Current volume level for the channel (0-1) */
|
|
127
|
+
volume: number;
|
|
82
128
|
}
|
|
83
129
|
/**
|
|
84
130
|
* Callback function type for audio progress updates
|
|
@@ -101,15 +147,70 @@ export type AudioStartCallback = (audioInfo: AudioStartInfo) => void;
|
|
|
101
147
|
*/
|
|
102
148
|
export type AudioCompleteCallback = (audioInfo: AudioCompleteInfo) => void;
|
|
103
149
|
/**
|
|
104
|
-
*
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
150
|
+
* Callback function type for audio pause notifications
|
|
151
|
+
* @param channelNumber Channel that was paused
|
|
152
|
+
* @param audioInfo Information about the audio that was paused
|
|
153
|
+
*/
|
|
154
|
+
export type AudioPauseCallback = (channelNumber: number, audioInfo: AudioInfo) => void;
|
|
155
|
+
/**
|
|
156
|
+
* Callback function type for audio resume notifications
|
|
157
|
+
* @param channelNumber Channel that was resumed
|
|
158
|
+
* @param audioInfo Information about the audio that was resumed
|
|
159
|
+
*/
|
|
160
|
+
export type AudioResumeCallback = (channelNumber: number, audioInfo: AudioInfo) => void;
|
|
161
|
+
/**
|
|
162
|
+
* Information about an audio error that occurred
|
|
163
|
+
*/
|
|
164
|
+
export interface AudioErrorInfo {
|
|
165
|
+
channelNumber: number;
|
|
166
|
+
src: string;
|
|
167
|
+
fileName: string;
|
|
168
|
+
error: Error;
|
|
169
|
+
errorType: 'network' | 'decode' | 'unsupported' | 'permission' | 'abort' | 'timeout' | 'unknown';
|
|
170
|
+
timestamp: number;
|
|
171
|
+
retryAttempt?: number;
|
|
172
|
+
remainingInQueue: number;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Configuration for automatic retry behavior when audio fails to load or play
|
|
176
|
+
*/
|
|
177
|
+
export interface RetryConfig {
|
|
178
|
+
enabled: boolean;
|
|
179
|
+
maxRetries: number;
|
|
180
|
+
baseDelay: number;
|
|
181
|
+
exponentialBackoff: boolean;
|
|
182
|
+
timeoutMs: number;
|
|
183
|
+
fallbackUrls?: string[];
|
|
184
|
+
skipOnFailure: boolean;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Configuration options for error recovery mechanisms
|
|
188
|
+
*/
|
|
189
|
+
export interface ErrorRecoveryOptions {
|
|
190
|
+
autoRetry: boolean;
|
|
191
|
+
showUserFeedback: boolean;
|
|
192
|
+
logErrorsToAnalytics: boolean;
|
|
193
|
+
preserveQueueOnError: boolean;
|
|
194
|
+
fallbackToNextTrack: boolean;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Callback function type for audio error events
|
|
198
|
+
*/
|
|
199
|
+
export type AudioErrorCallback = (errorInfo: AudioErrorInfo) => void;
|
|
200
|
+
/**
|
|
201
|
+
* Extended audio queue channel with error handling capabilities
|
|
202
|
+
*/
|
|
203
|
+
export interface ExtendedAudioQueueChannel {
|
|
204
|
+
audioCompleteCallbacks: Set<AudioCompleteCallback>;
|
|
205
|
+
audioErrorCallbacks: Set<AudioErrorCallback>;
|
|
206
|
+
audioPauseCallbacks: Set<AudioPauseCallback>;
|
|
207
|
+
audioResumeCallbacks: Set<AudioResumeCallback>;
|
|
208
|
+
audioStartCallbacks: Set<AudioStartCallback>;
|
|
209
|
+
isPaused?: boolean;
|
|
210
|
+
progressCallbacks: Map<HTMLAudioElement | null, Set<ProgressCallback>>;
|
|
211
|
+
queue: HTMLAudioElement[];
|
|
212
|
+
queueChangeCallbacks: Set<QueueChangeCallback>;
|
|
213
|
+
retryConfig?: RetryConfig;
|
|
214
|
+
volume?: number;
|
|
215
|
+
volumeConfig?: VolumeConfig;
|
|
216
|
+
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -16,15 +16,21 @@ export declare const extractFileName: (url: string) => string;
|
|
|
16
16
|
/**
|
|
17
17
|
* Extracts comprehensive audio information from an HTMLAudioElement
|
|
18
18
|
* @param audio - The HTML audio element to extract information from
|
|
19
|
+
* @param channelNumber - Optional channel number to include remaining queue info
|
|
20
|
+
* @param audioChannels - Optional audio channels array to calculate remainingInQueue
|
|
19
21
|
* @returns AudioInfo object with current playback state or null if audio is invalid
|
|
20
22
|
* @example
|
|
21
23
|
* ```typescript
|
|
22
24
|
* const audioElement = new Audio('song.mp3');
|
|
23
25
|
* const info = getAudioInfoFromElement(audioElement);
|
|
24
26
|
* console.log(info?.progress); // Current progress as decimal (0-1)
|
|
27
|
+
*
|
|
28
|
+
* // With channel context for remainingInQueue
|
|
29
|
+
* const infoWithQueue = getAudioInfoFromElement(audioElement, 0, audioChannels);
|
|
30
|
+
* console.log(infoWithQueue?.remainingInQueue); // Number of items left in queue
|
|
25
31
|
* ```
|
|
26
32
|
*/
|
|
27
|
-
export declare const getAudioInfoFromElement: (audio: HTMLAudioElement) => AudioInfo | null;
|
|
33
|
+
export declare const getAudioInfoFromElement: (audio: HTMLAudioElement, channelNumber?: number, audioChannels?: ExtendedAudioQueueChannel[]) => AudioInfo | null;
|
|
28
34
|
/**
|
|
29
35
|
* Creates a complete snapshot of a queue's current state
|
|
30
36
|
* @param channelNumber - The channel number to create a snapshot for
|
package/dist/utils.js
CHANGED
|
@@ -33,28 +33,44 @@ exports.extractFileName = extractFileName;
|
|
|
33
33
|
/**
|
|
34
34
|
* Extracts comprehensive audio information from an HTMLAudioElement
|
|
35
35
|
* @param audio - The HTML audio element to extract information from
|
|
36
|
+
* @param channelNumber - Optional channel number to include remaining queue info
|
|
37
|
+
* @param audioChannels - Optional audio channels array to calculate remainingInQueue
|
|
36
38
|
* @returns AudioInfo object with current playback state or null if audio is invalid
|
|
37
39
|
* @example
|
|
38
40
|
* ```typescript
|
|
39
41
|
* const audioElement = new Audio('song.mp3');
|
|
40
42
|
* const info = getAudioInfoFromElement(audioElement);
|
|
41
43
|
* console.log(info?.progress); // Current progress as decimal (0-1)
|
|
44
|
+
*
|
|
45
|
+
* // With channel context for remainingInQueue
|
|
46
|
+
* const infoWithQueue = getAudioInfoFromElement(audioElement, 0, audioChannels);
|
|
47
|
+
* console.log(infoWithQueue?.remainingInQueue); // Number of items left in queue
|
|
42
48
|
* ```
|
|
43
49
|
*/
|
|
44
|
-
const getAudioInfoFromElement = (audio) => {
|
|
50
|
+
const getAudioInfoFromElement = (audio, channelNumber, audioChannels) => {
|
|
45
51
|
if (!audio)
|
|
46
52
|
return null;
|
|
47
53
|
const duration = isNaN(audio.duration) ? 0 : audio.duration * 1000; // Convert to milliseconds
|
|
48
54
|
const currentTime = isNaN(audio.currentTime) ? 0 : audio.currentTime * 1000; // Convert to milliseconds
|
|
49
55
|
const progress = duration > 0 ? Math.min(currentTime / duration, 1) : 0;
|
|
50
56
|
const isPlaying = !audio.paused && !audio.ended && audio.readyState > 2;
|
|
57
|
+
// Calculate remainingInQueue if channel context is provided
|
|
58
|
+
let remainingInQueue = 0;
|
|
59
|
+
if (channelNumber !== undefined && audioChannels && audioChannels[channelNumber]) {
|
|
60
|
+
const channel = audioChannels[channelNumber];
|
|
61
|
+
remainingInQueue = Math.max(0, channel.queue.length - 1); // Exclude current playing audio
|
|
62
|
+
}
|
|
51
63
|
return {
|
|
52
64
|
currentTime,
|
|
53
65
|
duration,
|
|
54
66
|
fileName: (0, exports.extractFileName)(audio.src),
|
|
67
|
+
isLooping: audio.loop,
|
|
68
|
+
isPaused: audio.paused && !audio.ended,
|
|
55
69
|
isPlaying,
|
|
56
70
|
progress,
|
|
57
|
-
|
|
71
|
+
remainingInQueue,
|
|
72
|
+
src: audio.src,
|
|
73
|
+
volume: audio.volume
|
|
58
74
|
};
|
|
59
75
|
};
|
|
60
76
|
exports.getAudioInfoFromElement = getAudioInfoFromElement;
|
|
@@ -77,13 +93,17 @@ const createQueueSnapshot = (channelNumber, audioChannels) => {
|
|
|
77
93
|
duration: isNaN(audio.duration) ? 0 : audio.duration * 1000,
|
|
78
94
|
fileName: (0, exports.extractFileName)(audio.src),
|
|
79
95
|
isCurrentlyPlaying: index === 0 && !audio.paused && !audio.ended,
|
|
80
|
-
|
|
96
|
+
isLooping: audio.loop,
|
|
97
|
+
src: audio.src,
|
|
98
|
+
volume: audio.volume
|
|
81
99
|
}));
|
|
82
100
|
return {
|
|
83
101
|
channelNumber,
|
|
84
102
|
currentIndex: 0, // Current playing is always index 0 in our queue structure
|
|
103
|
+
isPaused: channel.isPaused || false,
|
|
85
104
|
items,
|
|
86
|
-
totalItems: channel.queue.length
|
|
105
|
+
totalItems: channel.queue.length,
|
|
106
|
+
volume: channel.volume || 1.0
|
|
87
107
|
};
|
|
88
108
|
};
|
|
89
109
|
exports.createQueueSnapshot = createQueueSnapshot;
|
package/dist/volume.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Volume management functions for the audio-channel-queue package
|
|
3
|
+
*/
|
|
4
|
+
import { VolumeConfig } from './types';
|
|
5
|
+
/**
|
|
6
|
+
* Smoothly transitions volume for a specific channel over time
|
|
7
|
+
* @param channelNumber - The channel number to transition
|
|
8
|
+
* @param targetVolume - Target volume level (0-1)
|
|
9
|
+
* @param duration - Transition duration in milliseconds
|
|
10
|
+
* @param easing - Easing function type
|
|
11
|
+
* @returns Promise that resolves when transition completes
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* await transitionVolume(0, 0.2, 500, 'ease-out'); // Duck to 20% over 500ms
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare const transitionVolume: (channelNumber: number, targetVolume: number, duration?: number, easing?: "linear" | "ease-in" | "ease-out" | "ease-in-out") => Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Sets the volume for a specific channel with optional smooth transition
|
|
20
|
+
* @param channelNumber - The channel number to set volume for
|
|
21
|
+
* @param volume - Volume level (0-1)
|
|
22
|
+
* @param transitionDuration - Optional transition duration in milliseconds
|
|
23
|
+
* @param easing - Optional easing function
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* setChannelVolume(0, 0.5); // Set channel 0 to 50%
|
|
27
|
+
* setChannelVolume(0, 0.5, 300, 'ease-out'); // Smooth transition over 300ms
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare const setChannelVolume: (channelNumber: number, volume: number, transitionDuration?: number, easing?: "linear" | "ease-in" | "ease-out" | "ease-in-out") => Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Gets the current volume for a specific channel
|
|
33
|
+
* @param channelNumber - The channel number to get volume for (defaults to 0)
|
|
34
|
+
* @returns Current volume level (0-1) or 1.0 if channel doesn't exist
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* const volume = getChannelVolume(0);
|
|
38
|
+
* const defaultChannelVolume = getChannelVolume(); // Gets channel 0
|
|
39
|
+
* console.log(`Channel 0 volume: ${volume * 100}%`);
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare const getChannelVolume: (channelNumber?: number) => number;
|
|
43
|
+
/**
|
|
44
|
+
* Gets the volume levels for all channels
|
|
45
|
+
* @returns Array of volume levels (0-1) for each channel
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* const volumes = getAllChannelsVolume();
|
|
49
|
+
* volumes.forEach((volume, index) => {
|
|
50
|
+
* console.log(`Channel ${index}: ${volume * 100}%`);
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare const getAllChannelsVolume: () => number[];
|
|
55
|
+
/**
|
|
56
|
+
* Sets volume for all channels to the same level
|
|
57
|
+
* @param volume - Volume level (0-1) to apply to all channels
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* await setAllChannelsVolume(0.6); // Set all channels to 60% volume
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare const setAllChannelsVolume: (volume: number) => Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Configures volume ducking for channels. When the priority channel plays audio,
|
|
66
|
+
* all other channels will be automatically reduced to the ducking volume level
|
|
67
|
+
* @param config - Volume ducking configuration
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* // When channel 1 plays, reduce all other channels to 20% volume
|
|
71
|
+
* setVolumeDucking({
|
|
72
|
+
* priorityChannel: 1,
|
|
73
|
+
* priorityVolume: 1.0,
|
|
74
|
+
* duckingVolume: 0.2
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export declare const setVolumeDucking: (config: VolumeConfig) => void;
|
|
79
|
+
/**
|
|
80
|
+
* Removes volume ducking configuration from all channels
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* clearVolumeDucking(); // Remove all volume ducking effects
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export declare const clearVolumeDucking: () => void;
|
|
87
|
+
/**
|
|
88
|
+
* Applies volume ducking effects based on current playback state with smooth transitions
|
|
89
|
+
* @param activeChannelNumber - The channel that just started playing
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
export declare const applyVolumeDucking: (activeChannelNumber: number) => Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Restores normal volume levels when priority channel stops with smooth transitions
|
|
95
|
+
* @param stoppedChannelNumber - The channel that just stopped playing
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
export declare const restoreVolumeLevels: (stoppedChannelNumber: number) => Promise<void>;
|
package/dist/volume.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Volume management functions for the audio-channel-queue package
|
|
4
|
+
*/
|
|
5
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
6
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
7
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
8
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
9
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
10
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
11
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.restoreVolumeLevels = exports.applyVolumeDucking = exports.clearVolumeDucking = exports.setVolumeDucking = exports.setAllChannelsVolume = exports.getAllChannelsVolume = exports.getChannelVolume = exports.setChannelVolume = exports.transitionVolume = void 0;
|
|
16
|
+
const info_1 = require("./info");
|
|
17
|
+
// Store active volume transitions to handle interruptions
|
|
18
|
+
const activeTransitions = new Map();
|
|
19
|
+
/**
|
|
20
|
+
* Easing functions for smooth volume transitions
|
|
21
|
+
*/
|
|
22
|
+
const easingFunctions = {
|
|
23
|
+
linear: (t) => t,
|
|
24
|
+
'ease-in': (t) => t * t,
|
|
25
|
+
'ease-out': (t) => t * (2 - t),
|
|
26
|
+
'ease-in-out': (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Smoothly transitions volume for a specific channel over time
|
|
30
|
+
* @param channelNumber - The channel number to transition
|
|
31
|
+
* @param targetVolume - Target volume level (0-1)
|
|
32
|
+
* @param duration - Transition duration in milliseconds
|
|
33
|
+
* @param easing - Easing function type
|
|
34
|
+
* @returns Promise that resolves when transition completes
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* await transitionVolume(0, 0.2, 500, 'ease-out'); // Duck to 20% over 500ms
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
const transitionVolume = (channelNumber_1, targetVolume_1, ...args_1) => __awaiter(void 0, [channelNumber_1, targetVolume_1, ...args_1], void 0, function* (channelNumber, targetVolume, duration = 250, easing = 'ease-out') {
|
|
41
|
+
const channel = info_1.audioChannels[channelNumber];
|
|
42
|
+
if (!channel || channel.queue.length === 0)
|
|
43
|
+
return;
|
|
44
|
+
const currentAudio = channel.queue[0];
|
|
45
|
+
const startVolume = currentAudio.volume;
|
|
46
|
+
const volumeDelta = targetVolume - startVolume;
|
|
47
|
+
// Cancel any existing transition for this channel
|
|
48
|
+
if (activeTransitions.has(channelNumber)) {
|
|
49
|
+
clearTimeout(activeTransitions.get(channelNumber));
|
|
50
|
+
activeTransitions.delete(channelNumber);
|
|
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
|
+
// Handle zero duration - instant change
|
|
58
|
+
if (duration === 0) {
|
|
59
|
+
channel.volume = targetVolume;
|
|
60
|
+
if (channel.queue.length > 0) {
|
|
61
|
+
channel.queue[0].volume = targetVolume;
|
|
62
|
+
}
|
|
63
|
+
return Promise.resolve();
|
|
64
|
+
}
|
|
65
|
+
const startTime = performance.now();
|
|
66
|
+
const easingFn = easingFunctions[easing];
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
const updateVolume = () => {
|
|
69
|
+
const elapsed = performance.now() - startTime;
|
|
70
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
71
|
+
const easedProgress = easingFn(progress);
|
|
72
|
+
const currentVolume = startVolume + (volumeDelta * easedProgress);
|
|
73
|
+
const clampedVolume = Math.max(0, Math.min(1, currentVolume));
|
|
74
|
+
// Apply volume to both channel config and current audio
|
|
75
|
+
channel.volume = clampedVolume;
|
|
76
|
+
if (channel.queue.length > 0) {
|
|
77
|
+
channel.queue[0].volume = clampedVolume;
|
|
78
|
+
}
|
|
79
|
+
if (progress >= 1) {
|
|
80
|
+
// Transition complete
|
|
81
|
+
activeTransitions.delete(channelNumber);
|
|
82
|
+
resolve();
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Use requestAnimationFrame in browser, setTimeout in tests
|
|
86
|
+
if (typeof requestAnimationFrame !== 'undefined') {
|
|
87
|
+
const rafId = requestAnimationFrame(updateVolume);
|
|
88
|
+
activeTransitions.set(channelNumber, rafId);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// In test environment, use shorter intervals
|
|
92
|
+
const timeoutId = setTimeout(updateVolume, 1);
|
|
93
|
+
activeTransitions.set(channelNumber, timeoutId);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
updateVolume();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
exports.transitionVolume = transitionVolume;
|
|
101
|
+
/**
|
|
102
|
+
* Sets the volume for a specific channel with optional smooth transition
|
|
103
|
+
* @param channelNumber - The channel number to set volume for
|
|
104
|
+
* @param volume - Volume level (0-1)
|
|
105
|
+
* @param transitionDuration - Optional transition duration in milliseconds
|
|
106
|
+
* @param easing - Optional easing function
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* setChannelVolume(0, 0.5); // Set channel 0 to 50%
|
|
110
|
+
* setChannelVolume(0, 0.5, 300, 'ease-out'); // Smooth transition over 300ms
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
const setChannelVolume = (channelNumber, volume, transitionDuration, easing) => __awaiter(void 0, void 0, void 0, function* () {
|
|
114
|
+
const clampedVolume = Math.max(0, Math.min(1, volume));
|
|
115
|
+
if (!info_1.audioChannels[channelNumber]) {
|
|
116
|
+
info_1.audioChannels[channelNumber] = {
|
|
117
|
+
audioCompleteCallbacks: new Set(),
|
|
118
|
+
audioErrorCallbacks: new Set(),
|
|
119
|
+
audioPauseCallbacks: new Set(),
|
|
120
|
+
audioResumeCallbacks: new Set(),
|
|
121
|
+
audioStartCallbacks: new Set(),
|
|
122
|
+
isPaused: false,
|
|
123
|
+
progressCallbacks: new Map(),
|
|
124
|
+
queue: [],
|
|
125
|
+
queueChangeCallbacks: new Set(),
|
|
126
|
+
volume: clampedVolume
|
|
127
|
+
};
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (transitionDuration && transitionDuration > 0) {
|
|
131
|
+
// Smooth transition
|
|
132
|
+
yield (0, exports.transitionVolume)(channelNumber, clampedVolume, transitionDuration, easing);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// Instant change (backward compatibility)
|
|
136
|
+
info_1.audioChannels[channelNumber].volume = clampedVolume;
|
|
137
|
+
const channel = info_1.audioChannels[channelNumber];
|
|
138
|
+
if (channel.queue.length > 0) {
|
|
139
|
+
const currentAudio = channel.queue[0];
|
|
140
|
+
currentAudio.volume = clampedVolume;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
exports.setChannelVolume = setChannelVolume;
|
|
145
|
+
/**
|
|
146
|
+
* Gets the current volume for a specific channel
|
|
147
|
+
* @param channelNumber - The channel number to get volume for (defaults to 0)
|
|
148
|
+
* @returns Current volume level (0-1) or 1.0 if channel doesn't exist
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* const volume = getChannelVolume(0);
|
|
152
|
+
* const defaultChannelVolume = getChannelVolume(); // Gets channel 0
|
|
153
|
+
* console.log(`Channel 0 volume: ${volume * 100}%`);
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
const getChannelVolume = (channelNumber = 0) => {
|
|
157
|
+
const channel = info_1.audioChannels[channelNumber];
|
|
158
|
+
return (channel === null || channel === void 0 ? void 0 : channel.volume) || 1.0;
|
|
159
|
+
};
|
|
160
|
+
exports.getChannelVolume = getChannelVolume;
|
|
161
|
+
/**
|
|
162
|
+
* Gets the volume levels for all channels
|
|
163
|
+
* @returns Array of volume levels (0-1) for each channel
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* const volumes = getAllChannelsVolume();
|
|
167
|
+
* volumes.forEach((volume, index) => {
|
|
168
|
+
* console.log(`Channel ${index}: ${volume * 100}%`);
|
|
169
|
+
* });
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
const getAllChannelsVolume = () => {
|
|
173
|
+
return info_1.audioChannels.map((channel) => (channel === null || channel === void 0 ? void 0 : channel.volume) || 1.0);
|
|
174
|
+
};
|
|
175
|
+
exports.getAllChannelsVolume = getAllChannelsVolume;
|
|
176
|
+
/**
|
|
177
|
+
* Sets volume for all channels to the same level
|
|
178
|
+
* @param volume - Volume level (0-1) to apply to all channels
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript
|
|
181
|
+
* await setAllChannelsVolume(0.6); // Set all channels to 60% volume
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
const setAllChannelsVolume = (volume) => __awaiter(void 0, void 0, void 0, function* () {
|
|
185
|
+
const promises = [];
|
|
186
|
+
info_1.audioChannels.forEach((_channel, index) => {
|
|
187
|
+
promises.push((0, exports.setChannelVolume)(index, volume));
|
|
188
|
+
});
|
|
189
|
+
yield Promise.all(promises);
|
|
190
|
+
});
|
|
191
|
+
exports.setAllChannelsVolume = setAllChannelsVolume;
|
|
192
|
+
/**
|
|
193
|
+
* Configures volume ducking for channels. When the priority channel plays audio,
|
|
194
|
+
* all other channels will be automatically reduced to the ducking volume level
|
|
195
|
+
* @param config - Volume ducking configuration
|
|
196
|
+
* @example
|
|
197
|
+
* ```typescript
|
|
198
|
+
* // When channel 1 plays, reduce all other channels to 20% volume
|
|
199
|
+
* setVolumeDucking({
|
|
200
|
+
* priorityChannel: 1,
|
|
201
|
+
* priorityVolume: 1.0,
|
|
202
|
+
* duckingVolume: 0.2
|
|
203
|
+
* });
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
const setVolumeDucking = (config) => {
|
|
207
|
+
// First, ensure we have enough channels for the priority channel
|
|
208
|
+
while (info_1.audioChannels.length <= config.priorityChannel) {
|
|
209
|
+
info_1.audioChannels.push({
|
|
210
|
+
audioCompleteCallbacks: new Set(),
|
|
211
|
+
audioErrorCallbacks: new Set(),
|
|
212
|
+
audioPauseCallbacks: new Set(),
|
|
213
|
+
audioResumeCallbacks: new Set(),
|
|
214
|
+
audioStartCallbacks: new Set(),
|
|
215
|
+
isPaused: false,
|
|
216
|
+
progressCallbacks: new Map(),
|
|
217
|
+
queue: [],
|
|
218
|
+
queueChangeCallbacks: new Set(),
|
|
219
|
+
volume: 1.0
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
// Apply the config to all existing channels
|
|
223
|
+
info_1.audioChannels.forEach((channel, index) => {
|
|
224
|
+
if (!info_1.audioChannels[index]) {
|
|
225
|
+
info_1.audioChannels[index] = {
|
|
226
|
+
audioCompleteCallbacks: new Set(),
|
|
227
|
+
audioErrorCallbacks: new Set(),
|
|
228
|
+
audioPauseCallbacks: new Set(),
|
|
229
|
+
audioResumeCallbacks: new Set(),
|
|
230
|
+
audioStartCallbacks: new Set(),
|
|
231
|
+
isPaused: false,
|
|
232
|
+
progressCallbacks: new Map(),
|
|
233
|
+
queue: [],
|
|
234
|
+
queueChangeCallbacks: new Set(),
|
|
235
|
+
volume: 1.0
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
info_1.audioChannels[index].volumeConfig = config;
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
exports.setVolumeDucking = setVolumeDucking;
|
|
242
|
+
/**
|
|
243
|
+
* Removes volume ducking configuration from all channels
|
|
244
|
+
* @example
|
|
245
|
+
* ```typescript
|
|
246
|
+
* clearVolumeDucking(); // Remove all volume ducking effects
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
const clearVolumeDucking = () => {
|
|
250
|
+
info_1.audioChannels.forEach((channel) => {
|
|
251
|
+
if (channel) {
|
|
252
|
+
delete channel.volumeConfig;
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
};
|
|
256
|
+
exports.clearVolumeDucking = clearVolumeDucking;
|
|
257
|
+
/**
|
|
258
|
+
* Applies volume ducking effects based on current playback state with smooth transitions
|
|
259
|
+
* @param activeChannelNumber - The channel that just started playing
|
|
260
|
+
* @internal
|
|
261
|
+
*/
|
|
262
|
+
const applyVolumeDucking = (activeChannelNumber) => __awaiter(void 0, void 0, void 0, function* () {
|
|
263
|
+
const transitionPromises = [];
|
|
264
|
+
info_1.audioChannels.forEach((channel, channelNumber) => {
|
|
265
|
+
if (channel === null || channel === void 0 ? void 0 : channel.volumeConfig) {
|
|
266
|
+
const config = channel.volumeConfig;
|
|
267
|
+
if (activeChannelNumber === config.priorityChannel) {
|
|
268
|
+
const duration = config.duckTransitionDuration || 250;
|
|
269
|
+
const easing = config.transitionEasing || 'ease-out';
|
|
270
|
+
// Priority channel is active, duck other channels
|
|
271
|
+
if (channelNumber === config.priorityChannel) {
|
|
272
|
+
transitionPromises.push((0, exports.transitionVolume)(channelNumber, config.priorityVolume, duration, easing));
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
transitionPromises.push((0, exports.transitionVolume)(channelNumber, config.duckingVolume, duration, easing));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
// Wait for all transitions to complete
|
|
281
|
+
yield Promise.all(transitionPromises);
|
|
282
|
+
});
|
|
283
|
+
exports.applyVolumeDucking = applyVolumeDucking;
|
|
284
|
+
/**
|
|
285
|
+
* Restores normal volume levels when priority channel stops with smooth transitions
|
|
286
|
+
* @param stoppedChannelNumber - The channel that just stopped playing
|
|
287
|
+
* @internal
|
|
288
|
+
*/
|
|
289
|
+
const restoreVolumeLevels = (stoppedChannelNumber) => __awaiter(void 0, void 0, void 0, function* () {
|
|
290
|
+
const transitionPromises = [];
|
|
291
|
+
info_1.audioChannels.forEach((channel, channelNumber) => {
|
|
292
|
+
if (channel === null || channel === void 0 ? void 0 : channel.volumeConfig) {
|
|
293
|
+
const config = channel.volumeConfig;
|
|
294
|
+
if (stoppedChannelNumber === config.priorityChannel) {
|
|
295
|
+
const duration = config.restoreTransitionDuration || 500;
|
|
296
|
+
const easing = config.transitionEasing || 'ease-out';
|
|
297
|
+
// Priority channel stopped, restore normal volumes
|
|
298
|
+
transitionPromises.push((0, exports.transitionVolume)(channelNumber, channel.volume || 1.0, duration, easing));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
// Wait for all transitions to complete
|
|
303
|
+
yield Promise.all(transitionPromises);
|
|
304
|
+
});
|
|
305
|
+
exports.restoreVolumeLevels = restoreVolumeLevels;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "audio-channel-queue",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Allows you to queue audio files to different playback channels.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -31,6 +31,9 @@
|
|
|
31
31
|
"url": "https://github.com/tonycarpenter21/audio-queue-package/issues"
|
|
32
32
|
},
|
|
33
33
|
"homepage": "https://github.com/tonycarpenter21/audio-queue-package#readme",
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=14.0.0"
|
|
36
|
+
},
|
|
34
37
|
"devDependencies": {
|
|
35
38
|
"@types/jest": "^29.5.13",
|
|
36
39
|
"jest": "^29.7.0",
|