streamify-audio 2.0.1 → 2.0.3
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/package.json +1 -1
- package/src/discord/Manager.js +12 -2
- package/src/discord/Stream.js +19 -6
- package/src/providers/youtube.js +10 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "streamify-audio",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "Dual-mode audio library: HTTP streaming proxy + Discord player (Lavalink alternative). Supports YouTube, Spotify, SoundCloud with audio filters.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
package/src/discord/Manager.js
CHANGED
|
@@ -198,6 +198,11 @@ class Manager extends EventEmitter {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
async search(query, options = {}) {
|
|
201
|
+
const isUrl = query.startsWith('http://') || query.startsWith('https://');
|
|
202
|
+
if (isUrl) {
|
|
203
|
+
return this.resolve(query);
|
|
204
|
+
}
|
|
205
|
+
|
|
201
206
|
const source = options.source || this._detectSource(query) || 'youtube';
|
|
202
207
|
const limit = options.limit || 10;
|
|
203
208
|
|
|
@@ -313,6 +318,10 @@ class Manager extends EventEmitter {
|
|
|
313
318
|
}
|
|
314
319
|
}
|
|
315
320
|
|
|
321
|
+
const isUrl = query.startsWith('http://') || query.startsWith('https://');
|
|
322
|
+
if (isUrl) {
|
|
323
|
+
return { loadType: 'empty', tracks: [] };
|
|
324
|
+
}
|
|
316
325
|
return this.search(query);
|
|
317
326
|
}
|
|
318
327
|
|
|
@@ -332,7 +341,8 @@ class Manager extends EventEmitter {
|
|
|
332
341
|
youtube: [
|
|
333
342
|
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/,
|
|
334
343
|
/(?:youtube\.com\/shorts\/)([a-zA-Z0-9_-]{11})/,
|
|
335
|
-
/(?:music\.youtube\.com\/watch\?v=)([a-zA-Z0-9_-]{11})
|
|
344
|
+
/(?:music\.youtube\.com\/watch\?v=)([a-zA-Z0-9_-]{11})/,
|
|
345
|
+
/(?:youtube\.com\/live\/)([a-zA-Z0-9_-]{11})/
|
|
336
346
|
],
|
|
337
347
|
spotify: [
|
|
338
348
|
/open\.spotify\.com\/track\/([a-zA-Z0-9]+)/,
|
|
@@ -357,7 +367,7 @@ class Manager extends EventEmitter {
|
|
|
357
367
|
|
|
358
368
|
_extractId(input, source) {
|
|
359
369
|
const patterns = {
|
|
360
|
-
youtube: /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/shorts\/|music\.youtube\.com\/watch\?v
|
|
370
|
+
youtube: /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/shorts\/|music\.youtube\.com\/watch\?v=|youtube\.com\/live\/)([a-zA-Z0-9_-]{11})/,
|
|
361
371
|
youtube_playlist: /youtube\.com\/playlist\?list=([a-zA-Z0-9_-]+)/,
|
|
362
372
|
spotify: /(?:open\.spotify\.com\/track\/|spotify:track:)([a-zA-Z0-9]+)/,
|
|
363
373
|
spotify_playlist: /open\.spotify\.com\/playlist\/([a-zA-Z0-9]+)/,
|
package/src/discord/Stream.js
CHANGED
|
@@ -47,8 +47,17 @@ class StreamController {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
const isYouTube = source === 'youtube' || source === 'spotify';
|
|
50
|
+
const isLive = this.track.isLive === true || this.track.duration === 0;
|
|
51
|
+
|
|
52
|
+
let formatString;
|
|
53
|
+
if (isLive) {
|
|
54
|
+
formatString = 'bestaudio*/best';
|
|
55
|
+
} else {
|
|
56
|
+
formatString = isYouTube ? '18/22/bestaudio[ext=webm]/bestaudio/best' : 'bestaudio/best';
|
|
57
|
+
}
|
|
58
|
+
|
|
50
59
|
const ytdlpArgs = [
|
|
51
|
-
'-f',
|
|
60
|
+
'-f', formatString,
|
|
52
61
|
'--no-playlist',
|
|
53
62
|
'--no-check-certificates',
|
|
54
63
|
'--no-warnings',
|
|
@@ -58,7 +67,10 @@ class StreamController {
|
|
|
58
67
|
url
|
|
59
68
|
];
|
|
60
69
|
|
|
61
|
-
if (
|
|
70
|
+
if (isLive) {
|
|
71
|
+
ytdlpArgs.push('--no-live-from-start');
|
|
72
|
+
log.info('STREAM', `Live stream detected, using live-compatible format`);
|
|
73
|
+
} else if (isYouTube) {
|
|
62
74
|
ytdlpArgs.push('--extractor-args', 'youtube:player_client=web_creator');
|
|
63
75
|
}
|
|
64
76
|
|
|
@@ -163,7 +175,7 @@ class StreamController {
|
|
|
163
175
|
}
|
|
164
176
|
});
|
|
165
177
|
|
|
166
|
-
await this._waitForData();
|
|
178
|
+
await this._waitForData(isLive);
|
|
167
179
|
|
|
168
180
|
this.resource = createAudioResource(this.ffmpeg.stdout, {
|
|
169
181
|
inputType: StreamType.OggOpus,
|
|
@@ -177,12 +189,13 @@ class StreamController {
|
|
|
177
189
|
return this.resource;
|
|
178
190
|
}
|
|
179
191
|
|
|
180
|
-
_waitForData() {
|
|
192
|
+
_waitForData(isLive = false) {
|
|
181
193
|
return new Promise((resolve, reject) => {
|
|
194
|
+
const timeoutMs = isLive ? 30000 : 15000;
|
|
182
195
|
const timeout = setTimeout(() => {
|
|
183
|
-
log.warn('STREAM', `Timeout waiting for data, proceeding anyway (received: ${this.bytesReceived})`);
|
|
196
|
+
log.warn('STREAM', `Timeout waiting for data, proceeding anyway (received: ${this.bytesReceived}, isLive: ${isLive})`);
|
|
184
197
|
resolve();
|
|
185
|
-
},
|
|
198
|
+
}, timeoutMs);
|
|
186
199
|
|
|
187
200
|
let resolved = false;
|
|
188
201
|
|
package/src/providers/youtube.js
CHANGED
|
@@ -39,12 +39,13 @@ async function search(query, limit, config) {
|
|
|
39
39
|
const tracks = (data.entries || []).map(entry => ({
|
|
40
40
|
id: entry.id,
|
|
41
41
|
title: entry.title,
|
|
42
|
-
duration: entry.duration,
|
|
42
|
+
duration: entry.duration || 0,
|
|
43
43
|
author: entry.channel || entry.uploader,
|
|
44
44
|
thumbnail: entry.thumbnails?.[0]?.url,
|
|
45
45
|
uri: `https://www.youtube.com/watch?v=${entry.id}`,
|
|
46
46
|
streamUrl: `/youtube/stream/${entry.id}`,
|
|
47
|
-
source: 'youtube'
|
|
47
|
+
source: 'youtube',
|
|
48
|
+
isLive: entry.live_status === 'is_live' || entry.is_live === true || !entry.duration
|
|
48
49
|
}));
|
|
49
50
|
const elapsed = Date.now() - startTime;
|
|
50
51
|
log.info('YOUTUBE', `Found ${tracks.length} results (${elapsed}ms)`);
|
|
@@ -87,15 +88,17 @@ async function getInfo(videoId, config) {
|
|
|
87
88
|
}
|
|
88
89
|
try {
|
|
89
90
|
const data = JSON.parse(stdout);
|
|
91
|
+
const isLive = data.live_status === 'is_live' || data.is_live === true || !data.duration;
|
|
90
92
|
resolve({
|
|
91
93
|
id: data.id,
|
|
92
94
|
title: data.title,
|
|
93
|
-
duration: data.duration,
|
|
95
|
+
duration: data.duration || 0,
|
|
94
96
|
author: data.channel || data.uploader,
|
|
95
97
|
thumbnail: data.thumbnail,
|
|
96
98
|
uri: data.webpage_url,
|
|
97
99
|
streamUrl: `/youtube/stream/${data.id}`,
|
|
98
|
-
source: 'youtube'
|
|
100
|
+
source: 'youtube',
|
|
101
|
+
isLive
|
|
99
102
|
});
|
|
100
103
|
} catch (e) {
|
|
101
104
|
reject(new Error('Failed to parse yt-dlp output'));
|
|
@@ -238,12 +241,13 @@ async function getPlaylist(playlistId, config) {
|
|
|
238
241
|
const tracks = (data.entries || []).map(entry => ({
|
|
239
242
|
id: entry.id,
|
|
240
243
|
title: entry.title,
|
|
241
|
-
duration: entry.duration,
|
|
244
|
+
duration: entry.duration || 0,
|
|
242
245
|
author: entry.channel || entry.uploader,
|
|
243
246
|
thumbnail: entry.thumbnails?.[0]?.url,
|
|
244
247
|
uri: `https://www.youtube.com/watch?v=${entry.id}`,
|
|
245
248
|
streamUrl: `/youtube/stream/${entry.id}`,
|
|
246
|
-
source: 'youtube'
|
|
249
|
+
source: 'youtube',
|
|
250
|
+
isLive: entry.live_status === 'is_live' || entry.is_live === true || !entry.duration
|
|
247
251
|
}));
|
|
248
252
|
log.info('YOUTUBE', `Playlist loaded: ${data.title || playlistId} (${tracks.length} tracks)`);
|
|
249
253
|
resolve({
|