streamify-audio 2.3.0 → 2.3.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.
Files changed (39) hide show
  1. package/index.d.ts +9 -0
  2. package/package.json +1 -1
  3. package/src/config.js +11 -0
  4. package/src/discord/Manager.js +1 -1
  5. package/src/discord/Stream.js +9 -12
  6. package/src/providers/spotify.js +2 -2
  7. package/docs/README.md +0 -31
  8. package/docs/automation.md +0 -186
  9. package/docs/configuration.md +0 -198
  10. package/docs/discord/events.md +0 -206
  11. package/docs/discord/manager.md +0 -195
  12. package/docs/discord/player.md +0 -263
  13. package/docs/discord/queue.md +0 -197
  14. package/docs/examples/advanced-bot.md +0 -391
  15. package/docs/examples/basic-bot.md +0 -182
  16. package/docs/examples/lavalink.md +0 -156
  17. package/docs/filters.md +0 -347
  18. package/docs/http/endpoints.md +0 -224
  19. package/docs/http/server.md +0 -174
  20. package/docs/plans/2026-02-22-stream-revamp-design.md +0 -88
  21. package/docs/plans/2026-02-22-stream-revamp-plan.md +0 -814
  22. package/docs/quick-start.md +0 -92
  23. package/docs/sources.md +0 -189
  24. package/docs/sponsorblock.md +0 -95
  25. package/tests/cache.test.js +0 -234
  26. package/tests/config.test.js +0 -44
  27. package/tests/error-handling.test.js +0 -318
  28. package/tests/ffmpeg.test.js +0 -66
  29. package/tests/filters-edge.test.js +0 -333
  30. package/tests/http.test.js +0 -24
  31. package/tests/integration.test.js +0 -325
  32. package/tests/local.test.js +0 -37
  33. package/tests/queue.test.js +0 -94
  34. package/tests/spotify.test.js +0 -238
  35. package/tests/stream.test.js +0 -217
  36. package/tests/twitch.test.js +0 -42
  37. package/tests/utils.test.js +0 -60
  38. package/tests/youtube.test.js +0 -219
  39. package/youtube-cookies.txt +0 -26
package/index.d.ts CHANGED
@@ -203,6 +203,15 @@ declare module 'streamify-audio' {
203
203
  enabled?: boolean;
204
204
  maxTracks?: number;
205
205
  };
206
+ stream?: {
207
+ dataTimeout?: number;
208
+ pipeDataTimeout?: number;
209
+ liveDataTimeout?: number;
210
+ extractTimeout?: number;
211
+ urlCacheTTL?: number;
212
+ bufferSize?: string;
213
+ maxRetries?: number;
214
+ };
206
215
  }
207
216
 
208
217
  export interface ManagerStats {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "streamify-audio",
3
- "version": "2.3.0",
3
+ "version": "2.3.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/config.js CHANGED
@@ -41,10 +41,16 @@ const defaults = {
41
41
  },
42
42
  stream: {
43
43
  dataTimeout: 8000,
44
+ pipeDataTimeout: 20000,
44
45
  liveDataTimeout: 15000,
46
+ extractTimeout: 30000,
45
47
  urlCacheTTL: 1800,
46
48
  bufferSize: '1M',
47
49
  maxRetries: 2
50
+ },
51
+ sponsorblock: {
52
+ enabled: false,
53
+ categories: ['sponsor', 'selfpromo']
48
54
  }
49
55
  };
50
56
 
@@ -119,6 +125,11 @@ function load(options = {}) {
119
125
  ...defaults.stream,
120
126
  ...fileConfig.stream,
121
127
  ...options.stream
128
+ },
129
+ sponsorblock: {
130
+ ...defaults.sponsorblock,
131
+ ...fileConfig.sponsorblock,
132
+ ...options.sponsorblock
122
133
  }
123
134
  };
124
135
 
@@ -403,7 +403,7 @@ class Manager extends EventEmitter {
403
403
  }
404
404
 
405
405
  async _resolveSpotifyToYouTube(track) {
406
- const videoId = await spotify.resolveToYouTube(track.id, this.config);
406
+ const videoId = await spotify.resolveToYouTube(track.id, this.config, track);
407
407
  return videoId;
408
408
  }
409
409
 
