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,156 @@
|
|
|
1
|
+
# HTTP Server with Lavalink
|
|
2
|
+
|
|
3
|
+
Use Streamify's HTTP server as a stream source for Lavalink.
|
|
4
|
+
|
|
5
|
+
## Why?
|
|
6
|
+
|
|
7
|
+
- Lavalink's YouTube plugin can be unreliable
|
|
8
|
+
- Streamify uses yt-dlp which handles YouTube's restrictions better
|
|
9
|
+
- Get audio filters without Lavalink's filter support
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
### 1. Start Streamify Server
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
// streamify-server.js
|
|
17
|
+
const Streamify = require('streamify-audio');
|
|
18
|
+
|
|
19
|
+
const streamify = new Streamify({
|
|
20
|
+
port: 8787,
|
|
21
|
+
cookiesPath: './cookies.txt',
|
|
22
|
+
spotify: {
|
|
23
|
+
clientId: process.env.SPOTIFY_CLIENT_ID,
|
|
24
|
+
clientSecret: process.env.SPOTIFY_CLIENT_SECRET
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
streamify.start().then(() => {
|
|
29
|
+
console.log('Streamify running at http://127.0.0.1:8787');
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. Configure Lavalink
|
|
34
|
+
|
|
35
|
+
In `application.yml`, allow local addresses:
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
lavalink:
|
|
39
|
+
server:
|
|
40
|
+
sources:
|
|
41
|
+
http: true
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 3. Bot Integration
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
const { Client } = require('discord.js');
|
|
48
|
+
const { Manager } = require('erela.js'); // or your Lavalink client
|
|
49
|
+
|
|
50
|
+
const client = new Client({ /* intents */ });
|
|
51
|
+
|
|
52
|
+
// Initialize Streamify client
|
|
53
|
+
const Streamify = require('streamify-audio');
|
|
54
|
+
const streamify = new Streamify({ port: 8787 });
|
|
55
|
+
|
|
56
|
+
// Initialize Lavalink
|
|
57
|
+
const lavalinkManager = new Manager({
|
|
58
|
+
nodes: [{ host: 'localhost', port: 2333, password: 'youshallnotpass' }],
|
|
59
|
+
send: (id, payload) => {
|
|
60
|
+
const guild = client.guilds.cache.get(id);
|
|
61
|
+
if (guild) guild.shard.send(payload);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
client.on('ready', async () => {
|
|
66
|
+
await streamify.start();
|
|
67
|
+
lavalinkManager.init(client.user.id);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
client.on('raw', d => lavalinkManager.updateVoiceState(d));
|
|
71
|
+
|
|
72
|
+
// Play command
|
|
73
|
+
client.on('messageCreate', async (message) => {
|
|
74
|
+
if (!message.content.startsWith('!play')) return;
|
|
75
|
+
|
|
76
|
+
const query = message.content.slice(6);
|
|
77
|
+
const vc = message.member.voice.channel;
|
|
78
|
+
|
|
79
|
+
// Search using Streamify
|
|
80
|
+
const results = await streamify.youtube.search(query);
|
|
81
|
+
if (!results.tracks.length) return message.reply('No results');
|
|
82
|
+
|
|
83
|
+
const track = results.tracks[0];
|
|
84
|
+
|
|
85
|
+
// Get stream URL from Streamify
|
|
86
|
+
const streamUrl = streamify.youtube.getStreamUrl(track.id, {
|
|
87
|
+
bass: 5 // Optional: apply filters
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Create Lavalink player
|
|
91
|
+
const player = lavalinkManager.create({
|
|
92
|
+
guild: message.guild.id,
|
|
93
|
+
voiceChannel: vc.id,
|
|
94
|
+
textChannel: message.channel.id
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (player.state !== 'CONNECTED') {
|
|
98
|
+
player.connect();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Load Streamify URL in Lavalink
|
|
102
|
+
const res = await lavalinkManager.search(streamUrl, message.author);
|
|
103
|
+
player.queue.add(res.tracks[0]);
|
|
104
|
+
|
|
105
|
+
if (!player.playing) player.play();
|
|
106
|
+
|
|
107
|
+
message.reply(`Playing: **${track.title}**`);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
client.login(process.env.TOKEN);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## With Filters
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
// Apply filters via Streamify URL
|
|
117
|
+
const streamUrl = streamify.youtube.getStreamUrl(trackId, {
|
|
118
|
+
bass: 10,
|
|
119
|
+
nightcore: true,
|
|
120
|
+
volume: 80
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Lavalink plays the already-filtered audio
|
|
124
|
+
const res = await lavalinkManager.search(streamUrl, user);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Changing Filters Mid-Song
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
// Get current position from Streamify's stream tracking
|
|
131
|
+
const position = await streamify.getPosition(currentStreamId);
|
|
132
|
+
|
|
133
|
+
// Create new URL with filters + position
|
|
134
|
+
const newUrl = streamify.youtube.getStreamUrl(trackId, {
|
|
135
|
+
bass: 10,
|
|
136
|
+
nightcore: true,
|
|
137
|
+
start: Math.floor(position) // Seek to current position
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Tell Lavalink to switch
|
|
141
|
+
const res = await lavalinkManager.search(newUrl, user);
|
|
142
|
+
player.play(res.tracks[0]);
|
|
143
|
+
// Seamless transition with new filters
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Benefits
|
|
147
|
+
|
|
148
|
+
1. **Reliable extraction** - yt-dlp handles YouTube's anti-bot
|
|
149
|
+
2. **Cookie support** - Play age-restricted content
|
|
150
|
+
3. **Pre-applied filters** - Don't need Lavalink filter support
|
|
151
|
+
4. **Spotify support** - Automatic YouTube resolution
|
|
152
|
+
5. **Sponsorblock** - Skip sponsors before Lavalink receives audio
|
|
153
|
+
|
|
154
|
+
## Note
|
|
155
|
+
|
|
156
|
+
This approach adds one network hop (Lavalink fetches from Streamify). For direct playback without Lavalink, use Streamify's [Discord Player mode](../discord/manager.md) instead.
|
package/docs/filters.md
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# Audio Filters
|
|
2
|
+
|
|
3
|
+
Filters are applied in real-time via ffmpeg. When you change a filter during playback, Streamify recreates the stream and seeks to the current position.
|
|
4
|
+
|
|
5
|
+
## Available Filters
|
|
6
|
+
|
|
7
|
+
| Filter | Type | Range | Description |
|
|
8
|
+
|--------|------|-------|-------------|
|
|
9
|
+
| `bass` | number | -20 to 20 | Bass boost/cut in dB |
|
|
10
|
+
| `treble` | number | -20 to 20 | Treble boost/cut in dB |
|
|
11
|
+
| `speed` | number | 0.5 to 2.0 | Playback speed |
|
|
12
|
+
| `pitch` | number | 0.5 to 2.0 | Pitch shift |
|
|
13
|
+
| `volume` | number | 0 to 200 | Volume percentage |
|
|
14
|
+
| `tremolo` | object | see below | Volume oscillation |
|
|
15
|
+
| `vibrato` | object | see below | Pitch oscillation |
|
|
16
|
+
| `rotation` | object | see below | Audio rotation (8D) |
|
|
17
|
+
| `lowpass` | number | 100 to 20000 | Low-pass filter (Hz) |
|
|
18
|
+
| `highpass` | number | 20 to 10000 | High-pass filter (Hz) |
|
|
19
|
+
| `karaoke` | boolean | — | Reduce vocals |
|
|
20
|
+
| `nightcore` | boolean | — | Speed + pitch up preset |
|
|
21
|
+
| `vaporwave` | boolean | — | Speed + pitch down preset |
|
|
22
|
+
| `bassboost` | boolean | — | Strong bass boost preset |
|
|
23
|
+
| `8d` | boolean | — | 8D panning effect |
|
|
24
|
+
|
|
25
|
+
## Usage (Discord Player)
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
// Set individual filters
|
|
29
|
+
await player.setFilter('bass', 10);
|
|
30
|
+
await player.setFilter('treble', 5);
|
|
31
|
+
await player.setFilter('speed', 1.25);
|
|
32
|
+
await player.setFilter('volume', 120);
|
|
33
|
+
|
|
34
|
+
// Object filters
|
|
35
|
+
await player.setFilter('tremolo', { frequency: 4, depth: 0.5 });
|
|
36
|
+
await player.setFilter('vibrato', { frequency: 4, depth: 0.5 });
|
|
37
|
+
await player.setFilter('rotation', { speed: 0.125 });
|
|
38
|
+
|
|
39
|
+
// Frequency filters
|
|
40
|
+
await player.setFilter('lowpass', 1000); // Cut above 1000Hz
|
|
41
|
+
await player.setFilter('highpass', 200); // Cut below 200Hz
|
|
42
|
+
|
|
43
|
+
// Presets (boolean)
|
|
44
|
+
await player.setFilter('nightcore', true);
|
|
45
|
+
await player.setFilter('vaporwave', true);
|
|
46
|
+
await player.setFilter('bassboost', true);
|
|
47
|
+
await player.setFilter('karaoke', true);
|
|
48
|
+
await player.setFilter('8d', true);
|
|
49
|
+
|
|
50
|
+
// Clear all filters
|
|
51
|
+
await player.clearFilters();
|
|
52
|
+
|
|
53
|
+
// Get current filters
|
|
54
|
+
console.log(player.filters);
|
|
55
|
+
// { bass: 10, nightcore: true, ... }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Usage (HTTP Server)
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
// Add filters as query parameters
|
|
62
|
+
const url = streamify.youtube.getStreamUrl('dQw4w9WgXcQ', {
|
|
63
|
+
bass: 10,
|
|
64
|
+
speed: 1.25,
|
|
65
|
+
nightcore: true
|
|
66
|
+
});
|
|
67
|
+
// http://127.0.0.1:8787/youtube/stream/dQw4w9WgXcQ?bass=10&speed=1.25&nightcore=true
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Filter Details
|
|
71
|
+
|
|
72
|
+
### Tremolo
|
|
73
|
+
Volume oscillation effect.
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
await player.setFilter('tremolo', {
|
|
77
|
+
frequency: 4, // 0.1 to 20 Hz
|
|
78
|
+
depth: 0.5 // 0 to 1
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Vibrato
|
|
83
|
+
Pitch oscillation effect.
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
await player.setFilter('vibrato', {
|
|
87
|
+
frequency: 4, // 0.1 to 14 Hz
|
|
88
|
+
depth: 0.5 // 0 to 1
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Rotation
|
|
93
|
+
Advanced 8D effect with configurable speed.
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
await player.setFilter('rotation', {
|
|
97
|
+
speed: 0.125 // 0.01 to 5 rotations per second
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Karaoke
|
|
102
|
+
Reduces vocals by removing the center channel. Works best on stereo tracks with centered vocals.
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
await player.setFilter('karaoke', true);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Presets
|
|
109
|
+
|
|
110
|
+
### Nightcore
|
|
111
|
+
Speeds up and raises pitch for that anime soundtrack feel.
|
|
112
|
+
|
|
113
|
+
- Speed: 1.25x
|
|
114
|
+
- Pitch: 1.25x
|
|
115
|
+
|
|
116
|
+
### Vaporwave
|
|
117
|
+
Slows down and lowers pitch for that aesthetic.
|
|
118
|
+
|
|
119
|
+
- Speed: 0.8x
|
|
120
|
+
- Pitch: 0.8x
|
|
121
|
+
|
|
122
|
+
### Bassboost
|
|
123
|
+
Strong bass boost (+10dB).
|
|
124
|
+
|
|
125
|
+
### 8D
|
|
126
|
+
Audio pans around in a circle. Same as `rotation` with speed 0.125.
|
|
127
|
+
|
|
128
|
+
## Combining Filters
|
|
129
|
+
|
|
130
|
+
Filters can be combined:
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
await player.setFilter('bass', 10);
|
|
134
|
+
await player.setFilter('speed', 1.25);
|
|
135
|
+
await player.setFilter('8d', true);
|
|
136
|
+
// All three active simultaneously
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Note: `nightcore` and `vaporwave` both modify speed/pitch, so using both may produce unexpected results.
|
|
140
|
+
|
|
141
|
+
## 15-Band Equalizer
|
|
142
|
+
|
|
143
|
+
Fine-grained control over frequency response with 15 bands.
|
|
144
|
+
|
|
145
|
+
### Bands
|
|
146
|
+
|
|
147
|
+
| Band | Frequency |
|
|
148
|
+
|------|-----------|
|
|
149
|
+
| 0 | 25 Hz |
|
|
150
|
+
| 1 | 40 Hz |
|
|
151
|
+
| 2 | 63 Hz |
|
|
152
|
+
| 3 | 100 Hz |
|
|
153
|
+
| 4 | 160 Hz |
|
|
154
|
+
| 5 | 250 Hz |
|
|
155
|
+
| 6 | 400 Hz |
|
|
156
|
+
| 7 | 630 Hz |
|
|
157
|
+
| 8 | 1000 Hz |
|
|
158
|
+
| 9 | 1600 Hz |
|
|
159
|
+
| 10 | 2500 Hz |
|
|
160
|
+
| 11 | 4000 Hz |
|
|
161
|
+
| 12 | 6300 Hz |
|
|
162
|
+
| 13 | 10000 Hz |
|
|
163
|
+
| 14 | 16000 Hz |
|
|
164
|
+
|
|
165
|
+
### Usage
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
// Set custom EQ (15 band values, -0.25 to 1.0)
|
|
169
|
+
await player.setEQ([
|
|
170
|
+
0.3, // 25 Hz (sub bass)
|
|
171
|
+
0.25, // 40 Hz
|
|
172
|
+
0.2, // 63 Hz
|
|
173
|
+
0.1, // 100 Hz
|
|
174
|
+
0, // 160 Hz
|
|
175
|
+
-0.1, // 250 Hz
|
|
176
|
+
0, // 400 Hz
|
|
177
|
+
0.1, // 630 Hz
|
|
178
|
+
0.2, // 1000 Hz
|
|
179
|
+
0.25, // 1600 Hz
|
|
180
|
+
0.3, // 2500 Hz
|
|
181
|
+
0.3, // 4000 Hz
|
|
182
|
+
0.25, // 6300 Hz
|
|
183
|
+
0.2, // 10000 Hz
|
|
184
|
+
0.15 // 16000 Hz
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
// Clear EQ
|
|
188
|
+
await player.clearEQ();
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## EQ Presets
|
|
192
|
+
|
|
193
|
+
Built-in presets for common genres and use cases.
|
|
194
|
+
|
|
195
|
+
### Available Presets
|
|
196
|
+
|
|
197
|
+
| Preset | Description |
|
|
198
|
+
|--------|-------------|
|
|
199
|
+
| `flat` | No EQ changes |
|
|
200
|
+
| `rock` | Enhanced mids and highs |
|
|
201
|
+
| `pop` | Balanced with slight bass |
|
|
202
|
+
| `jazz` | Warm, smooth sound |
|
|
203
|
+
| `classical` | Wide, natural sound |
|
|
204
|
+
| `electronic` | Heavy bass, crisp highs |
|
|
205
|
+
| `hiphop` | Deep bass, clear vocals |
|
|
206
|
+
| `acoustic` | Natural, warm |
|
|
207
|
+
| `rnb` | Smooth bass, warm mids |
|
|
208
|
+
| `latin` | Punchy, rhythmic |
|
|
209
|
+
| `loudness` | Overall boost |
|
|
210
|
+
| `piano` | Clear mids |
|
|
211
|
+
| `vocal` | Enhanced vocal range |
|
|
212
|
+
| `bass_heavy` | Maximum bass |
|
|
213
|
+
| `treble_heavy` | Maximum highs |
|
|
214
|
+
|
|
215
|
+
### Usage
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
// Apply preset
|
|
219
|
+
await player.setPreset('rock');
|
|
220
|
+
await player.setPreset('electronic');
|
|
221
|
+
|
|
222
|
+
// List available presets
|
|
223
|
+
const presets = player.getPresets();
|
|
224
|
+
// ['flat', 'rock', 'pop', ...]
|
|
225
|
+
|
|
226
|
+
// Clear preset
|
|
227
|
+
await player.clearEQ();
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Biquad Filters
|
|
231
|
+
|
|
232
|
+
Professional-grade frequency filters.
|
|
233
|
+
|
|
234
|
+
### Bandpass
|
|
235
|
+
|
|
236
|
+
Only allows frequencies within a range.
|
|
237
|
+
|
|
238
|
+
```javascript
|
|
239
|
+
await player.setFilter('bandpass', {
|
|
240
|
+
frequency: 1000, // Center frequency (Hz)
|
|
241
|
+
width: 200 // Bandwidth (Hz)
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Band Reject (Notch)
|
|
246
|
+
|
|
247
|
+
Removes frequencies within a range.
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
await player.setFilter('bandreject', {
|
|
251
|
+
frequency: 1000,
|
|
252
|
+
width: 200
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Low Shelf
|
|
257
|
+
|
|
258
|
+
Boosts or cuts frequencies below a point.
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
await player.setFilter('lowshelf', {
|
|
262
|
+
frequency: 200, // Cutoff frequency (Hz)
|
|
263
|
+
gain: 6 // dB (-20 to 20)
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### High Shelf
|
|
268
|
+
|
|
269
|
+
Boosts or cuts frequencies above a point.
|
|
270
|
+
|
|
271
|
+
```javascript
|
|
272
|
+
await player.setFilter('highshelf', {
|
|
273
|
+
frequency: 3000,
|
|
274
|
+
gain: 6
|
|
275
|
+
});
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Peaking EQ
|
|
279
|
+
|
|
280
|
+
Boosts or cuts a specific frequency range.
|
|
281
|
+
|
|
282
|
+
```javascript
|
|
283
|
+
await player.setFilter('peaking', {
|
|
284
|
+
frequency: 1000, // Center frequency
|
|
285
|
+
gain: 6, // dB
|
|
286
|
+
q: 1 // Width (higher = narrower)
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Additional Effects
|
|
291
|
+
|
|
292
|
+
| Effect | Description |
|
|
293
|
+
|--------|-------------|
|
|
294
|
+
| `flanger` | Sweeping comb filter effect |
|
|
295
|
+
| `phaser` | Phase-shifting effect |
|
|
296
|
+
| `chorus` | Thickens sound with slight detuning |
|
|
297
|
+
| `compressor` | Dynamic range compression |
|
|
298
|
+
| `normalizer` | Loudness normalization |
|
|
299
|
+
| `mono` | Convert stereo to mono |
|
|
300
|
+
| `surround` | Surround sound effect |
|
|
301
|
+
|
|
302
|
+
```javascript
|
|
303
|
+
await player.setFilter('flanger', true);
|
|
304
|
+
await player.setFilter('phaser', true);
|
|
305
|
+
await player.setFilter('chorus', true);
|
|
306
|
+
await player.setFilter('compressor', true);
|
|
307
|
+
await player.setFilter('normalizer', true);
|
|
308
|
+
await player.setFilter('mono', true);
|
|
309
|
+
```
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# HTTP Endpoints
|
|
2
|
+
|
|
3
|
+
## YouTube
|
|
4
|
+
|
|
5
|
+
### Search
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
GET /youtube/search?q=query&limit=10
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Response:**
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"tracks": [
|
|
15
|
+
{
|
|
16
|
+
"id": "dQw4w9WgXcQ",
|
|
17
|
+
"title": "Rick Astley - Never Gonna Give You Up",
|
|
18
|
+
"author": "Rick Astley",
|
|
19
|
+
"duration": 213,
|
|
20
|
+
"thumbnail": "https://...",
|
|
21
|
+
"uri": "https://youtube.com/watch?v=dQw4w9WgXcQ",
|
|
22
|
+
"streamUrl": "/youtube/stream/dQw4w9WgXcQ",
|
|
23
|
+
"source": "youtube"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"source": "youtube",
|
|
27
|
+
"searchTime": 1234
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Get Info
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
GET /youtube/info/:videoId
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Response:**
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"id": "dQw4w9WgXcQ",
|
|
41
|
+
"title": "Rick Astley - Never Gonna Give You Up",
|
|
42
|
+
"author": "Rick Astley",
|
|
43
|
+
"duration": 213,
|
|
44
|
+
"thumbnail": "https://...",
|
|
45
|
+
"uri": "https://youtube.com/watch?v=dQw4w9WgXcQ",
|
|
46
|
+
"streamUrl": "/youtube/stream/dQw4w9WgXcQ",
|
|
47
|
+
"source": "youtube"
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Stream
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
GET /youtube/stream/:videoId
|
|
55
|
+
GET /youtube/stream/:videoId?bass=10&nightcore=true&start=30
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Query Parameters:**
|
|
59
|
+
- All [filters](../filters.md) are supported
|
|
60
|
+
- `start` - Seek to position in seconds
|
|
61
|
+
|
|
62
|
+
**Response:** Audio stream (`audio/ogg`)
|
|
63
|
+
|
|
64
|
+
## Spotify
|
|
65
|
+
|
|
66
|
+
### Search
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
GET /spotify/search?q=query&limit=10
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Get Info
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
GET /spotify/info/:trackId
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Stream
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
GET /spotify/stream/:trackId
|
|
82
|
+
GET /spotify/stream/:trackId?bass=10
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Spotify tracks are resolved to YouTube and streamed from there.
|
|
86
|
+
|
|
87
|
+
## SoundCloud
|
|
88
|
+
|
|
89
|
+
### Search
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
GET /soundcloud/search?q=query&limit=10
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Stream
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
GET /soundcloud/stream/:trackUrl
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Note: `trackUrl` should be URL-encoded.
|
|
102
|
+
|
|
103
|
+
## Stream Management
|
|
104
|
+
|
|
105
|
+
### List Active Streams
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
GET /streams
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Response:**
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"streams": [
|
|
115
|
+
{
|
|
116
|
+
"id": "yt-dQw4w9WgXcQ-1234567890",
|
|
117
|
+
"source": "youtube",
|
|
118
|
+
"trackId": "dQw4w9WgXcQ",
|
|
119
|
+
"filters": { "bass": 10 },
|
|
120
|
+
"startTime": 1234567890,
|
|
121
|
+
"bytesReceived": 1048576,
|
|
122
|
+
"bytesSent": 524288
|
|
123
|
+
}
|
|
124
|
+
],
|
|
125
|
+
"count": 1
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Get Stream Info
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
GET /streams/:streamId
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Response:**
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"id": "yt-dQw4w9WgXcQ-1234567890",
|
|
139
|
+
"source": "youtube",
|
|
140
|
+
"trackId": "dQw4w9WgXcQ",
|
|
141
|
+
"filters": { "bass": 10 },
|
|
142
|
+
"startTime": 1234567890,
|
|
143
|
+
"elapsed": 45000,
|
|
144
|
+
"bytesReceived": 1048576,
|
|
145
|
+
"bytesSent": 524288
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Get Stream Position
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
GET /streams/:streamId/position
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Response:**
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"position": 45.5,
|
|
159
|
+
"elapsed": 45000
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Position is in seconds, useful for seeking when applying filters.
|
|
164
|
+
|
|
165
|
+
## Filter Parameters
|
|
166
|
+
|
|
167
|
+
All stream endpoints accept filter query parameters:
|
|
168
|
+
|
|
169
|
+
| Parameter | Type | Description |
|
|
170
|
+
|-----------|------|-------------|
|
|
171
|
+
| `bass` | number | -20 to 20 |
|
|
172
|
+
| `treble` | number | -20 to 20 |
|
|
173
|
+
| `speed` | number | 0.5 to 2.0 |
|
|
174
|
+
| `pitch` | number | 0.5 to 2.0 |
|
|
175
|
+
| `volume` | number | 0 to 200 |
|
|
176
|
+
| `nightcore` | boolean | true |
|
|
177
|
+
| `vaporwave` | boolean | true |
|
|
178
|
+
| `bassboost` | boolean | true |
|
|
179
|
+
| `8d` | boolean | true |
|
|
180
|
+
| `start` | number | Seek seconds |
|
|
181
|
+
|
|
182
|
+
**Example:**
|
|
183
|
+
```
|
|
184
|
+
GET /youtube/stream/dQw4w9WgXcQ?bass=10&speed=1.25&nightcore=true&start=30
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Error Responses
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"error": "Track not found",
|
|
192
|
+
"code": 404
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Common error codes:
|
|
197
|
+
- `400` - Invalid parameters
|
|
198
|
+
- `404` - Track/stream not found
|
|
199
|
+
- `500` - Internal error (yt-dlp/ffmpeg failure)
|