streamify-audio 2.2.13 → 2.3.0

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.2.13",
3
+ "version": "2.3.0",
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
@@ -26,8 +26,8 @@ const defaults = {
26
26
  bitrate: '128k',
27
27
  format: 'opus',
28
28
  vbr: true,
29
- compressionLevel: 10,
30
- application: 'audio'
29
+ compressionLevel: 5,
30
+ application: 'lowdelay'
31
31
  },
32
32
  ytdlp: {
33
33
  format: 'bestaudio/bestaudio[ext=webm]/bestaudio[ext=mp4]/18/22/best',
@@ -38,6 +38,13 @@ const defaults = {
38
38
  searchTTL: 300,
39
39
  infoTTL: 3600,
40
40
  redis: null
41
+ },
42
+ stream: {
43
+ dataTimeout: 8000,
44
+ liveDataTimeout: 15000,
45
+ urlCacheTTL: 1800,
46
+ bufferSize: '1M',
47
+ maxRetries: 2
41
48
  }
42
49
  };
43
50
 
@@ -107,6 +114,11 @@ function load(options = {}) {
107
114
  ...defaults.cache,
108
115
  ...fileConfig.cache,
109
116
  ...options.cache
117
+ },
118
+ stream: {
119
+ ...defaults.stream,
120
+ ...fileConfig.stream,
121
+ ...options.stream
110
122
  }
111
123
  };
112
124
 
