streamify-audio 2.0.0 → 2.0.2
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 +31 -0
- package/src/discord/Player.js +12 -4
- package/src/discord/Stream.js +29 -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.2",
|
|
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
|
@@ -6,9 +6,40 @@ const soundcloud = require('../providers/soundcloud');
|
|
|
6
6
|
const log = require('../utils/logger');
|
|
7
7
|
const { loadConfig } = require('../config');
|
|
8
8
|
|
|
9
|
+
function checkDependencies() {
|
|
10
|
+
const missing = [];
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
require('@discordjs/voice');
|
|
14
|
+
} catch (e) {
|
|
15
|
+
missing.push('@discordjs/voice');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
require('@discordjs/opus');
|
|
20
|
+
} catch (e) {
|
|
21
|
+
try {
|
|
22
|
+
require('opusscript');
|
|
23
|
+
} catch (e2) {
|
|
24
|
+
missing.push('@discordjs/opus (or opusscript)');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (missing.length > 0) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Missing required dependencies for Discord mode:\n\n` +
|
|
31
|
+
` npm install ${missing.join(' ')}\n\n` +
|
|
32
|
+
`These packages are required for voice playback.`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
9
37
|
class Manager extends EventEmitter {
|
|
10
38
|
constructor(client, options = {}) {
|
|
11
39
|
super();
|
|
40
|
+
|
|
41
|
+
checkDependencies();
|
|
42
|
+
|
|
12
43
|
this.client = client;
|
|
13
44
|
this.players = new Map();
|
|
14
45
|
|
package/src/discord/Player.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
const { EventEmitter } = require('events');
|
|
2
|
+
const Queue = require('./Queue');
|
|
3
|
+
const { createStream } = require('./Stream');
|
|
4
|
+
const log = require('../utils/logger');
|
|
5
|
+
|
|
6
|
+
let voiceModule;
|
|
7
|
+
try {
|
|
8
|
+
voiceModule = require('@discordjs/voice');
|
|
9
|
+
} catch (e) {
|
|
10
|
+
voiceModule = {};
|
|
11
|
+
}
|
|
12
|
+
|
|
2
13
|
const {
|
|
3
14
|
joinVoiceChannel,
|
|
4
15
|
createAudioPlayer,
|
|
@@ -6,10 +17,7 @@ const {
|
|
|
6
17
|
VoiceConnectionStatus,
|
|
7
18
|
entersState,
|
|
8
19
|
NoSubscriberBehavior
|
|
9
|
-
} =
|
|
10
|
-
const Queue = require('./Queue');
|
|
11
|
-
const { createStream } = require('./Stream');
|
|
12
|
-
const log = require('../utils/logger');
|
|
20
|
+
} = voiceModule;
|
|
13
21
|
|
|
14
22
|
class Player extends EventEmitter {
|
|
15
23
|
constructor(manager, options) {
|
package/src/discord/Stream.js
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
const { spawn } = require('child_process');
|
|
2
|
-
const { createAudioResource, StreamType } = require('@discordjs/voice');
|
|
3
2
|
const { buildFfmpegArgs } = require('../filters/ffmpeg');
|
|
4
3
|
const log = require('../utils/logger');
|
|
5
4
|
|
|
5
|
+
let voiceModule;
|
|
6
|
+
try {
|
|
7
|
+
voiceModule = require('@discordjs/voice');
|
|
8
|
+
} catch (e) {
|
|
9
|
+
voiceModule = null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { createAudioResource, StreamType } = voiceModule || {};
|
|
13
|
+
|
|
6
14
|
class StreamController {
|
|
7
15
|
constructor(track, filters, config) {
|
|
8
16
|
this.track = track;
|
|
@@ -39,8 +47,17 @@ class StreamController {
|
|
|
39
47
|
}
|
|
40
48
|
|
|
41
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[ext=webm]/bestaudio/best';
|
|
55
|
+
} else {
|
|
56
|
+
formatString = isYouTube ? '18/22/bestaudio[ext=webm]/bestaudio/best' : 'bestaudio/best';
|
|
57
|
+
}
|
|
58
|
+
|
|
42
59
|
const ytdlpArgs = [
|
|
43
|
-
'-f',
|
|
60
|
+
'-f', formatString,
|
|
44
61
|
'--no-playlist',
|
|
45
62
|
'--no-check-certificates',
|
|
46
63
|
'--no-warnings',
|
|
@@ -50,6 +67,11 @@ class StreamController {
|
|
|
50
67
|
url
|
|
51
68
|
];
|
|
52
69
|
|
|
70
|
+
if (isLive) {
|
|
71
|
+
ytdlpArgs.push('--live-from-start');
|
|
72
|
+
log.info('STREAM', `Live stream detected, using live-compatible format`);
|
|
73
|
+
}
|
|
74
|
+
|
|
53
75
|
if (isYouTube) {
|
|
54
76
|
ytdlpArgs.push('--extractor-args', 'youtube:player_client=web_creator');
|
|
55
77
|
}
|
|
@@ -155,7 +177,7 @@ class StreamController {
|
|
|
155
177
|
}
|
|
156
178
|
});
|
|
157
179
|
|
|
158
|
-
await this._waitForData();
|
|
180
|
+
await this._waitForData(isLive);
|
|
159
181
|
|
|
160
182
|
this.resource = createAudioResource(this.ffmpeg.stdout, {
|
|
161
183
|
inputType: StreamType.OggOpus,
|
|
@@ -169,12 +191,13 @@ class StreamController {
|
|
|
169
191
|
return this.resource;
|
|
170
192
|
}
|
|
171
193
|
|
|
172
|
-
_waitForData() {
|
|
194
|
+
_waitForData(isLive = false) {
|
|
173
195
|
return new Promise((resolve, reject) => {
|
|
196
|
+
const timeoutMs = isLive ? 30000 : 15000;
|
|
174
197
|
const timeout = setTimeout(() => {
|
|
175
|
-
log.warn('STREAM', `Timeout waiting for data, proceeding anyway (received: ${this.bytesReceived})`);
|
|
198
|
+
log.warn('STREAM', `Timeout waiting for data, proceeding anyway (received: ${this.bytesReceived}, isLive: ${isLive})`);
|
|
176
199
|
resolve();
|
|
177
|
-
},
|
|
200
|
+
}, timeoutMs);
|
|
178
201
|
|
|
179
202
|
let resolved = false;
|
|
180
203
|
|
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({
|