ziplayer 0.3.3 → 0.3.4
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/AGENTS.md +717 -653
- package/README.md +658 -639
- package/dist/extensions/BaseExtension.d.ts +10 -1
- package/dist/extensions/BaseExtension.d.ts.map +1 -1
- package/dist/extensions/BaseExtension.js +27 -1
- package/dist/extensions/BaseExtension.js.map +1 -1
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/index.js +24 -6
- package/dist/extensions/index.js.map +1 -1
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +104 -50
- package/dist/plugins/index.js.map +1 -1
- package/dist/structures/Player.d.ts +74 -43
- package/dist/structures/Player.d.ts.map +1 -1
- package/dist/structures/Player.js +440 -114
- package/dist/structures/Player.js.map +1 -1
- package/dist/structures/PlayerManager.d.ts +41 -6
- package/dist/structures/PlayerManager.d.ts.map +1 -1
- package/dist/structures/PlayerManager.js +94 -125
- package/dist/structures/PlayerManager.js.map +1 -1
- package/dist/types/extension.d.ts +3 -0
- package/dist/types/extension.d.ts.map +1 -1
- package/dist/types/index.d.ts +38 -11
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
- package/src/extensions/BaseExtension.ts +31 -1
- package/src/extensions/index.ts +30 -7
- package/src/plugins/index.ts +136 -53
- package/src/structures/Player.ts +2937 -2544
- package/src/structures/PlayerManager.ts +916 -955
- package/src/structures/Queue.ts +621 -621
- package/src/types/extension.ts +3 -0
- package/src/types/index.ts +43 -11
|
@@ -52,12 +52,14 @@ class Player extends events_1.EventEmitter {
|
|
|
52
52
|
super();
|
|
53
53
|
this.connection = null;
|
|
54
54
|
this.volume = 100;
|
|
55
|
-
this.isPlaying = false;
|
|
56
|
-
this.isPaused = false;
|
|
57
|
-
this.forwardMode = false;
|
|
58
55
|
this._lastActivity = Date.now();
|
|
59
|
-
this.
|
|
56
|
+
this._remotePaused = false;
|
|
60
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;
|
|
61
63
|
this.volumeInterval = null;
|
|
62
64
|
this.stuckTimer = null;
|
|
63
65
|
this.skipLoop = false;
|
|
@@ -105,7 +107,6 @@ class Player extends events_1.EventEmitter {
|
|
|
105
107
|
this.loudnessMaxBoostDb = 8;
|
|
106
108
|
this.loudnessMaxCutDb = 10;
|
|
107
109
|
this.loudnessLimiterCeiling = 0.95;
|
|
108
|
-
this.destroyed = false;
|
|
109
110
|
this.SEARCH_CACHE_TTL = 2 * 60 * 1000; // 2 minutes
|
|
110
111
|
this.ttsPlayer = null;
|
|
111
112
|
this.lastDuration = 0;
|
|
@@ -378,6 +379,10 @@ class Player extends events_1.EventEmitter {
|
|
|
378
379
|
* await player.play(null); // play from queue
|
|
379
380
|
*/
|
|
380
381
|
async play(query, requestedBy) {
|
|
382
|
+
if (this.playbackMode === types_1.PlaybackMode.FORWARD) {
|
|
383
|
+
this.debug("[Player] Cannot play while subscribed to another player. Call unsubscribeForward() first.");
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
381
386
|
const debugInfo = query === null ? "null"
|
|
382
387
|
: typeof query === "string" ? query
|
|
383
388
|
: "tracks" in query ? `${query.tracks.length} tracks`
|
|
@@ -812,6 +817,7 @@ class Player extends events_1.EventEmitter {
|
|
|
812
817
|
this.debug(`[Stream] Using existing stream from manager for: ${track.title}`);
|
|
813
818
|
return { stream: existingStream, type: "arbitrary" };
|
|
814
819
|
}
|
|
820
|
+
// FIRST: Try to get stream from extensions
|
|
815
821
|
let stream = await this.extensionManager.provideStream(track);
|
|
816
822
|
if (this.destroyed) {
|
|
817
823
|
if (stream?.stream && typeof stream.stream.destroy === "function" && !stream.stream.destroyed) {
|
|
@@ -819,10 +825,24 @@ class Player extends events_1.EventEmitter {
|
|
|
819
825
|
}
|
|
820
826
|
throw new Error("PLAYER_DESTROYED");
|
|
821
827
|
}
|
|
828
|
+
// Handle remote playback - THIS SHOULD BE FIRST PRIORITY
|
|
829
|
+
if (stream?.remote && stream.handle) {
|
|
830
|
+
this.debug(`[Stream] Remote handle provided by extension for: ${track.title}`);
|
|
831
|
+
this.playbackMode = types_1.PlaybackMode.REMOTE;
|
|
832
|
+
this.preloadEnabled = false;
|
|
833
|
+
this.crossfadeEnabled = false;
|
|
834
|
+
// Clear any existing preload for remote mode
|
|
835
|
+
this.cancelPreload();
|
|
836
|
+
return stream;
|
|
837
|
+
}
|
|
838
|
+
// If extension returned a regular stream
|
|
822
839
|
if (stream?.stream) {
|
|
823
840
|
this.debug(`[Stream] Extension provided stream for: ${track.title}`);
|
|
841
|
+
this.playbackMode = types_1.PlaybackMode.NATIVE;
|
|
824
842
|
return stream;
|
|
825
843
|
}
|
|
844
|
+
// SECOND: Try plugins only if extension didn't handle it
|
|
845
|
+
this.debug(`[Stream] Extension didn't provide stream, trying plugins for: ${track.title}`);
|
|
826
846
|
stream = await this.pluginManager.getStream(track);
|
|
827
847
|
if (this.destroyed) {
|
|
828
848
|
if (stream?.stream && typeof stream.stream.destroy === "function" && !stream.stream.destroyed) {
|
|
@@ -837,10 +857,11 @@ class Player extends events_1.EventEmitter {
|
|
|
837
857
|
stream.stream.destroy();
|
|
838
858
|
return { stream: existingAgain, type: "arbitrary" };
|
|
839
859
|
}
|
|
840
|
-
// Register with StreamManager
|
|
841
860
|
this.debug(`[Stream] Plugin provided stream for: ${track.title}`);
|
|
861
|
+
this.playbackMode = types_1.PlaybackMode.NATIVE;
|
|
842
862
|
return stream;
|
|
843
863
|
}
|
|
864
|
+
// Check if any plugin claims to support this track but failed
|
|
844
865
|
if (!this.pluginManager.hasStreamCandidate(track)) {
|
|
845
866
|
throw new Error(`UNRECOVERABLE_NO_PLUGIN:${track.title}`);
|
|
846
867
|
}
|
|
@@ -857,13 +878,25 @@ class Player extends events_1.EventEmitter {
|
|
|
857
878
|
async startTrack(track) {
|
|
858
879
|
if (this.destroyed)
|
|
859
880
|
return false;
|
|
881
|
+
// First, get stream info (this will handle remote detection)
|
|
882
|
+
let streamInfo = null;
|
|
883
|
+
try {
|
|
884
|
+
streamInfo = await this.getStream(track);
|
|
885
|
+
}
|
|
886
|
+
catch (error) {
|
|
887
|
+
this.debug(`[Player] Failed to get stream for track: ${track.title}`, error);
|
|
888
|
+
throw error;
|
|
889
|
+
}
|
|
890
|
+
// Handle remote playback
|
|
891
|
+
if (streamInfo?.remote && streamInfo.handle) {
|
|
892
|
+
return await this.playRemote(track, streamInfo);
|
|
893
|
+
}
|
|
894
|
+
// Handle native playback
|
|
860
895
|
try {
|
|
861
896
|
// Try to use preloaded resource
|
|
862
897
|
if (this.preloadManager.hasValidPreload(track)) {
|
|
863
898
|
this.debug(`[Player] Using preloaded stream for: ${track.title}`);
|
|
864
|
-
// Stop current playback
|
|
865
899
|
this.audioPlayer.stop(true);
|
|
866
|
-
// Clean up old current stream (but delay to be safe)
|
|
867
900
|
const oldStreamId = this.currentSlot.streamId;
|
|
868
901
|
if (oldStreamId && this.streamManager) {
|
|
869
902
|
setTimeout(() => {
|
|
@@ -872,29 +905,24 @@ class Player extends events_1.EventEmitter {
|
|
|
872
905
|
}
|
|
873
906
|
}, 3000);
|
|
874
907
|
}
|
|
875
|
-
// Set current slot from preload
|
|
876
908
|
this.promotePreloadToCurrent(track);
|
|
877
909
|
const currentResource = this.currentSlot.resource;
|
|
878
910
|
if (!currentResource) {
|
|
879
911
|
return false;
|
|
880
912
|
}
|
|
881
913
|
const targetVolume = this.getTrackTargetVolume(track);
|
|
882
|
-
// Apply volume
|
|
883
914
|
if (currentResource.volume) {
|
|
884
915
|
currentResource.volume.setVolume(this.crossfadeEnabled ? 0 : targetVolume);
|
|
885
916
|
}
|
|
886
|
-
// Play
|
|
887
917
|
await this.maybeAlignToBeatBoundary();
|
|
888
918
|
this.audioPlayer.play(currentResource);
|
|
889
919
|
await (0, voice_1.entersState)(this.audioPlayer, voice_1.AudioPlayerStatus.Playing, 10_000);
|
|
890
920
|
await this.applyCrossfadeIn(currentResource, track);
|
|
891
|
-
// Start preloading next track (async, don't await)
|
|
892
921
|
this.preloadNextTrack().catch((err) => {
|
|
893
922
|
this.debug(`[Player] Preload error:`, err);
|
|
894
923
|
});
|
|
895
924
|
return true;
|
|
896
925
|
}
|
|
897
|
-
// No valid preload, load fresh
|
|
898
926
|
this.debug(`[Player] No preload available, loading fresh: ${track.title}`);
|
|
899
927
|
return await this.loadFreshStream(track);
|
|
900
928
|
}
|
|
@@ -904,53 +932,6 @@ class Player extends events_1.EventEmitter {
|
|
|
904
932
|
return false;
|
|
905
933
|
}
|
|
906
934
|
}
|
|
907
|
-
/**
|
|
908
|
-
* Swap preload slot to current slot
|
|
909
|
-
*/
|
|
910
|
-
async swapToCurrent(track) {
|
|
911
|
-
if (!this.preloadManager.hasValidPreload(track)) {
|
|
912
|
-
return false;
|
|
913
|
-
}
|
|
914
|
-
const oldStreamId = this.currentSlot.streamId;
|
|
915
|
-
// Stop current playback
|
|
916
|
-
this.audioPlayer.stop(true);
|
|
917
|
-
// Clean up old current stream (but keep it for a moment)
|
|
918
|
-
if (oldStreamId && this.streamManager) {
|
|
919
|
-
// Delay cleanup to avoid destroying if still needed
|
|
920
|
-
setTimeout(() => {
|
|
921
|
-
if (this.currentSlot.streamId === oldStreamId) {
|
|
922
|
-
this.streamManager.unregisterStream(oldStreamId, true);
|
|
923
|
-
}
|
|
924
|
-
}, 5000);
|
|
925
|
-
}
|
|
926
|
-
// Set new current
|
|
927
|
-
this.promotePreloadToCurrent(track);
|
|
928
|
-
const currentResource = this.currentSlot.resource;
|
|
929
|
-
if (!currentResource) {
|
|
930
|
-
return false;
|
|
931
|
-
}
|
|
932
|
-
const targetVolume = this.getTrackTargetVolume(track);
|
|
933
|
-
// Apply volume
|
|
934
|
-
if (currentResource.volume) {
|
|
935
|
-
currentResource.volume.setVolume(this.crossfadeEnabled ? 0 : targetVolume);
|
|
936
|
-
}
|
|
937
|
-
// Play
|
|
938
|
-
await this.maybeAlignToBeatBoundary();
|
|
939
|
-
this.audioPlayer.play(currentResource);
|
|
940
|
-
try {
|
|
941
|
-
await (0, voice_1.entersState)(this.audioPlayer, voice_1.AudioPlayerStatus.Playing, 10_000);
|
|
942
|
-
await this.applyCrossfadeIn(currentResource, track);
|
|
943
|
-
// Start preloading next track
|
|
944
|
-
this.preloadNextTrack().catch((err) => {
|
|
945
|
-
this.debug(`[Player] Preload error:`, err);
|
|
946
|
-
});
|
|
947
|
-
return true;
|
|
948
|
-
}
|
|
949
|
-
catch (err) {
|
|
950
|
-
this.debug(`[Player] Failed to play swapped track:`, err);
|
|
951
|
-
return false;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
935
|
/**
|
|
955
936
|
* Load fresh stream when no preload available
|
|
956
937
|
*/
|
|
@@ -961,6 +942,10 @@ class Player extends events_1.EventEmitter {
|
|
|
961
942
|
await this.safeCancelPreload();
|
|
962
943
|
try {
|
|
963
944
|
const streamInfo = await this.getStream(track);
|
|
945
|
+
// Handle remote playback
|
|
946
|
+
if (streamInfo?.remote && streamInfo.handle) {
|
|
947
|
+
return await this.playRemote(track, streamInfo);
|
|
948
|
+
}
|
|
964
949
|
if (!streamInfo?.stream) {
|
|
965
950
|
throw new Error(`No stream available`);
|
|
966
951
|
}
|
|
@@ -1013,8 +998,6 @@ class Player extends events_1.EventEmitter {
|
|
|
1013
998
|
if (this.destroyed)
|
|
1014
999
|
return false;
|
|
1015
1000
|
this.debug("[Player] playNext called");
|
|
1016
|
-
// Don't cancel preload here unless absolutely necessary
|
|
1017
|
-
// Let startTrack handle it
|
|
1018
1001
|
while (true) {
|
|
1019
1002
|
const track = this.queue.next(this.skipLoop);
|
|
1020
1003
|
this.skipLoop = false;
|
|
@@ -1035,7 +1018,6 @@ class Player extends events_1.EventEmitter {
|
|
|
1035
1018
|
}
|
|
1036
1019
|
}
|
|
1037
1020
|
this.debug(`[Player] No next track in queue`);
|
|
1038
|
-
this.isPlaying = false;
|
|
1039
1021
|
this.emit("queueEnd");
|
|
1040
1022
|
// Clean up both slots when queue is empty
|
|
1041
1023
|
this.clearSlot(this.currentSlot);
|
|
@@ -1054,6 +1036,11 @@ class Player extends events_1.EventEmitter {
|
|
|
1054
1036
|
this.antiStuckConsecutiveFailures = 0;
|
|
1055
1037
|
return true;
|
|
1056
1038
|
}
|
|
1039
|
+
// For remote playback, if startTrack returns false, it's a failure
|
|
1040
|
+
if (this.playbackMode === types_1.PlaybackMode.REMOTE) {
|
|
1041
|
+
this.debug(`[Player] Remote track failed to start: ${track.title}`);
|
|
1042
|
+
continue; // Skip to next track
|
|
1043
|
+
}
|
|
1057
1044
|
const recovered = await this.attemptTrackRecovery(track, new Error("TRACK_START_RETURNED_FALSE"));
|
|
1058
1045
|
if (recovered) {
|
|
1059
1046
|
return true;
|
|
@@ -1068,6 +1055,11 @@ class Player extends events_1.EventEmitter {
|
|
|
1068
1055
|
catch (err) {
|
|
1069
1056
|
this.debug(`[Player] playNext error:`, err);
|
|
1070
1057
|
this.emit("playerError", err, track);
|
|
1058
|
+
// For remote playback, just skip to next track
|
|
1059
|
+
if (this.playbackMode === types_1.PlaybackMode.REMOTE) {
|
|
1060
|
+
this.debug(`[Player] Remote track error, skipping: ${track.title}`);
|
|
1061
|
+
continue;
|
|
1062
|
+
}
|
|
1071
1063
|
if (this.isUnrecoverableStreamError(err)) {
|
|
1072
1064
|
this.debug(`[Player] Skipping unrecoverable track (no plugin): ${track.title}`);
|
|
1073
1065
|
continue;
|
|
@@ -1086,6 +1078,27 @@ class Player extends events_1.EventEmitter {
|
|
|
1086
1078
|
}
|
|
1087
1079
|
}
|
|
1088
1080
|
}
|
|
1081
|
+
async playRemote(track, stream) {
|
|
1082
|
+
if (!stream.handle)
|
|
1083
|
+
return false;
|
|
1084
|
+
try {
|
|
1085
|
+
// Store the remote handle for later use
|
|
1086
|
+
this.remoteHandle = stream.handle;
|
|
1087
|
+
// Set current track before playing
|
|
1088
|
+
this.queue.setCurrentTrack(track);
|
|
1089
|
+
// Emit track start event before playing (so UI updates)
|
|
1090
|
+
this.emit("trackStart", track);
|
|
1091
|
+
// Start playback via remote handle
|
|
1092
|
+
await stream.handle.play();
|
|
1093
|
+
this.debug(`[Player] Remote playback started for: ${track.title}`);
|
|
1094
|
+
return true;
|
|
1095
|
+
}
|
|
1096
|
+
catch (error) {
|
|
1097
|
+
this.debug(`[Player] Remote playback error:`, error);
|
|
1098
|
+
this.emit("playerError", error, track);
|
|
1099
|
+
return false;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1089
1102
|
//#endregion
|
|
1090
1103
|
//#region TTS
|
|
1091
1104
|
ensureTTSPlayer() {
|
|
@@ -1237,51 +1250,142 @@ class Player extends events_1.EventEmitter {
|
|
|
1237
1250
|
}
|
|
1238
1251
|
}
|
|
1239
1252
|
/**
|
|
1240
|
-
* Subscribe this player's
|
|
1241
|
-
*
|
|
1253
|
+
* Subscribe this player to another player's playback stream.
|
|
1254
|
+
*
|
|
1255
|
+
* This enables "forward mode", where the follower player directly subscribes
|
|
1256
|
+
* to the leader player's {@link audioPlayer} instead of creating its own stream.
|
|
1242
1257
|
*
|
|
1243
|
-
*
|
|
1244
|
-
*
|
|
1245
|
-
* - radio/broadcast systems
|
|
1246
|
-
* - multi-guild synchronized playback
|
|
1247
|
-
* - forwardMode shared streaming
|
|
1258
|
+
* Greatly reduces CPU, bandwidth, and extractor usage because only the leader
|
|
1259
|
+
* creates and decodes the audio resource.
|
|
1248
1260
|
*
|
|
1249
|
-
*
|
|
1250
|
-
*
|
|
1251
|
-
*
|
|
1261
|
+
* ## Features
|
|
1262
|
+
* - Real-time shared playback
|
|
1263
|
+
* - Followers may join at any time
|
|
1264
|
+
* - Automatic track synchronization
|
|
1265
|
+
* - Optional volume synchronization
|
|
1266
|
+
* - Automatic cleanup on destroy
|
|
1267
|
+
* - Supports unlimited followers
|
|
1252
1268
|
*
|
|
1253
|
-
*
|
|
1254
|
-
* -
|
|
1255
|
-
* -
|
|
1256
|
-
* -
|
|
1257
|
-
* -
|
|
1269
|
+
* ## Lifecycle
|
|
1270
|
+
* - When the leader starts a track, followers automatically receive the same track metadata.
|
|
1271
|
+
* - When the leader pauses/resumes/stops, followers are synchronized.
|
|
1272
|
+
* - Destroying the leader automatically unsubscribes all followers.
|
|
1273
|
+
* - Destroying a follower only removes that follower.
|
|
1258
1274
|
*
|
|
1259
|
-
*
|
|
1260
|
-
* -
|
|
1261
|
-
* -
|
|
1262
|
-
* -
|
|
1275
|
+
* ## Notes
|
|
1276
|
+
* - Both players must already be connected to voice.
|
|
1277
|
+
* - A player cannot subscribe to itself.
|
|
1278
|
+
* - Existing playback subscriptions are automatically replaced.
|
|
1263
1279
|
*
|
|
1264
|
-
* @param {Player}
|
|
1280
|
+
* @param {Player} leader The leader player to subscribe to.
|
|
1281
|
+
* @param options Additional playback mirror options.
|
|
1282
|
+
* @param options.syncVolume When true, follower volume automatically follows the leader. Default: true.
|
|
1265
1283
|
*
|
|
1266
|
-
* @returns {boolean}
|
|
1267
|
-
* Returns true if subscription succeeded,
|
|
1268
|
-
* otherwise false.
|
|
1284
|
+
* @returns {boolean} True if subscription succeeded.
|
|
1269
1285
|
*
|
|
1270
1286
|
* @example
|
|
1271
1287
|
* follower.subscribeTo(leader);
|
|
1272
1288
|
*
|
|
1273
1289
|
* @example
|
|
1274
|
-
*
|
|
1275
|
-
*
|
|
1276
|
-
* }
|
|
1290
|
+
* follower.subscribeTo(leader, {
|
|
1291
|
+
* syncVolume: true,
|
|
1292
|
+
* });
|
|
1277
1293
|
*/
|
|
1278
|
-
subscribeTo(
|
|
1279
|
-
if (!
|
|
1294
|
+
subscribeTo(leader, options) {
|
|
1295
|
+
if (!leader)
|
|
1296
|
+
return false;
|
|
1297
|
+
if (leader === this) {
|
|
1298
|
+
this.debug(`[Player] Cannot subscribe to self`);
|
|
1280
1299
|
return false;
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1300
|
+
}
|
|
1301
|
+
if (leader.destroyed) {
|
|
1302
|
+
this.debug("[Player] Cannot subscribe to destroyed leader");
|
|
1303
|
+
return false;
|
|
1304
|
+
}
|
|
1305
|
+
if (this.destroyed) {
|
|
1306
|
+
this.debug("[Player] Cannot subscribe destroyed player");
|
|
1307
|
+
return false;
|
|
1308
|
+
}
|
|
1309
|
+
if (!!leader.forwardLeader) {
|
|
1310
|
+
this.debug("[Player] Cannot subscribe to follower player");
|
|
1311
|
+
return false;
|
|
1312
|
+
}
|
|
1313
|
+
if (!this.connection || !leader.connection) {
|
|
1314
|
+
this.debug(`[Player] Missing connection for subscribeTo`);
|
|
1315
|
+
return false;
|
|
1316
|
+
}
|
|
1317
|
+
// cleanup old leader
|
|
1318
|
+
if (this.forwardLeader) {
|
|
1319
|
+
this.unsubscribeForward("This Player new subscribeTo " + leader.guildId);
|
|
1320
|
+
}
|
|
1321
|
+
this.forwardLeader = leader;
|
|
1322
|
+
leader.forwardFollowers.add(this);
|
|
1323
|
+
try {
|
|
1324
|
+
// clear local playback
|
|
1325
|
+
this.stop();
|
|
1326
|
+
// detach current followers first
|
|
1327
|
+
for (const fp of [...this.forwardFollowers]) {
|
|
1328
|
+
try {
|
|
1329
|
+
fp.unsubscribeForward("Leader new subscribeTo " + leader.guildId);
|
|
1330
|
+
}
|
|
1331
|
+
catch { }
|
|
1332
|
+
}
|
|
1333
|
+
this.forwardFollowers.clear();
|
|
1334
|
+
this.queue.clear();
|
|
1335
|
+
if (leader.currentTrack) {
|
|
1336
|
+
this.queue.setCurrentTrack(leader.currentTrack);
|
|
1337
|
+
}
|
|
1338
|
+
if (options?.forwardMode ?? true)
|
|
1339
|
+
this.playbackMode = types_1.PlaybackMode.FORWARD;
|
|
1340
|
+
if (this.playbackMode === types_1.PlaybackMode.FORWARD && this.connection) {
|
|
1341
|
+
this.connection.subscribe(leader.audioPlayer);
|
|
1342
|
+
}
|
|
1343
|
+
this.volume = leader.volume;
|
|
1344
|
+
this.emit("forwardModeStart", leader);
|
|
1345
|
+
this.debug(`[Player] Forward mode subscribed ${this.guildId} -> ${leader.guildId}`);
|
|
1346
|
+
return true;
|
|
1347
|
+
}
|
|
1348
|
+
catch (e) {
|
|
1349
|
+
this.debug(`[Player] subscribeTo error:`, e);
|
|
1350
|
+
this.forwardLeader = null;
|
|
1351
|
+
leader.forwardFollowers.delete(this);
|
|
1352
|
+
return false;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* Unsubscribe this player from its current playback leader.
|
|
1357
|
+
*
|
|
1358
|
+
* This disables forward mode and restores the player's own audioPlayer
|
|
1359
|
+
* subscription back to its voice connection.
|
|
1360
|
+
*
|
|
1361
|
+
* Automatically emitted when:
|
|
1362
|
+
* - The leader player is destroyed
|
|
1363
|
+
* - This player is destroyed
|
|
1364
|
+
* - A new leader subscription replaces the old one
|
|
1365
|
+
*
|
|
1366
|
+
* Emits:
|
|
1367
|
+
* - `forwardModeEnd`
|
|
1368
|
+
*
|
|
1369
|
+
* @returns {boolean} True if a playback subscription existed and was removed.
|
|
1370
|
+
*
|
|
1371
|
+
* @example
|
|
1372
|
+
* follower.unsubscribeForward();
|
|
1373
|
+
*/
|
|
1374
|
+
unsubscribeForward(reason) {
|
|
1375
|
+
if (!this.forwardLeader) {
|
|
1376
|
+
return false;
|
|
1377
|
+
}
|
|
1378
|
+
const leader = this.forwardLeader;
|
|
1379
|
+
leader.forwardFollowers.delete(this);
|
|
1380
|
+
this.forwardLeader = null;
|
|
1381
|
+
this.playbackMode = types_1.PlaybackMode.NATIVE;
|
|
1382
|
+
try {
|
|
1383
|
+
this.connection?.subscribe(this.audioPlayer);
|
|
1384
|
+
}
|
|
1385
|
+
catch { }
|
|
1386
|
+
this.queue.clear();
|
|
1387
|
+
this.emit("forwardModeEnd", leader, reason);
|
|
1388
|
+
this.debug(`[Player] Forward mode unsubscribed ${this.guildId} <- ${leader.guildId}: ${reason ?? null}`);
|
|
1285
1389
|
return true;
|
|
1286
1390
|
}
|
|
1287
1391
|
/**
|
|
@@ -1293,10 +1397,22 @@ class Player extends events_1.EventEmitter {
|
|
|
1293
1397
|
* console.log(`Paused: ${paused}`);
|
|
1294
1398
|
*/
|
|
1295
1399
|
pause() {
|
|
1400
|
+
if (this.playbackMode === types_1.PlaybackMode.FORWARD) {
|
|
1401
|
+
this.debug("[Player] Cannot pause while subscribed to another player");
|
|
1402
|
+
return false;
|
|
1403
|
+
}
|
|
1296
1404
|
this.debug(`[Player] pause called`);
|
|
1297
|
-
if (this.
|
|
1298
|
-
|
|
1405
|
+
if (this.playbackMode === types_1.PlaybackMode.REMOTE) {
|
|
1406
|
+
if (!this.remoteHandle)
|
|
1407
|
+
return false;
|
|
1408
|
+
void this.remoteHandle.pause().catch((e) => this.debug("[Player] Remote pause:", e));
|
|
1409
|
+
const track = this.queue.currentTrack;
|
|
1410
|
+
if (track)
|
|
1411
|
+
this.emit("playerPause", track);
|
|
1412
|
+
return true;
|
|
1299
1413
|
}
|
|
1414
|
+
if (this.isPlaying && !this.isPaused)
|
|
1415
|
+
return this.audioPlayer.pause();
|
|
1300
1416
|
return false;
|
|
1301
1417
|
}
|
|
1302
1418
|
/**
|
|
@@ -1308,7 +1424,20 @@ class Player extends events_1.EventEmitter {
|
|
|
1308
1424
|
* console.log(`Resumed: ${resumed}`);
|
|
1309
1425
|
*/
|
|
1310
1426
|
resume() {
|
|
1427
|
+
if (this.playbackMode === types_1.PlaybackMode.FORWARD) {
|
|
1428
|
+
this.debug("[Player] Cannot resume while subscribed to another player");
|
|
1429
|
+
return false;
|
|
1430
|
+
}
|
|
1311
1431
|
this.debug(`[Player] resume called`);
|
|
1432
|
+
if (this.playbackMode === types_1.PlaybackMode.REMOTE) {
|
|
1433
|
+
if (!this.remoteHandle)
|
|
1434
|
+
return false;
|
|
1435
|
+
void this.remoteHandle.resume().catch((e) => this.debug("[Player] Remote resume:", e));
|
|
1436
|
+
const track = this.queue.currentTrack;
|
|
1437
|
+
if (track)
|
|
1438
|
+
this.emit("playerResume", track);
|
|
1439
|
+
return true;
|
|
1440
|
+
}
|
|
1312
1441
|
if (this.isPaused) {
|
|
1313
1442
|
const result = this.audioPlayer.unpause();
|
|
1314
1443
|
if (result) {
|
|
@@ -1331,16 +1460,33 @@ class Player extends events_1.EventEmitter {
|
|
|
1331
1460
|
* console.log(`Stopped: ${stopped}`);
|
|
1332
1461
|
*/
|
|
1333
1462
|
stop() {
|
|
1463
|
+
if (this.playbackMode === types_1.PlaybackMode.FORWARD) {
|
|
1464
|
+
this.debug("[Player] Cannot stop while subscribed to another player");
|
|
1465
|
+
return false;
|
|
1466
|
+
}
|
|
1334
1467
|
this.debug(`[Player] stop called`);
|
|
1468
|
+
if (this.playbackMode === types_1.PlaybackMode.REMOTE) {
|
|
1469
|
+
this.cancelPreload();
|
|
1470
|
+
this.queue.clear();
|
|
1471
|
+
void this.remoteHandle?.stop().catch((e) => this.debug("[Player] Remote stop:", e));
|
|
1472
|
+
this.emit("playerStop");
|
|
1473
|
+
return true;
|
|
1474
|
+
}
|
|
1335
1475
|
// Cancel preload when stopping
|
|
1336
1476
|
this.cancelPreload();
|
|
1337
1477
|
this.queue.clear();
|
|
1338
1478
|
const result = this.audioPlayer.stop();
|
|
1339
1479
|
this.destroyCurrentStream();
|
|
1340
1480
|
this.currentResource = null;
|
|
1341
|
-
this.isPlaying = false;
|
|
1342
|
-
this.isPaused = false;
|
|
1343
1481
|
this.emit("playerStop");
|
|
1482
|
+
for (const fp of this.forwardFollowers) {
|
|
1483
|
+
try {
|
|
1484
|
+
fp.connection?.subscribe(fp.audioPlayer);
|
|
1485
|
+
fp.audioPlayer.stop(true);
|
|
1486
|
+
fp.emit("playerStop");
|
|
1487
|
+
}
|
|
1488
|
+
catch { }
|
|
1489
|
+
}
|
|
1344
1490
|
return result;
|
|
1345
1491
|
}
|
|
1346
1492
|
/**
|
|
@@ -1357,7 +1503,17 @@ class Player extends events_1.EventEmitter {
|
|
|
1357
1503
|
* await player.seek(90000);
|
|
1358
1504
|
*/
|
|
1359
1505
|
async seek(position) {
|
|
1506
|
+
if (this.playbackMode === types_1.PlaybackMode.FORWARD) {
|
|
1507
|
+
this.debug("[Player] Cannot seek while subscribed to another player");
|
|
1508
|
+
return false;
|
|
1509
|
+
}
|
|
1360
1510
|
this.debug(`[Player] seek called with position: ${position}ms`);
|
|
1511
|
+
if (this.playbackMode === types_1.PlaybackMode.REMOTE) {
|
|
1512
|
+
if (!this.remoteHandle)
|
|
1513
|
+
return false;
|
|
1514
|
+
await this.remoteHandle.seek(position);
|
|
1515
|
+
return true;
|
|
1516
|
+
}
|
|
1361
1517
|
const track = this.queue.currentTrack;
|
|
1362
1518
|
if (!track) {
|
|
1363
1519
|
this.debug(`[Player] No current track to seek`);
|
|
@@ -1382,7 +1538,20 @@ class Player extends events_1.EventEmitter {
|
|
|
1382
1538
|
* console.log(`Skipped: ${skipped}`);
|
|
1383
1539
|
*/
|
|
1384
1540
|
skip(index) {
|
|
1541
|
+
if (this.playbackMode === types_1.PlaybackMode.FORWARD) {
|
|
1542
|
+
this.debug("[Player] Cannot skip while subscribed to another player");
|
|
1543
|
+
return false;
|
|
1544
|
+
}
|
|
1385
1545
|
this.debug(`[Player] skip called with index: ${index}`);
|
|
1546
|
+
if (this.playbackMode === types_1.PlaybackMode.REMOTE) {
|
|
1547
|
+
if (typeof index === "number" && index >= 0) {
|
|
1548
|
+
for (let i = 0; i < index; i++)
|
|
1549
|
+
this.queue.remove(0);
|
|
1550
|
+
}
|
|
1551
|
+
// signal the remote backend to stop; TrackEndEvent triggers playNext()
|
|
1552
|
+
void this.remoteHandle?.stop().catch((e) => this.debug("[Player] Remote skip:", e));
|
|
1553
|
+
return true;
|
|
1554
|
+
}
|
|
1386
1555
|
try {
|
|
1387
1556
|
if (typeof index === "number" && index >= 0) {
|
|
1388
1557
|
const targetTrack = this.queue.getTrack(index);
|
|
@@ -1419,6 +1588,10 @@ class Player extends events_1.EventEmitter {
|
|
|
1419
1588
|
* console.log(`Previous: ${previous}`);
|
|
1420
1589
|
*/
|
|
1421
1590
|
async previous() {
|
|
1591
|
+
if (this.playbackMode === types_1.PlaybackMode.FORWARD) {
|
|
1592
|
+
this.debug("[Player] Cannot previous while subscribed to another player");
|
|
1593
|
+
return false;
|
|
1594
|
+
}
|
|
1422
1595
|
this.debug(`[Player] previous called`);
|
|
1423
1596
|
const track = this.queue.previous();
|
|
1424
1597
|
if (!track)
|
|
@@ -1548,6 +1721,12 @@ class Player extends events_1.EventEmitter {
|
|
|
1548
1721
|
* console.log(`Auto-play mode: ${autoPlayMode}`);
|
|
1549
1722
|
*/
|
|
1550
1723
|
autoPlay(mode) {
|
|
1724
|
+
if (this.playbackMode === types_1.PlaybackMode.FORWARD) {
|
|
1725
|
+
if (!mode)
|
|
1726
|
+
return this.forwardLeader?.autoPlay() ?? false;
|
|
1727
|
+
this.debug("[Player] Cannot autoPlay while subscribed to another player");
|
|
1728
|
+
return false;
|
|
1729
|
+
}
|
|
1551
1730
|
return this.queue.autoPlay(mode);
|
|
1552
1731
|
}
|
|
1553
1732
|
/**
|
|
@@ -1560,11 +1739,20 @@ class Player extends events_1.EventEmitter {
|
|
|
1560
1739
|
* console.log(`Volume set: ${volumeSet}`);
|
|
1561
1740
|
*/
|
|
1562
1741
|
setVolume(volume) {
|
|
1742
|
+
if (this.playbackMode === types_1.PlaybackMode.FORWARD) {
|
|
1743
|
+
this.debug("[Player] Cannot setVolume while subscribed to another player");
|
|
1744
|
+
return false;
|
|
1745
|
+
}
|
|
1563
1746
|
this.debug(`[Player] setVolume called: ${volume}`);
|
|
1564
1747
|
if (volume < 0 || volume > 200)
|
|
1565
1748
|
return false;
|
|
1566
1749
|
const oldVolume = this.volume;
|
|
1567
1750
|
this.volume = volume;
|
|
1751
|
+
if (this.playbackMode === types_1.PlaybackMode.REMOTE) {
|
|
1752
|
+
void this.remoteHandle?.setVolume(volume).catch((e) => this.debug("[Player] Remote volume:", e));
|
|
1753
|
+
this.emit("volumeChange", oldVolume, volume);
|
|
1754
|
+
return true;
|
|
1755
|
+
}
|
|
1568
1756
|
const resourceVolume = this.currentResource?.volume;
|
|
1569
1757
|
if (resourceVolume) {
|
|
1570
1758
|
if (this.volumeInterval)
|
|
@@ -1584,6 +1772,12 @@ class Player extends events_1.EventEmitter {
|
|
|
1584
1772
|
}, 300);
|
|
1585
1773
|
}
|
|
1586
1774
|
this.emit("volumeChange", oldVolume, volume);
|
|
1775
|
+
for (const fp of this.forwardFollowers) {
|
|
1776
|
+
try {
|
|
1777
|
+
fp.volume = volume;
|
|
1778
|
+
}
|
|
1779
|
+
catch { }
|
|
1780
|
+
}
|
|
1587
1781
|
return true;
|
|
1588
1782
|
}
|
|
1589
1783
|
/**
|
|
@@ -1624,6 +1818,10 @@ class Player extends events_1.EventEmitter {
|
|
|
1624
1818
|
*/
|
|
1625
1819
|
async insert(query, index, requestedBy) {
|
|
1626
1820
|
try {
|
|
1821
|
+
if (this.playbackMode === types_1.PlaybackMode.FORWARD) {
|
|
1822
|
+
this.debug("[Player] Cannot insert while subscribed to another player");
|
|
1823
|
+
return false;
|
|
1824
|
+
}
|
|
1627
1825
|
this.debug(`[Player] insert called at index ${index} with type: ${typeof query}`);
|
|
1628
1826
|
let tracksToAdd = [];
|
|
1629
1827
|
let isPlaylist = false;
|
|
@@ -1834,6 +2032,10 @@ class Player extends events_1.EventEmitter {
|
|
|
1834
2032
|
if (this.destroyed)
|
|
1835
2033
|
return;
|
|
1836
2034
|
this.destroyed = true;
|
|
2035
|
+
if (this.remoteHandle?.destroy) {
|
|
2036
|
+
this.remoteHandle.destroy().catch(() => { });
|
|
2037
|
+
this.remoteHandle = undefined;
|
|
2038
|
+
}
|
|
1837
2039
|
if (this.leaveTimeout) {
|
|
1838
2040
|
clearTimeout(this.leaveTimeout);
|
|
1839
2041
|
this.leaveTimeout = null;
|
|
@@ -1861,14 +2063,21 @@ class Player extends events_1.EventEmitter {
|
|
|
1861
2063
|
this.pluginManager.clear();
|
|
1862
2064
|
this.filter.destroy();
|
|
1863
2065
|
this.extensionManager.destroy();
|
|
1864
|
-
this.isPlaying = false;
|
|
1865
|
-
this.isPaused = false;
|
|
1866
2066
|
// Clear any remaining intervals
|
|
1867
2067
|
if (this.volumeInterval) {
|
|
1868
2068
|
clearInterval(this.volumeInterval);
|
|
1869
2069
|
this.volumeInterval = null;
|
|
1870
2070
|
}
|
|
1871
2071
|
this.emit("playerDestroy");
|
|
2072
|
+
this.unsubscribeForward("Player destroy");
|
|
2073
|
+
// release followers
|
|
2074
|
+
for (const fp of [...this.forwardFollowers]) {
|
|
2075
|
+
try {
|
|
2076
|
+
fp.unsubscribeForward("Leader destroy");
|
|
2077
|
+
}
|
|
2078
|
+
catch { }
|
|
2079
|
+
}
|
|
2080
|
+
this.forwardFollowers.clear();
|
|
1872
2081
|
this.removeAllListeners();
|
|
1873
2082
|
}
|
|
1874
2083
|
//#endregion
|
|
@@ -1940,13 +2149,7 @@ class Player extends events_1.EventEmitter {
|
|
|
1940
2149
|
this.audioPlayer.play(resource);
|
|
1941
2150
|
}
|
|
1942
2151
|
// Restore playing state
|
|
1943
|
-
if (
|
|
1944
|
-
this.isPlaying = true;
|
|
1945
|
-
this.isPaused = false;
|
|
1946
|
-
}
|
|
1947
|
-
else if (wasPaused) {
|
|
1948
|
-
this.isPlaying = false;
|
|
1949
|
-
this.isPaused = true;
|
|
2152
|
+
if (wasPaused) {
|
|
1950
2153
|
this.audioPlayer.pause();
|
|
1951
2154
|
}
|
|
1952
2155
|
this.debug(`[Player] Successfully applied filter to current track at position ${currentPosition}ms`);
|
|
@@ -2012,6 +2215,9 @@ class Player extends events_1.EventEmitter {
|
|
|
2012
2215
|
if (track) {
|
|
2013
2216
|
this.debug(`[Player] Track ended: ${track.title}`);
|
|
2014
2217
|
this.emit("trackEnd", track);
|
|
2218
|
+
for (const fp of this.forwardFollowers) {
|
|
2219
|
+
fp.emit("trackEnd", track);
|
|
2220
|
+
}
|
|
2015
2221
|
}
|
|
2016
2222
|
void this.playNext();
|
|
2017
2223
|
}
|
|
@@ -2019,30 +2225,41 @@ class Player extends events_1.EventEmitter {
|
|
|
2019
2225
|
(oldState.status === voice_1.AudioPlayerStatus.Idle || oldState.status === voice_1.AudioPlayerStatus.Buffering)) {
|
|
2020
2226
|
// Track started
|
|
2021
2227
|
this.clearLeaveTimeout();
|
|
2022
|
-
this.isPlaying = true;
|
|
2023
|
-
this.isPaused = false;
|
|
2024
2228
|
const track = this.queue.currentTrack;
|
|
2025
2229
|
if (track) {
|
|
2026
2230
|
this.debug(`[Player] Track started: ${track.title}`);
|
|
2027
2231
|
this.emit("trackStart", track);
|
|
2232
|
+
for (const fp of this.forwardFollowers) {
|
|
2233
|
+
try {
|
|
2234
|
+
fp.queue.clear();
|
|
2235
|
+
fp.connection?.subscribe(this.audioPlayer);
|
|
2236
|
+
fp.queue.setCurrentTrack(track);
|
|
2237
|
+
fp.emit("trackStart", track);
|
|
2238
|
+
}
|
|
2239
|
+
catch (e) {
|
|
2240
|
+
this.debug(`[Player] Failed to sync follower ${fp.guildId}:`, e);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2028
2243
|
}
|
|
2029
2244
|
}
|
|
2030
2245
|
else if (newState.status === voice_1.AudioPlayerStatus.Paused && oldState.status !== voice_1.AudioPlayerStatus.Paused) {
|
|
2031
|
-
// Track paused
|
|
2032
|
-
this.isPaused = true;
|
|
2033
2246
|
const track = this.queue.currentTrack;
|
|
2034
2247
|
if (track) {
|
|
2035
2248
|
this.debug(`[Player] Player paused on track: ${track.title}`);
|
|
2036
2249
|
this.emit("playerPause", track);
|
|
2250
|
+
for (const fp of this.forwardFollowers) {
|
|
2251
|
+
fp.emit("playerPause", track);
|
|
2252
|
+
}
|
|
2037
2253
|
}
|
|
2038
2254
|
}
|
|
2039
2255
|
else if (newState.status !== voice_1.AudioPlayerStatus.Paused && oldState.status === voice_1.AudioPlayerStatus.Paused) {
|
|
2040
|
-
// Track resumed
|
|
2041
|
-
this.isPaused = false;
|
|
2042
2256
|
const track = this.queue.currentTrack;
|
|
2043
2257
|
if (track) {
|
|
2044
2258
|
this.debug(`[Player] Player resumed on track: ${track.title}`);
|
|
2045
2259
|
this.emit("playerResume", track);
|
|
2260
|
+
for (const fp of this.forwardFollowers) {
|
|
2261
|
+
fp.emit("playerResume", track);
|
|
2262
|
+
}
|
|
2046
2263
|
}
|
|
2047
2264
|
}
|
|
2048
2265
|
else if (newState.status === voice_1.AudioPlayerStatus.AutoPaused) {
|
|
@@ -2136,6 +2353,25 @@ class Player extends events_1.EventEmitter {
|
|
|
2136
2353
|
plugins: this.pluginManager.getAll().map((plugin) => plugin.name),
|
|
2137
2354
|
};
|
|
2138
2355
|
}
|
|
2356
|
+
exitRemoteMode() {
|
|
2357
|
+
if (this.playbackMode !== types_1.PlaybackMode.REMOTE)
|
|
2358
|
+
return;
|
|
2359
|
+
this.debug("[Player] Exiting REMOTE mode, restoring native playback");
|
|
2360
|
+
void this.remoteHandle?.destroy().catch(() => { });
|
|
2361
|
+
this.remoteHandle = undefined;
|
|
2362
|
+
this.playbackMode = types_1.PlaybackMode.NATIVE;
|
|
2363
|
+
this._remotePaused = false;
|
|
2364
|
+
// Restore preload/crossfade from original options
|
|
2365
|
+
const preloadOptions = this.options.preload ?? {};
|
|
2366
|
+
const autoDisable = preloadOptions.autoDisableInLowPerformance ?? true;
|
|
2367
|
+
this.preloadEnabled = (preloadOptions.enabled ?? true) && !(this.lowPerformanceMode && autoDisable);
|
|
2368
|
+
const crossfadeOptions = this.options.crossfade ?? {};
|
|
2369
|
+
const cfAutoDisable = crossfadeOptions.autoDisableInLowPerformance ?? true;
|
|
2370
|
+
this.crossfadeEnabled =
|
|
2371
|
+
typeof crossfadeOptions.enabled === "boolean" ? crossfadeOptions.enabled : (crossfadeOptions.autoEnable ?? true);
|
|
2372
|
+
if (this.lowPerformanceMode && cfAutoDisable)
|
|
2373
|
+
this.crossfadeEnabled = false;
|
|
2374
|
+
}
|
|
2139
2375
|
/**
|
|
2140
2376
|
* Get serializable state (for manual persistence)
|
|
2141
2377
|
*/
|
|
@@ -2189,6 +2425,59 @@ class Player extends events_1.EventEmitter {
|
|
|
2189
2425
|
totalStreams: this.streamManager.getStreamCount(),
|
|
2190
2426
|
};
|
|
2191
2427
|
}
|
|
2428
|
+
getForwardHealthStatus() {
|
|
2429
|
+
const issues = [];
|
|
2430
|
+
const details = {};
|
|
2431
|
+
if (this.playbackMode === types_1.PlaybackMode.FORWARD && this.forwardLeader) {
|
|
2432
|
+
// This player is a follower
|
|
2433
|
+
details.leaderId = this.forwardLeader.guildId;
|
|
2434
|
+
details.connectionState = this.connection?.state.status;
|
|
2435
|
+
details.audioPlayerState = this.audioPlayer.state.status;
|
|
2436
|
+
if (this.forwardLeader.destroyed) {
|
|
2437
|
+
issues.push("Leader is destroyed");
|
|
2438
|
+
}
|
|
2439
|
+
if (!this.forwardLeader.connection) {
|
|
2440
|
+
issues.push("Leader has no connection");
|
|
2441
|
+
}
|
|
2442
|
+
if (this.forwardLeader.destroyed || !this.forwardLeader.connection) {
|
|
2443
|
+
issues.push("Leader is unavailable");
|
|
2444
|
+
}
|
|
2445
|
+
return {
|
|
2446
|
+
guildId: this.guildId,
|
|
2447
|
+
healthy: issues.length === 0,
|
|
2448
|
+
role: "follower",
|
|
2449
|
+
issues,
|
|
2450
|
+
details,
|
|
2451
|
+
};
|
|
2452
|
+
}
|
|
2453
|
+
else if (this.forwardFollowers.size > 0) {
|
|
2454
|
+
// This player is a leader
|
|
2455
|
+
details.followerCount = this.forwardFollowers.size;
|
|
2456
|
+
details.connectionState = this.connection?.state.status;
|
|
2457
|
+
const deadFollowers = [];
|
|
2458
|
+
for (const follower of this.forwardFollowers) {
|
|
2459
|
+
if (follower.destroyed)
|
|
2460
|
+
deadFollowers.push(follower.guildId);
|
|
2461
|
+
}
|
|
2462
|
+
if (deadFollowers.length > 0) {
|
|
2463
|
+
issues.push(`Has ${deadFollowers.length} dead followers: ${deadFollowers.join(", ")}`);
|
|
2464
|
+
}
|
|
2465
|
+
return {
|
|
2466
|
+
guildId: this.guildId,
|
|
2467
|
+
healthy: true, // Leader being healthy doesn't depend on followers
|
|
2468
|
+
role: "leader",
|
|
2469
|
+
issues,
|
|
2470
|
+
details,
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
return {
|
|
2474
|
+
guildId: this.guildId,
|
|
2475
|
+
healthy: true,
|
|
2476
|
+
role: "none",
|
|
2477
|
+
issues: [],
|
|
2478
|
+
details: {},
|
|
2479
|
+
};
|
|
2480
|
+
}
|
|
2192
2481
|
//#endregion
|
|
2193
2482
|
//#region Getters
|
|
2194
2483
|
/**
|
|
@@ -2269,8 +2558,45 @@ class Player extends events_1.EventEmitter {
|
|
|
2269
2558
|
return this.queue.relatedTracks();
|
|
2270
2559
|
}
|
|
2271
2560
|
get isLive() {
|
|
2561
|
+
if (this.playbackMode === types_1.PlaybackMode.FORWARD)
|
|
2562
|
+
return true; //forward Mode -> live from Leader
|
|
2272
2563
|
return this.currentTrack?.isLive === true;
|
|
2273
2564
|
}
|
|
2565
|
+
get isPlaying() {
|
|
2566
|
+
if (this.playbackMode === types_1.PlaybackMode.FORWARD) {
|
|
2567
|
+
if (!this.forwardLeader || this.forwardLeader.destroyed) {
|
|
2568
|
+
this.unsubscribeForward("Leader destroyed");
|
|
2569
|
+
return false;
|
|
2570
|
+
}
|
|
2571
|
+
return this.forwardLeader.isPlaying;
|
|
2572
|
+
}
|
|
2573
|
+
if (this.playbackMode === types_1.PlaybackMode.REMOTE) {
|
|
2574
|
+
return !!this.queue.currentTrack; // driven by queue state, not audioPlayer
|
|
2575
|
+
}
|
|
2576
|
+
return (this.audioPlayer.state.status === voice_1.AudioPlayerStatus.Playing || this.audioPlayer.state.status === voice_1.AudioPlayerStatus.Buffering);
|
|
2577
|
+
}
|
|
2578
|
+
get isPaused() {
|
|
2579
|
+
if (this.playbackMode === types_1.PlaybackMode.FORWARD) {
|
|
2580
|
+
return this.forwardLeader?.isPaused ?? false;
|
|
2581
|
+
}
|
|
2582
|
+
if (this.playbackMode === types_1.PlaybackMode.REMOTE) {
|
|
2583
|
+
// Extension tracks pause state via handle; Player exposes a flag
|
|
2584
|
+
return this._remotePaused;
|
|
2585
|
+
}
|
|
2586
|
+
return this.audioPlayer.state.status === voice_1.AudioPlayerStatus.Paused;
|
|
2587
|
+
}
|
|
2588
|
+
get isIdle() {
|
|
2589
|
+
if (this.playbackMode === types_1.PlaybackMode.FORWARD) {
|
|
2590
|
+
return this.forwardLeader?.isIdle ?? false;
|
|
2591
|
+
}
|
|
2592
|
+
return this.audioPlayer.state.status === voice_1.AudioPlayerStatus.Idle;
|
|
2593
|
+
}
|
|
2594
|
+
get isBuffering() {
|
|
2595
|
+
if (this.playbackMode === types_1.PlaybackMode.FORWARD) {
|
|
2596
|
+
return this.forwardLeader?.isBuffering ?? false;
|
|
2597
|
+
}
|
|
2598
|
+
return this.audioPlayer.state.status === voice_1.AudioPlayerStatus.Buffering;
|
|
2599
|
+
}
|
|
2274
2600
|
}
|
|
2275
2601
|
exports.Player = Player;
|
|
2276
2602
|
//# sourceMappingURL=Player.js.map
|