@@ -48,6 +48,7 @@ class Player extends EventEmitter {
48
48
  this._prefetching = false;
49
49
  this._changingStream = false;
50
50
  this._manualSkip = false;
51
+ this._pauseStreamTimeout = null;
51
52
 
52
53
  this.autoLeave = {
53
54
  enabled: options.autoLeave?.enabled ?? true,
@@ -316,22 +317,23 @@ class Player extends EventEmitter {
316
317
  this.emit('trackStart', track);
317
318
 
318
319
  let newStream = null;
320
+ let resource = null;
321
+
319
322
  try {
320
323
  const filtersWithVolume = { ...this._filters, volume: this._volume };
321
324
 
322
- if (startPosition === 0 && this._prefetchedTrack?.id === track.id && this._prefetchedStream) {
323
- log.debug('PLAYER', `Using prefetched stream for ${track.id}`);
325
+ if (startPosition === 0 && this._prefetchedTrack?.id === track.id && this._prefetchedStream?.resource) {
326
+ log.info('PLAYER', `Using prefetched stream for ${track.id}`);
324
327
  newStream = this._prefetchedStream;
328
+ resource = newStream.resource;
325
329
  this._prefetchedStream = null;
326
330
  this._prefetchedTrack = null;
327
331
  } else {
328
- // If not using prefetch, we don't clear it yet in case we need to fallback
332
+ this._clearPrefetch();
329
333
  newStream = createStream(track, filtersWithVolume, this.config);
334
+ resource = await newStream.start(startPosition);
330
335
  }
331
336
 
332
- const resource = await newStream.create(startPosition);
333
-
334
- // SEAMLESS SWAP: Only destroy old stream AFTER the new one is ready
335
337
  const oldStream = this.stream;
336
338
  this.stream = newStream;
337
339
  this.audioPlayer.play(resource);
@@ -361,7 +363,7 @@ class Player extends EventEmitter {
361
363
 
362
364
  log.error('PLAYER', `Failed to play track: ${error.message}`);
363
365
  this.emit('trackError', track, error);
364
-
366
+
365
367
  const next = this.queue.shift();
366
368
  if (next) {
367
369
  setImmediate(() => this._playTrack(next));
@@ -383,19 +385,35 @@ class Player extends EventEmitter {
383
385
  if (this._prefetching || this.queue.tracks.length === 0) return;
384
386
 
385
387
  const nextTrack = this.queue.tracks[0];
386
- if (!nextTrack || nextTrack._directUrl) return; // Already prefetched or resolved
388
+ if (!nextTrack) return;
389
+
390
+ if (this._prefetchedTrack?.id === nextTrack.id && this._prefetchedStream) return;
387
391
 
392
+ this._clearPrefetch();
388
393
  this._prefetching = true;
389
- log.debug('PLAYER', `Prefetching metadata: ${nextTrack.title}`);
394
+ log.debug('PLAYER', `Prefetching stream: ${nextTrack.title}`);
390
395
 
391
396
  try {
392
- // Only resolve the metadata and stream URL
393
- const result = await this.manager.getInfo(nextTrack.id, nextTrack.source);
394
- if (result) {
395
- // Merge prefetch data into the existing queue object
396
- Object.assign(nextTrack, result);
397
- log.debug('PLAYER', `Prefetch ready (Metadata only): ${nextTrack.id}`);
397
+ const filtersWithVolume = { ...this._filters, volume: this._volume };
398
+ const stream = createStream(nextTrack, filtersWithVolume, this.config);
399
+
400
+ await stream.prepare();
401
+
402
+ if (this._destroyed || this.queue.tracks[0]?.id !== nextTrack.id) {
403
+ stream.destroy();
404
+ return;
405
+ }
406
+
407
+ await stream.start(0);
408
+
409
+ if (this._destroyed || this.queue.tracks[0]?.id !== nextTrack.id) {
410
+ stream.destroy();
411
+ return;
398
412
  }
413
+
414
+ this._prefetchedStream = stream;
415
+ this._prefetchedTrack = nextTrack;
416
+ log.info('PLAYER', `Prefetch ready: ${nextTrack.title} (${stream.metrics.total}ms)`);
399
417
  } catch (error) {
400
418
  log.debug('PLAYER', `Prefetch failed: ${error.message}`);
401
419
  } finally {
@@ -492,7 +510,7 @@ class Player extends EventEmitter {
492
510
  }
493
511
  }
494
512
 
495
- pause(destroyStream = true) {
513
+ pause(destroyStream = false) {
496
514
  if (!this._playing || this._paused) return false;
497
515
 
498
516
  this._positionMs = this.position;
@@ -502,10 +520,23 @@ class Player extends EventEmitter {
502
520
  this._clearPrefetch();
503
521
  this.stream.destroy();
504
522
  this.stream = null;
523
+ this.audioPlayer.stop(true);
524
+ } else {
525
+ this.audioPlayer.pause();
505
526
  }
506
527
 
507
- this.audioPlayer.stop(true);
508
- log.info('PLAYER', `Paused playback at ${Math.floor(this._positionMs / 1000)}s (Reason: User Request)`);
528
+ if (!destroyStream && this.stream) {
529
+ this._pauseStreamTimeout = setTimeout(() => {
530
+ if (this._paused && this.stream) {
531
+ log.info('PLAYER', 'Destroying paused stream after 5m idle');
532
+ this._clearPrefetch();
533
+ this.stream.destroy();
534
+ this.stream = null;
535
+ }
536
+ }, 300000);
537
+ }
538
+
539
+ log.info('PLAYER', `Paused playback at ${Math.floor(this._positionMs / 1000)}s (stream kept: ${!destroyStream})`);
509
540
  this._clearVoiceChannelStatus();
510
541
 
511
542
  return true;
@@ -514,8 +545,13 @@ class Player extends EventEmitter {
514
545
  async resume() {
515
546
  if (!this._playing || !this._paused) return false;
516
547
 
548
+ if (this._pauseStreamTimeout) {
549
+ clearTimeout(this._pauseStreamTimeout);
550
+ this._pauseStreamTimeout = null;
551
+ }
552
+
517
553
  if (!this.stream && this.queue.current) {
518
- log.info('PLAYER', `Resuming playback from ${Math.floor(this._positionMs / 1000)}s`);
554
+ log.info('PLAYER', `Resuming playback from ${Math.floor(this._positionMs / 1000)}s (recreating stream)`);
519
555
 
520
556
  this._changingStream = true;
521
557
  const track = this.queue.current;
@@ -547,6 +583,7 @@ class Player extends EventEmitter {
547
583
  this.audioPlayer.unpause();
548
584
  this._paused = false;
549
585
  this._positionTimestamp = Date.now();
586
+ log.info('PLAYER', `Resumed playback instantly at ${Math.floor(this._positionMs / 1000)}s`);
550
587
  this._updateVoiceChannelStatus(this.queue.current, true);
551
588
  }
552
589
 
@@ -630,10 +667,21 @@ class Player extends EventEmitter {
630
667
  return true;
631
668
  }
632
669
 
633
- setVolume(volume) {
670
+ async setVolume(volume) {
671
+ this._clearPrefetch();
634
672
  const oldVolume = this._volume;
635
673
  this._volume = Math.max(0, Math.min(200, volume));
636
674
  log.info('PLAYER', `Volume adjusted: ${oldVolume}% -> ${this._volume}%`);
675
+
676
+ if (this._playing && this.queue.current && !this._paused) {
677
+ this._changingStream = true;
678
+ try {
679
+ await this._playTrack(this.queue.current, this.position);
680
+ } finally {
681
+ this._changingStream = false;
682
+ }
683
+ }
684
+
637
685
  return this._volume;
638
686
  }
639
687
 
@@ -659,6 +707,7 @@ class Player extends EventEmitter {
659
707
  }
660
708
 
661
709
  async setFilter(name, value) {
710
+ this._clearPrefetch();
662
711
  this._filters[name] = value;
663
712
 
664
713
  if (this._playing && this.queue.current) {
@@ -757,6 +806,7 @@ class Player extends EventEmitter {
757
806
  }
758
807
  }
759
808
 
809
+ this._clearPrefetch();
760
810
  log.info('PLAYER', `Active effects: ${this._effectPresets.map(p => p.name).join(' + ') || 'NONE'}`);
761
811
 
762
812
  if (this._playing && this.queue.current) {
@@ -801,6 +851,10 @@ class Player extends EventEmitter {
801
851
  clearTimeout(this._inactivityTimeout);
802
852
  this._inactivityTimeout = null;
803
853
  }
854
+ if (this._pauseStreamTimeout) {
855
+ clearTimeout(this._pauseStreamTimeout);
856
+ this._pauseStreamTimeout = null;
857
+ }
804
858
 
805
859
  this._clearPrefetch();
806
860