ziplayer 0.3.8 → 0.3.10
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/dist/extensions/BaseExtension.js +1 -0
- package/dist/extensions/BaseExtension.js.map +1 -1
- package/dist/extensions/index.js +15 -4
- package/dist/extensions/index.js.map +1 -1
- package/dist/plugins/BasePlugin.js +1 -3
- package/dist/plugins/BasePlugin.js.map +1 -1
- package/dist/plugins/index.js +10 -6
- package/dist/plugins/index.js.map +1 -1
- package/dist/structures/FilterManager.d.ts +4 -3
- package/dist/structures/FilterManager.d.ts.map +1 -1
- package/dist/structures/FilterManager.js +54 -64
- package/dist/structures/FilterManager.js.map +1 -1
- package/dist/structures/Player.d.ts +2 -1
- package/dist/structures/Player.d.ts.map +1 -1
- package/dist/structures/Player.js +131 -92
- package/dist/structures/Player.js.map +1 -1
- package/dist/structures/PlayerManager.js +16 -12
- package/dist/structures/PlayerManager.js.map +1 -1
- package/dist/structures/PreloadManager.js +16 -10
- package/dist/structures/PreloadManager.js.map +1 -1
- package/dist/structures/Queue.js +10 -12
- package/dist/structures/Queue.js.map +1 -1
- package/dist/structures/StreamManager.js +11 -10
- package/dist/structures/StreamManager.js.map +1 -1
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
- package/src/structures/FilterManager.ts +50 -77
- package/src/structures/Player.ts +57 -31
- package/src/types/index.ts +630 -628
- package/tsconfig.json +22 -22
|
@@ -48,70 +48,86 @@ const PreloadManager_1 = require("./PreloadManager");
|
|
|
48
48
|
*
|
|
49
49
|
*/
|
|
50
50
|
class Player extends events_1.EventEmitter {
|
|
51
|
+
guildId;
|
|
52
|
+
connection = null;
|
|
53
|
+
audioPlayer;
|
|
54
|
+
queue;
|
|
55
|
+
volume = 100;
|
|
56
|
+
options;
|
|
57
|
+
userdata;
|
|
58
|
+
_lastActivity = Date.now();
|
|
59
|
+
_remotePaused = false;
|
|
60
|
+
currentResource = null;
|
|
61
|
+
destroyed = false;
|
|
62
|
+
manager;
|
|
63
|
+
pluginManager;
|
|
64
|
+
extensionManager;
|
|
65
|
+
streamManager;
|
|
66
|
+
preloadManager;
|
|
67
|
+
filter;
|
|
68
|
+
playbackMode = types_1.PlaybackMode.NATIVE;
|
|
69
|
+
forwardFollowers = new Set();
|
|
70
|
+
forwardLeader = null;
|
|
71
|
+
leaveTimeout = null;
|
|
72
|
+
volumeInterval = null;
|
|
73
|
+
stuckTimer = null;
|
|
74
|
+
skipLoop = false;
|
|
75
|
+
refreshLock = false;
|
|
76
|
+
seekInProgress = false;
|
|
77
|
+
remoteHandle;
|
|
78
|
+
currentSlot = {
|
|
79
|
+
resource: null,
|
|
80
|
+
track: null,
|
|
81
|
+
streamId: null,
|
|
82
|
+
abortController: null,
|
|
83
|
+
isValid: false,
|
|
84
|
+
isLoading: false,
|
|
85
|
+
loadPromise: null,
|
|
86
|
+
};
|
|
87
|
+
preloadEnabled = true;
|
|
88
|
+
crossfadeEnabled = true;
|
|
89
|
+
crossfadeDurationMs = 500;
|
|
90
|
+
lowPerformanceMode = false;
|
|
91
|
+
crossfadeTransitionLock = false;
|
|
92
|
+
smartTransitionEnabled = true;
|
|
93
|
+
smartTransitionGenreAware = true;
|
|
94
|
+
smartTransitionBeatAlign = true;
|
|
95
|
+
smartTransitionBaseMs = 800;
|
|
96
|
+
smartTransitionMinMs = 120;
|
|
97
|
+
smartTransitionMaxMs = 8000;
|
|
98
|
+
smartTransitionGenreDurations = {
|
|
99
|
+
chill: 700,
|
|
100
|
+
ambient: 750,
|
|
101
|
+
lofi: 650,
|
|
102
|
+
pop: 450,
|
|
103
|
+
rock: 350,
|
|
104
|
+
edm: 220,
|
|
105
|
+
house: 250,
|
|
106
|
+
techno: 200,
|
|
107
|
+
};
|
|
108
|
+
smartTransitionBeatAlignMaxWaitMs = 180;
|
|
109
|
+
antiStuckEnabled = true;
|
|
110
|
+
antiStuckMaxRetries = 2;
|
|
111
|
+
antiStuckRetryDelayMs = 900;
|
|
112
|
+
antiStuckReusePreloadFirst = true;
|
|
113
|
+
antiStuckReduceQualityOnRetry = true;
|
|
114
|
+
antiStuckControlledSkipThreshold = 3;
|
|
115
|
+
antiStuckConsecutiveFailures = 0;
|
|
116
|
+
loudnessNormalizationEnabled = false;
|
|
117
|
+
loudnessTargetLUFS = -14;
|
|
118
|
+
loudnessMaxBoostDb = 8;
|
|
119
|
+
loudnessMaxCutDb = 10;
|
|
120
|
+
loudnessLimiterCeiling = 0.95;
|
|
121
|
+
trackMiddlewareChain;
|
|
122
|
+
// Cache for search results to avoid duplicate calls
|
|
123
|
+
searchCache;
|
|
124
|
+
SEARCH_CACHE_TTL = 2 * 60 * 1000; // 2 minutes
|
|
125
|
+
ttsPlayer = null;
|
|
126
|
+
lastDuration = 0;
|
|
127
|
+
seekOffset = 0;
|
|
128
|
+
recoveryInProgress = false;
|
|
51
129
|
constructor(guildId, options = {}, manager) {
|
|
52
130
|
super();
|
|
53
|
-
this.connection = null;
|
|
54
|
-
this.volume = 100;
|
|
55
|
-
this._lastActivity = Date.now();
|
|
56
|
-
this._remotePaused = false;
|
|
57
|
-
this.currentResource = null;
|
|
58
|
-
this.destroyed = false;
|
|
59
|
-
this.playbackMode = types_1.PlaybackMode.NATIVE;
|
|
60
|
-
this.forwardFollowers = new Set();
|
|
61
|
-
this.forwardLeader = null;
|
|
62
|
-
this.leaveTimeout = null;
|
|
63
|
-
this.volumeInterval = null;
|
|
64
|
-
this.stuckTimer = null;
|
|
65
|
-
this.skipLoop = false;
|
|
66
|
-
this.refreshLock = false;
|
|
67
|
-
this.seekInProgress = false;
|
|
68
|
-
this.currentSlot = {
|
|
69
|
-
resource: null,
|
|
70
|
-
track: null,
|
|
71
|
-
streamId: null,
|
|
72
|
-
abortController: null,
|
|
73
|
-
isValid: false,
|
|
74
|
-
isLoading: false,
|
|
75
|
-
loadPromise: null,
|
|
76
|
-
};
|
|
77
|
-
this.preloadEnabled = true;
|
|
78
|
-
this.crossfadeEnabled = true;
|
|
79
|
-
this.crossfadeDurationMs = 500;
|
|
80
|
-
this.lowPerformanceMode = false;
|
|
81
|
-
this.crossfadeTransitionLock = false;
|
|
82
|
-
this.smartTransitionEnabled = true;
|
|
83
|
-
this.smartTransitionGenreAware = true;
|
|
84
|
-
this.smartTransitionBeatAlign = true;
|
|
85
|
-
this.smartTransitionBaseMs = 800;
|
|
86
|
-
this.smartTransitionMinMs = 120;
|
|
87
|
-
this.smartTransitionMaxMs = 8000;
|
|
88
|
-
this.smartTransitionGenreDurations = {
|
|
89
|
-
chill: 700,
|
|
90
|
-
ambient: 750,
|
|
91
|
-
lofi: 650,
|
|
92
|
-
pop: 450,
|
|
93
|
-
rock: 350,
|
|
94
|
-
edm: 220,
|
|
95
|
-
house: 250,
|
|
96
|
-
techno: 200,
|
|
97
|
-
};
|
|
98
|
-
this.smartTransitionBeatAlignMaxWaitMs = 180;
|
|
99
|
-
this.antiStuckEnabled = true;
|
|
100
|
-
this.antiStuckMaxRetries = 2;
|
|
101
|
-
this.antiStuckRetryDelayMs = 900;
|
|
102
|
-
this.antiStuckReusePreloadFirst = true;
|
|
103
|
-
this.antiStuckReduceQualityOnRetry = true;
|
|
104
|
-
this.antiStuckControlledSkipThreshold = 3;
|
|
105
|
-
this.antiStuckConsecutiveFailures = 0;
|
|
106
|
-
this.loudnessNormalizationEnabled = false;
|
|
107
|
-
this.loudnessTargetLUFS = -14;
|
|
108
|
-
this.loudnessMaxBoostDb = 8;
|
|
109
|
-
this.loudnessMaxCutDb = 10;
|
|
110
|
-
this.loudnessLimiterCeiling = 0.95;
|
|
111
|
-
this.SEARCH_CACHE_TTL = 2 * 60 * 1000; // 2 minutes
|
|
112
|
-
this.ttsPlayer = null;
|
|
113
|
-
this.lastDuration = 0;
|
|
114
|
-
this.seekOffset = 0;
|
|
115
131
|
this.debug(`[Player] Constructor called for guildId: ${guildId}`);
|
|
116
132
|
this.guildId = guildId;
|
|
117
133
|
this.queue = new Queue_1.Queue();
|
|
@@ -193,7 +209,7 @@ class Player extends events_1.EventEmitter {
|
|
|
193
209
|
extractorTimeout: this.options.extractorTimeout,
|
|
194
210
|
});
|
|
195
211
|
this.streamManager = new StreamManager_1.StreamManager({
|
|
196
|
-
maxConcurrentStreams: 4,
|
|
212
|
+
maxConcurrentStreams: this.options?.maxStreamStore ?? 4,
|
|
197
213
|
streamTimeout: 5 * 60 * 1000,
|
|
198
214
|
maxListenersPerStream: 15,
|
|
199
215
|
enableMetrics: true,
|
|
@@ -659,6 +675,7 @@ class Player extends events_1.EventEmitter {
|
|
|
659
675
|
async attemptTrackRecovery(track, reason) {
|
|
660
676
|
if (!this.antiStuckEnabled)
|
|
661
677
|
return false;
|
|
678
|
+
this.recoveryInProgress = true;
|
|
662
679
|
this.debug(`[AntiStuck] Recovery started for: ${track.title}`, reason);
|
|
663
680
|
const originalQuality = this.options.quality;
|
|
664
681
|
let attempted = 0;
|
|
@@ -676,6 +693,7 @@ class Player extends events_1.EventEmitter {
|
|
|
676
693
|
if (startedFromPreload) {
|
|
677
694
|
this.antiStuckConsecutiveFailures = 0;
|
|
678
695
|
this.options.quality = originalQuality;
|
|
696
|
+
this.recoveryInProgress = false;
|
|
679
697
|
return true;
|
|
680
698
|
}
|
|
681
699
|
}
|
|
@@ -683,6 +701,7 @@ class Player extends events_1.EventEmitter {
|
|
|
683
701
|
if (started) {
|
|
684
702
|
this.antiStuckConsecutiveFailures = 0;
|
|
685
703
|
this.options.quality = originalQuality;
|
|
704
|
+
this.recoveryInProgress = false;
|
|
686
705
|
return true;
|
|
687
706
|
}
|
|
688
707
|
}
|
|
@@ -694,8 +713,10 @@ class Player extends events_1.EventEmitter {
|
|
|
694
713
|
this.antiStuckConsecutiveFailures++;
|
|
695
714
|
if (this.antiStuckConsecutiveFailures >= this.antiStuckControlledSkipThreshold) {
|
|
696
715
|
this.debug(`[AntiStuck] Controlled skip threshold reached for ${track.title}`);
|
|
716
|
+
this.recoveryInProgress = false;
|
|
697
717
|
return false;
|
|
698
718
|
}
|
|
719
|
+
this.recoveryInProgress = false;
|
|
699
720
|
// Avoid hard skip storm by leaving track for next natural retry window.
|
|
700
721
|
this.debug(`[AntiStuck] Keeping track for controlled retry window: ${track.title}`);
|
|
701
722
|
return false;
|
|
@@ -754,14 +775,15 @@ class Player extends events_1.EventEmitter {
|
|
|
754
775
|
this.filter.setSourceStreamType(streamInfo.type);
|
|
755
776
|
const seekArg = position > 0 ? position : -1;
|
|
756
777
|
if (filterString || position > 0) {
|
|
757
|
-
const processedStream = await this.filter.applyFiltersAndSeek(streamInfo
|
|
758
|
-
|
|
759
|
-
const resource = (0, voice_1.createAudioResource)(processedStream, {
|
|
778
|
+
const processedStream = await this.filter.applyFiltersAndSeek(streamInfo, seekArg);
|
|
779
|
+
const resource = (0, voice_1.createAudioResource)(processedStream.stream, {
|
|
760
780
|
metadata: track,
|
|
761
|
-
inputType: voice_1.StreamType.Arbitrary
|
|
781
|
+
inputType: processedStream.wasRecreated && !filterString ? voice_1.StreamType.Arbitrary
|
|
782
|
+
: position > 0 ? voice_1.StreamType.Raw
|
|
783
|
+
: voice_1.StreamType.Arbitrary,
|
|
762
784
|
inlineVolume: true,
|
|
763
785
|
});
|
|
764
|
-
return { resource, processedStream };
|
|
786
|
+
return { resource, processedStream: processedStream.stream };
|
|
765
787
|
}
|
|
766
788
|
const resource = (0, voice_1.createAudioResource)(streamInfo.stream, {
|
|
767
789
|
metadata: track,
|
|
@@ -907,6 +929,7 @@ class Player extends events_1.EventEmitter {
|
|
|
907
929
|
const currentResource = this.currentSlot.resource;
|
|
908
930
|
if (!currentResource)
|
|
909
931
|
return false;
|
|
932
|
+
// Ensure seekOffset is always an integer (milliseconds)
|
|
910
933
|
this.seekOffset = 0;
|
|
911
934
|
const targetVolume = this.getTrackTargetVolume(track);
|
|
912
935
|
if (currentResource.volume) {
|
|
@@ -1490,6 +1513,7 @@ class Player extends events_1.EventEmitter {
|
|
|
1490
1513
|
this.debug("[Player] Cannot stop while subscribed to another player");
|
|
1491
1514
|
return false;
|
|
1492
1515
|
}
|
|
1516
|
+
this.recoveryInProgress = false;
|
|
1493
1517
|
this.debug(`[Player] stop called`);
|
|
1494
1518
|
if (this.playbackMode === types_1.PlaybackMode.REMOTE) {
|
|
1495
1519
|
this.cancelPreload();
|
|
@@ -1573,6 +1597,7 @@ class Player extends events_1.EventEmitter {
|
|
|
1573
1597
|
return false;
|
|
1574
1598
|
}
|
|
1575
1599
|
this.debug(`[Player] skip called with index: ${index}`);
|
|
1600
|
+
this.recoveryInProgress = false;
|
|
1576
1601
|
if (this.playbackMode === types_1.PlaybackMode.REMOTE) {
|
|
1577
1602
|
if (typeof index === "number" && index >= 0) {
|
|
1578
1603
|
for (let i = 0; i < index; i++)
|
|
@@ -1686,7 +1711,7 @@ class Player extends events_1.EventEmitter {
|
|
|
1686
1711
|
this.debug(`[Player] Save options - filename: ${saveOptions.filename}, quality: ${saveOptions.quality || "default"}`);
|
|
1687
1712
|
}
|
|
1688
1713
|
// Apply filters if any are active
|
|
1689
|
-
let finalStream = streamInfo
|
|
1714
|
+
let finalStream = streamInfo;
|
|
1690
1715
|
if (saveOptions.filter || saveOptions.seek) {
|
|
1691
1716
|
try {
|
|
1692
1717
|
this.filter.clearAll();
|
|
@@ -1696,13 +1721,13 @@ class Player extends events_1.EventEmitter {
|
|
|
1696
1721
|
this.debug(`[Player] Error applying save filters:`, err);
|
|
1697
1722
|
}
|
|
1698
1723
|
this.debug(`[Player] Applying filters to save stream: ${this.filter.getFilterString() || "none"}`);
|
|
1699
|
-
finalStream = await this.filter.applyFiltersAndSeek(streamInfo
|
|
1724
|
+
finalStream = await this.filter.applyFiltersAndSeek(streamInfo, saveOptions.seek || 0).catch((err) => {
|
|
1700
1725
|
this.debug(`[Player] Error applying filters to save stream:`, err);
|
|
1701
|
-
return streamInfo
|
|
1726
|
+
return streamInfo; // Fallback to original stream
|
|
1702
1727
|
});
|
|
1703
1728
|
}
|
|
1704
1729
|
// Return the stream directly - caller can pipe it to fs.createWriteStream()
|
|
1705
|
-
return finalStream;
|
|
1730
|
+
return finalStream.stream;
|
|
1706
1731
|
}
|
|
1707
1732
|
catch (error) {
|
|
1708
1733
|
this.debug(`[Player] save error:`, error);
|
|
@@ -1980,27 +2005,33 @@ class Player extends events_1.EventEmitter {
|
|
|
1980
2005
|
* @returns Formatted time string with leading zeros
|
|
1981
2006
|
*/
|
|
1982
2007
|
formatTime(ms) {
|
|
1983
|
-
|
|
1984
|
-
const
|
|
1985
|
-
const
|
|
1986
|
-
const
|
|
2008
|
+
// Ensure ms is an integer and convert to seconds
|
|
2009
|
+
const totalSeconds = Math.floor(ms / 1000) | 0;
|
|
2010
|
+
const hours = Math.floor(totalSeconds / 3600) | 0;
|
|
2011
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60) | 0;
|
|
2012
|
+
const seconds = (totalSeconds % 60) | 0;
|
|
1987
2013
|
const parts = [];
|
|
1988
|
-
if (hours > 0)
|
|
1989
|
-
parts.push(String(hours)
|
|
1990
|
-
|
|
2014
|
+
if (hours > 0) {
|
|
2015
|
+
parts.push(String(hours)); // Giờ không padStart (ví dụ: 1:05:00)
|
|
2016
|
+
parts.push(String(minutes).padStart(2, "0"));
|
|
2017
|
+
}
|
|
2018
|
+
else {
|
|
2019
|
+
parts.push(String(minutes)); // Phút không padStart nếu là số đầu tiên (ví dụ: 0:30 thay vì 00:30)
|
|
2020
|
+
}
|
|
1991
2021
|
parts.push(String(seconds).padStart(2, "0"));
|
|
1992
2022
|
return parts.join(":");
|
|
1993
2023
|
}
|
|
1994
2024
|
/**
|
|
1995
2025
|
* Format time without leading zeros for hours (1:22:12 or 3:45)
|
|
1996
|
-
* @param ms - Time in milliseconds
|
|
2026
|
+
* @param ms - Time in milliseconds (must be integer)
|
|
1997
2027
|
* @returns Compact formatted time string
|
|
1998
2028
|
*/
|
|
1999
2029
|
formatTimeCompact(ms) {
|
|
2000
|
-
|
|
2001
|
-
const
|
|
2002
|
-
const
|
|
2003
|
-
const
|
|
2030
|
+
// Ensure ms is an integer and convert to seconds
|
|
2031
|
+
const totalSeconds = Math.floor(ms / 1000) | 0;
|
|
2032
|
+
const hours = Math.floor(totalSeconds / 3600) | 0;
|
|
2033
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60) | 0;
|
|
2034
|
+
const seconds = (totalSeconds % 60) | 0;
|
|
2004
2035
|
if (hours > 0) {
|
|
2005
2036
|
return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
2006
2037
|
}
|
|
@@ -2039,8 +2070,9 @@ class Player extends events_1.EventEmitter {
|
|
|
2039
2070
|
},
|
|
2040
2071
|
};
|
|
2041
2072
|
}
|
|
2042
|
-
|
|
2043
|
-
const
|
|
2073
|
+
// Ensure all time values are integers (milliseconds)
|
|
2074
|
+
const total = Math.floor(track.duration > 1000 ? track.duration : track.duration * 1000) | 0;
|
|
2075
|
+
const current = Math.floor(resource.playbackDuration + this.seekOffset) | 0;
|
|
2044
2076
|
return {
|
|
2045
2077
|
current: current,
|
|
2046
2078
|
total: total,
|
|
@@ -2151,18 +2183,19 @@ class Player extends events_1.EventEmitter {
|
|
|
2151
2183
|
try {
|
|
2152
2184
|
const track = this.queue.currentTrack;
|
|
2153
2185
|
this.debug(`[Player] Refreshing player resource for track: ${track.title}`);
|
|
2154
|
-
|
|
2155
|
-
this.seekOffset
|
|
2186
|
+
// Ensure all time values are integers (milliseconds)
|
|
2187
|
+
const currentPosition = (position >= 0 ? position : (this.currentResource?.playbackDuration ?? 0) + this.seekOffset) | 0;
|
|
2188
|
+
this.seekOffset = currentPosition | 0;
|
|
2156
2189
|
const wasPaused = this.isPaused;
|
|
2157
|
-
const playbackDuration = this.currentResource?.playbackDuration ?? 0;
|
|
2190
|
+
const playbackDuration = (this.currentResource?.playbackDuration ?? 0) | 0;
|
|
2158
2191
|
const isForwardSeek = position < 0 || position >= playbackDuration;
|
|
2159
2192
|
const currentStreamId = this.currentSlot.streamId;
|
|
2160
|
-
// Try to grab the raw source stream for reuse (
|
|
2193
|
+
// Try to grab the raw source stream for reuse (only when not seeking, i.e. just applying filters)
|
|
2161
2194
|
let reuseStream = null;
|
|
2162
|
-
if (isForwardSeek && currentStreamId) {
|
|
2195
|
+
if (position < 0 && isForwardSeek && currentStreamId) {
|
|
2163
2196
|
reuseStream = this.streamManager.getRawStream(currentStreamId);
|
|
2164
2197
|
if (reuseStream) {
|
|
2165
|
-
this.debug(`[Player] Will reuse source stream for
|
|
2198
|
+
this.debug(`[Player] Will reuse source stream for filter application`);
|
|
2166
2199
|
}
|
|
2167
2200
|
}
|
|
2168
2201
|
if (reuseStream) {
|
|
@@ -2301,6 +2334,10 @@ class Player extends events_1.EventEmitter {
|
|
|
2301
2334
|
this.debug(`[Player] AudioPlayer went idle during resource refresh — skipping trackEnd/playNext`);
|
|
2302
2335
|
return;
|
|
2303
2336
|
}
|
|
2337
|
+
if (this.recoveryInProgress) {
|
|
2338
|
+
this.debug(`[Player] AudioPlayer went idle during recovery — skipping playNext`);
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2304
2341
|
// Track ended
|
|
2305
2342
|
const track = this.queue.currentTrack;
|
|
2306
2343
|
if (track) {
|
|
@@ -2397,6 +2434,8 @@ class Player extends events_1.EventEmitter {
|
|
|
2397
2434
|
this.audioPlayer.on("error", (error) => {
|
|
2398
2435
|
if (this.destroyed)
|
|
2399
2436
|
return;
|
|
2437
|
+
if (this.recoveryInProgress)
|
|
2438
|
+
return;
|
|
2400
2439
|
this.debug(`[Player] AudioPlayer error:`, error);
|
|
2401
2440
|
this.emit("playerError", error, this.queue.currentTrack || undefined);
|
|
2402
2441
|
const track = this.queue.currentTrack;
|