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
package/src/server.js
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const youtube = require('./providers/youtube');
|
|
3
|
+
const spotify = require('./providers/spotify');
|
|
4
|
+
const soundcloud = require('./providers/soundcloud');
|
|
5
|
+
const cache = require('./cache');
|
|
6
|
+
const { getActiveStreams, getStreamById, getStreamPosition } = require('./utils/stream');
|
|
7
|
+
const log = require('./utils/logger');
|
|
8
|
+
|
|
9
|
+
class Server {
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.app = express();
|
|
13
|
+
this.server = null;
|
|
14
|
+
this.startTime = null;
|
|
15
|
+
this.setupRoutes();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_isProviderEnabled(provider) {
|
|
19
|
+
return this.config.providers?.[provider]?.enabled !== false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
setupRoutes() {
|
|
23
|
+
this.app.use(express.json());
|
|
24
|
+
|
|
25
|
+
this.app.get('/health', (req, res) => {
|
|
26
|
+
res.json({
|
|
27
|
+
status: 'ok',
|
|
28
|
+
uptime: this.startTime ? Math.floor((Date.now() - this.startTime) / 1000) : 0,
|
|
29
|
+
activeStreams: getActiveStreams().size
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
this.app.get('/stats', (req, res) => {
|
|
34
|
+
const mem = process.memoryUsage();
|
|
35
|
+
res.json({
|
|
36
|
+
uptime: this.startTime ? Math.floor((Date.now() - this.startTime) / 1000) : 0,
|
|
37
|
+
activeStreams: getActiveStreams().size,
|
|
38
|
+
memory: {
|
|
39
|
+
heapUsed: Math.round(mem.heapUsed / 1024 / 1024) + 'MB',
|
|
40
|
+
heapTotal: Math.round(mem.heapTotal / 1024 / 1024) + 'MB',
|
|
41
|
+
rss: Math.round(mem.rss / 1024 / 1024) + 'MB'
|
|
42
|
+
},
|
|
43
|
+
cache: cache.stats(),
|
|
44
|
+
config: {
|
|
45
|
+
spotify: !!this.config.spotify?.clientId,
|
|
46
|
+
cookies: !!this.config.cookiesPath
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
this.app.get('/streams', (req, res) => {
|
|
52
|
+
const streams = [];
|
|
53
|
+
for (const [id, stream] of getActiveStreams()) {
|
|
54
|
+
streams.push({
|
|
55
|
+
id,
|
|
56
|
+
source: stream.source,
|
|
57
|
+
trackId: stream.videoId || stream.trackId,
|
|
58
|
+
position: getStreamPosition(id),
|
|
59
|
+
filters: stream.filters || {},
|
|
60
|
+
startTime: stream.startTime,
|
|
61
|
+
duration: Date.now() - stream.startTime
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
res.json({ streams });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
this.app.get('/streams/:streamId', (req, res) => {
|
|
68
|
+
const stream = getStreamById(req.params.streamId);
|
|
69
|
+
if (!stream) {
|
|
70
|
+
return res.status(404).json({ error: 'Stream not found' });
|
|
71
|
+
}
|
|
72
|
+
res.json({
|
|
73
|
+
id: req.params.streamId,
|
|
74
|
+
source: stream.source,
|
|
75
|
+
trackId: stream.videoId || stream.trackId,
|
|
76
|
+
position: getStreamPosition(req.params.streamId),
|
|
77
|
+
filters: stream.filters || {},
|
|
78
|
+
startTime: stream.startTime,
|
|
79
|
+
duration: Date.now() - stream.startTime
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
this.app.get('/streams/:streamId/position', (req, res) => {
|
|
84
|
+
const position = getStreamPosition(req.params.streamId);
|
|
85
|
+
if (position === null) {
|
|
86
|
+
return res.status(404).json({ error: 'Stream not found' });
|
|
87
|
+
}
|
|
88
|
+
res.json({ position, positionMs: Math.floor(position * 1000) });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
this.app.get('/youtube/search', async (req, res) => {
|
|
92
|
+
try {
|
|
93
|
+
if (!this._isProviderEnabled('youtube')) {
|
|
94
|
+
return res.status(400).json({ error: 'YouTube provider is disabled' });
|
|
95
|
+
}
|
|
96
|
+
const { q, limit = 10 } = req.query;
|
|
97
|
+
if (!q) return res.status(400).json({ error: 'Missing query parameter: q' });
|
|
98
|
+
|
|
99
|
+
const cacheKey = `yt:search:${q}:${limit}`;
|
|
100
|
+
const cached = cache.get(cacheKey);
|
|
101
|
+
if (cached) return res.json(cached);
|
|
102
|
+
|
|
103
|
+
const results = await youtube.search(q, parseInt(limit), this.config);
|
|
104
|
+
cache.set(cacheKey, results, this.config.cache.searchTTL);
|
|
105
|
+
res.json(results);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
log.error('YOUTUBE', 'Search failed:', error.message);
|
|
108
|
+
res.status(500).json({ error: error.message });
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
this.app.get('/youtube/info/:videoId', async (req, res) => {
|
|
113
|
+
try {
|
|
114
|
+
if (!this._isProviderEnabled('youtube')) {
|
|
115
|
+
return res.status(400).json({ error: 'YouTube provider is disabled' });
|
|
116
|
+
}
|
|
117
|
+
const { videoId } = req.params;
|
|
118
|
+
|
|
119
|
+
const cacheKey = `yt:info:${videoId}`;
|
|
120
|
+
const cached = cache.get(cacheKey);
|
|
121
|
+
if (cached) return res.json(cached);
|
|
122
|
+
|
|
123
|
+
const info = await youtube.getInfo(videoId, this.config);
|
|
124
|
+
cache.set(cacheKey, info, this.config.cache.infoTTL);
|
|
125
|
+
res.json(info);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
log.error('YOUTUBE', 'Info failed:', error.message);
|
|
128
|
+
res.status(500).json({ error: error.message });
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
this.app.get('/youtube/stream/:videoId', (req, res) => {
|
|
133
|
+
if (!this._isProviderEnabled('youtube')) {
|
|
134
|
+
return res.status(400).json({ error: 'YouTube provider is disabled' });
|
|
135
|
+
}
|
|
136
|
+
youtube.stream(req.params.videoId, req.query, this.config, res);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
this.app.get('/spotify/search', async (req, res) => {
|
|
140
|
+
try {
|
|
141
|
+
if (!this._isProviderEnabled('spotify')) {
|
|
142
|
+
return res.status(400).json({ error: 'Spotify provider is disabled' });
|
|
143
|
+
}
|
|
144
|
+
if (!this.config.spotify?.clientId) {
|
|
145
|
+
return res.status(400).json({ error: 'Spotify not configured' });
|
|
146
|
+
}
|
|
147
|
+
const { q, limit = 10 } = req.query;
|
|
148
|
+
if (!q) return res.status(400).json({ error: 'Missing query parameter: q' });
|
|
149
|
+
|
|
150
|
+
const cacheKey = `sp:search:${q}:${limit}`;
|
|
151
|
+
const cached = cache.get(cacheKey);
|
|
152
|
+
if (cached) return res.json(cached);
|
|
153
|
+
|
|
154
|
+
const results = await spotify.search(q, parseInt(limit), this.config);
|
|
155
|
+
cache.set(cacheKey, results, this.config.cache.searchTTL);
|
|
156
|
+
res.json(results);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
log.error('SPOTIFY', 'Search failed:', error.message);
|
|
159
|
+
res.status(500).json({ error: error.message });
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
this.app.get('/spotify/info/:trackId', async (req, res) => {
|
|
164
|
+
try {
|
|
165
|
+
if (!this._isProviderEnabled('spotify')) {
|
|
166
|
+
return res.status(400).json({ error: 'Spotify provider is disabled' });
|
|
167
|
+
}
|
|
168
|
+
if (!this.config.spotify?.clientId) {
|
|
169
|
+
return res.status(400).json({ error: 'Spotify not configured' });
|
|
170
|
+
}
|
|
171
|
+
const { trackId } = req.params;
|
|
172
|
+
|
|
173
|
+
const cacheKey = `sp:info:${trackId}`;
|
|
174
|
+
const cached = cache.get(cacheKey);
|
|
175
|
+
if (cached) return res.json(cached);
|
|
176
|
+
|
|
177
|
+
const info = await spotify.getInfo(trackId, this.config);
|
|
178
|
+
cache.set(cacheKey, info, this.config.cache.infoTTL);
|
|
179
|
+
res.json(info);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
log.error('SPOTIFY', 'Info failed:', error.message);
|
|
182
|
+
res.status(500).json({ error: error.message });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
this.app.get('/spotify/stream/:trackId', async (req, res) => {
|
|
187
|
+
try {
|
|
188
|
+
if (!this._isProviderEnabled('spotify')) {
|
|
189
|
+
return res.status(400).json({ error: 'Spotify provider is disabled' });
|
|
190
|
+
}
|
|
191
|
+
if (!this.config.spotify?.clientId) {
|
|
192
|
+
return res.status(400).json({ error: 'Spotify not configured' });
|
|
193
|
+
}
|
|
194
|
+
await spotify.stream(req.params.trackId, req.query, this.config, res);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
log.error('SPOTIFY', 'Stream failed:', error.message);
|
|
197
|
+
res.status(500).json({ error: error.message });
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
this.app.get('/soundcloud/search', async (req, res) => {
|
|
202
|
+
try {
|
|
203
|
+
if (!this._isProviderEnabled('soundcloud')) {
|
|
204
|
+
return res.status(400).json({ error: 'SoundCloud provider is disabled' });
|
|
205
|
+
}
|
|
206
|
+
const { q, limit = 10 } = req.query;
|
|
207
|
+
if (!q) return res.status(400).json({ error: 'Missing query parameter: q' });
|
|
208
|
+
|
|
209
|
+
const cacheKey = `sc:search:${q}:${limit}`;
|
|
210
|
+
const cached = cache.get(cacheKey);
|
|
211
|
+
if (cached) return res.json(cached);
|
|
212
|
+
|
|
213
|
+
const results = await soundcloud.search(q, parseInt(limit), this.config);
|
|
214
|
+
cache.set(cacheKey, results, this.config.cache.searchTTL);
|
|
215
|
+
res.json(results);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
log.error('SOUNDCLOUD', 'Search failed:', error.message);
|
|
218
|
+
res.status(500).json({ error: error.message });
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
this.app.get('/soundcloud/stream/:trackId', (req, res) => {
|
|
223
|
+
if (!this._isProviderEnabled('soundcloud')) {
|
|
224
|
+
return res.status(400).json({ error: 'SoundCloud provider is disabled' });
|
|
225
|
+
}
|
|
226
|
+
soundcloud.stream(req.params.trackId, req.query, this.config, res);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
this.app.get('/search', async (req, res) => {
|
|
230
|
+
try {
|
|
231
|
+
const { q, source = 'youtube', limit = 10 } = req.query;
|
|
232
|
+
if (!q) return res.status(400).json({ error: 'Missing query parameter: q' });
|
|
233
|
+
|
|
234
|
+
let results;
|
|
235
|
+
switch (source) {
|
|
236
|
+
case 'spotify':
|
|
237
|
+
if (!this._isProviderEnabled('spotify')) {
|
|
238
|
+
return res.status(400).json({ error: 'Spotify provider is disabled' });
|
|
239
|
+
}
|
|
240
|
+
if (!this.config.spotify?.clientId) {
|
|
241
|
+
return res.status(400).json({ error: 'Spotify not configured' });
|
|
242
|
+
}
|
|
243
|
+
results = await spotify.search(q, parseInt(limit), this.config);
|
|
244
|
+
break;
|
|
245
|
+
case 'soundcloud':
|
|
246
|
+
if (!this._isProviderEnabled('soundcloud')) {
|
|
247
|
+
return res.status(400).json({ error: 'SoundCloud provider is disabled' });
|
|
248
|
+
}
|
|
249
|
+
results = await soundcloud.search(q, parseInt(limit), this.config);
|
|
250
|
+
break;
|
|
251
|
+
default:
|
|
252
|
+
if (!this._isProviderEnabled('youtube')) {
|
|
253
|
+
return res.status(400).json({ error: 'YouTube provider is disabled' });
|
|
254
|
+
}
|
|
255
|
+
results = await youtube.search(q, parseInt(limit), this.config);
|
|
256
|
+
}
|
|
257
|
+
res.json(results);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
log.error('SEARCH', error.message);
|
|
260
|
+
res.status(500).json({ error: error.message });
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
this.app.get('/stream/:source/:id', (req, res) => {
|
|
265
|
+
const { source, id } = req.params;
|
|
266
|
+
switch (source) {
|
|
267
|
+
case 'youtube':
|
|
268
|
+
if (!this._isProviderEnabled('youtube')) {
|
|
269
|
+
return res.status(400).json({ error: 'YouTube provider is disabled' });
|
|
270
|
+
}
|
|
271
|
+
youtube.stream(id, req.query, this.config, res);
|
|
272
|
+
break;
|
|
273
|
+
case 'spotify':
|
|
274
|
+
if (!this._isProviderEnabled('spotify')) {
|
|
275
|
+
return res.status(400).json({ error: 'Spotify provider is disabled' });
|
|
276
|
+
}
|
|
277
|
+
spotify.stream(id, req.query, this.config, res);
|
|
278
|
+
break;
|
|
279
|
+
case 'soundcloud':
|
|
280
|
+
if (!this._isProviderEnabled('soundcloud')) {
|
|
281
|
+
return res.status(400).json({ error: 'SoundCloud provider is disabled' });
|
|
282
|
+
}
|
|
283
|
+
soundcloud.stream(id, req.query, this.config, res);
|
|
284
|
+
break;
|
|
285
|
+
default:
|
|
286
|
+
res.status(400).json({ error: 'Invalid source' });
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async start() {
|
|
292
|
+
return new Promise((resolve, reject) => {
|
|
293
|
+
this.server = this.app.listen(this.config.port, this.config.host, () => {
|
|
294
|
+
this.startTime = Date.now();
|
|
295
|
+
log.success('STREAMIFY', `Running on http://${this.config.host}:${this.config.port}`);
|
|
296
|
+
log.info('STREAMIFY', `YouTube: ${this._isProviderEnabled('youtube') ? 'enabled' : 'disabled'}`);
|
|
297
|
+
log.info('STREAMIFY', `Spotify: ${!this._isProviderEnabled('spotify') ? 'disabled' : (this.config.spotify?.clientId ? 'enabled' : 'disabled (no credentials)')}`);
|
|
298
|
+
log.info('STREAMIFY', `SoundCloud: ${this._isProviderEnabled('soundcloud') ? 'enabled' : 'disabled'}`);
|
|
299
|
+
log.info('STREAMIFY', `Cookies: ${this.config.cookiesPath ? 'configured' : 'not configured'}`);
|
|
300
|
+
resolve();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
this.server.on('error', reject);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async stop() {
|
|
308
|
+
return new Promise((resolve) => {
|
|
309
|
+
if (this.server) {
|
|
310
|
+
this.server.close(resolve);
|
|
311
|
+
} else {
|
|
312
|
+
resolve();
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
module.exports = Server;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
const colors = {
|
|
2
|
+
reset: '\x1b[0m',
|
|
3
|
+
bold: '\x1b[1m',
|
|
4
|
+
dim: '\x1b[2m',
|
|
5
|
+
|
|
6
|
+
red: '\x1b[31m',
|
|
7
|
+
green: '\x1b[32m',
|
|
8
|
+
yellow: '\x1b[33m',
|
|
9
|
+
blue: '\x1b[34m',
|
|
10
|
+
magenta: '\x1b[35m',
|
|
11
|
+
cyan: '\x1b[36m',
|
|
12
|
+
white: '\x1b[37m',
|
|
13
|
+
gray: '\x1b[90m',
|
|
14
|
+
|
|
15
|
+
bgRed: '\x1b[41m',
|
|
16
|
+
bgGreen: '\x1b[42m',
|
|
17
|
+
bgYellow: '\x1b[43m',
|
|
18
|
+
bgBlue: '\x1b[44m',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const LOG_LEVELS = {
|
|
22
|
+
none: 0,
|
|
23
|
+
error: 1,
|
|
24
|
+
warn: 2,
|
|
25
|
+
info: 3,
|
|
26
|
+
debug: 4
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let currentLevel = LOG_LEVELS.info;
|
|
30
|
+
let useColors = true;
|
|
31
|
+
|
|
32
|
+
function setLevel(level) {
|
|
33
|
+
if (typeof level === 'string') {
|
|
34
|
+
currentLevel = LOG_LEVELS[level.toLowerCase()] ?? LOG_LEVELS.info;
|
|
35
|
+
} else if (typeof level === 'number') {
|
|
36
|
+
currentLevel = level;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function setColors(enabled) {
|
|
41
|
+
useColors = enabled;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function colorize(color, text) {
|
|
45
|
+
if (!useColors) return text;
|
|
46
|
+
return `${colors[color] || ''}${text}${colors.reset}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function formatTime() {
|
|
50
|
+
const now = new Date();
|
|
51
|
+
return colorize('gray', `[${now.toTimeString().split(' ')[0]}]`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function formatTag(tag, color) {
|
|
55
|
+
return colorize(color, `[${tag}]`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function debug(tag, ...args) {
|
|
59
|
+
if (currentLevel < LOG_LEVELS.debug) return;
|
|
60
|
+
console.log(formatTime(), formatTag(tag, 'gray'), colorize('dim', args.join(' ')));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function info(tag, ...args) {
|
|
64
|
+
if (currentLevel < LOG_LEVELS.info) return;
|
|
65
|
+
console.log(formatTime(), formatTag(tag, 'cyan'), ...args);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function success(tag, ...args) {
|
|
69
|
+
if (currentLevel < LOG_LEVELS.info) return;
|
|
70
|
+
console.log(formatTime(), formatTag(tag, 'green'), colorize('green', args.join(' ')));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function warn(tag, ...args) {
|
|
74
|
+
if (currentLevel < LOG_LEVELS.warn) return;
|
|
75
|
+
console.warn(formatTime(), formatTag(tag, 'yellow'), colorize('yellow', args.join(' ')));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function error(tag, ...args) {
|
|
79
|
+
if (currentLevel < LOG_LEVELS.error) return;
|
|
80
|
+
console.error(formatTime(), formatTag(tag, 'red'), colorize('red', args.join(' ')));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function stream(source, id, message) {
|
|
84
|
+
if (currentLevel < LOG_LEVELS.debug) return;
|
|
85
|
+
const sourceColor = {
|
|
86
|
+
youtube: 'red',
|
|
87
|
+
spotify: 'green',
|
|
88
|
+
soundcloud: 'yellow'
|
|
89
|
+
}[source] || 'white';
|
|
90
|
+
|
|
91
|
+
console.log(
|
|
92
|
+
formatTime(),
|
|
93
|
+
formatTag('STREAM', sourceColor),
|
|
94
|
+
colorize('dim', `[${id}]`),
|
|
95
|
+
message
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function banner() {
|
|
100
|
+
if (currentLevel < LOG_LEVELS.info) return;
|
|
101
|
+
const text = `
|
|
102
|
+
${colorize('cyan', '╔═══════════════════════════════════════════════╗')}
|
|
103
|
+
${colorize('cyan', '║')}${colorize('bold', ' STREAMIFY v1.0.0 ')}${colorize('cyan', '║')}
|
|
104
|
+
${colorize('cyan', '║')} Audio Streaming Library for Discord Bots ${colorize('cyan', '║')}
|
|
105
|
+
${colorize('cyan', '╚═══════════════════════════════════════════════╝')}`;
|
|
106
|
+
console.log(text);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function init(config = {}) {
|
|
110
|
+
if (config.logLevel !== undefined) {
|
|
111
|
+
setLevel(config.logLevel);
|
|
112
|
+
} else if (process.env.LOG_LEVEL) {
|
|
113
|
+
setLevel(process.env.LOG_LEVEL);
|
|
114
|
+
} else if (process.env.DEBUG === 'true' || process.env.DEBUG === '1') {
|
|
115
|
+
setLevel('debug');
|
|
116
|
+
} else if (config.silent || process.env.SILENT === 'true') {
|
|
117
|
+
setLevel('none');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (config.colors !== undefined) {
|
|
121
|
+
setColors(config.colors);
|
|
122
|
+
} else if (process.env.NO_COLOR) {
|
|
123
|
+
setColors(false);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
debug,
|
|
129
|
+
info,
|
|
130
|
+
success,
|
|
131
|
+
warn,
|
|
132
|
+
error,
|
|
133
|
+
stream,
|
|
134
|
+
banner,
|
|
135
|
+
setLevel,
|
|
136
|
+
setColors,
|
|
137
|
+
init,
|
|
138
|
+
LOG_LEVELS
|
|
139
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const activeStreams = new Map();
|
|
2
|
+
let emitter = null;
|
|
3
|
+
|
|
4
|
+
function setEventEmitter(eventEmitter) {
|
|
5
|
+
emitter = eventEmitter;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function registerStream(id, streamData) {
|
|
9
|
+
const data = {
|
|
10
|
+
...streamData,
|
|
11
|
+
startTime: Date.now(),
|
|
12
|
+
streamStartTime: Date.now(),
|
|
13
|
+
seekOffset: streamData.filters?.start || 0,
|
|
14
|
+
paused: false,
|
|
15
|
+
pausedAt: null
|
|
16
|
+
};
|
|
17
|
+
activeStreams.set(id, data);
|
|
18
|
+
|
|
19
|
+
if (emitter) {
|
|
20
|
+
emitter.emit('streamStart', {
|
|
21
|
+
id,
|
|
22
|
+
source: streamData.source,
|
|
23
|
+
trackId: streamData.videoId || streamData.trackId || id,
|
|
24
|
+
filters: streamData.filters || {},
|
|
25
|
+
startTime: data.startTime
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function unregisterStream(id, code = 0, error = null) {
|
|
31
|
+
const stream = activeStreams.get(id);
|
|
32
|
+
if (!stream) return;
|
|
33
|
+
|
|
34
|
+
activeStreams.delete(id);
|
|
35
|
+
|
|
36
|
+
if (emitter) {
|
|
37
|
+
if (error) {
|
|
38
|
+
emitter.emit('streamError', {
|
|
39
|
+
id,
|
|
40
|
+
source: stream.source,
|
|
41
|
+
trackId: stream.videoId || stream.trackId || id,
|
|
42
|
+
filters: stream.filters || {},
|
|
43
|
+
startTime: stream.startTime,
|
|
44
|
+
error
|
|
45
|
+
});
|
|
46
|
+
} else {
|
|
47
|
+
emitter.emit('streamEnd', {
|
|
48
|
+
id,
|
|
49
|
+
source: stream.source,
|
|
50
|
+
trackId: stream.videoId || stream.trackId || id,
|
|
51
|
+
filters: stream.filters || {},
|
|
52
|
+
startTime: stream.startTime,
|
|
53
|
+
duration: Date.now() - stream.startTime,
|
|
54
|
+
code
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getActiveStreams() {
|
|
61
|
+
return activeStreams;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getStreamPosition(id) {
|
|
65
|
+
const stream = activeStreams.get(id);
|
|
66
|
+
if (!stream) return null;
|
|
67
|
+
|
|
68
|
+
const elapsed = (Date.now() - stream.streamStartTime) / 1000;
|
|
69
|
+
return stream.seekOffset + elapsed;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getStreamById(id) {
|
|
73
|
+
return activeStreams.get(id);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function updateStreamFilters(id, newFilters) {
|
|
77
|
+
const stream = activeStreams.get(id);
|
|
78
|
+
if (!stream) return null;
|
|
79
|
+
|
|
80
|
+
stream.filters = { ...stream.filters, ...newFilters };
|
|
81
|
+
return stream;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function killAllStreams() {
|
|
85
|
+
for (const [id, stream] of activeStreams) {
|
|
86
|
+
if (stream.ytdlp && !stream.ytdlp.killed) {
|
|
87
|
+
stream.ytdlp.kill('SIGTERM');
|
|
88
|
+
}
|
|
89
|
+
if (stream.ffmpeg && !stream.ffmpeg.killed) {
|
|
90
|
+
stream.ffmpeg.kill('SIGTERM');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
activeStreams.clear();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
process.on('SIGTERM', killAllStreams);
|
|
97
|
+
process.on('SIGINT', killAllStreams);
|
|
98
|
+
|
|
99
|
+
module.exports = {
|
|
100
|
+
registerStream,
|
|
101
|
+
unregisterStream,
|
|
102
|
+
getActiveStreams,
|
|
103
|
+
getStreamById,
|
|
104
|
+
getStreamPosition,
|
|
105
|
+
updateStreamFilters,
|
|
106
|
+
killAllStreams,
|
|
107
|
+
setEventEmitter
|
|
108
|
+
};
|