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.
@@ -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.11",
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": "./bin/streamify.js"
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": {
@@ -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 startPosition = options.startPosition || options.seek || 0;
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 (this.queue.current) {
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();
@@ -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
- module.exports = { buildFfmpegArgs, getAvailableFilters, getPresets, getEQBands, PRESETS, EQ_BANDS };
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
+ };