ziplayer 0.3.7 → 0.3.8
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/plugins/index.d.ts +1 -8
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +52 -106
- package/dist/plugins/index.js.map +1 -1
- package/dist/structures/FilterManager.d.ts +7 -24
- package/dist/structures/FilterManager.d.ts.map +1 -1
- package/dist/structures/FilterManager.js +123 -99
- package/dist/structures/FilterManager.js.map +1 -1
- package/dist/structures/Player.d.ts +1 -0
- package/dist/structures/Player.d.ts.map +1 -1
- package/dist/structures/Player.js +99 -91
- package/dist/structures/Player.js.map +1 -1
- package/dist/structures/PreloadManager.d.ts +1 -0
- package/dist/structures/PreloadManager.d.ts.map +1 -1
- package/dist/structures/PreloadManager.js +26 -6
- package/dist/structures/PreloadManager.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.map +1 -1
- package/package.json +1 -1
- package/src/plugins/index.ts +63 -119
- package/src/structures/FilterManager.ts +439 -380
- package/src/structures/Player.ts +120 -97
- package/src/structures/PreloadManager.ts +293 -274
- package/src/structures/StreamManager.ts +2 -0
package/src/structures/Player.ts
CHANGED
|
@@ -269,7 +269,7 @@ export class Player extends EventEmitter {
|
|
|
269
269
|
extractorTimeout: this.options.extractorTimeout,
|
|
270
270
|
});
|
|
271
271
|
this.streamManager = new StreamManager({
|
|
272
|
-
maxConcurrentStreams:
|
|
272
|
+
maxConcurrentStreams: 4,
|
|
273
273
|
streamTimeout: 5 * 60 * 1000,
|
|
274
274
|
maxListenersPerStream: 15,
|
|
275
275
|
enableMetrics: true,
|
|
@@ -887,26 +887,32 @@ export class Player extends EventEmitter {
|
|
|
887
887
|
* @param {number} position - Position in milliseconds to seek to (0 = no seek)
|
|
888
888
|
* @returns {Promise<AudioResource>} The AudioResource with filters and seek applied
|
|
889
889
|
*/
|
|
890
|
-
private async createResource(
|
|
890
|
+
private async createResource(
|
|
891
|
+
streamInfo: StreamInfo,
|
|
892
|
+
track: Track,
|
|
893
|
+
position: number = 0,
|
|
894
|
+
): Promise<{ resource: AudioResource; processedStream: import("stream").Readable | null }> {
|
|
891
895
|
const filterString = this.filter.getFilterString();
|
|
892
896
|
this.debug(`[Player] Creating AudioResource — filters: ${filterString || "none"}, seek: ${position}ms`);
|
|
893
897
|
|
|
894
|
-
|
|
898
|
+
this.filter.setSourceStreamType(streamInfo.type);
|
|
899
|
+
|
|
895
900
|
const seekArg = position > 0 ? position : -1;
|
|
896
901
|
|
|
897
902
|
if (filterString || position > 0) {
|
|
898
|
-
// throws on failure — do NOT fall back to the already-piped stream
|
|
899
903
|
const processedStream = await this.filter.applyFiltersAndSeek(streamInfo.stream, seekArg);
|
|
900
|
-
streamInfo.type = StreamType.Arbitrary as any;
|
|
901
904
|
|
|
902
|
-
|
|
905
|
+
// rawStream. Always use Arbitrary so discordjs/voice doesn't re-encode.
|
|
906
|
+
const resource = createAudioResource(processedStream, {
|
|
903
907
|
metadata: track,
|
|
904
908
|
inputType: StreamType.Arbitrary,
|
|
905
909
|
inlineVolume: true,
|
|
906
910
|
});
|
|
911
|
+
|
|
912
|
+
return { resource, processedStream };
|
|
907
913
|
}
|
|
908
914
|
|
|
909
|
-
|
|
915
|
+
const resource = createAudioResource(streamInfo.stream, {
|
|
910
916
|
metadata: track,
|
|
911
917
|
inputType:
|
|
912
918
|
streamInfo.type === "webm/opus" ? StreamType.WebmOpus
|
|
@@ -914,6 +920,8 @@ export class Player extends EventEmitter {
|
|
|
914
920
|
: StreamType.Arbitrary,
|
|
915
921
|
inlineVolume: true,
|
|
916
922
|
});
|
|
923
|
+
|
|
924
|
+
return { resource, processedStream: null };
|
|
917
925
|
}
|
|
918
926
|
|
|
919
927
|
private mergeTrackPreserveRef(target: Track, source: Track): void {
|
|
@@ -1027,9 +1035,16 @@ export class Player extends EventEmitter {
|
|
|
1027
1035
|
private async startTrack(track: Track): Promise<boolean> {
|
|
1028
1036
|
if (this.destroyed) return false;
|
|
1029
1037
|
|
|
1030
|
-
//
|
|
1031
|
-
|
|
1038
|
+
// Check preload BEFORE calling getStream so we never fetch a
|
|
1039
|
+
// stream we're about to throw away. The original code called getStream()
|
|
1040
|
+
// unconditionally at the top, then used the preload if available — leaking
|
|
1041
|
+
// the just-fetched stream and running middleware twice.
|
|
1042
|
+
if (this.preloadManager.hasValidPreload(track)) {
|
|
1043
|
+
return await this.startFromPreload(track);
|
|
1044
|
+
}
|
|
1032
1045
|
|
|
1046
|
+
// Only fetch a stream when there is no usable preload.
|
|
1047
|
+
let streamInfo: StreamInfo | null = null;
|
|
1033
1048
|
try {
|
|
1034
1049
|
streamInfo = await this.getStream(track);
|
|
1035
1050
|
} catch (error) {
|
|
@@ -1037,78 +1052,70 @@ export class Player extends EventEmitter {
|
|
|
1037
1052
|
throw error;
|
|
1038
1053
|
}
|
|
1039
1054
|
|
|
1040
|
-
//
|
|
1055
|
+
// Remote playback
|
|
1041
1056
|
if (streamInfo?.remote && streamInfo.handle) {
|
|
1042
1057
|
return await this.playRemote(track, streamInfo);
|
|
1043
1058
|
}
|
|
1044
1059
|
|
|
1045
|
-
//
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
const oldStreamId = this.currentSlot.streamId;
|
|
1052
|
-
if (oldStreamId && this.streamManager) {
|
|
1053
|
-
setTimeout(() => {
|
|
1054
|
-
if (this.currentSlot.streamId === oldStreamId) {
|
|
1055
|
-
this.streamManager.unregisterStream(oldStreamId, true);
|
|
1056
|
-
}
|
|
1057
|
-
}, 3000);
|
|
1058
|
-
}
|
|
1060
|
+
// Native playback — pass the already-fetched streamInfo to avoid a second fetch
|
|
1061
|
+
return await this.loadFreshStream(track, streamInfo);
|
|
1062
|
+
}
|
|
1063
|
+
private async startFromPreload(track: Track): Promise<boolean> {
|
|
1064
|
+
if (this.destroyed) return false;
|
|
1059
1065
|
|
|
1060
|
-
|
|
1061
|
-
const currentResource = this.currentSlot.resource;
|
|
1062
|
-
if (!currentResource) {
|
|
1063
|
-
return false;
|
|
1064
|
-
}
|
|
1065
|
-
this.seekOffset = 0;
|
|
1066
|
-
const targetVolume = this.getTrackTargetVolume(track);
|
|
1066
|
+
this.debug(`[Player] Using preloaded stream for: ${track.title}`);
|
|
1067
1067
|
|
|
1068
|
-
|
|
1069
|
-
currentResource.volume.setVolume(this.crossfadeEnabled ? 0 : targetVolume);
|
|
1070
|
-
}
|
|
1068
|
+
const oldStreamId = this.currentSlot.streamId;
|
|
1071
1069
|
|
|
1072
|
-
|
|
1073
|
-
this.refreshLock = true;
|
|
1074
|
-
try {
|
|
1075
|
-
this.audioPlayer.stop(true);
|
|
1076
|
-
this.audioPlayer.play(currentResource);
|
|
1077
|
-
await entersState(this.audioPlayer, AudioPlayerStatus.Playing, 10_000);
|
|
1078
|
-
} finally {
|
|
1079
|
-
this.refreshLock = false;
|
|
1080
|
-
}
|
|
1081
|
-
await this.applyCrossfadeIn(currentResource, track);
|
|
1070
|
+
this.promotePreloadToCurrent(track);
|
|
1082
1071
|
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1072
|
+
if (oldStreamId && oldStreamId !== this.currentSlot.streamId) {
|
|
1073
|
+
this.streamManager.unregisterStream(oldStreamId, true);
|
|
1074
|
+
this.debug(`[Player] Released old stream ${oldStreamId} after preload promotion`);
|
|
1075
|
+
}
|
|
1086
1076
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1077
|
+
const currentResource = this.currentSlot.resource;
|
|
1078
|
+
if (!currentResource) return false;
|
|
1089
1079
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
this.
|
|
1095
|
-
|
|
1080
|
+
this.seekOffset = 0;
|
|
1081
|
+
const targetVolume = this.getTrackTargetVolume(track);
|
|
1082
|
+
|
|
1083
|
+
if (currentResource.volume) {
|
|
1084
|
+
currentResource.volume.setVolume(this.crossfadeEnabled ? 0 : targetVolume);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
await this.maybeAlignToBeatBoundary();
|
|
1088
|
+
this.refreshLock = true;
|
|
1089
|
+
try {
|
|
1090
|
+
this.audioPlayer.stop(true);
|
|
1091
|
+
this.audioPlayer.play(currentResource);
|
|
1092
|
+
await entersState(this.audioPlayer, AudioPlayerStatus.Playing, 10_000);
|
|
1093
|
+
} finally {
|
|
1094
|
+
this.refreshLock = false;
|
|
1096
1095
|
}
|
|
1096
|
+
|
|
1097
|
+
await this.applyCrossfadeIn(currentResource, track);
|
|
1098
|
+
|
|
1099
|
+
this.preloadNextTrack().catch((err: any) => {
|
|
1100
|
+
this.debug(`[Player] Preload error:`, err);
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
return true;
|
|
1097
1104
|
}
|
|
1098
1105
|
|
|
1099
1106
|
/**
|
|
1100
1107
|
* Load fresh stream when no preload available
|
|
1101
1108
|
*/
|
|
1102
|
-
private async loadFreshStream(track: Track): Promise<boolean> {
|
|
1109
|
+
private async loadFreshStream(track: Track, preloadedStreamInfo?: StreamInfo | null): Promise<boolean> {
|
|
1103
1110
|
if (this.destroyed) return false;
|
|
1104
1111
|
|
|
1105
|
-
// Cancel preload to free resources
|
|
1106
1112
|
await this.safeCancelPreload();
|
|
1107
1113
|
|
|
1108
1114
|
try {
|
|
1109
|
-
|
|
1115
|
+
// use caller-supplied streamInfo when available so we don't
|
|
1116
|
+
// call getStream() a second time and run middleware twice.
|
|
1117
|
+
const streamInfo = preloadedStreamInfo ?? (await this.getStream(track));
|
|
1110
1118
|
|
|
1111
|
-
// Handle remote playback
|
|
1112
1119
|
if (streamInfo?.remote && streamInfo.handle) {
|
|
1113
1120
|
return await this.playRemote(track, streamInfo);
|
|
1114
1121
|
}
|
|
@@ -1117,37 +1124,50 @@ export class Player extends EventEmitter {
|
|
|
1117
1124
|
throw new Error(`No stream available`);
|
|
1118
1125
|
}
|
|
1119
1126
|
|
|
1120
|
-
// Register
|
|
1121
|
-
const
|
|
1127
|
+
// Register the RAW source stream — this is what we can reuse on seek
|
|
1128
|
+
const rawStreamId = this.streamManager.registerStream(streamInfo.stream, track, {
|
|
1122
1129
|
source: track.source || "stream",
|
|
1123
1130
|
isPreload: false,
|
|
1124
1131
|
isRemote: !!streamInfo?.remote,
|
|
1125
1132
|
priority: 10,
|
|
1126
1133
|
});
|
|
1127
1134
|
|
|
1128
|
-
//
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1135
|
+
// createResource now returns both the AudioResource
|
|
1136
|
+
// AND the processedStream (ffmpeg stdout) when filters/seek are involved.
|
|
1137
|
+
const { resource, processedStream } = await this.createResource(streamInfo, track, 0);
|
|
1138
|
+
|
|
1139
|
+
// when a processedStream exists, register it too so its
|
|
1140
|
+
// lifecycle is tracked. Store its id separately in currentSlot so
|
|
1141
|
+
// destroyCurrentStream() and refreshPlayerResource() clean the right object.
|
|
1142
|
+
let playStreamId = rawStreamId;
|
|
1143
|
+
if (processedStream && processedStream !== streamInfo.stream) {
|
|
1144
|
+
playStreamId = this.streamManager.registerStream(processedStream, track, {
|
|
1145
|
+
source: track.source || "stream-processed",
|
|
1146
|
+
isPreload: false,
|
|
1147
|
+
priority: 10,
|
|
1148
|
+
});
|
|
1149
|
+
this.debug(`[Player] Registered processedStream ${playStreamId} (rawStream: ${rawStreamId})`);
|
|
1150
|
+
}
|
|
1151
|
+
if (this.currentSlot.streamId && this.currentSlot.streamId !== rawStreamId) {
|
|
1133
1152
|
this.streamManager.unregisterStream(this.currentSlot.streamId, true);
|
|
1134
1153
|
}
|
|
1154
|
+
if ((this.currentSlot as any).processedStreamId && (this.currentSlot as any).processedStreamId !== playStreamId) {
|
|
1155
|
+
this.streamManager.unregisterStream((this.currentSlot as any).processedStreamId, true);
|
|
1156
|
+
}
|
|
1135
1157
|
|
|
1136
|
-
// Set current slot
|
|
1137
1158
|
this.currentSlot.resource = resource;
|
|
1138
1159
|
this.currentSlot.track = track;
|
|
1139
|
-
this.currentSlot.streamId =
|
|
1160
|
+
this.currentSlot.streamId = rawStreamId;
|
|
1161
|
+
(this.currentSlot as any).processedStreamId = processedStream ? playStreamId : null;
|
|
1140
1162
|
this.currentSlot.isValid = true;
|
|
1141
1163
|
this.currentResource = resource;
|
|
1142
|
-
this.seekOffset = 0;
|
|
1164
|
+
this.seekOffset = 0;
|
|
1143
1165
|
|
|
1144
|
-
// Apply volume
|
|
1145
1166
|
const targetVolume = this.getTrackTargetVolume(track);
|
|
1146
1167
|
if (resource.volume) {
|
|
1147
1168
|
resource.volume.setVolume(this.crossfadeEnabled ? 0 : targetVolume);
|
|
1148
1169
|
}
|
|
1149
1170
|
|
|
1150
|
-
// Play — lock refresh so Idle event doesn't spawn duplicate playNext
|
|
1151
1171
|
await this.maybeAlignToBeatBoundary();
|
|
1152
1172
|
this.refreshLock = true;
|
|
1153
1173
|
try {
|
|
@@ -1157,11 +1177,11 @@ export class Player extends EventEmitter {
|
|
|
1157
1177
|
} finally {
|
|
1158
1178
|
this.refreshLock = false;
|
|
1159
1179
|
}
|
|
1180
|
+
|
|
1160
1181
|
await this.applyCrossfadeIn(resource, track);
|
|
1161
1182
|
|
|
1162
|
-
// Preload next (async)
|
|
1163
1183
|
if (!this.destroyed) {
|
|
1164
|
-
this.preloadNextTrack().catch((err) => {
|
|
1184
|
+
this.preloadNextTrack().catch((err: any) => {
|
|
1165
1185
|
this.debug(`[Player] Preload error:`, err);
|
|
1166
1186
|
});
|
|
1167
1187
|
}
|
|
@@ -1347,7 +1367,7 @@ export class Player extends EventEmitter {
|
|
|
1347
1367
|
throw new Error(`No stream available for track: ${track.title}`);
|
|
1348
1368
|
}
|
|
1349
1369
|
ttsStream = streamInfo.stream;
|
|
1350
|
-
const resource = await this.createResource(streamInfo as StreamInfo, track);
|
|
1370
|
+
const { resource, processedStream } = await this.createResource(streamInfo as StreamInfo, track);
|
|
1351
1371
|
if (!resource) {
|
|
1352
1372
|
throw new Error(`No resource available for track: ${track.title}`);
|
|
1353
1373
|
}
|
|
@@ -2434,11 +2454,8 @@ export class Player extends EventEmitter {
|
|
|
2434
2454
|
return false;
|
|
2435
2455
|
}
|
|
2436
2456
|
|
|
2437
|
-
// Lock before anything so stateChange idle sees it when stop() fires.
|
|
2438
2457
|
this.refreshLock = true;
|
|
2439
2458
|
|
|
2440
|
-
// Clear any existing stuckTimer from the previous playback cycle so it
|
|
2441
|
-
// cannot fire while we are mid-refresh.
|
|
2442
2459
|
if (this.stuckTimer) {
|
|
2443
2460
|
clearTimeout(this.stuckTimer);
|
|
2444
2461
|
this.stuckTimer = null;
|
|
@@ -2453,13 +2470,11 @@ export class Player extends EventEmitter {
|
|
|
2453
2470
|
const wasPaused = this.isPaused;
|
|
2454
2471
|
const playbackDuration = this.currentResource?.playbackDuration ?? 0;
|
|
2455
2472
|
|
|
2456
|
-
// Reuse is only viable for forward seeks (stream is sequential).
|
|
2457
2473
|
const isForwardSeek = position < 0 || position >= playbackDuration;
|
|
2458
2474
|
const currentStreamId = this.currentSlot.streamId;
|
|
2459
2475
|
|
|
2460
|
-
// Try to grab the
|
|
2461
|
-
|
|
2462
|
-
let reuseStream: Readable | null = null;
|
|
2476
|
+
// Try to grab the raw source stream for reuse (forward seeks only)
|
|
2477
|
+
let reuseStream: import("stream").Readable | null = null;
|
|
2463
2478
|
if (isForwardSeek && currentStreamId) {
|
|
2464
2479
|
reuseStream = this.streamManager.getRawStream(currentStreamId);
|
|
2465
2480
|
if (reuseStream) {
|
|
@@ -2467,31 +2482,29 @@ export class Player extends EventEmitter {
|
|
|
2467
2482
|
}
|
|
2468
2483
|
}
|
|
2469
2484
|
|
|
2470
|
-
// ── CRITICAL: unpipe BEFORE stop ──────────────────────────────────────
|
|
2471
|
-
// stop() kills discordjs/voice internal FFmpeg → EPIPE on source stream.
|
|
2472
|
-
// unpipe() first disconnects our stream cleanly before that happens.
|
|
2473
2485
|
if (reuseStream) {
|
|
2474
2486
|
reuseStream.unpipe();
|
|
2475
2487
|
}
|
|
2476
2488
|
|
|
2477
|
-
//
|
|
2478
|
-
|
|
2489
|
+
// Clean up processedStream first (it's what AudioResource reads)
|
|
2490
|
+
const processedStreamId = (this.currentSlot as any).processedStreamId;
|
|
2491
|
+
if (processedStreamId && processedStreamId !== currentStreamId) {
|
|
2492
|
+
this.streamManager.unregisterStream(processedStreamId, true);
|
|
2493
|
+
(this.currentSlot as any).processedStreamId = null;
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2479
2496
|
if (currentStreamId) {
|
|
2480
2497
|
this.streamManager.unregisterStream(currentStreamId, !reuseStream);
|
|
2481
2498
|
this.currentSlot.streamId = null;
|
|
2482
2499
|
}
|
|
2483
2500
|
|
|
2484
|
-
// Stop the AudioPlayer.
|
|
2485
|
-
// stateChange (playing→idle) fires; refreshLock=true guards it (v4 fix).
|
|
2486
2501
|
this.audioPlayer.stop(true);
|
|
2487
2502
|
this.currentResource = null;
|
|
2488
2503
|
this.currentSlot.resource = null;
|
|
2489
2504
|
this.currentSlot.isValid = false;
|
|
2490
2505
|
|
|
2491
|
-
// One event-loop tick: lets deferred stream events settle.
|
|
2492
2506
|
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
2493
2507
|
|
|
2494
|
-
// Verify the reuse stream survived stop().
|
|
2495
2508
|
if (reuseStream) {
|
|
2496
2509
|
if (reuseStream.destroyed || (reuseStream as any).readable === false) {
|
|
2497
2510
|
this.debug(`[Player] Source stream did not survive stop — falling back to fresh stream`);
|
|
@@ -2504,7 +2517,6 @@ export class Player extends EventEmitter {
|
|
|
2504
2517
|
if (reuseStream) {
|
|
2505
2518
|
streaminfo = { stream: reuseStream, type: "arbitrary" };
|
|
2506
2519
|
} else {
|
|
2507
|
-
// Clear caches so we don't get the dead Readable back.
|
|
2508
2520
|
this.pluginManager.clearStreamCache();
|
|
2509
2521
|
this.extensionManager.clearCache("stream");
|
|
2510
2522
|
this.debug(`[Player] Fetching fresh stream${!isForwardSeek ? " (backward seek)" : " (reuse failed)"}`);
|
|
@@ -2516,22 +2528,33 @@ export class Player extends EventEmitter {
|
|
|
2516
2528
|
return false;
|
|
2517
2529
|
}
|
|
2518
2530
|
|
|
2519
|
-
|
|
2520
|
-
|
|
2531
|
+
const createPosition = reuseStream ? -1 : currentPosition;
|
|
2532
|
+
|
|
2533
|
+
const { resource, processedStream } = await this.createResource(streaminfo, track, createPosition);
|
|
2521
2534
|
|
|
2522
|
-
// Register
|
|
2535
|
+
// Register raw source stream
|
|
2523
2536
|
const newStreamId = this.streamManager.registerStream(streaminfo.stream, track, {
|
|
2524
2537
|
source: track.source || "stream",
|
|
2525
2538
|
isPreload: false,
|
|
2526
2539
|
priority: 10,
|
|
2527
2540
|
});
|
|
2541
|
+
|
|
2542
|
+
let newProcessedStreamId: string | null = null;
|
|
2543
|
+
if (processedStream && processedStream !== streaminfo.stream) {
|
|
2544
|
+
newProcessedStreamId = this.streamManager.registerStream(processedStream, track, {
|
|
2545
|
+
source: track.source || "stream-processed",
|
|
2546
|
+
isPreload: false,
|
|
2547
|
+
priority: 10,
|
|
2548
|
+
});
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2528
2551
|
this.currentSlot.resource = resource;
|
|
2529
2552
|
this.currentSlot.track = track;
|
|
2530
2553
|
this.currentSlot.streamId = newStreamId;
|
|
2554
|
+
(this.currentSlot as any).processedStreamId = newProcessedStreamId;
|
|
2531
2555
|
this.currentSlot.isValid = true;
|
|
2532
2556
|
this.currentResource = resource;
|
|
2533
2557
|
|
|
2534
|
-
// ── Set seek flag BEFORE play so the Buffering handler sees it ────────
|
|
2535
2558
|
if (position >= 0) {
|
|
2536
2559
|
this.seekInProgress = true;
|
|
2537
2560
|
}
|
|
@@ -2546,11 +2569,11 @@ export class Player extends EventEmitter {
|
|
|
2546
2569
|
return true;
|
|
2547
2570
|
} catch (error) {
|
|
2548
2571
|
this.debug(`[Player] refreshPlayerResource error:`, error);
|
|
2549
|
-
this.seekInProgress = false;
|
|
2572
|
+
this.seekInProgress = false;
|
|
2550
2573
|
this.emit("playerError", error as Error, this.queue.currentTrack ?? undefined);
|
|
2551
2574
|
return false;
|
|
2552
2575
|
} finally {
|
|
2553
|
-
this.refreshLock = false;
|
|
2576
|
+
this.refreshLock = false;
|
|
2554
2577
|
}
|
|
2555
2578
|
}
|
|
2556
2579
|
|