perf(play): 음성접속과 곡 해석 병렬화 + 첫 곡 yt-dlp 예열로 첫 재생 단축
기존: player_play 가 channelJoin() 완료 후에야 search() 를 시작(직렬)하고, 첫 곡은 playNext 시점에 yt-dlp 리졸브(콜드 3.6~4.2s)를 그제서야 수행했다. - LavalinkManager.search 를 resolveQuery(해석) + addResolved(큐 반영)로 분리. resolveQuery 는 플레이어가 필요 없어 음성접속과 동시에 실행 가능. - resolveQuery 가 해석 직후 prewarmTrack() 으로 첫 곡 오디오 리졸브를 즉시 예열. GuildPlayer.resolvePlayable 과 동일한 target 계산식을 써 캐시가 확실히 적중하게 함. - RedisClient player_play/player_playlist: 해석을 먼저 출발시키고 음성접속과 병렬 진행. - 구간 계측 로그 추가(음성접속/해석대기/오디오 리졸브 대기). 검증: tsc 통과. 실측 A/B(동일 조건, 미캐시 곡): 기존 직렬 접속2.00s+리졸브3.03s = 5.03s → 신규 병렬 2.00s+0.97s = 2.98s (약 2s 단축).
This commit is contained in:
@@ -73,16 +73,29 @@ class RedisClientClass {
|
||||
const guild = await getGuildById(data.serverId);
|
||||
if (!guild) return await this.reply(resultKey, 60, JSON.stringify({ success: false, message: "guild를 찾을수 없습니다." }));
|
||||
if (!(await this.isMember(guild, data.userId))) return await this.reply(resultKey, 60, JSON.stringify({ success: false, message: "이 서버의 멤버가 아닙니다." }));
|
||||
// 곡 해석(검색+Lavalink+yt-dlp 예열)은 음성접속과 무관하므로 먼저 출발시켜 둘을 겹친다.
|
||||
const t0 = Date.now();
|
||||
const resolving = lavalinkManager.resolveQuery(data.track.url)
|
||||
.catch((e) => { Logger.warn(`[player_play] 곡 해석 실패: ${String(e)}`); return null; });
|
||||
let player = lavalinkManager.getPlayer(guild.id);
|
||||
const voiceChannel = await getVoiceChannelById(guild, data.userId);
|
||||
if (!player) {
|
||||
if (!voiceChannel) return await this.reply(resultKey, 60, JSON.stringify({ success: false, message: "음성채널에 들어가서 이용해주세요." }));
|
||||
if (!voiceChannel) { await resolving; return await this.reply(resultKey, 60, JSON.stringify({ success: false, message: "음성채널에 들어가서 이용해주세요." })); }
|
||||
player = (await channelJoin(guild, voiceChannel.id)).player;
|
||||
} else if (!voiceChannel || voiceChannel.id !== player.voiceChannelId) {
|
||||
await resolving;
|
||||
return await this.reply(resultKey, 60, JSON.stringify({ success: false, message: "봇과 같은 음성채널에 있어야 합니다." }));
|
||||
}
|
||||
if (!player) return await this.reply(resultKey, 60, JSON.stringify({ success: false, message: "세션을 찾을수 없습니다." }));
|
||||
await lavalinkManager.search(guild.id, data.track.url, data.userId, player);
|
||||
const tJoined = Date.now();
|
||||
if (!player) { await resolving; return await this.reply(resultKey, 60, JSON.stringify({ success: false, message: "세션을 찾을수 없습니다." })); }
|
||||
const resolved = await resolving;
|
||||
const tResolved = Date.now();
|
||||
if (!resolved) {
|
||||
player.errMsg(`노래를 찾을수 없습니다.`);
|
||||
return await this.reply(resultKey, 60, JSON.stringify({ success: false, message: "노래를 찾을수 없습니다." }));
|
||||
}
|
||||
await lavalinkManager.addResolved(player, resolved, data.userId);
|
||||
Logger.log(`[player_play] 음성접속 ${tJoined - t0}ms / 곡해석 대기 ${tResolved - tJoined}ms / 큐반영까지 ${Date.now() - t0}ms`);
|
||||
await this.reply(resultKey, 60, JSON.stringify({ success: true, message: "노래 추가 완료" }));
|
||||
}
|
||||
if (data.action === "player_playlist") {
|
||||
@@ -92,16 +105,25 @@ class RedisClientClass {
|
||||
const guild = await getGuildById(data.serverId);
|
||||
if (!guild) return await this.reply(resultKey, 60, JSON.stringify({ success: false, message: "guild를 찾을수 없습니다." }));
|
||||
if (!(await this.isMember(guild, data.userId))) return await this.reply(resultKey, 60, JSON.stringify({ success: false, message: "이 서버의 멤버가 아닙니다." }));
|
||||
// 재생목록 해석도 음성접속과 병렬로 진행한다.
|
||||
const resolvingPl = lavalinkManager.resolveQuery(data.playlistUrl)
|
||||
.catch((e) => { Logger.warn(`[player_playlist] 해석 실패: ${String(e)}`); return null; });
|
||||
let player = lavalinkManager.getPlayer(guild.id);
|
||||
const voiceChannel = await getVoiceChannelById(guild, data.userId);
|
||||
if (!player) {
|
||||
if (!voiceChannel) return await this.reply(resultKey, 60, JSON.stringify({ success: false, message: "음성채널에 들어가서 이용해주세요." }));
|
||||
if (!voiceChannel) { await resolvingPl; return await this.reply(resultKey, 60, JSON.stringify({ success: false, message: "음성채널에 들어가서 이용해주세요." })); }
|
||||
player = (await channelJoin(guild, voiceChannel.id)).player;
|
||||
} else if (!voiceChannel || voiceChannel.id !== player.voiceChannelId) {
|
||||
await resolvingPl;
|
||||
return await this.reply(resultKey, 60, JSON.stringify({ success: false, message: "봇과 같은 음성채널에 있어야 합니다." }));
|
||||
}
|
||||
if (!player) return await this.reply(resultKey, 60, JSON.stringify({ success: false, message: "세션을 찾을수 없습니다." }));
|
||||
await lavalinkManager.search(guild.id, data.playlistUrl, data.userId, player);
|
||||
if (!player) { await resolvingPl; return await this.reply(resultKey, 60, JSON.stringify({ success: false, message: "세션을 찾을수 없습니다." })); }
|
||||
const resolvedPl = await resolvingPl;
|
||||
if (!resolvedPl) {
|
||||
player.errMsg(`노래를 찾을수 없습니다.`);
|
||||
return await this.reply(resultKey, 60, JSON.stringify({ success: false, message: "노래를 찾을수 없습니다." }));
|
||||
}
|
||||
await lavalinkManager.addResolved(player, resolvedPl, data.userId);
|
||||
await this.reply(resultKey, 60, JSON.stringify({ success: true, message: "플레이리스트 추가 완료" }));
|
||||
}
|
||||
if (data.action === "player_now") {
|
||||
|
||||
Reference in New Issue
Block a user