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