streamify-audio 2.1.12 → 2.1.14
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/docs/discord/player.md +58 -2
- package/index.d.ts +23 -0
- package/index.js +4 -0
- package/package.json +3 -3
- package/src/discord/Player.js +98 -12
- package/src/filters/ffmpeg.js +66 -1
package/docs/discord/player.md
CHANGED
|
@@ -27,6 +27,7 @@ await player.play(result.tracks[0]);
|
|
|
27
27
|
- `startPosition` - Start playback at a specific position in milliseconds
|
|
28
28
|
- `volume` - Set volume before playing (0-200)
|
|
29
29
|
- `filters` - Apply filters before playing
|
|
30
|
+
- `replace` - Replace current track without adding to queue/history
|
|
30
31
|
|
|
31
32
|
```javascript
|
|
32
33
|
// Start playing at 30 seconds
|
|
@@ -38,11 +39,15 @@ await player.play(track, { volume: 50 });
|
|
|
38
39
|
// Start with filters applied
|
|
39
40
|
await player.play(track, { filters: { bass: 10, nightcore: true } });
|
|
40
41
|
|
|
41
|
-
//
|
|
42
|
+
// Replace current track without queueing
|
|
43
|
+
await player.play(track, { replace: true });
|
|
44
|
+
|
|
45
|
+
// Combine options
|
|
42
46
|
await player.play(track, {
|
|
43
47
|
startPosition: savedPositionMs,
|
|
44
48
|
volume: savedVolume,
|
|
45
|
-
filters: savedFilters
|
|
49
|
+
filters: savedFilters,
|
|
50
|
+
replace: true
|
|
46
51
|
});
|
|
47
52
|
```
|
|
48
53
|
|
|
@@ -120,6 +125,57 @@ console.log(player.filters); // { bass: 10, nightcore: true }
|
|
|
120
125
|
|
|
121
126
|
See [Filters](../filters.md) for all available filters.
|
|
122
127
|
|
|
128
|
+
## Effect Presets
|
|
129
|
+
|
|
130
|
+
Effect presets are pre-configured filter combinations that stack by default.
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
// Apply a single preset
|
|
134
|
+
await player.setEffectPresets(['bassboost']);
|
|
135
|
+
|
|
136
|
+
// Apply multiple presets (they stack)
|
|
137
|
+
await player.setEffectPresets(['nightcore', 'bassboost']);
|
|
138
|
+
|
|
139
|
+
// Apply with custom intensity (0.1 - 1.0)
|
|
140
|
+
await player.setEffectPresets([
|
|
141
|
+
{ name: 'nightcore', intensity: 0.8 },
|
|
142
|
+
{ name: 'bassboost', intensity: 0.5 }
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
// Replace all presets instead of stacking
|
|
146
|
+
await player.setEffectPresets(['8d'], { replace: true });
|
|
147
|
+
|
|
148
|
+
// Get active presets
|
|
149
|
+
const active = player.getActiveEffectPresets();
|
|
150
|
+
// [{ name: 'nightcore', intensity: 0.8 }, { name: 'bassboost', intensity: 0.5 }]
|
|
151
|
+
|
|
152
|
+
// Clear all effect presets
|
|
153
|
+
await player.clearEffectPresets();
|
|
154
|
+
|
|
155
|
+
// List available presets
|
|
156
|
+
const presets = player.getEffectPresets();
|
|
157
|
+
// ['bassboost', 'nightcore', 'vaporwave', '8d', 'karaoke', ...]
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Available Presets:**
|
|
161
|
+
- `bassboost` - Boost bass frequencies
|
|
162
|
+
- `nightcore` - Speed up with higher pitch
|
|
163
|
+
- `vaporwave` - Slow down with lower pitch
|
|
164
|
+
- `8d` - 8D rotating audio effect
|
|
165
|
+
- `karaoke` - Reduce vocals
|
|
166
|
+
- `trebleboost` - Boost treble frequencies
|
|
167
|
+
- `deep` - Deep bass with lower pitch
|
|
168
|
+
- `lofi` - Lo-fi aesthetic
|
|
169
|
+
- `radio` - Radio/telephone effect
|
|
170
|
+
- `telephone` - Old telephone effect
|
|
171
|
+
- `soft` - Softer, quieter sound
|
|
172
|
+
- `loud` - Louder, compressed sound
|
|
173
|
+
- `chipmunk` - High-pitched voice
|
|
174
|
+
- `darth` - Deep Darth Vader voice
|
|
175
|
+
- `echo` - Echo/reverb effect
|
|
176
|
+
- `vibrato` - Vibrato effect
|
|
177
|
+
- `tremolo` - Tremolo effect
|
|
178
|
+
|
|
123
179
|
## Loop Modes
|
|
124
180
|
|
|
125
181
|
```javascript
|
package/index.d.ts
CHANGED
|
@@ -163,6 +163,8 @@ declare module 'streamify-audio' {
|
|
|
163
163
|
static Manager: typeof Manager;
|
|
164
164
|
static Player: typeof Player;
|
|
165
165
|
static Queue: typeof Queue;
|
|
166
|
+
static getEffectPresetsInfo(): EffectPresetInfo[];
|
|
167
|
+
static EFFECT_PRESETS: Record<string, { filters: Filters; description: string }>;
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
// ========================================================================
|
|
@@ -242,6 +244,22 @@ declare module 'streamify-audio' {
|
|
|
242
244
|
seek?: number;
|
|
243
245
|
volume?: number;
|
|
244
246
|
filters?: Filters;
|
|
247
|
+
replace?: boolean;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export interface EffectPreset {
|
|
251
|
+
name: string;
|
|
252
|
+
intensity?: number;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export interface EffectPresetInfo {
|
|
256
|
+
name: string;
|
|
257
|
+
description: string;
|
|
258
|
+
filters: string[];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export interface SetEffectPresetsOptions {
|
|
262
|
+
replace?: boolean;
|
|
245
263
|
}
|
|
246
264
|
|
|
247
265
|
export class Player extends EventEmitter {
|
|
@@ -288,6 +306,11 @@ declare module 'streamify-audio' {
|
|
|
288
306
|
clearEQ(): Promise<boolean>;
|
|
289
307
|
getPresets(): string[];
|
|
290
308
|
|
|
309
|
+
setEffectPresets(presets: (string | EffectPreset)[], options?: SetEffectPresetsOptions): Promise<boolean>;
|
|
310
|
+
getActiveEffectPresets(): EffectPreset[];
|
|
311
|
+
clearEffectPresets(): Promise<boolean>;
|
|
312
|
+
getEffectPresets(): string[];
|
|
313
|
+
|
|
291
314
|
toJSON(): any;
|
|
292
315
|
|
|
293
316
|
on(event: 'trackStart', listener: (track: Track) => void): this;
|
package/index.js
CHANGED
|
@@ -249,4 +249,8 @@ Streamify.Manager = Manager;
|
|
|
249
249
|
Streamify.Player = Manager ? require('./src/discord/Player') : null;
|
|
250
250
|
Streamify.Queue = Manager ? require('./src/discord/Queue') : null;
|
|
251
251
|
|
|
252
|
+
const { getEffectPresetsInfo, EFFECT_PRESETS } = require('./src/filters/ffmpeg');
|
|
253
|
+
Streamify.getEffectPresetsInfo = getEffectPresetsInfo;
|
|
254
|
+
Streamify.EFFECT_PRESETS = EFFECT_PRESETS;
|
|
255
|
+
|
|
252
256
|
module.exports = Streamify;
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "streamify-audio",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.14",
|
|
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",
|
|
7
7
|
"bin": {
|
|
8
|
-
"streamify": "
|
|
8
|
+
"streamify": "bin/streamify.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node index.js",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"repository": {
|
|
50
50
|
"type": "git",
|
|
51
|
-
"url": "https://github.com/LucasCzechia/streamify.git"
|
|
51
|
+
"url": "git+https://github.com/LucasCzechia/streamify.git"
|
|
52
52
|
},
|
|
53
53
|
"homepage": "https://github.com/LucasCzechia/streamify#readme",
|
|
54
54
|
"bugs": {
|
package/src/discord/Player.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { EventEmitter } = require('events');
|
|
2
2
|
const Queue = require('./Queue');
|
|
3
3
|
const { createStream } = require('./Stream');
|
|
4
|
+
const { applyEffectPreset, EFFECT_PRESETS } = require('../filters/ffmpeg');
|
|
4
5
|
const log = require('../utils/logger');
|
|
5
6
|
|
|
6
7
|
let voiceModule;
|
|
@@ -35,6 +36,7 @@ class Player extends EventEmitter {
|
|
|
35
36
|
|
|
36
37
|
this._volume = options.volume || manager.config.defaultVolume || 80;
|
|
37
38
|
this._filters = {};
|
|
39
|
+
this._effectPresets = [];
|
|
38
40
|
this._playing = false;
|
|
39
41
|
this._paused = false;
|
|
40
42
|
this._positionTimestamp = 0;
|
|
@@ -45,6 +47,7 @@ class Player extends EventEmitter {
|
|
|
45
47
|
this._prefetchedTrack = null;
|
|
46
48
|
this._prefetching = false;
|
|
47
49
|
this._changingStream = false;
|
|
50
|
+
this._manualSkip = false;
|
|
48
51
|
|
|
49
52
|
this.autoLeave = {
|
|
50
53
|
enabled: options.autoLeave?.enabled ?? true,
|
|
@@ -195,15 +198,17 @@ class Player extends EventEmitter {
|
|
|
195
198
|
|
|
196
199
|
this.emit('trackEnd', track, 'finished');
|
|
197
200
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (this.autoplay.enabled && track) {
|
|
203
|
-
await this._handleAutoplay(track);
|
|
201
|
+
if (!this._manualSkip) {
|
|
202
|
+
const next = this.queue.shift();
|
|
203
|
+
if (next) {
|
|
204
|
+
this._playTrack(next);
|
|
204
205
|
} else {
|
|
205
|
-
this.
|
|
206
|
-
|
|
206
|
+
if (this.autoplay.enabled && track) {
|
|
207
|
+
await this._handleAutoplay(track);
|
|
208
|
+
} else {
|
|
209
|
+
this.emit('queueEnd');
|
|
210
|
+
this._resetInactivityTimeout();
|
|
211
|
+
}
|
|
207
212
|
}
|
|
208
213
|
}
|
|
209
214
|
});
|
|
@@ -264,7 +269,8 @@ class Player extends EventEmitter {
|
|
|
264
269
|
const playOptions = {
|
|
265
270
|
startPosition: options.startPosition || options.seek || 0,
|
|
266
271
|
volume: options.volume,
|
|
267
|
-
filters: options.filters
|
|
272
|
+
filters: options.filters,
|
|
273
|
+
replace: options.replace || false
|
|
268
274
|
};
|
|
269
275
|
|
|
270
276
|
if (options.volume !== undefined) {
|
|
@@ -275,11 +281,16 @@ class Player extends EventEmitter {
|
|
|
275
281
|
this._filters = { ...this._filters, ...options.filters };
|
|
276
282
|
}
|
|
277
283
|
|
|
278
|
-
if (this.queue.current) {
|
|
284
|
+
if (this.queue.current && !playOptions.replace) {
|
|
279
285
|
this.queue.add(track, 0);
|
|
280
286
|
return this.skip();
|
|
281
287
|
}
|
|
282
288
|
|
|
289
|
+
if (playOptions.replace && this.stream) {
|
|
290
|
+
this.stream.destroy();
|
|
291
|
+
this.stream = null;
|
|
292
|
+
}
|
|
293
|
+
|
|
283
294
|
this.queue.setCurrent(track);
|
|
284
295
|
return this._playTrack(track, playOptions.startPosition);
|
|
285
296
|
}
|
|
@@ -473,8 +484,16 @@ class Player extends EventEmitter {
|
|
|
473
484
|
this.stream = null;
|
|
474
485
|
}
|
|
475
486
|
|
|
487
|
+
this._manualSkip = true;
|
|
488
|
+
const next = this.queue.shift();
|
|
476
489
|
this.audioPlayer.stop();
|
|
477
|
-
|
|
490
|
+
|
|
491
|
+
if (next) {
|
|
492
|
+
await this._playTrack(next);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
this._manualSkip = false;
|
|
496
|
+
return next;
|
|
478
497
|
}
|
|
479
498
|
|
|
480
499
|
async previous() {
|
|
@@ -487,8 +506,11 @@ class Player extends EventEmitter {
|
|
|
487
506
|
this.stream = null;
|
|
488
507
|
}
|
|
489
508
|
|
|
509
|
+
this._manualSkip = true;
|
|
490
510
|
this.audioPlayer.stop();
|
|
491
|
-
|
|
511
|
+
const result = await this._playTrack(prev);
|
|
512
|
+
this._manualSkip = false;
|
|
513
|
+
return result;
|
|
492
514
|
}
|
|
493
515
|
|
|
494
516
|
stop() {
|
|
@@ -616,6 +638,70 @@ class Player extends EventEmitter {
|
|
|
616
638
|
return Object.keys(PRESETS);
|
|
617
639
|
}
|
|
618
640
|
|
|
641
|
+
async setEffectPresets(presets, options = {}) {
|
|
642
|
+
if (!Array.isArray(presets)) {
|
|
643
|
+
presets = [presets];
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const replace = options.replace ?? false;
|
|
647
|
+
const appliedFilters = {};
|
|
648
|
+
const newPresets = [];
|
|
649
|
+
|
|
650
|
+
for (const preset of presets) {
|
|
651
|
+
const name = typeof preset === 'string' ? preset : preset.name;
|
|
652
|
+
const intensity = typeof preset === 'object' ? (preset.intensity ?? 1.0) : 1.0;
|
|
653
|
+
|
|
654
|
+
if (!EFFECT_PRESETS[name]) {
|
|
655
|
+
log.warn('PLAYER', `Unknown effect preset: ${name}`);
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const filters = applyEffectPreset(name, intensity);
|
|
660
|
+
if (filters) {
|
|
661
|
+
Object.assign(appliedFilters, filters);
|
|
662
|
+
newPresets.push({ name, intensity });
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if (replace) {
|
|
667
|
+
this._effectPresets = newPresets;
|
|
668
|
+
this._filters = appliedFilters;
|
|
669
|
+
} else {
|
|
670
|
+
const existingNames = this._effectPresets.map(p => p.name);
|
|
671
|
+
for (const preset of newPresets) {
|
|
672
|
+
const idx = existingNames.indexOf(preset.name);
|
|
673
|
+
if (idx >= 0) {
|
|
674
|
+
this._effectPresets[idx] = preset;
|
|
675
|
+
} else {
|
|
676
|
+
this._effectPresets.push(preset);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
this._filters = { ...this._filters, ...appliedFilters };
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (this._playing && this.queue.current) {
|
|
683
|
+
return this.setFilter('_trigger', null);
|
|
684
|
+
}
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
getActiveEffectPresets() {
|
|
689
|
+
return [...this._effectPresets];
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
async clearEffectPresets() {
|
|
693
|
+
this._effectPresets = [];
|
|
694
|
+
this._filters = {};
|
|
695
|
+
if (this._playing && this.queue.current) {
|
|
696
|
+
return this.setFilter('_trigger', null);
|
|
697
|
+
}
|
|
698
|
+
return true;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
getEffectPresets() {
|
|
702
|
+
return Object.keys(EFFECT_PRESETS);
|
|
703
|
+
}
|
|
704
|
+
|
|
619
705
|
disconnect() {
|
|
620
706
|
if (this.connection) {
|
|
621
707
|
this.connection.destroy();
|
package/src/filters/ffmpeg.js
CHANGED
|
@@ -18,6 +18,26 @@ const PRESETS = {
|
|
|
18
18
|
treble_heavy: [0, 0, 0, 0, 0, 0, 0, 0, 0.1, 0.2, 0.3, 0.4, 0.45, 0.5, 0.5]
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
const EFFECT_PRESETS = {
|
|
22
|
+
bassboost: { filters: { bass: 10 }, description: 'Boost bass frequencies' },
|
|
23
|
+
nightcore: { filters: { speed: 1.25, pitch: 1.25 }, description: 'Speed up with higher pitch' },
|
|
24
|
+
vaporwave: { filters: { speed: 0.8, pitch: 0.8 }, description: 'Slow down with lower pitch' },
|
|
25
|
+
'8d': { filters: { rotation: { speed: 0.2 } }, description: '8D rotating audio effect' },
|
|
26
|
+
karaoke: { filters: { karaoke: true }, description: 'Reduce vocals' },
|
|
27
|
+
trebleboost: { filters: { treble: 10 }, description: 'Boost treble frequencies' },
|
|
28
|
+
deep: { filters: { bass: 15, pitch: 0.9 }, description: 'Deep bass with lower pitch' },
|
|
29
|
+
lofi: { filters: { lowpass: 3000, bass: 5 }, description: 'Lo-fi aesthetic' },
|
|
30
|
+
radio: { filters: { highpass: 300, lowpass: 5000 }, description: 'Radio/telephone effect' },
|
|
31
|
+
telephone: { filters: { highpass: 500, lowpass: 3500 }, description: 'Old telephone effect' },
|
|
32
|
+
soft: { filters: { bass: -5, treble: -3, volume: 70 }, description: 'Softer, quieter sound' },
|
|
33
|
+
loud: { filters: { bass: 5, treble: 3, compressor: true }, description: 'Louder, compressed sound' },
|
|
34
|
+
chipmunk: { filters: { pitch: 1.5 }, description: 'High-pitched chipmunk voice' },
|
|
35
|
+
darth: { filters: { pitch: 0.7 }, description: 'Deep Darth Vader voice' },
|
|
36
|
+
echo: { filters: { echo: true }, description: 'Echo/reverb effect' },
|
|
37
|
+
vibrato: { filters: { vibrato: { frequency: 5, depth: 0.5 } }, description: 'Vibrato effect' },
|
|
38
|
+
tremolo: { filters: { tremolo: { frequency: 5, depth: 0.5 } }, description: 'Tremolo effect' }
|
|
39
|
+
};
|
|
40
|
+
|
|
21
41
|
function buildEqualizer(bands) {
|
|
22
42
|
if (!bands || !Array.isArray(bands)) return null;
|
|
23
43
|
|
|
@@ -267,4 +287,49 @@ function getEQBands() {
|
|
|
267
287
|
return EQ_BANDS;
|
|
268
288
|
}
|
|
269
289
|
|
|
270
|
-
|
|
290
|
+
function getEffectPresets() {
|
|
291
|
+
return EFFECT_PRESETS;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function getEffectPresetsInfo() {
|
|
295
|
+
return Object.entries(EFFECT_PRESETS).map(([name, data]) => ({
|
|
296
|
+
name,
|
|
297
|
+
description: data.description,
|
|
298
|
+
filters: Object.keys(data.filters)
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function applyEffectPreset(name, intensity = 1.0) {
|
|
303
|
+
const preset = EFFECT_PRESETS[name];
|
|
304
|
+
if (!preset) return null;
|
|
305
|
+
|
|
306
|
+
const filters = {};
|
|
307
|
+
for (const [key, value] of Object.entries(preset.filters)) {
|
|
308
|
+
if (typeof value === 'number') {
|
|
309
|
+
filters[key] = value * intensity;
|
|
310
|
+
} else if (typeof value === 'boolean') {
|
|
311
|
+
filters[key] = value;
|
|
312
|
+
} else if (typeof value === 'object') {
|
|
313
|
+
filters[key] = { ...value };
|
|
314
|
+
for (const [k, v] of Object.entries(filters[key])) {
|
|
315
|
+
if (typeof v === 'number') {
|
|
316
|
+
filters[key][k] = v * intensity;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return filters;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
module.exports = {
|
|
325
|
+
buildFfmpegArgs,
|
|
326
|
+
getAvailableFilters,
|
|
327
|
+
getPresets,
|
|
328
|
+
getEQBands,
|
|
329
|
+
getEffectPresets,
|
|
330
|
+
getEffectPresetsInfo,
|
|
331
|
+
applyEffectPreset,
|
|
332
|
+
PRESETS,
|
|
333
|
+
EQ_BANDS,
|
|
334
|
+
EFFECT_PRESETS
|
|
335
|
+
};
|