fix: 재생 멈춤(stall) 시 다음 곡으로 자동 진행
노래가 2/3 지점 등에서 버퍼링/멈춘 뒤 다음 곡으로 넘어가지 않고 프리징되던 문제 해결. - stuck 이벤트: 기존 errMsg는 5초 뒤 같은 곡(queue[0])을 다시 재생해 무한 반복될 수 있었음. stopTrack으로 end 이벤트를 유도해 실제 다음 곡으로 넘김. - 재생 위치 멈춤 감시(watchdog) 추가: TrackStuckEvent가 안 뜨는 느린 버퍼링 상황에서도 position이 20초간 진행 없으면 자동 스킵. start에서 시작, clearAllTimers에서 정리. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,9 @@ import { Logger } from "../utils/Logger";
|
||||
|
||||
const DelayAfterErrMs = 1000 * 5;
|
||||
const idleEndTime = 1000 * 60 * 10;
|
||||
// 재생 위치가 진행되지 않는지 감시하는 주기 / 멈춤으로 판단하는 임계값
|
||||
const StallCheckIntervalMs = 1000 * 5;
|
||||
const StallThresholdMs = 1000 * 20;
|
||||
|
||||
type QueueTrack = Track & { userId: string; };
|
||||
|
||||
@@ -23,6 +26,9 @@ export class GuildPlayer {
|
||||
private errorTimer: NodeJS.Timeout | undefined;
|
||||
private endTimer: NodeJS.Timeout | undefined;
|
||||
private closedTimer: NodeJS.Timeout | undefined;
|
||||
private stallWatchdog: NodeJS.Timeout | undefined;
|
||||
private lastPosition = 0;
|
||||
private lastProgressAt = 0;
|
||||
|
||||
constructor(
|
||||
public guild: Guild,
|
||||
@@ -42,6 +48,8 @@ export class GuildPlayer {
|
||||
clearTimeout(this.endTimer);
|
||||
this.endTimer = undefined;
|
||||
}
|
||||
// 새 곡이 시작되면 재생 위치 멈춤 감시 시작
|
||||
this.startStallWatchdog();
|
||||
Redis?.publishState("player_update", {
|
||||
guildId: this.guild.id,
|
||||
});
|
||||
@@ -103,8 +111,10 @@ export class GuildPlayer {
|
||||
});
|
||||
this.player.on("stuck", async (data) => {
|
||||
try {
|
||||
Logger.error(`[Lavalink] 곡 로딩 멈춤(Stuck) 발생: ${data.thresholdMs}ms 초과`);
|
||||
await this.errMsg("음원 로딩이 멈췄습니다. 다음 곡으로 넘어갑니다.");
|
||||
Logger.error(`[Lavalink] 곡 로딩 멈춤(Stuck) 발생: ${data.thresholdMs}ms 초과. 다음 곡으로 넘어갑니다.`);
|
||||
// errMsg는 5초 뒤 같은 곡(queue[0])을 다시 재생해 무한 반복될 수 있으므로,
|
||||
// stopTrack으로 end 이벤트를 유도해 실제로 다음 곡으로 넘긴다.
|
||||
await this.player.stopTrack();
|
||||
} catch (err) {
|
||||
Logger.error(`[GuildPlayer] stuck 이벤트 처리 중 에러: ${String(err)}`);
|
||||
}
|
||||
@@ -375,6 +385,52 @@ export class GuildPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 재생 위치가 일정 시간 동안 전혀 진행되지 않으면(=버퍼링/멈춤) 다음 곡으로 넘긴다.
|
||||
* Lavalink가 TrackStuckEvent를 못 던지는 트리클 스트리밍(느린 버퍼링) 상황까지 커버하는 백스탑.
|
||||
*/
|
||||
private startStallWatchdog() {
|
||||
this.stopStallWatchdog();
|
||||
this.lastPosition = this.position;
|
||||
this.lastProgressAt = Date.now();
|
||||
this.stallWatchdog = setInterval(() => {
|
||||
if (this.isDead) {
|
||||
this.stopStallWatchdog();
|
||||
return;
|
||||
}
|
||||
// 재생 중이 아니거나 일시정지면 정상 상태 → 진행시각 리셋
|
||||
if (!this.isPlaying || this.isPaused) {
|
||||
this.lastPosition = this.player.position;
|
||||
this.lastProgressAt = Date.now();
|
||||
return;
|
||||
}
|
||||
const pos = this.player.position;
|
||||
if (pos > this.lastPosition + 500) {
|
||||
// 정상적으로 재생 위치가 진행됨
|
||||
this.lastPosition = pos;
|
||||
this.lastProgressAt = Date.now();
|
||||
return;
|
||||
}
|
||||
// 재생 위치가 StallThresholdMs 동안 전혀 진행되지 않음 → 멈춤으로 판단하고 스킵
|
||||
if (Date.now() - this.lastProgressAt >= StallThresholdMs) {
|
||||
Logger.error(`[GuildPlayer] 재생 위치가 ${StallThresholdMs}ms 동안 진행되지 않음(멈춤 감지). 다음 곡으로 넘어갑니다. (position=${pos}ms)`);
|
||||
this.lastPosition = pos;
|
||||
this.lastProgressAt = Date.now();
|
||||
// stopTrack → end 이벤트 → 다음 곡 진행
|
||||
this.player.stopTrack().catch((err) => {
|
||||
Logger.error(`[GuildPlayer] 멈춤 감지 후 stopTrack 에러: ${String(err)}`);
|
||||
});
|
||||
}
|
||||
}, StallCheckIntervalMs);
|
||||
}
|
||||
|
||||
private stopStallWatchdog() {
|
||||
if (this.stallWatchdog !== undefined) {
|
||||
clearInterval(this.stallWatchdog);
|
||||
this.stallWatchdog = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private clearAllTimers() {
|
||||
if (this.errorTimer !== undefined) {
|
||||
clearTimeout(this.errorTimer);
|
||||
@@ -388,6 +444,7 @@ export class GuildPlayer {
|
||||
clearTimeout(this.closedTimer);
|
||||
this.closedTimer = undefined;
|
||||
}
|
||||
this.stopStallWatchdog();
|
||||
}
|
||||
|
||||
public end() {
|
||||
|
||||
Reference in New Issue
Block a user