@students-dev/audify-js 1.0.0 → 1.0.2
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 +93 -310
- package/dist/AudioEngine.js +232 -0
- package/dist/cjs/index.js +1497 -1392
- package/dist/cjs/index.js.map +1 -1
- package/dist/constants/index.js +35 -0
- package/dist/engine/Filters.js +137 -0
- package/dist/engine/MockAudioContext.js +53 -0
- package/dist/engine/Player.js +209 -0
- package/dist/esm/index.js +1490 -1389
- package/dist/esm/index.js.map +1 -1
- package/dist/events/EventBus.js +61 -0
- package/dist/index.js +18 -0
- package/dist/interfaces/index.js +1 -0
- package/dist/plugins/Plugin.js +27 -0
- package/dist/plugins/PluginManager.js +106 -0
- package/dist/providers/LavalinkProvider.js +81 -0
- package/dist/providers/LocalProvider.js +70 -0
- package/dist/providers/ProviderRegistry.js +20 -0
- package/dist/providers/SpotifyProvider.js +59 -0
- package/dist/providers/YouTubeProvider.js +48 -0
- package/dist/queue/Queue.js +186 -0
- package/dist/queue/Track.js +54 -0
- package/dist/types/AudioEngine.d.ts +107 -0
- package/dist/types/constants/index.d.ts +39 -0
- package/dist/types/engine/AudioEngine.d.ts +44 -1
- package/dist/types/engine/Filters.d.ts +25 -24
- package/dist/types/engine/MockAudioContext.d.ts +43 -0
- package/dist/types/engine/Player.d.ts +25 -21
- package/dist/types/events/EventBus.d.ts +17 -15
- package/dist/types/index.d.ts +17 -13
- package/dist/types/interfaces/index.d.ts +31 -0
- package/dist/types/plugins/Plugin.d.ts +11 -43
- package/dist/types/plugins/PluginManager.d.ts +19 -19
- package/dist/types/providers/LavalinkProvider.d.ts +17 -0
- package/dist/types/providers/LocalProvider.d.ts +11 -22
- package/dist/types/providers/ProviderRegistry.d.ts +10 -0
- package/dist/types/providers/SpotifyProvider.d.ts +14 -0
- package/dist/types/providers/YouTubeProvider.d.ts +11 -28
- package/dist/types/queue/Queue.d.ts +28 -22
- package/dist/types/queue/Track.d.ts +18 -16
- package/dist/types/utils/Logger.d.ts +12 -16
- package/dist/types/utils/Metadata.d.ts +16 -15
- package/dist/types/utils/Probe.d.ts +7 -7
- package/dist/types/utils/Time.d.ts +9 -9
- package/dist/utils/Logger.js +59 -0
- package/dist/utils/Metadata.js +90 -0
- package/dist/utils/Probe.js +59 -0
- package/dist/utils/Time.js +54 -0
- package/package.json +19 -9
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { LOOP_MODES, EVENTS } from '../constants';
|
|
2
|
+
import { EventBus } from '../events/EventBus';
|
|
3
|
+
import { MockAudioContext } from './MockAudioContext';
|
|
4
|
+
/**
|
|
5
|
+
* Audio player with playback controls
|
|
6
|
+
*/
|
|
7
|
+
export class Player {
|
|
8
|
+
constructor(audioEngine) {
|
|
9
|
+
this.audioEngine = audioEngine;
|
|
10
|
+
let AudioContextClass;
|
|
11
|
+
if (typeof window !== 'undefined') {
|
|
12
|
+
AudioContextClass = window.AudioContext || window.webkitAudioContext;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
AudioContextClass = global.AudioContext;
|
|
16
|
+
}
|
|
17
|
+
if (AudioContextClass) {
|
|
18
|
+
this.audioContext = new AudioContextClass();
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
this.audioContext = new MockAudioContext();
|
|
22
|
+
}
|
|
23
|
+
this.source = null;
|
|
24
|
+
this.isPlaying = false;
|
|
25
|
+
this.currentTime = 0;
|
|
26
|
+
this.duration = 0;
|
|
27
|
+
this.volume = 1;
|
|
28
|
+
this.loopMode = LOOP_MODES.OFF;
|
|
29
|
+
this.eventBus = new EventBus();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Play audio track
|
|
33
|
+
* @param track - Track to play
|
|
34
|
+
*/
|
|
35
|
+
async play(track) {
|
|
36
|
+
if (!track)
|
|
37
|
+
return;
|
|
38
|
+
// Reset state
|
|
39
|
+
this.stop();
|
|
40
|
+
try {
|
|
41
|
+
this.eventBus.emit(EVENTS.PLAY, track);
|
|
42
|
+
// Check providers via registry
|
|
43
|
+
const provider = this.audioEngine.getProvider(track.source || 'local');
|
|
44
|
+
if (provider) {
|
|
45
|
+
await provider.play(track);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Fallback to direct URL playback if no specific provider found
|
|
49
|
+
await this.playStream(track);
|
|
50
|
+
}
|
|
51
|
+
this.eventBus.emit(EVENTS.TRACK_START, track);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error(error);
|
|
55
|
+
this.eventBus.emit(EVENTS.ERROR, error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Play audio from URL/Stream directly
|
|
60
|
+
* This is called by Providers or as fallback
|
|
61
|
+
* @param track - Track object with URL
|
|
62
|
+
*/
|
|
63
|
+
async playStream(track) {
|
|
64
|
+
if (!this.audioContext)
|
|
65
|
+
throw new Error('AudioContext not available');
|
|
66
|
+
// If already playing, stop
|
|
67
|
+
if (this.source) {
|
|
68
|
+
this.source.stop();
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
// Fetch audio data
|
|
72
|
+
// For Node.js (Mock), we might fail to fetch if it's a real URL
|
|
73
|
+
// If MockAudioContext is used, we probably want to skip fetch?
|
|
74
|
+
// Or Mock fetch?
|
|
75
|
+
// In Node environment, fetch is global in recent versions (v18+)
|
|
76
|
+
// But if we are mocking, we can't really "decode" the buffer from a remote stream easily without logic.
|
|
77
|
+
// My MockAudioContext.decodeAudioData returns a mock buffer.
|
|
78
|
+
let audioBuffer;
|
|
79
|
+
// Check if real fetch is feasible
|
|
80
|
+
if (this.audioContext instanceof MockAudioContext) {
|
|
81
|
+
// Mock fetch behavior if needed or just create dummy buffer
|
|
82
|
+
audioBuffer = await this.audioContext.decodeAudioData(new ArrayBuffer(0));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const response = await fetch(track.url);
|
|
86
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
87
|
+
audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
|
|
88
|
+
}
|
|
89
|
+
this.source = this.audioContext.createBufferSource();
|
|
90
|
+
this.source.buffer = audioBuffer;
|
|
91
|
+
this.duration = audioBuffer.duration;
|
|
92
|
+
// Connect through filters
|
|
93
|
+
this.audioEngine.filters.connect(this.source, this.audioContext.destination);
|
|
94
|
+
// Handle end of track
|
|
95
|
+
this.source.onended = () => {
|
|
96
|
+
this.isPlaying = false;
|
|
97
|
+
this.eventBus.emit(EVENTS.TRACK_END, track);
|
|
98
|
+
this.handleTrackEnd();
|
|
99
|
+
};
|
|
100
|
+
this.source.start(0);
|
|
101
|
+
this.isPlaying = true;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
throw new Error(`Failed to play stream: ${error}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Pause playback
|
|
109
|
+
*/
|
|
110
|
+
pause() {
|
|
111
|
+
if (this.audioContext.state === 'running') {
|
|
112
|
+
this.audioContext.suspend();
|
|
113
|
+
this.isPlaying = false;
|
|
114
|
+
this.eventBus.emit(EVENTS.PAUSE);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Resume playback
|
|
119
|
+
*/
|
|
120
|
+
resume() {
|
|
121
|
+
if (this.audioContext.state === 'suspended') {
|
|
122
|
+
this.audioContext.resume();
|
|
123
|
+
this.isPlaying = true;
|
|
124
|
+
this.eventBus.emit(EVENTS.PLAY);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Stop playback
|
|
129
|
+
*/
|
|
130
|
+
stop() {
|
|
131
|
+
if (this.source) {
|
|
132
|
+
try {
|
|
133
|
+
this.source.stop();
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
// Ignore if already stopped
|
|
137
|
+
}
|
|
138
|
+
this.source = null;
|
|
139
|
+
}
|
|
140
|
+
this.isPlaying = false;
|
|
141
|
+
this.currentTime = 0;
|
|
142
|
+
this.eventBus.emit(EVENTS.STOP);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Seek to position
|
|
146
|
+
* @param time - Time in seconds
|
|
147
|
+
*/
|
|
148
|
+
seek(time) {
|
|
149
|
+
if (this.source && this.isPlaying) {
|
|
150
|
+
// TODO: Implement proper seek
|
|
151
|
+
console.warn('Seek not fully implemented for Web Audio BufferSource');
|
|
152
|
+
}
|
|
153
|
+
this.currentTime = Math.max(0, Math.min(time, this.duration));
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Set volume
|
|
157
|
+
* @param volume - Volume level (0-1)
|
|
158
|
+
*/
|
|
159
|
+
setVolume(volume) {
|
|
160
|
+
this.volume = Math.max(0, Math.min(1, volume));
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Set loop mode
|
|
164
|
+
* @param mode - Loop mode
|
|
165
|
+
*/
|
|
166
|
+
setLoopMode(mode) {
|
|
167
|
+
this.loopMode = mode;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Handle track end based on loop mode
|
|
171
|
+
*/
|
|
172
|
+
handleTrackEnd() {
|
|
173
|
+
if (this.loopMode === LOOP_MODES.TRACK) {
|
|
174
|
+
// Replay current track
|
|
175
|
+
const current = this.audioEngine.queue.getCurrent();
|
|
176
|
+
if (current)
|
|
177
|
+
this.play(current);
|
|
178
|
+
}
|
|
179
|
+
else if (this.loopMode === LOOP_MODES.QUEUE) {
|
|
180
|
+
// Play next in queue
|
|
181
|
+
const next = this.audioEngine.queue.next(true);
|
|
182
|
+
if (next)
|
|
183
|
+
this.play(next);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
// Loop OFF: Play next or stop
|
|
187
|
+
const next = this.audioEngine.queue.next(false);
|
|
188
|
+
if (next) {
|
|
189
|
+
this.play(next);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
this.stop();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get current playback state
|
|
198
|
+
* @returns State object
|
|
199
|
+
*/
|
|
200
|
+
getState() {
|
|
201
|
+
return {
|
|
202
|
+
isPlaying: this.isPlaying,
|
|
203
|
+
currentTime: this.audioContext.currentTime,
|
|
204
|
+
duration: this.duration,
|
|
205
|
+
volume: this.volume,
|
|
206
|
+
loopMode: this.loopMode
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|