基于JDA+Lavaplayer的Java音乐机器人无法播放音乐求助
JDA + Lavaplayer 音乐机器人无法播放音乐排查
问题现象
机器人能成功加入语音频道,能找到目标曲目,AudioLoadResultHandler的trackLoaded函数正常调用,但始终没有声音输出。环境为Windows系统+Java 19,使用最新版JDA和Lavaplayer,机器人在服务器拥有最高权限,几周前可正常运行,无重大改动后出现该问题。
Maven依赖配置
<repositories> <repository> <id>dv8tion</id> <name>m2-dv8tion</name> <url>https://m2.dv8tion.net/releases</url> </repository> </repositories> <dependencies> <!-- Discord Bot - JDA--> <dependency> <groupId>net.dv8tion</groupId> <artifactId>JDA</artifactId> <version>5.0.0-beta.21</version> </dependency> <!-- Music library - Lavaplayer --> <dependency> <groupId>com.sedmelluq</groupId> <artifactId>lavaplayer</artifactId> <version>1.3.78</version> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.30</version> <scope>provided</scope> </dependency> <!-- Injection - Guice --> <dependency> <groupId>com.google.inject</groupId> <artifactId>guice</artifactId> <version>5.1.0</version> </dependency> </dependencies>
核心代码
PlayerManager类
public class PlayerManager { private Map<Long, GuildMusicManager> guildMusicManagers = new HashMap<>(); private AudioPlayerManager audioPlayerManager = new DefaultAudioPlayerManager(); public PlayerManager() { registerRemoteSources(audioPlayerManager); registerLocalSource(audioPlayerManager); } public GuildMusicManager getGuildMusicManager(Guild guild) { return guildMusicManagers.computeIfAbsent(guild.getIdLong(), (guildId) -> { GuildMusicManager musicManager = new GuildMusicManager(audioPlayerManager, guild); guild.getAudioManager().setSendingHandler(musicManager.getAudioForwarder()); return musicManager; }); } public void play(Guild guild, String trackURL) { GuildMusicManager guildMusicManager = getGuildMusicManager(guild); audioPlayerManager.loadItemOrdered(guildMusicManager, trackURL, new AudioLoadResultHandler() { @Override public void trackLoaded(AudioTrack audioTrack) { guildMusicManager.getTrackScheduler().queue(audioTrack); } @Override public void playlistLoaded(AudioPlaylist audioPlaylist) { guildMusicManager.getTrackScheduler().queue(audioPlaylist.getTracks().get(0)); } @Override public void noMatches() { System.out.println("not matches"); } @Override public void loadFailed(FriendlyException e) { System.out.println("load failed"); } }); }
GuildMusicManager类
@Getter public class GuildMusicManager { private TrackScheduler trackScheduler; private AudioForwarder audioForwarder; public GuildMusicManager(AudioPlayerManager audioPlayerManager, Guild guild) { AudioPlayer audioPlayer = audioPlayerManager.createPlayer(); trackScheduler = new TrackScheduler(audioPlayer, guild); audioPlayer.addListener(trackScheduler); audioForwarder = new AudioForwarder(audioPlayer); } }
TrackScheduler类
@Getter public class TrackScheduler extends AudioEventAdapter { private AudioPlayer audioPlayer; private BlockingQueue<AudioTrack> queue = new LinkedBlockingQueue<>(); private Guild guild; TrackScheduler(AudioPlayer audioPlayer, Guild guild) { this.audioPlayer = audioPlayer; this.guild = guild; } @Override public void onTrackStart(AudioPlayer player, AudioTrack track) { EmbedBuilder builder = new EmbedBuilder(); builder.setColor(MAGENTA); AudioTrackInfo info = track.getInfo(); builder.setTitle("Jetzt läuft: " + info.title); long sekunden = info.length/1000; long minuten = sekunden/60; long stunden = minuten/60; minuten %= 60; sekunden %= 60; String url = info.uri; builder.addField(info.author, "[" + info.title +"](" + url + ")", false); builder.addField("Länge", info.isStream ? ":red_circle: Stream" : (stunden > 0 ? stunden + "h " : "") + minuten + "min " + sekunden + "s", true); map.get(guild.getIdLong()).sendMessageEmbeds(builder.build()).queue(); } @Override public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) { audioPlayer.startTrack(queue.poll(), false); newSingleThreadExecutor().execute(() -> { try { sleep(60000); } catch (InterruptedException e) { throw new RuntimeException(e); } if(isNull(player.getPlayingTrack())) { if(nonNull(guild.getAudioManager().getConnectedChannel())) { guild.getAudioManager().closeAudioConnection(); } } }); } public void queue(AudioTrack audioTrack) { if (!audioPlayer.startTrack(audioTrack, true)) { queue.offer(audioTrack); } } }
AudioForwarder类
public class AudioForwarder implements AudioSendHandler { private final AudioPlayer audioPlayer; private final ByteBuffer byteBuffer = allocate(1024); private final MutableAudioFrame audioFrame = new MutableAudioFrame(); public AudioForwarder(AudioPlayer audioPlayer) { this.audioPlayer = audioPlayer; this.audioFrame.setBuffer(byteBuffer); } @Override public boolean canProvide() { return audioPlayer.provide(audioFrame); } @Nullable @Override public ByteBuffer provide20MsAudio() { return byteBuffer.flip(); } @Override public boolean isOpus() { return true; } }
Play命令类
@CommandBase.Command(name = "play", description = "Wähle ein Lied, das abgespielt werden soll.", hasOptions = true) public class PlayCommand extends CommandBase { @Inject private JavaBot javaBot; public static HashMap<Long, TextChannel> map = new HashMap<>(); public PlayCommand(@NotNull Command command) { super(command); } @Override public void execute(Member member, TextChannel textChannel, List<OptionMapping> options, SlashCommandInteractionEvent event) { GuildVoiceState guildVoiceState = member.getVoiceState(); if (isNull(guildVoiceState) || isNull(guildVoiceState.getChannel()) || isNull(guildVoiceState.getChannel().asVoiceChannel())) { event.reply("Du musst in einem Sprachkanal sein.").queue(); //TODO Replace with embed (EmbedBuilder builder = new EmbedBuilder()) return; } VoiceChannel voiceChannel = guildVoiceState.getChannel().asVoiceChannel(); PlayerManager playerManager = javaBot.getPlayerManager(); AudioManager manager = voiceChannel.getGuild().getAudioManager(); manager.openAudioConnection(voiceChannel); String url = options.get(0).getAsString(); if (!url.startsWith("http")) { url = "ytsearch:" + url + " audio"; } event.reply("Suche nach dem Titel...").queue(); //TODO Replace with embed (EmbedBuilder builder = new EmbedBuilder()) playerManager.play(event.getGuild(), url); map.put(voiceChannel.getGuild().getIdLong(), textChannel); } @Override public void autoComplete(String optionName, CommandAutoCompleteInteractionEvent event) { List<String> options = of("Achterbahn wise guys", "Ich trink uso was trinkst denn du so", "for the night pop smoke"); //TODO add logic to get famous songs if (optionName.equalsIgnoreCase("song")) { List<net.dv8tion.jda.api.interactions.commands.Command.Choice> returnChoices = options.stream() .filter(option -> option.toLowerCase().startsWith(event.getFocusedOption().getValue().toLowerCase())) .map(option -> new net.dv8tion.jda.api.interactions.commands.Command.Choice(option, option)) .collect(toList()); event.replyChoices(returnChoices).queue(); } } @Override public List<OptionData> getOptions() { return of(new OptionData(STRING, "song", "Titel oder URL vom Lied", true, true)); } }
排查与修复方案
1. 修复AudioForwarder缓冲区大小问题
当前ByteBuffer设置为1024字节,不足以容纳Lavaplayer输出的20ms Opus音频帧,需调整为标准的4096字节:
private final ByteBuffer byteBuffer = allocate(4096);
2. 调整语音连接设置顺序
先获取GuildMusicManager(自动设置发送处理器),再打开语音连接,避免初始音频帧丢失:
// 先初始化音乐管理器,确保发送处理器已绑定 PlayerManager playerManager = javaBot.getPlayerManager(); playerManager.getGuildMusicManager(voiceChannel.getGuild()); // 再打开语音连接 AudioManager manager = voiceChannel.getGuild().getAudioManager(); manager.openAudioConnection(voiceChannel);
3. 修正TrackScheduler中map赋值顺序
将map.put移至playerManager.play之前,避免onTrackStart触发时map未赋值:
map.put(voiceChannel.getGuild().getIdLong(), textChannel); playerManager.play(event.getGuild(), url);
4. 排查JDA Beta版本兼容性
使用的JDA为Beta版本,可能存在音频发送隐性bug,尝试升级至最新稳定版或回退到之前可正常运行的版本。
5. 检查语音权限与本地音频设置
- 确认机器人在语音频道拥有Speak权限,未被频道静音;
- 检查Windows系统中Java进程的音频输出未被静音、音量正常。
内容的提问来源于stack exchange,提问作者Kifo
相关产品推荐
相关产品推荐

