fix: rewrite radio voice connection — no thread blocking, no abort, no URL loss

- remove 30s+30s VoiceHelper.waitForCondition that blocked JDA event thread
- remove abort on voice/player timeout (TrackScheduler retries sendTrack itself)
- remove openAudioConnection() that conflicted with Lavalink voice interceptor
- preserve stream URL across stopRadio/restart instead of losing it
- pass null queueRepository to GuildMusicManager (radio has no queue persistence)
- run startRadio in virtual thread from RadioBotListener/RadioCommand
This commit is contained in:
loki5512344 2026-07-13 11:20:55 +02:00
parent 37d50ab240
commit 25cf66aea2
Signed by: boba
GPG key ID: 253067914055423B
3 changed files with 17 additions and 41 deletions

View file

@ -91,7 +91,7 @@ public class RadioBotListener extends ListenerAdapter {
radioManager.setRadio(guild.getIdLong(), url); radioManager.setRadio(guild.getIdLong(), url);
VoiceChannel vc = member.getVoiceState().getChannel().asVoiceChannel(); VoiceChannel vc = member.getVoiceState().getChannel().asVoiceChannel();
radioManager.startRadio(guild.getIdLong(), vc.getIdLong()); Thread.startVirtualThread(() -> radioManager.startRadio(guild.getIdLong(), vc.getIdLong()));
event.getChannel().sendMessage("📻 Радио запущено: **" + url + "**").queue(); event.getChannel().sendMessage("📻 Радио запущено: **" + url + "**").queue();
} }

View file

@ -2,7 +2,6 @@ package creator.loki.feature.radio;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
@ -10,7 +9,6 @@ import creator.loki.feature.music.core.LavalinkManager;
import creator.loki.feature.music.manage.GuildMusicManager; import creator.loki.feature.music.manage.GuildMusicManager;
import creator.loki.feature.music.repository.QueueRepository; import creator.loki.feature.music.repository.QueueRepository;
import creator.loki.util.LoggerDecorator; import creator.loki.util.LoggerDecorator;
import creator.loki.util.VoiceHelper;
import dev.arbjerg.lavalink.client.AbstractAudioLoadResultHandler; import dev.arbjerg.lavalink.client.AbstractAudioLoadResultHandler;
import dev.arbjerg.lavalink.client.Link; import dev.arbjerg.lavalink.client.Link;
import dev.arbjerg.lavalink.client.player.TrackLoaded; import dev.arbjerg.lavalink.client.player.TrackLoaded;
@ -22,7 +20,6 @@ import org.slf4j.LoggerFactory;
@Singleton @Singleton
public class RadioManager { public class RadioManager {
private static final Logger logger = LoggerFactory.getLogger(RadioManager.class); private static final Logger logger = LoggerFactory.getLogger(RadioManager.class);
private static final long CONNECT_TIMEOUT_MS = 30000;
private final Map<Long, GuildRadioState> radioStates = new ConcurrentHashMap<>(); private final Map<Long, GuildRadioState> radioStates = new ConcurrentHashMap<>();
private final QueueRepository queueRepository; private final QueueRepository queueRepository;
@ -62,11 +59,15 @@ public class RadioManager {
GuildRadioState state = radioStates.get(guildId); GuildRadioState state = radioStates.get(guildId);
if (state == null || state.streamUrl == null || state.streamUrl.isBlank()) return; if (state == null || state.streamUrl == null || state.streamUrl.isBlank()) return;
if (radioJda == null || lavalinkManager == null) return; if (radioJda == null || lavalinkManager == null) return;
if (isRadioActive(guildId) && state.musicManager != null) {
stopRadio(guildId); String url = state.streamUrl;
} int vol = state.volume;
var finalState = radioStates.computeIfAbsent(guildId, id -> new GuildRadioState()); if (state.musicManager != null) stopRadio(guildId);
finalState.streamUrl = getRadioUrl(guildId);
GuildRadioState finalState = radioStates.computeIfAbsent(guildId, id -> new GuildRadioState());
finalState.streamUrl = url;
finalState.volume = vol;
Guild guild = radioJda.getGuildById(guildId); Guild guild = radioJda.getGuildById(guildId);
if (guild == null) { if (guild == null) {
logger.warn("Radio: guild {} not found in radio bot cache", guildId); logger.warn("Radio: guild {} not found in radio bot cache", guildId);
@ -77,45 +78,20 @@ public class RadioManager {
logger.warn("Radio: voice channel {} not found in guild {}", channelId, guildId); logger.warn("Radio: voice channel {} not found in guild {}", channelId, guildId);
return; return;
} }
logger.debug("Radio: connecting to channel {}/{} in guild {}", vc.getName(), channelId, guildId);
var self = guild.getMember(guild.getJDA().getSelfUser()); var self = guild.getMember(guild.getJDA().getSelfUser());
if (self == null) { if (self == null) {
logger.warn("Radio: self member is null for guild {}", guildId); logger.warn("Radio: self member is null for guild {}", guildId);
return; return;
} }
logger.debug( if (!self.hasPermission(vc, net.dv8tion.jda.api.Permission.VOICE_CONNECT)) {
"Radio: bot has CONNECT permission: {}", logger.warn("Radio: no CONNECT permission in channel {}/{}", channelId, guildId);
self.hasPermission(vc, net.dv8tion.jda.api.Permission.VOICE_CONNECT));
guild.getJDA().getDirectAudioController().connect(vc);
if (!guild.getAudioManager().isConnected()) {
guild.getAudioManager().openAudioConnection(vc);
}
var link = lavalinkManager.getOrCreateLink(guildId);
VoiceHelper.waitForCondition(
() -> self.getVoiceState().inAudioChannel(), CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
if (self.getVoiceState().inAudioChannel()) {
logger.info("Radio: voice connected for guild {}", guildId);
} else {
logger.warn("Radio: voice not connected after {}ms for guild {}", CONNECT_TIMEOUT_MS, guildId);
}
VoiceHelper.waitForCondition(
() -> link.getCachedPlayer() != null, CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
if (link.getCachedPlayer() != null) {
logger.info("Radio: player created for guild {}", guildId);
} else {
logger.warn("Radio: no player after {}ms for guild {}", CONNECT_TIMEOUT_MS, guildId);
}
if (!self.getVoiceState().inAudioChannel() || link.getCachedPlayer() == null) {
LoggerDecorator.warn(logger, "Radio: skipping — no voice or player for guild " + guildId);
return; return;
} }
var mm = new GuildMusicManager(guildId, lavalinkManager, queueRepository); var link = lavalinkManager.getOrCreateLink(guildId);
int vol = finalState.volume; guild.getJDA().getDirectAudioController().connect(vc);
var url = finalState.streamUrl;
var mm = new GuildMusicManager(guildId, lavalinkManager, null);
finalState.musicManager = mm; finalState.musicManager = mm;
lavalinkManager lavalinkManager
.getOrCreateLink(guildId) .getOrCreateLink(guildId)

View file

@ -132,7 +132,7 @@ public class RadioCommand implements Command {
} }
long channelId = member.getVoiceState().getChannel().getIdLong(); long channelId = member.getVoiceState().getChannel().getIdLong();
radioManager.startRadio(guildId, channelId); Thread.startVirtualThread(() -> radioManager.startRadio(guildId, channelId));
context context
.getChannel() .getChannel()
.sendMessageEmbeds( .sendMessageEmbeds(