@students-dev/audify-js 1.0.0 → 1.0.1
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 +139 -7
- package/dist/cjs/index.js +380 -7
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +378 -7
- package/dist/esm/index.js.map +1 -1
- package/dist/types/engine/AudioEngine.d.ts +44 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/providers/LavalinkProvider.d.ts +48 -0
- package/dist/types/providers/SpotifyProvider.d.ts +54 -0
- package/package.json +12 -8
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 @students-dev/audify-js
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ A lightweight, modern, modular audio engine that provides playback, filters, que
|
|
|
12
12
|
- 🎛️ **Audio Filters**: Bassboost, nightcore, vaporwave, 8D rotate, pitch/speed adjustment, reverb
|
|
13
13
|
- 🔌 **Plugin System**: Extensible architecture with lifecycle hooks
|
|
14
14
|
- 📡 **Event-Driven**: Comprehensive event system for all operations
|
|
15
|
-
- 🌐 **Multi-Source Support**: Local files, remote URLs, YouTube, SoundCloud
|
|
15
|
+
- 🌐 **Multi-Source Support**: Local files, remote URLs, YouTube, SoundCloud, Spotify, Lavalink
|
|
16
16
|
- 🛠️ **Developer-Friendly**: TypeScript declarations, ESM/CJS builds
|
|
17
17
|
|
|
18
18
|
## Installation
|
|
@@ -76,6 +76,39 @@ The main class that orchestrates all audio functionality.
|
|
|
76
76
|
const engine = new AudioEngine(options);
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
#### Spotify Integration
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
// Initialize Spotify provider
|
|
83
|
+
engine.initSpotify({
|
|
84
|
+
clientId: 'your_client_id',
|
|
85
|
+
clientSecret: 'your_client_secret'
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Search tracks
|
|
89
|
+
const results = await engine.searchSpotifyTracks('query', { token: 'access_token' });
|
|
90
|
+
|
|
91
|
+
// Load track
|
|
92
|
+
const track = await engine.loadSpotifyTrack('track_id', { token: 'access_token' });
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### Lavalink Integration
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
// Connect to Lavalink server
|
|
99
|
+
await engine.connectLavalink({
|
|
100
|
+
host: 'localhost',
|
|
101
|
+
port: 2333,
|
|
102
|
+
password: 'youshallnotpass'
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Load track from Lavalink
|
|
106
|
+
const track = await engine.loadLavalinkTrack('ytsearch:query');
|
|
107
|
+
|
|
108
|
+
// Get Lavalink player for Discord bots
|
|
109
|
+
const player = engine.getLavalinkPlayer('guild_id', 'channel_id');
|
|
110
|
+
```
|
|
111
|
+
|
|
79
112
|
#### Methods
|
|
80
113
|
|
|
81
114
|
##### Playback Controls
|
|
@@ -219,7 +252,7 @@ engine.pluginManager.enable('my-plugin');
|
|
|
219
252
|
Fetch metadata from different sources.
|
|
220
253
|
|
|
221
254
|
```javascript
|
|
222
|
-
import { YouTubeProvider, SoundCloudProvider, LocalProvider } from '@students-dev/audify-js';
|
|
255
|
+
import { YouTubeProvider, SoundCloudProvider, LocalProvider, SpotifyProvider, LavalinkProvider } from '@students-dev/audify-js';
|
|
223
256
|
|
|
224
257
|
// YouTube
|
|
225
258
|
const ytInfo = await YouTubeProvider.getInfo('https://youtube.com/watch?v=VIDEO_ID');
|
|
@@ -229,6 +262,16 @@ const scInfo = await SoundCloudProvider.getInfo('https://soundcloud.com/artist/t
|
|
|
229
262
|
|
|
230
263
|
// Local file (Node.js only)
|
|
231
264
|
const localInfo = await LocalProvider.getInfo('/path/to/file.mp3');
|
|
265
|
+
|
|
266
|
+
// Spotify (requires access token)
|
|
267
|
+
const spotify = new SpotifyProvider({ clientId: 'your_client_id' });
|
|
268
|
+
spotify.setAccessToken('access_token');
|
|
269
|
+
const trackInfo = await spotify.getTrack('track_id');
|
|
270
|
+
|
|
271
|
+
// Lavalink (requires Lavalink server)
|
|
272
|
+
const lavalink = new LavalinkProvider({ host: 'localhost', port: 2333, password: 'password' });
|
|
273
|
+
await lavalink.connect();
|
|
274
|
+
const lavalinkTrack = await lavalink.loadTrack('ytsearch:query');
|
|
232
275
|
```
|
|
233
276
|
|
|
234
277
|
### Utils
|
|
@@ -250,6 +293,17 @@ const probe = await ProbeUtils.probe(audioBuffer);
|
|
|
250
293
|
|
|
251
294
|
## Examples
|
|
252
295
|
|
|
296
|
+
The `examples/` directory contains comprehensive examples demonstrating various features:
|
|
297
|
+
|
|
298
|
+
### 📁 Available Examples
|
|
299
|
+
|
|
300
|
+
- **`browser-example.html`** - Interactive browser demo with UI controls
|
|
301
|
+
- **`nodejs-example.js`** - Node.js usage with event handling
|
|
302
|
+
- **`queue-example.js`** - Queue management operations
|
|
303
|
+
- **`plugin-examples.js`** - Custom plugin implementations
|
|
304
|
+
- **`spotify-example.js`** - Spotify API integration
|
|
305
|
+
- **`lavalink-example.js`** - Lavalink server integration
|
|
306
|
+
|
|
253
307
|
### Basic Playback
|
|
254
308
|
|
|
255
309
|
```javascript
|
|
@@ -291,15 +345,46 @@ engine.add([
|
|
|
291
345
|
{ url: 'song2.mp3', title: 'Song Two' }
|
|
292
346
|
]);
|
|
293
347
|
|
|
294
|
-
//
|
|
348
|
+
// Navigation
|
|
295
349
|
engine.next();
|
|
350
|
+
engine.previous();
|
|
351
|
+
engine.jump(2); // Jump to track at index 2
|
|
296
352
|
|
|
297
|
-
//
|
|
353
|
+
// Modify queue
|
|
298
354
|
engine.shuffle();
|
|
299
|
-
|
|
300
|
-
// Clear and add new tracks
|
|
301
355
|
engine.clear();
|
|
302
|
-
|
|
356
|
+
|
|
357
|
+
// Remove specific track
|
|
358
|
+
engine.remove(0); // Remove by index
|
|
359
|
+
engine.remove('track-id'); // Remove by ID
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Audio Filters
|
|
363
|
+
|
|
364
|
+
```javascript
|
|
365
|
+
// Bass boost
|
|
366
|
+
engine.applyFilter('bassboost', { gain: 1.5 });
|
|
367
|
+
|
|
368
|
+
// Nightcore effect
|
|
369
|
+
engine.applyFilter('nightcore', { rate: 1.2 });
|
|
370
|
+
|
|
371
|
+
// Vaporwave effect
|
|
372
|
+
engine.applyFilter('vaporwave', { rate: 0.8 });
|
|
373
|
+
|
|
374
|
+
// 8D Audio
|
|
375
|
+
engine.applyFilter('8d');
|
|
376
|
+
|
|
377
|
+
// Pitch adjustment
|
|
378
|
+
engine.applyFilter('pitch', { pitch: 1.1 });
|
|
379
|
+
|
|
380
|
+
// Speed adjustment
|
|
381
|
+
engine.applyFilter('speed', { speed: 1.25 });
|
|
382
|
+
|
|
383
|
+
// Reverb
|
|
384
|
+
engine.applyFilter('reverb', { preset: 'hall' });
|
|
385
|
+
|
|
386
|
+
// Remove filter
|
|
387
|
+
engine.removeFilter('bassboost');
|
|
303
388
|
```
|
|
304
389
|
|
|
305
390
|
### Custom Plugin
|
|
@@ -331,6 +416,46 @@ engine.pluginManager.load(loggerPlugin);
|
|
|
331
416
|
engine.pluginManager.enable('logger');
|
|
332
417
|
```
|
|
333
418
|
|
|
419
|
+
### Browser Usage
|
|
420
|
+
|
|
421
|
+
```html
|
|
422
|
+
<!DOCTYPE html>
|
|
423
|
+
<html>
|
|
424
|
+
<head>
|
|
425
|
+
<title>audify-js Demo</title>
|
|
426
|
+
</head>
|
|
427
|
+
<body>
|
|
428
|
+
<button id="playBtn">Play</button>
|
|
429
|
+
<button id="bassBtn">Bass Boost</button>
|
|
430
|
+
|
|
431
|
+
<script type="module">
|
|
432
|
+
import { AudioEngine } from '@students-dev/audify-js';
|
|
433
|
+
|
|
434
|
+
const engine = new AudioEngine();
|
|
435
|
+
|
|
436
|
+
engine.on('ready', () => {
|
|
437
|
+
engine.add('audio.mp3');
|
|
438
|
+
|
|
439
|
+
document.getElementById('playBtn').onclick = () => engine.play();
|
|
440
|
+
document.getElementById('bassBtn').onclick = () =>
|
|
441
|
+
engine.applyFilter('bassboost');
|
|
442
|
+
});
|
|
443
|
+
</script>
|
|
444
|
+
</body>
|
|
445
|
+
</html>
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Running Examples
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
# Browser example
|
|
452
|
+
# Open examples/browser-example.html in your browser
|
|
453
|
+
|
|
454
|
+
# Node.js examples
|
|
455
|
+
node examples/nodejs-example.js
|
|
456
|
+
node examples/queue-example.js
|
|
457
|
+
```
|
|
458
|
+
|
|
334
459
|
## Browser Compatibility
|
|
335
460
|
|
|
336
461
|
- Chrome 14+
|
|
@@ -385,6 +510,13 @@ Some filters require AudioWorklet support in modern browsers. Check browser comp
|
|
|
385
510
|
|
|
386
511
|
## Changelog
|
|
387
512
|
|
|
513
|
+
### v1.1.0
|
|
514
|
+
- Added Spotify integration with client-side API support
|
|
515
|
+
- Added Lavalink integration for server-based audio streaming
|
|
516
|
+
- Updated dependencies for better performance and security
|
|
517
|
+
- Enhanced TypeScript type definitions
|
|
518
|
+
- Added comprehensive examples for new integrations
|
|
519
|
+
|
|
388
520
|
### v1.0.0
|
|
389
521
|
- Initial release
|
|
390
522
|
- Core audio engine
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var SpotifyWebApi = require('spotify-web-api-node');
|
|
4
|
+
var lavalinkClient = require('lavalink-client');
|
|
3
5
|
var fs = require('fs');
|
|
4
6
|
var path = require('path');
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Loop modes for playback
|
|
8
10
|
*/
|
|
9
|
-
const LOOP_MODES
|
|
11
|
+
const LOOP_MODES = {
|
|
10
12
|
OFF: 'off',
|
|
11
13
|
TRACK: 'track',
|
|
12
14
|
QUEUE: 'queue'
|
|
@@ -15,7 +17,7 @@ const LOOP_MODES$1 = {
|
|
|
15
17
|
/**
|
|
16
18
|
* Repeat modes (alias for loop modes)
|
|
17
19
|
*/
|
|
18
|
-
const REPEAT_MODES = LOOP_MODES
|
|
20
|
+
const REPEAT_MODES = LOOP_MODES;
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Filter types
|
|
@@ -126,7 +128,7 @@ class Player {
|
|
|
126
128
|
this.currentTime = 0;
|
|
127
129
|
this.duration = 0;
|
|
128
130
|
this.volume = 1;
|
|
129
|
-
this.loopMode = LOOP_MODES
|
|
131
|
+
this.loopMode = LOOP_MODES.OFF;
|
|
130
132
|
this.eventBus = new EventBus();
|
|
131
133
|
}
|
|
132
134
|
|
|
@@ -239,14 +241,14 @@ class Player {
|
|
|
239
241
|
*/
|
|
240
242
|
handleTrackEnd() {
|
|
241
243
|
switch (this.loopMode) {
|
|
242
|
-
case LOOP_MODES
|
|
244
|
+
case LOOP_MODES.TRACK:
|
|
243
245
|
// Replay current track
|
|
244
246
|
break;
|
|
245
|
-
case LOOP_MODES
|
|
247
|
+
case LOOP_MODES.QUEUE:
|
|
246
248
|
// Play next in queue
|
|
247
249
|
this.audioEngine.queue.getNext();
|
|
248
250
|
break;
|
|
249
|
-
case LOOP_MODES
|
|
251
|
+
case LOOP_MODES.OFF:
|
|
250
252
|
}
|
|
251
253
|
}
|
|
252
254
|
|
|
@@ -740,6 +742,276 @@ class Queue {
|
|
|
740
742
|
}
|
|
741
743
|
}
|
|
742
744
|
|
|
745
|
+
/**
|
|
746
|
+
* Spotify provider for client-side API integration
|
|
747
|
+
*/
|
|
748
|
+
class SpotifyProvider {
|
|
749
|
+
constructor(options = {}) {
|
|
750
|
+
this.spotifyApi = new SpotifyWebApi({
|
|
751
|
+
clientId: options.clientId,
|
|
752
|
+
clientSecret: options.clientSecret,
|
|
753
|
+
redirectUri: options.redirectUri,
|
|
754
|
+
accessToken: options.accessToken,
|
|
755
|
+
refreshToken: options.refreshToken
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Set access token
|
|
761
|
+
* @param {string} token - OAuth access token
|
|
762
|
+
*/
|
|
763
|
+
setAccessToken(token) {
|
|
764
|
+
this.spotifyApi.setAccessToken(token);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Set refresh token
|
|
769
|
+
* @param {string} token - OAuth refresh token
|
|
770
|
+
*/
|
|
771
|
+
setRefreshToken(token) {
|
|
772
|
+
this.spotifyApi.setRefreshToken(token);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Refresh access token
|
|
777
|
+
* @returns {Promise<Object>} Token response
|
|
778
|
+
*/
|
|
779
|
+
async refreshAccessToken() {
|
|
780
|
+
try {
|
|
781
|
+
const data = await this.spotifyApi.refreshAccessToken();
|
|
782
|
+
this.spotifyApi.setAccessToken(data.body.access_token);
|
|
783
|
+
return data.body;
|
|
784
|
+
} catch (error) {
|
|
785
|
+
throw new Error(`Failed to refresh token: ${error.message}`);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* Search tracks
|
|
791
|
+
* @param {string} query - Search query
|
|
792
|
+
* @param {Object} options - Search options
|
|
793
|
+
* @returns {Promise<Array>} Array of track objects
|
|
794
|
+
*/
|
|
795
|
+
async searchTracks(query, options = {}) {
|
|
796
|
+
try {
|
|
797
|
+
const data = await this.spotifyApi.searchTracks(query, {
|
|
798
|
+
limit: options.limit || 20,
|
|
799
|
+
offset: options.offset || 0
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
return data.body.tracks.items.map(track => this._formatTrack(track));
|
|
803
|
+
} catch (error) {
|
|
804
|
+
throw new Error(`Failed to search tracks: ${error.message}`);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* Get track by ID
|
|
810
|
+
* @param {string} trackId - Spotify track ID
|
|
811
|
+
* @returns {Promise<Object>} Track object
|
|
812
|
+
*/
|
|
813
|
+
async getTrack(trackId) {
|
|
814
|
+
try {
|
|
815
|
+
const data = await this.spotifyApi.getTrack(trackId);
|
|
816
|
+
return this._formatTrack(data.body);
|
|
817
|
+
} catch (error) {
|
|
818
|
+
throw new Error(`Failed to get track: ${error.message}`);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Get tracks by IDs
|
|
824
|
+
* @param {Array<string>} trackIds - Array of Spotify track IDs
|
|
825
|
+
* @returns {Promise<Array>} Array of track objects
|
|
826
|
+
*/
|
|
827
|
+
async getTracks(trackIds) {
|
|
828
|
+
try {
|
|
829
|
+
const data = await this.spotifyApi.getTracks(trackIds);
|
|
830
|
+
return data.body.tracks.map(track => this._formatTrack(track));
|
|
831
|
+
} catch (error) {
|
|
832
|
+
throw new Error(`Failed to get tracks: ${error.message}`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Get audio features for track
|
|
838
|
+
* @param {string} trackId - Spotify track ID
|
|
839
|
+
* @returns {Promise<Object>} Audio features
|
|
840
|
+
*/
|
|
841
|
+
async getAudioFeatures(trackId) {
|
|
842
|
+
try {
|
|
843
|
+
const data = await this.spotifyApi.getAudioFeaturesForTrack(trackId);
|
|
844
|
+
return data.body;
|
|
845
|
+
} catch (error) {
|
|
846
|
+
throw new Error(`Failed to get audio features: ${error.message}`);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Format Spotify track to internal format
|
|
852
|
+
* @param {Object} spotifyTrack - Spotify track object
|
|
853
|
+
* @returns {Object} Formatted track
|
|
854
|
+
* @private
|
|
855
|
+
*/
|
|
856
|
+
_formatTrack(spotifyTrack) {
|
|
857
|
+
return {
|
|
858
|
+
id: spotifyTrack.id,
|
|
859
|
+
title: spotifyTrack.name,
|
|
860
|
+
artist: spotifyTrack.artists.map(artist => artist.name).join(', '),
|
|
861
|
+
duration: Math.floor(spotifyTrack.duration_ms / 1000),
|
|
862
|
+
thumbnail: spotifyTrack.album.images[0]?.url,
|
|
863
|
+
url: spotifyTrack.external_urls.spotify,
|
|
864
|
+
source: 'spotify',
|
|
865
|
+
album: spotifyTrack.album.name,
|
|
866
|
+
popularity: spotifyTrack.popularity,
|
|
867
|
+
preview_url: spotifyTrack.preview_url,
|
|
868
|
+
metadata: {
|
|
869
|
+
spotifyId: spotifyTrack.id,
|
|
870
|
+
artists: spotifyTrack.artists,
|
|
871
|
+
album: spotifyTrack.album
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Lavalink provider for connecting to Lavalink server
|
|
879
|
+
*/
|
|
880
|
+
class LavalinkProvider {
|
|
881
|
+
constructor(options = {}) {
|
|
882
|
+
this.host = options.host || 'localhost';
|
|
883
|
+
this.port = options.port || 2333;
|
|
884
|
+
this.password = options.password || 'youshallnotpass';
|
|
885
|
+
this.secure = options.secure || false;
|
|
886
|
+
this.manager = null;
|
|
887
|
+
this.node = null;
|
|
888
|
+
this.isConnected = false;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Connect to Lavalink server
|
|
893
|
+
* @returns {Promise<void>}
|
|
894
|
+
*/
|
|
895
|
+
async connect() {
|
|
896
|
+
try {
|
|
897
|
+
this.manager = new lavalinkClient.LavalinkManager({
|
|
898
|
+
nodes: [{
|
|
899
|
+
host: this.host,
|
|
900
|
+
port: this.port,
|
|
901
|
+
password: this.password,
|
|
902
|
+
secure: this.secure,
|
|
903
|
+
id: 'main'
|
|
904
|
+
}],
|
|
905
|
+
sendToShard: (guildId, payload) => {
|
|
906
|
+
// This would need to be implemented to send to Discord gateway
|
|
907
|
+
// For now, this is a placeholder
|
|
908
|
+
console.log('Send to shard:', guildId, payload);
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
await this.manager.connect();
|
|
913
|
+
this.node = this.manager.nodes.get('main');
|
|
914
|
+
this.isConnected = true;
|
|
915
|
+
} catch (error) {
|
|
916
|
+
throw new Error(`Failed to connect to Lavalink: ${error.message}`);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Disconnect from Lavalink server
|
|
922
|
+
*/
|
|
923
|
+
disconnect() {
|
|
924
|
+
if (this.manager) {
|
|
925
|
+
this.manager.destroy();
|
|
926
|
+
this.isConnected = false;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Create a player for a guild/channel
|
|
932
|
+
* @param {string} guildId - Guild ID
|
|
933
|
+
* @param {string} channelId - Voice channel ID
|
|
934
|
+
* @returns {Object} Player instance
|
|
935
|
+
*/
|
|
936
|
+
createPlayer(guildId, channelId) {
|
|
937
|
+
if (!this.isConnected) {
|
|
938
|
+
throw new Error('Not connected to Lavalink');
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
return this.manager.createPlayer({
|
|
942
|
+
guildId,
|
|
943
|
+
voiceChannelId: channelId,
|
|
944
|
+
textChannelId: channelId, // Optional
|
|
945
|
+
selfDeaf: false,
|
|
946
|
+
selfMute: false
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Load track from Lavalink
|
|
952
|
+
* @param {string} identifier - Track identifier (URL or search query)
|
|
953
|
+
* @returns {Promise<Object>} Track info
|
|
954
|
+
*/
|
|
955
|
+
async loadTrack(identifier) {
|
|
956
|
+
if (!this.isConnected) {
|
|
957
|
+
throw new Error('Not connected to Lavalink');
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
try {
|
|
961
|
+
const result = await this.node.rest.loadTracks(identifier);
|
|
962
|
+
|
|
963
|
+
if (result.loadType === 'TRACK_LOADED') {
|
|
964
|
+
return this._formatTrack(result.tracks[0]);
|
|
965
|
+
} else if (result.loadType === 'PLAYLIST_LOADED') {
|
|
966
|
+
return result.tracks.map(track => this._formatTrack(track));
|
|
967
|
+
} else if (result.loadType === 'SEARCH_RESULT') {
|
|
968
|
+
return result.tracks.map(track => this._formatTrack(track));
|
|
969
|
+
} else {
|
|
970
|
+
throw new Error('No tracks found');
|
|
971
|
+
}
|
|
972
|
+
} catch (error) {
|
|
973
|
+
throw new Error(`Failed to load track: ${error.message}`);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* Format Lavalink track to internal format
|
|
979
|
+
* @param {Object} lavalinkTrack - Lavalink track object
|
|
980
|
+
* @returns {Object} Formatted track
|
|
981
|
+
* @private
|
|
982
|
+
*/
|
|
983
|
+
_formatTrack(lavalinkTrack) {
|
|
984
|
+
const info = lavalinkTrack.info;
|
|
985
|
+
return {
|
|
986
|
+
id: lavalinkTrack.track,
|
|
987
|
+
title: info.title,
|
|
988
|
+
artist: info.author,
|
|
989
|
+
duration: Math.floor(info.length / 1000),
|
|
990
|
+
thumbnail: info.artworkUrl,
|
|
991
|
+
url: info.uri,
|
|
992
|
+
source: 'lavalink',
|
|
993
|
+
isrc: info.isrc,
|
|
994
|
+
metadata: {
|
|
995
|
+
lavalinkTrack: lavalinkTrack.track,
|
|
996
|
+
identifier: info.identifier,
|
|
997
|
+
sourceName: info.sourceName
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* Get node stats
|
|
1004
|
+
* @returns {Promise<Object>} Node stats
|
|
1005
|
+
*/
|
|
1006
|
+
async getStats() {
|
|
1007
|
+
if (!this.isConnected) {
|
|
1008
|
+
throw new Error('Not connected to Lavalink');
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
return await this.node.rest.getStats();
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
743
1015
|
/**
|
|
744
1016
|
* Main audio engine class
|
|
745
1017
|
*/
|
|
@@ -751,6 +1023,8 @@ class AudioEngine {
|
|
|
751
1023
|
this.filters = null;
|
|
752
1024
|
this.queue = new Queue();
|
|
753
1025
|
this.eventBus = new EventBus();
|
|
1026
|
+
this.spotifyProvider = null;
|
|
1027
|
+
this.lavalinkProvider = null;
|
|
754
1028
|
this.isReady = false;
|
|
755
1029
|
|
|
756
1030
|
this.initialize();
|
|
@@ -933,6 +1207,100 @@ class AudioEngine {
|
|
|
933
1207
|
};
|
|
934
1208
|
}
|
|
935
1209
|
|
|
1210
|
+
/**
|
|
1211
|
+
* Initialize Spotify provider
|
|
1212
|
+
* @param {Object} options - Spotify options
|
|
1213
|
+
*/
|
|
1214
|
+
initSpotify(options = {}) {
|
|
1215
|
+
this.spotifyProvider = new SpotifyProvider(options);
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* Load Spotify track and add to queue
|
|
1220
|
+
* @param {string} trackId - Spotify track ID
|
|
1221
|
+
* @param {Object} options - Options including token
|
|
1222
|
+
* @returns {Promise<Track>} Added track
|
|
1223
|
+
*/
|
|
1224
|
+
async loadSpotifyTrack(trackId, options = {}) {
|
|
1225
|
+
if (!this.spotifyProvider) {
|
|
1226
|
+
throw new Error('Spotify provider not initialized');
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
if (options.token) {
|
|
1230
|
+
this.spotifyProvider.setAccessToken(options.token);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
const trackData = await this.spotifyProvider.getTrack(trackId);
|
|
1234
|
+
const track = new Track(trackData.url, trackData);
|
|
1235
|
+
this.add(track);
|
|
1236
|
+
return track;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/**
|
|
1240
|
+
* Search Spotify tracks
|
|
1241
|
+
* @param {string} query - Search query
|
|
1242
|
+
* @param {Object} options - Search options
|
|
1243
|
+
* @returns {Promise<Array>} Search results
|
|
1244
|
+
*/
|
|
1245
|
+
async searchSpotifyTracks(query, options = {}) {
|
|
1246
|
+
if (!this.spotifyProvider) {
|
|
1247
|
+
throw new Error('Spotify provider not initialized');
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
if (options.token) {
|
|
1251
|
+
this.spotifyProvider.setAccessToken(options.token);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
return await this.spotifyProvider.searchTracks(query, options);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
/**
|
|
1258
|
+
* Connect to Lavalink server
|
|
1259
|
+
* @param {Object} options - Lavalink connection options
|
|
1260
|
+
* @returns {Promise<void>}
|
|
1261
|
+
*/
|
|
1262
|
+
async connectLavalink(options = {}) {
|
|
1263
|
+
this.lavalinkProvider = new LavalinkProvider(options);
|
|
1264
|
+
await this.lavalinkProvider.connect();
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
/**
|
|
1268
|
+
* Load Lavalink track and add to queue
|
|
1269
|
+
* @param {string} identifier - Track identifier
|
|
1270
|
+
* @returns {Promise<Track|Array<Track>>} Added track(s)
|
|
1271
|
+
*/
|
|
1272
|
+
async loadLavalinkTrack(identifier) {
|
|
1273
|
+
if (!this.lavalinkProvider) {
|
|
1274
|
+
throw new Error('Lavalink provider not connected');
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
const trackData = await this.lavalinkProvider.loadTrack(identifier);
|
|
1278
|
+
|
|
1279
|
+
if (Array.isArray(trackData)) {
|
|
1280
|
+
const tracks = trackData.map(data => new Track(data.url, data));
|
|
1281
|
+
this.add(tracks);
|
|
1282
|
+
return tracks;
|
|
1283
|
+
} else {
|
|
1284
|
+
const track = new Track(trackData.url, trackData);
|
|
1285
|
+
this.add(track);
|
|
1286
|
+
return track;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* Get Lavalink player for guild/channel
|
|
1292
|
+
* @param {string} guildId - Guild ID
|
|
1293
|
+
* @param {string} channelId - Voice channel ID
|
|
1294
|
+
* @returns {Object} Lavalink player
|
|
1295
|
+
*/
|
|
1296
|
+
getLavalinkPlayer(guildId, channelId) {
|
|
1297
|
+
if (!this.lavalinkProvider) {
|
|
1298
|
+
throw new Error('Lavalink provider not connected');
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
return this.lavalinkProvider.createPlayer(guildId, channelId);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
936
1304
|
/**
|
|
937
1305
|
* Destroy the engine
|
|
938
1306
|
*/
|
|
@@ -942,6 +1310,9 @@ class AudioEngine {
|
|
|
942
1310
|
}
|
|
943
1311
|
this.filters.clear();
|
|
944
1312
|
this.player.stop();
|
|
1313
|
+
if (this.lavalinkProvider) {
|
|
1314
|
+
this.lavalinkProvider.disconnect();
|
|
1315
|
+
}
|
|
945
1316
|
}
|
|
946
1317
|
}
|
|
947
1318
|
|
|
@@ -1479,7 +1850,8 @@ exports.AudioEngine = AudioEngine;
|
|
|
1479
1850
|
exports.EVENTS = EVENTS;
|
|
1480
1851
|
exports.EventBus = EventBus;
|
|
1481
1852
|
exports.FILTER_TYPES = FILTER_TYPES;
|
|
1482
|
-
exports.LOOP_MODES = LOOP_MODES
|
|
1853
|
+
exports.LOOP_MODES = LOOP_MODES;
|
|
1854
|
+
exports.LavalinkProvider = LavalinkProvider;
|
|
1483
1855
|
exports.LocalProvider = LocalProvider;
|
|
1484
1856
|
exports.Logger = Logger;
|
|
1485
1857
|
exports.MetadataUtils = MetadataUtils;
|
|
@@ -1488,6 +1860,7 @@ exports.ProbeUtils = ProbeUtils;
|
|
|
1488
1860
|
exports.Queue = Queue;
|
|
1489
1861
|
exports.REPEAT_MODES = REPEAT_MODES;
|
|
1490
1862
|
exports.SoundCloudProvider = SoundCloudProvider;
|
|
1863
|
+
exports.SpotifyProvider = SpotifyProvider;
|
|
1491
1864
|
exports.TimeUtils = TimeUtils;
|
|
1492
1865
|
exports.Track = Track;
|
|
1493
1866
|
exports.YouTubeProvider = YouTubeProvider;
|