@@ -95,7 +95,7 @@ class StreamController {
95
95
  log.info('STREAM', `Resolving Spotify track to YouTube: ${this.track.title}`);
96
96
  try {
97
97
  const spotify = require('../providers/spotify');
98
- videoId = await spotify.resolveToYouTube(this.track.id, this.config);
98
+ videoId = await spotify.resolveToYouTube(this.track.id, this.config, this.track);
99
99
  this.track._resolvedId = videoId;
100
100
  this.metrics.metadata = Date.now() - this.startTime;
101
101
  } catch (error) {
@@ -238,7 +238,7 @@ class StreamController {
238
238
  ytdlpArgs.unshift('--cookies', this.config.cookiesPath);
239
239
  }
240
240
 
241
- if (this.config.sponsorblock?.enabled !== false && isYouTube) {
241
+ if (this.config.sponsorblock?.enabled === true && isYouTube) {
242
242
  const categories = this.config.sponsorblock?.categories || ['sponsor', 'selfpromo'];
243
243
  ytdlpArgs.push('--sponsorblock-remove', categories.join(','));
244
244
  }
@@ -365,11 +365,9 @@ class StreamController {
365
365
  const url = `https://www.youtube.com/watch?v=${videoId}`;
366
366
  const args = [
367
367
  '--get-url',
368
- '-f', config.ytdlp.format,
368
+ '-f', 'bestaudio/best',
369
369
  '--no-playlist',
370
- '--no-check-certificates',
371
370
  '--no-warnings',
372
- '--no-cache-dir',
373
371
  url
374
372
  ];
375
373
 
@@ -381,20 +379,16 @@ class StreamController {
381
379
  args.unshift('--cookies', config.cookiesPath);
382
380
  }
383
381
 
384
- if (config.sponsorblock?.enabled !== false && isYouTube) {
385
- const categories = config.sponsorblock?.categories || ['sponsor', 'selfpromo'];
386
- args.push('--sponsorblock-remove', categories.join(','));
387
- }
388
-
389
382
  const env = { ...process.env, PATH: '/usr/local/bin:/root/.deno/bin:' + process.env.PATH };
390
383
  const proc = spawn(config.ytdlpPath, args, { env });
391
384
 
392
385
  let stdout = '';
393
386
  let stderr = '';
387
+ const extractTimeout = config.stream?.extractTimeout || 30000;
394
388
  const timeout = setTimeout(() => {
395
389
  proc.kill('SIGKILL');
396
390
  reject(new Error('URL extraction timed out'));
397
- }, 15000);
391
+ }, extractTimeout);
398
392
 
399
393
  proc.stdout.on('data', (data) => { stdout += data; });
400
394
  proc.stderr.on('data', (data) => { stderr += data; });
@@ -422,9 +416,12 @@ class StreamController {
422
416
  const ffmpeg = this.ffmpeg;
423
417
  const ytdlp = this.ytdlp;
424
418
  const isLive = this.track.isLive === true || this.track.duration === 0;
419
+ const isPipe = !!this.ytdlp;
425
420
  const timeoutMs = isLive
426
421
  ? (this.config.stream?.liveDataTimeout || 15000)
427
- : (this.config.stream?.dataTimeout || 8000);
422
+ : isPipe
423
+ ? (this.config.stream?.pipeDataTimeout || 20000)
424
+ : (this.config.stream?.dataTimeout || 8000);
428
425
 
429
426
  return new Promise((resolve, reject) => {
430
427
  if (!ffmpeg) return resolve();
@@ -104,14 +104,14 @@ async function getInfo(trackId, config) {
104
104
  };
105
105
  }
106
106
 
107
- async function resolveToYouTube(trackId, config) {
107
+ async function resolveToYouTube(trackId, config, trackInfo = null) {
108
108
  const cached = youtubeIdCache.get(trackId);
109
109
  if (cached && Date.now() < cached.expires) {
110
110
  log.info('SPOTIFY', `Using cached YouTube ID: ${cached.videoId}`);
111
111
  return cached.videoId;
112
112
  }
113
113
 
114
- const track = await getInfo(trackId, config);
114
+ const track = trackInfo || await getInfo(trackId, config);
115
115
  const searchQuery = `${track.author} - ${track.title}`;
116
116
  log.info('SPOTIFY', `Searching YouTube: "${searchQuery}"`);
117
117
 
package/docs/README.md DELETED
@@ -1,31 +0,0 @@
1
- # Streamify Documentation
2
-
3
- ## Getting Started
4
-
5
- - [Quick Start](./quick-start.md) — Get up and running in 5 minutes
6
- - [Configuration](./configuration.md) — All configuration options
7
-
8
- ## Discord Player Mode
9
-
10
- - [Manager](./discord/manager.md) — Create and manage players
11
- - [Player](./discord/player.md) — Playback control and state
12
- - [Queue](./discord/queue.md) — Queue management
13
- - [Events](./discord/events.md) — All player events
14
-
15
- ## HTTP Server Mode
16
-
17
- - [Server Setup](./http/server.md) — Start the HTTP server
18
- - [Endpoints](./http/endpoints.md) — API reference
19
-
20
- ## Features
21
-
22
- - [Filters](./filters.md) — Audio filters and presets
23
- - [Sources](./sources.md) — YouTube, Spotify, SoundCloud
24
- - [Automation](./automation.md) — Auto-pause, auto-leave, autoplay
25
- - [Sponsorblock](./sponsorblock.md) — Skip YouTube sponsors
26
-
27
- ## Examples
28
-
29
- - [Basic Discord Bot](./examples/basic-bot.md)
30
- - [Advanced Bot with Queue](./examples/advanced-bot.md)
31
- - [HTTP Server with Lavalink](./examples/lavalink.md)
@@ -1,186 +0,0 @@
1
- # Automation
2
-
3
- Streamify includes built-in automation features for common scenarios.
4
-
5
- ## Auto-Pause
6
-
7
- Automatically pauses playback when users leave the voice channel and resumes when they return.
8
-
9
- ### Configuration
10
-
11
- ```javascript
12
- const manager = new Streamify.Manager(client, {
13
- autoPause: {
14
- enabled: true,
15
- minUsers: 1 // Pause when users drop below this
16
- }
17
- });
18
- ```
19
-
20
- ### Behavior
21
-
22
- 1. User count drops below `minUsers`
23
- 2. Playback pauses immediately
24
- 3. Stream is destroyed (saves resources)
25
- 4. Position is saved
26
- 5. When users rejoin and count reaches `minUsers`:
27
- 6. Stream recreates with seek
28
- 7. Playback resumes
29
-
30
- ### Events
31
-
32
- ```javascript
33
- player.on('autoPause', (userCount) => {
34
- channel.send(`Paused (${userCount} users in channel)`);
35
- });
36
-
37
- player.on('autoResume', (userCount) => {
38
- channel.send(`Resumed (${userCount} users now)`);
39
- });
40
- ```
41
-
42
- ### Runtime Toggle
43
-
44
- ```javascript
45
- player.setAutoPause(false); // Disable
46
- player.setAutoPause(true); // Enable
47
- ```
48
-
49
- ## Auto-Leave
50
-
51
- Automatically leaves the voice channel after a period of inactivity.
52
-
53
- ### Configuration
54
-
55
- ```javascript
56
- const manager = new Streamify.Manager(client, {
57
- autoLeave: {
58
- enabled: true,
59
- emptyDelay: 30000, // Leave 30s after channel empty
60
- inactivityTimeout: 300000 // Leave after 5min of not playing
61
- }
62
- });
63
- ```
64
-
65
- ### Behavior
66
-
67
- **Empty Channel:**
68
- 1. All users leave
69
- 2. 30 second countdown starts
70
- 3. If no one rejoins, player destroys
71
-
72
- **Inactivity:**
73
- 1. Queue ends, nothing playing
74
- 2. 5 minute countdown starts
75
- 3. If nothing plays, player destroys
76
-
77
- ### Events
78
-
79
- ```javascript
80
- player.on('channelEmpty', () => {
81
- channel.send('Everyone left. Leaving in 30 seconds...');
82
- });
83
-
84
- player.on('destroy', () => {
85
- channel.send('Disconnected due to inactivity.');
86
- });
87
- ```
88
-
89
- ## Autoplay
90
-
91
- Automatically plays related tracks when the queue ends.
92
-
93
- ### Configuration
94
-
95
- ```javascript
96
- const manager = new Streamify.Manager(client, {
97
- autoplay: {
98
- enabled: false, // Off by default
99
- maxTracks: 5 // How many related tracks to fetch
100
- }
101
- });
102
- ```
103
-
104
- ### Behavior
105
-
106
- 1. Queue ends (last track finishes)
107
- 2. Fetch related tracks from YouTube Mix or Spotify Recommendations
108
- 3. Add to queue and start playing
109
- 4. Repeat when those tracks end
110
-
111
- ### Events
112
-
113
- ```javascript
114
- player.on('autoplayStart', (lastTrack) => {
115
- console.log(`Finding tracks like: ${lastTrack.title}`);
116
- });
117
-
118
- player.on('autoplayAdd', (tracks) => {
119
- channel.send(`Autoplay added ${tracks.length} tracks`);
120
- });
121
- ```
122
-
123
- ### Runtime Toggle
124
-
125
- ```javascript
126
- player.setAutoplay(true); // Enable
127
- player.setAutoplay(false); // Disable
128
- ```
129
-
130
- ### Track Identification
131
-
132
- Autoplay tracks are marked:
133
-
134
- ```javascript
135
- player.on('trackStart', (track) => {
136
- if (track.isAutoplay) {
137
- channel.send(`Autoplay: **${track.title}**`);
138
- } else {
139
- channel.send(`Now playing: **${track.title}**`);
140
- }
141
- });
142
- ```
143
-
144
- ## Combining Features
145
-
146
- All automation features work together:
147
-
148
- ```javascript
149
- const manager = new Streamify.Manager(client, {
150
- autoPause: {
151
- enabled: true,
152
- minUsers: 1
153
- },
154
- autoLeave: {
155
- enabled: true,
156
- emptyDelay: 30000,
157
- inactivityTimeout: 300000
158
- },
159
- autoplay: {
160
- enabled: true,
161
- maxTracks: 5
162
- }
163
- });
164
- ```
165
-
166
- **Scenario: User leaves during playback**
167
- 1. `autoPause` triggers → pauses playback
168
- 2. `autoLeave` starts 30s countdown
169
- 3. User rejoins within 30s
170
- 4. `autoResume` triggers → playback continues
171
- 5. Countdown cancelled
172
-
173
- **Scenario: Queue ends**
174
- 1. `autoplay` fetches related tracks
175
- 2. Playback continues with recommendations
176
- 3. If autoplay disabled, `autoLeave` inactivity timeout starts
177
-
178
- ## Disabling All Automation
179
-
180
- ```javascript
181
- const manager = new Streamify.Manager(client, {
182
- autoPause: { enabled: false },
183
- autoLeave: { enabled: false },
184
- autoplay: { enabled: false }
185
- });
186
- ```
@@ -1,198 +0,0 @@
1
- # Configuration
2
-
3
- ## Discord Player Mode
4
-
5
- ```javascript
6
- const manager = new Streamify.Manager(client, {
7
- // Paths (required)
8
- ytdlpPath: '/usr/local/bin/yt-dlp',
9
- ffmpegPath: '/usr/bin/ffmpeg',
10
-
11
- // YouTube cookies (optional)
12
- cookiesPath: './cookies.txt',
13
-
14
- // Providers (optional - all enabled by default)
15
- providers: {
16
- youtube: { enabled: true },
17
- spotify: { enabled: true },
18
- soundcloud: { enabled: true }
19
- },
20
-
21
- // Spotify credentials (optional)
22
- spotify: {
23
- clientId: process.env.SPOTIFY_CLIENT_ID,
24
- clientSecret: process.env.SPOTIFY_CLIENT_SECRET
25
- },
26
-
27
- // Audio settings
28
- audio: {
29
- bitrate: '128k', // Audio bitrate
30
- format: 'opus', // opus, mp3, aac
31
- vbr: true, // Enable Variable Bitrate (Opus only)
32
- compressionLevel: 10, // Opus compression level (0-10)
33
- application: 'audio' // Opus application (audio, voip, lowdelay)
34
- },
35
-
36
- // yt-dlp settings
37
- ytdlp: {
38
- format: 'bestaudio/best', // Custom yt-dlp format string
39
- additionalArgs: [] // Extra arguments for yt-dlp (e.g. proxy)
40
- },
41
-
42
- // Defaults
43
- defaultVolume: 80, // 0-200
44
- maxPreviousTracks: 25, // History size
45
-
46
- // Sponsorblock
47
- sponsorblock: {
48
- enabled: true,
49
- categories: ['sponsor', 'selfpromo', 'intro', 'outro']
50
- },
51
-
52
- // Auto-leave
53
- autoLeave: {
54
- enabled: true,
55
- emptyDelay: 30000, // Leave 30s after channel empty
56
- inactivityTimeout: 300000 // Leave after 5min idle
57
- },
58
-
59
- // Auto-pause
60
- autoPause: {
61
- enabled: true,
62
- minUsers: 1 // Pause when users drop below this
63
- },
64
-
65
- // Autoplay
66
- autoplay: {
67
- enabled: false,
68
- maxTracks: 5 // Related tracks to fetch
69
- },
70
-
71
- // Voice Channel Status
72
- voiceChannelStatus: {
73
- enabled: true, // Show "Now Playing" in VC description
74
- template: '🎶 Now Playing: {title} - {artist}' // Custom template
75
- }
76
- });
77
- ```
78
-
79
- **Voice Channel Status Template Variables:**
80
- - `{title}` - Track title
81
- - `{artist}` - Track artist/author
82
- - `{requester}` - Username of the requester (if provided)
83
-
84
- ## HTTP Server Mode
85
-
86
- ```javascript
87
- const streamify = new Streamify({
88
- // Server
89
- port: 8787,
90
- host: '0.0.0.0',
91
-
92
- // Paths
93
- ytdlpPath: '/usr/local/bin/yt-dlp',
94
- ffmpegPath: '/usr/bin/ffmpeg',
95
-
96
- // YouTube cookies
97
- cookiesPath: './cookies.txt',
98
- // Or direct string:
99
- cookies: '# Netscape HTTP Cookie File\n...',
100
-
101
- // Providers (optional - all enabled by default)
102
- providers: {
103
- youtube: { enabled: true },
104
- spotify: { enabled: true },
105
- soundcloud: { enabled: true }
106
- },
107
-
108
- // Spotify
109
- spotify: {
110
- clientId: 'xxx',
111
- clientSecret: 'xxx'
112
- },
113
-
114
- // Audio
115
- audio: {
116
- bitrate: '128k',
117
- format: 'opus',
118
- vbr: true,
119
- compressionLevel: 10,
120
- application: 'audio'
121
- },
122
-
123
- // yt-dlp
124
- ytdlp: {
125
- format: 'bestaudio/best',
126
- additionalArgs: []
127
- },
128
-
129
- // Logging
130
- logLevel: 'info', // none, error, warn, info, debug
131
- silent: false,
132
- colors: true
133
- });
134
- ```
135
-
136
- ## Environment Variables
137
-
138
- | Variable | Description |
139
- |----------|-------------|
140
- | `SPOTIFY_CLIENT_ID` | Spotify API client ID |
141
- | `SPOTIFY_CLIENT_SECRET` | Spotify API client secret |
142
- | `YTDLP_PATH` | Path to yt-dlp binary |
143
- | `FFMPEG_PATH` | Path to ffmpeg binary |
144
- | `COOKIES_PATH` | Path to YouTube cookies file |
145
- | `PORT` | HTTP server port |
146
-
147
- ## YouTube Cookies
148
-
149
- For age-restricted or region-locked videos:
150
-
151
- 1. Install browser extension "Get cookies.txt"
152
- 2. Visit youtube.com while logged in
153
- 3. Export cookies in Netscape format
154
- 4. Save as `cookies.txt`
155
-
156
- ```javascript
157
- // File path
158
- cookiesPath: './cookies.txt'
159
-
160
- // Or direct string
161
- cookies: `# Netscape HTTP Cookie File
162
- .youtube.com TRUE / TRUE 0 COOKIE_NAME COOKIE_VALUE`
163
- ```
164
-
165
- ## Spotify Setup
166
-
167
- 1. Go to [Spotify Developer Dashboard](https://developer.spotify.com/dashboard)
168
- 2. Create an app
169
- 3. Copy Client ID and Client Secret
170
-
171
- ```javascript
172
- spotify: {
173
- clientId: process.env.SPOTIFY_CLIENT_ID,
174
- clientSecret: process.env.SPOTIFY_CLIENT_SECRET
175
- }
176
- ```
177
-
178
- Spotify tracks are resolved to YouTube for playback.
179
-
180
- ## Providers
181
-
182
- Enable or disable individual providers:
183
-
184
- ```javascript
185
- providers: {
186
- youtube: { enabled: true },
187
- spotify: { enabled: true },
188
- soundcloud: { enabled: false } // Disabled
189
- }
190
- ```
191
-
192
- All providers are enabled by default. When a disabled provider is accessed, an error is thrown:
193
-
194
- ```
195
- Error: SoundCloud provider is disabled
196
- ```
197
-
198
- This is useful if you only want to support specific platforms.