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);
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();
}

View file

@ -2,7 +2,6 @@ package creator.loki.feature.radio;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import com.google.inject.Inject;
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.repository.QueueRepository;
import creator.loki.util.LoggerDecorator;
import creator.loki.util.VoiceHelper;
import dev.arbjerg.lavalink.client.AbstractAudioLoadResultHandler;
import dev.arbjerg.lavalink.client.Link;
import dev.arbjerg.lavalink.client.player.TrackLoaded;
@ -22,7 +20,6 @@ import org.slf4j.LoggerFactory;
@Singleton
public class RadioManager {
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 QueueRepository queueRepository;
@ -62,11 +59,15 @@ public class RadioManager {
GuildRadioState state = radioStates.get(guildId);
if (state == null || state.streamUrl == null || state.streamUrl.isBlank()) return;
if (radioJda == null || lavalinkManager == null) return;
if (isRadioActive(guildId) && state.musicManager != null) {
stopRadio(guildId);
}
var finalState = radioStates.computeIfAbsent(guildId, id -> new GuildRadioState());
finalState.streamUrl = getRadioUrl(guildId);
String url = state.streamUrl;
int vol = state.volume;
if (state.musicManager != null) stopRadio(guildId);
GuildRadioState finalState = radioStates.computeIfAbsent(guildId, id -> new GuildRadioState());
finalState.streamUrl = url;
finalState.volume = vol;
Guild guild = radioJda.getGuildById(guildId);
if (guild == null) {
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);
return;
}
logger.debug("Radio: connecting to channel {}/{} in guild {}", vc.getName(), channelId, guildId);
var self = guild.getMember(guild.getJDA().getSelfUser());
if (self == null) {
logger.warn("Radio: self member is null for guild {}", guildId);
return;
}
logger.debug(
"Radio: bot has CONNECT permission: {}",
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);
if (!self.hasPermission(vc, net.dv8tion.jda.api.Permission.VOICE_CONNECT)) {
logger.warn("Radio: no CONNECT permission in channel {}/{}", channelId, guildId);
return;
}
var mm = new GuildMusicManager(guildId, lavalinkManager, queueRepository);
int vol = finalState.volume;
var url = finalState.streamUrl;
var link = lavalinkManager.getOrCreateLink(guildId);
guild.getJDA().getDirectAudioController().connect(vc);
var mm = new GuildMusicManager(guildId, lavalinkManager, null);
finalState.musicManager = mm;
lavalinkManager
.getOrCreateLink(guildId)

View file

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