streamify-audio 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 +430 -0
- package/bin/streamify.js +84 -0
- package/docs/README.md +31 -0
- package/docs/automation.md +186 -0
- package/docs/configuration.md +169 -0
- package/docs/discord/events.md +206 -0
- package/docs/discord/manager.md +180 -0
- package/docs/discord/player.md +180 -0
- package/docs/discord/queue.md +197 -0
- package/docs/examples/advanced-bot.md +391 -0
- package/docs/examples/basic-bot.md +182 -0
- package/docs/examples/lavalink.md +156 -0
- package/docs/filters.md +309 -0
- package/docs/http/endpoints.md +199 -0
- package/docs/http/server.md +172 -0
- package/docs/quick-start.md +92 -0
- package/docs/sources.md +141 -0
- package/docs/sponsorblock.md +95 -0
- package/index.d.ts +350 -0
- package/index.js +252 -0
- package/package.json +57 -0
- package/silence.ogg +0 -0
- package/src/cache/index.js +61 -0
- package/src/config.js +133 -0
- package/src/discord/Manager.js +453 -0
- package/src/discord/Player.js +658 -0
- package/src/discord/Queue.js +129 -0
- package/src/discord/Stream.js +252 -0
- package/src/filters/ffmpeg.js +270 -0
- package/src/providers/soundcloud.js +166 -0
- package/src/providers/spotify.js +216 -0
- package/src/providers/youtube.js +320 -0
- package/src/server.js +318 -0
- package/src/utils/logger.js +139 -0
- package/src/utils/stream.js +108 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Manager
|
|
2
|
+
|
|
3
|
+
The Manager handles player creation, searching, and playlist loading.
|
|
4
|
+
|
|
5
|
+
## Creating a Manager
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
const Streamify = require('streamify-audio');
|
|
9
|
+
|
|
10
|
+
const manager = new Streamify.Manager(client, {
|
|
11
|
+
ytdlpPath: '/usr/local/bin/yt-dlp',
|
|
12
|
+
ffmpegPath: '/usr/bin/ffmpeg',
|
|
13
|
+
cookiesPath: './cookies.txt',
|
|
14
|
+
spotify: {
|
|
15
|
+
clientId: process.env.SPOTIFY_CLIENT_ID,
|
|
16
|
+
clientSecret: process.env.SPOTIFY_CLIENT_SECRET
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
See [Configuration](../configuration.md) for all options.
|
|
22
|
+
|
|
23
|
+
## Methods
|
|
24
|
+
|
|
25
|
+
### create(guildId, voiceChannelId, textChannelId)
|
|
26
|
+
|
|
27
|
+
Creates or retrieves a player for a guild.
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
const player = await manager.create(
|
|
31
|
+
message.guild.id,
|
|
32
|
+
voiceChannel.id,
|
|
33
|
+
message.channel.id
|
|
34
|
+
);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
If a player already exists for the guild, returns the existing player. If the voice channel differs, disconnects and reconnects.
|
|
38
|
+
|
|
39
|
+
### get(guildId)
|
|
40
|
+
|
|
41
|
+
Gets an existing player without creating one.
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
const player = manager.get(message.guild.id);
|
|
45
|
+
if (!player) {
|
|
46
|
+
return message.reply('No player in this server.');
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### destroy(guildId)
|
|
51
|
+
|
|
52
|
+
Destroys a player and disconnects from voice.
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
manager.destroy(message.guild.id);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### destroyAll()
|
|
59
|
+
|
|
60
|
+
Destroys all players. Useful for graceful shutdown.
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
process.on('SIGINT', () => {
|
|
64
|
+
manager.destroyAll();
|
|
65
|
+
process.exit();
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### search(query, options?)
|
|
70
|
+
|
|
71
|
+
Searches for tracks.
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
// Basic search (YouTube)
|
|
75
|
+
const result = await manager.search('never gonna give you up');
|
|
76
|
+
|
|
77
|
+
// With options
|
|
78
|
+
const result = await manager.search('never gonna give you up', {
|
|
79
|
+
source: 'spotify', // youtube, spotify, soundcloud
|
|
80
|
+
limit: 10
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Result
|
|
84
|
+
{
|
|
85
|
+
loadType: 'search', // search, empty, error
|
|
86
|
+
tracks: [{
|
|
87
|
+
id: 'dQw4w9WgXcQ',
|
|
88
|
+
title: 'Rick Astley - Never Gonna Give You Up',
|
|
89
|
+
author: 'Rick Astley',
|
|
90
|
+
duration: 213,
|
|
91
|
+
thumbnail: 'https://...',
|
|
92
|
+
uri: 'https://youtube.com/...',
|
|
93
|
+
source: 'youtube'
|
|
94
|
+
}]
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### resolve(query)
|
|
99
|
+
|
|
100
|
+
Resolves a URL or falls back to search.
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
// URL - loads directly
|
|
104
|
+
const result = await manager.resolve('https://youtube.com/watch?v=dQw4w9WgXcQ');
|
|
105
|
+
// { loadType: 'track', tracks: [track] }
|
|
106
|
+
|
|
107
|
+
// Search query - searches
|
|
108
|
+
const result = await manager.resolve('never gonna give you up');
|
|
109
|
+
// { loadType: 'search', tracks: [...] }
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### loadPlaylist(url)
|
|
113
|
+
|
|
114
|
+
Loads a playlist or album.
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
// YouTube playlist
|
|
118
|
+
const result = await manager.loadPlaylist('https://youtube.com/playlist?list=PLrAXtmErZgOeiKm4sgNOknGvNjby9efdf');
|
|
119
|
+
|
|
120
|
+
// Spotify playlist
|
|
121
|
+
const result = await manager.loadPlaylist('https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M');
|
|
122
|
+
|
|
123
|
+
// Spotify album
|
|
124
|
+
const result = await manager.loadPlaylist('https://open.spotify.com/album/4LH4d3cOWNNsVw41Gqt2kv');
|
|
125
|
+
|
|
126
|
+
// Result
|
|
127
|
+
{
|
|
128
|
+
loadType: 'playlist',
|
|
129
|
+
playlist: {
|
|
130
|
+
id: 'PLrAXtmErZgOeiKm4sgNOknGvNjby9efdf',
|
|
131
|
+
title: 'My Playlist',
|
|
132
|
+
author: 'User',
|
|
133
|
+
thumbnail: 'https://...',
|
|
134
|
+
source: 'youtube'
|
|
135
|
+
},
|
|
136
|
+
tracks: [...]
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### getRelated(track, limit?)
|
|
141
|
+
|
|
142
|
+
Gets related tracks for autoplay.
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
const result = await manager.getRelated(currentTrack, 5);
|
|
146
|
+
// { tracks: [...] }
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### getStats()
|
|
150
|
+
|
|
151
|
+
Gets manager statistics.
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
const stats = manager.getStats();
|
|
155
|
+
// {
|
|
156
|
+
// players: 5,
|
|
157
|
+
// playingPlayers: 3,
|
|
158
|
+
// memory: { heapUsed: 52428800, ... }
|
|
159
|
+
// }
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Events
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
manager.on('playerCreate', (player) => {
|
|
166
|
+
console.log(`Player created: ${player.guildId}`);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
manager.on('playerDestroy', (player) => {
|
|
170
|
+
console.log(`Player destroyed: ${player.guildId}`);
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Properties
|
|
175
|
+
|
|
176
|
+
```javascript
|
|
177
|
+
manager.players // Map<guildId, Player>
|
|
178
|
+
manager.client // Discord.js client
|
|
179
|
+
manager.config // Resolved configuration
|
|
180
|
+
```
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Player
|
|
2
|
+
|
|
3
|
+
The Player handles playback, filters, and voice connection for a single guild.
|
|
4
|
+
|
|
5
|
+
## Getting a Player
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
// Create new or get existing
|
|
9
|
+
const player = await manager.create(guildId, voiceChannelId, textChannelId);
|
|
10
|
+
|
|
11
|
+
// Get existing only
|
|
12
|
+
const player = manager.get(guildId);
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Playback Methods
|
|
16
|
+
|
|
17
|
+
### play(track)
|
|
18
|
+
|
|
19
|
+
Plays a track immediately. If something is playing, adds to queue and skips.
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
const result = await manager.search('never gonna give you up');
|
|
23
|
+
await player.play(result.tracks[0]);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### pause()
|
|
27
|
+
|
|
28
|
+
Pauses playback and destroys the stream to save resources.
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
player.pause();
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### resume()
|
|
35
|
+
|
|
36
|
+
Resumes playback by recreating the stream and seeking to the saved position.
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
await player.resume();
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### skip()
|
|
43
|
+
|
|
44
|
+
Skips to the next track in queue.
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
await player.skip();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### previous()
|
|
51
|
+
|
|
52
|
+
Goes back to the previous track.
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
await player.previous();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### stop()
|
|
59
|
+
|
|
60
|
+
Stops playback and clears the queue.
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
player.stop();
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### seek(positionMs)
|
|
67
|
+
|
|
68
|
+
Seeks to a position in milliseconds.
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
await player.seek(30000); // Seek to 30 seconds
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Volume
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
// Set volume (0-200)
|
|
78
|
+
player.setVolume(80);
|
|
79
|
+
|
|
80
|
+
// Get current volume
|
|
81
|
+
console.log(player.volume); // 80
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Filters
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
// Set a filter
|
|
88
|
+
await player.setFilter('bass', 10);
|
|
89
|
+
await player.setFilter('nightcore', true);
|
|
90
|
+
|
|
91
|
+
// Clear all filters
|
|
92
|
+
await player.clearFilters();
|
|
93
|
+
|
|
94
|
+
// Get current filters
|
|
95
|
+
console.log(player.filters); // { bass: 10, nightcore: true }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
See [Filters](../filters.md) for all available filters.
|
|
99
|
+
|
|
100
|
+
## Loop Modes
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
player.setLoop('off'); // No looping
|
|
104
|
+
player.setLoop('track'); // Loop current track
|
|
105
|
+
player.setLoop('queue'); // Loop entire queue
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Toggles
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
// Autoplay (play related tracks when queue ends)
|
|
112
|
+
player.setAutoplay(true);
|
|
113
|
+
player.setAutoplay(false);
|
|
114
|
+
|
|
115
|
+
// Auto-pause (pause when channel empty)
|
|
116
|
+
player.setAutoPause(true);
|
|
117
|
+
player.setAutoPause(false);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Connection
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
// Connect to voice
|
|
124
|
+
await player.connect();
|
|
125
|
+
|
|
126
|
+
// Disconnect (keeps player)
|
|
127
|
+
player.disconnect();
|
|
128
|
+
|
|
129
|
+
// Destroy player completely
|
|
130
|
+
player.destroy();
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## State Properties
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
player.connected // true if connected to voice
|
|
137
|
+
player.playing // true if playing (not paused)
|
|
138
|
+
player.paused // true if paused
|
|
139
|
+
player.position // Current position in ms
|
|
140
|
+
player.volume // Current volume (0-200)
|
|
141
|
+
player.filters // Current filters object
|
|
142
|
+
|
|
143
|
+
player.guildId // Guild ID
|
|
144
|
+
player.voiceChannelId // Voice channel ID
|
|
145
|
+
player.textChannelId // Text channel ID
|
|
146
|
+
|
|
147
|
+
player.autoplay // { enabled, maxTracks }
|
|
148
|
+
player.autoPause // { enabled, minUsers }
|
|
149
|
+
player.autoLeave // { enabled, emptyDelay, inactivityTimeout }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Queue Access
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
player.queue.current // Currently playing track
|
|
156
|
+
player.queue.tracks // Upcoming tracks
|
|
157
|
+
player.queue.previous // Previously played tracks
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
See [Queue](./queue.md) for queue methods.
|
|
161
|
+
|
|
162
|
+
## Serialization
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
const json = player.toJSON();
|
|
166
|
+
// {
|
|
167
|
+
// guildId: '...',
|
|
168
|
+
// voiceChannelId: '...',
|
|
169
|
+
// connected: true,
|
|
170
|
+
// playing: true,
|
|
171
|
+
// paused: false,
|
|
172
|
+
// volume: 80,
|
|
173
|
+
// position: 45000,
|
|
174
|
+
// filters: { bass: 10 },
|
|
175
|
+
// queue: { current: {...}, tracks: [...] },
|
|
176
|
+
// autoplay: { enabled: false },
|
|
177
|
+
// autoPause: { enabled: true },
|
|
178
|
+
// autoLeave: { enabled: true }
|
|
179
|
+
// }
|
|
180
|
+
```
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Queue
|
|
2
|
+
|
|
3
|
+
The Queue manages tracks for a player.
|
|
4
|
+
|
|
5
|
+
## Accessing the Queue
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
const player = manager.get(guildId);
|
|
9
|
+
const queue = player.queue;
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Properties
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
queue.current // Currently playing track (or null)
|
|
16
|
+
queue.tracks // Array of upcoming tracks
|
|
17
|
+
queue.previous // Array of previously played tracks
|
|
18
|
+
queue.size // Number of upcoming tracks
|
|
19
|
+
queue.isEmpty // true if no upcoming tracks
|
|
20
|
+
queue.totalDuration // Total duration in ms (current + upcoming)
|
|
21
|
+
queue.repeatMode // 'off', 'track', or 'queue'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Adding Tracks
|
|
25
|
+
|
|
26
|
+
### add(track, position?)
|
|
27
|
+
|
|
28
|
+
Add a single track.
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
// Add to end
|
|
32
|
+
queue.add(track);
|
|
33
|
+
|
|
34
|
+
// Add at specific position
|
|
35
|
+
queue.add(track, 0); // Next up
|
|
36
|
+
queue.add(track, 2); // Third in queue
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### addMany(tracks, position?)
|
|
40
|
+
|
|
41
|
+
Add multiple tracks.
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
// Add to end
|
|
45
|
+
queue.addMany(tracks);
|
|
46
|
+
|
|
47
|
+
// Add at position
|
|
48
|
+
queue.addMany(tracks, 0);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Removing Tracks
|
|
52
|
+
|
|
53
|
+
### remove(index)
|
|
54
|
+
|
|
55
|
+
Remove a track by index.
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
const removed = queue.remove(0); // Remove next track
|
|
59
|
+
console.log(removed.title);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### clear()
|
|
63
|
+
|
|
64
|
+
Clear all upcoming tracks.
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
const count = queue.clear();
|
|
68
|
+
console.log(`Cleared ${count} tracks`);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Reordering
|
|
72
|
+
|
|
73
|
+
### shuffle()
|
|
74
|
+
|
|
75
|
+
Randomize track order.
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
queue.shuffle();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### move(from, to)
|
|
82
|
+
|
|
83
|
+
Move a track to a different position.
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
queue.move(5, 0); // Move track 5 to next up
|
|
87
|
+
queue.move(0, 3); // Move next track to position 3
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Loop Mode
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
queue.setRepeatMode('off'); // No looping
|
|
94
|
+
queue.setRepeatMode('track'); // Repeat current track
|
|
95
|
+
queue.setRepeatMode('queue'); // Repeat entire queue
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Or use the player shorthand:
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
player.setLoop('queue');
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Internal Methods
|
|
105
|
+
|
|
106
|
+
These are used internally by the Player:
|
|
107
|
+
|
|
108
|
+
### shift()
|
|
109
|
+
|
|
110
|
+
Moves current to previous, gets next track as current.
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
const next = queue.shift(); // Returns next track or null
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### unshift()
|
|
117
|
+
|
|
118
|
+
Goes back to previous track.
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
const prev = queue.unshift(); // Returns previous track or null
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### setCurrent(track)
|
|
125
|
+
|
|
126
|
+
Sets the current track directly.
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
queue.setCurrent(track);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Example: Queue Display
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
function displayQueue(player) {
|
|
136
|
+
const { current, tracks } = player.queue;
|
|
137
|
+
|
|
138
|
+
let text = '';
|
|
139
|
+
|
|
140
|
+
if (current) {
|
|
141
|
+
text += `**Now Playing:** ${current.title}\n\n`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (tracks.length > 0) {
|
|
145
|
+
text += '**Up Next:**\n';
|
|
146
|
+
tracks.slice(0, 10).forEach((track, i) => {
|
|
147
|
+
text += `${i + 1}. ${track.title}\n`;
|
|
148
|
+
});
|
|
149
|
+
if (tracks.length > 10) {
|
|
150
|
+
text += `... and ${tracks.length - 10} more`;
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
text += 'Queue is empty.';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return text;
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Example: Playlist Loading
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
async function loadPlaylist(player, url) {
|
|
164
|
+
const result = await manager.loadPlaylist(url);
|
|
165
|
+
|
|
166
|
+
if (result.loadType === 'error') {
|
|
167
|
+
throw new Error(result.error);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const tracks = result.tracks;
|
|
171
|
+
|
|
172
|
+
if (player.queue.current) {
|
|
173
|
+
// Add all to queue
|
|
174
|
+
player.queue.addMany(tracks);
|
|
175
|
+
return `Added ${tracks.length} tracks to queue`;
|
|
176
|
+
} else {
|
|
177
|
+
// Play first, queue rest
|
|
178
|
+
const first = tracks.shift();
|
|
179
|
+
player.queue.addMany(tracks);
|
|
180
|
+
await player.play(first);
|
|
181
|
+
return `Playing **${result.playlist.title}** (${tracks.length + 1} tracks)`;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Serialization
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
const json = queue.toJSON();
|
|
190
|
+
// {
|
|
191
|
+
// current: { id, title, ... },
|
|
192
|
+
// tracks: [...],
|
|
193
|
+
// previous: [...],
|
|
194
|
+
// repeatMode: 'off',
|
|
195
|
+
// size: 5
|
|
196
|
+
// }
|
|
197
|
+
```
|