streamify-audio 2.1.11 → 2.1.13
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 +80 -1
- package/index.d.ts +31 -1
- package/index.js +4 -0
- package/package.json +3 -3
- package/src/discord/Player.js +87 -3
- package/src/filters/ffmpeg.js +66 -1
package/docs/discord/player.md
CHANGED
|
@@ -14,7 +14,7 @@ const player = manager.get(guildId);
|
|
|
14
14
|
|
|
15
15
|
## Playback Methods
|
|
16
16
|
|
|
17
|
-
### play(track)
|
|
17
|
+
### play(track, options?)
|
|
18
18
|
|
|
19
19
|
Plays a track immediately. If something is playing, adds to queue and skips.
|
|
20
20
|
|
|
@@ -23,6 +23,34 @@ const result = await manager.search('never gonna give you up');
|
|
|
23
23
|
await player.play(result.tracks[0]);
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
**Options:**
|
|
27
|
+
- `startPosition` - Start playback at a specific position in milliseconds
|
|
28
|
+
- `volume` - Set volume before playing (0-200)
|
|
29
|
+
- `filters` - Apply filters before playing
|
|
30
|
+
- `replace` - Replace current track without adding to queue/history
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
// Start playing at 30 seconds
|
|
34
|
+
await player.play(track, { startPosition: 30000 });
|
|
35
|
+
|
|
36
|
+
// Start with specific volume
|
|
37
|
+
await player.play(track, { volume: 50 });
|
|
38
|
+
|
|
39
|
+
// Start with filters applied
|
|
40
|
+
await player.play(track, { filters: { bass: 10, nightcore: true } });
|
|
41
|
+
|
|
42
|
+
// Replace current track without queueing
|
|
43
|
+
await player.play(track, { replace: true });
|
|
44
|
+
|
|
45
|
+
// Combine options
|
|
46
|
+
await player.play(track, {
|
|
47
|
+
startPosition: savedPositionMs,
|
|
48
|
+
volume: savedVolume,
|
|
49
|
+
filters: savedFilters,
|
|
50
|
+
replace: true
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
26
54
|
### pause()
|
|
27
55
|
|
|
28
56
|
Pauses playback and destroys the stream to save resources.
|
|
@@ -97,6 +125,57 @@ console.log(player.filters); // { bass: 10, nightcore: true }
|
|
|
97
125
|
|
|
98
126
|
See [Filters](../filters.md) for all available filters.
|
|
99
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
|
+
|
|
100
179
|
## Loop Modes
|
|
101
180
|
|
|
102
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
|
// ========================================================================
|
|
@@ -237,6 +239,29 @@ declare module 'streamify-audio' {
|
|
|
237
239
|
on(event: 'playerDestroy', listener: (player: Player) => void): this;
|
|
238
240
|
}
|
|
239
241
|
|
|
242
|
+
export interface PlayOptions {
|
|
243
|
+
startPosition?: number;
|
|
244
|
+
seek?: number;
|
|
245
|
+
volume?: number;
|
|
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;
|
|
263
|
+
}
|
|
264
|
+
|
|
240
265
|
export class Player extends EventEmitter {
|
|
241
266
|
constructor(manager: Manager, options: any);
|
|
242
267
|
|
|
@@ -261,7 +286,7 @@ declare module 'streamify-audio' {
|
|
|
261
286
|
disconnect(): boolean;
|
|
262
287
|
destroy(): void;
|
|
263
288
|
|
|
264
|
-
play(track: Track): Promise<void>;
|
|
289
|
+
play(track: Track, options?: PlayOptions): Promise<void>;
|
|
265
290
|
pause(destroyStream?: boolean): boolean;
|
|
266
291
|
resume(): Promise<boolean>;
|
|
267
292
|
skip(): Promise<Track | null>;
|
|
@@ -281,6 +306,11 @@ declare module 'streamify-audio' {
|
|
|
281
306
|
clearEQ(): Promise<boolean>;
|
|
282
307
|
getPresets(): string[];
|
|
283
308
|
|
|
309
|
+
setEffectPresets(presets: (string | EffectPreset)[], options?: SetEffectPresetsOptions): Promise<boolean>;
|
|
310
|
+
getActiveEffectPresets(): EffectPreset[];
|
|
311
|
+
clearEffectPresets(): Promise<boolean>;
|
|
312
|
+
getEffectPresets(): string[];
|
|
313
|
+
|
|
284
314
|
toJSON(): any;
|
|
285
315
|
|
|
286
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.13",
|
|
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;
|
|
@@ -261,15 +263,33 @@ class Player extends EventEmitter {
|
|
|
261
263
|
await this.connect();
|
|
262
264
|
}
|
|
263
265
|
|
|
264
|
-
const
|
|
266
|
+
const playOptions = {
|
|
267
|
+
startPosition: options.startPosition || options.seek || 0,
|
|
268
|
+
volume: options.volume,
|
|
269
|
+
filters: options.filters,
|
|
270
|
+
replace: options.replace || false
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
if (options.volume !== undefined) {
|
|
274
|
+
this._volume = Math.max(0, Math.min(200, options.volume));
|
|
275
|
+
}
|
|
265
276
|
|
|
266
|
-
if (
|
|
277
|
+
if (options.filters) {
|
|
278
|
+
this._filters = { ...this._filters, ...options.filters };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (this.queue.current && !playOptions.replace) {
|
|
267
282
|
this.queue.add(track, 0);
|
|
268
283
|
return this.skip();
|
|
269
284
|
}
|
|
270
285
|
|
|
286
|
+
if (playOptions.replace && this.stream) {
|
|
287
|
+
this.stream.destroy();
|
|
288
|
+
this.stream = null;
|
|
289
|
+
}
|
|
290
|
+
|
|
271
291
|
this.queue.setCurrent(track);
|
|
272
|
-
return this._playTrack(track, startPosition);
|
|
292
|
+
return this._playTrack(track, playOptions.startPosition);
|
|
273
293
|
}
|
|
274
294
|
|
|
275
295
|
async _playTrack(track, startPosition = 0) {
|
|
@@ -604,6 +624,70 @@ class Player extends EventEmitter {
|
|
|
604
624
|
return Object.keys(PRESETS);
|
|
605
625
|
}
|
|
606
626
|
|
|
627
|
+
async setEffectPresets(presets, options = {}) {
|
|
628
|
+
if (!Array.isArray(presets)) {
|
|
629
|
+
presets = [presets];
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const replace = options.replace ?? false;
|
|
633
|
+
const appliedFilters = {};
|
|
634
|
+
const newPresets = [];
|
|
635
|
+
|
|
636
|
+
for (const preset of presets) {
|
|
637
|
+
const name = typeof preset === 'string' ? preset : preset.name;
|
|
638
|
+
const intensity = typeof preset === 'object' ? (preset.intensity ?? 1.0) : 1.0;
|
|
639
|
+
|
|
640
|
+
if (!EFFECT_PRESETS[name]) {
|
|
641
|
+
log.warn('PLAYER', `Unknown effect preset: ${name}`);
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const filters = applyEffectPreset(name, intensity);
|
|
646
|
+
if (filters) {
|
|
647
|
+
Object.assign(appliedFilters, filters);
|
|
648
|
+
newPresets.push({ name, intensity });
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (replace) {
|
|
653
|
+
this._effectPresets = newPresets;
|
|
654
|
+
this._filters = appliedFilters;
|
|
655
|
+
} else {
|
|
656
|
+
const existingNames = this._effectPresets.map(p => p.name);
|
|
657
|
+
for (const preset of newPresets) {
|
|
658
|
+
const idx = existingNames.indexOf(preset.name);
|
|
659
|
+
if (idx >= 0) {
|
|
660
|
+
this._effectPresets[idx] = preset;
|
|
661
|
+
} else {
|
|
662
|
+
this._effectPresets.push(preset);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
this._filters = { ...this._filters, ...appliedFilters };
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (this._playing && this.queue.current) {
|
|
669
|
+
return this.setFilter('_trigger', null);
|
|
670
|
+
}
|
|
671
|
+
return true;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
getActiveEffectPresets() {
|
|
675
|
+
return [...this._effectPresets];
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
async clearEffectPresets() {
|
|
679
|
+
this._effectPresets = [];
|
|
680
|
+
this._filters = {};
|
|
681
|
+
if (this._playing && this.queue.current) {
|
|
682
|
+
return this.setFilter('_trigger', null);
|
|
683
|
+
}
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
getEffectPresets() {
|
|
688
|
+
return Object.keys(EFFECT_PRESETS);
|
|
689
|
+
}
|
|
690
|
+
|
|
607
691
|
disconnect() {
|
|
608
692
|
if (this.connection) {
|
|
609
693
|
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
|
+
};
|