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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "streamify-audio",
3
- "version": "2.0.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",
@@ -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
 
@@ -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
- } = require('@discordjs/voice');
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) {
@@ -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', isYouTube ? '18/22/bestaudio[ext=webm]/bestaudio/best' : 'bestaudio/best',
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
- }, 15000);
200
+ }, timeoutMs);
178
201
 
179
202
  let resolved = false;
180
203
 
@@ -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({