fix: music system improvements, radio refactoring, lavalink config
This commit is contained in:
parent
a764ab9b2d
commit
108f63f7ec
8 changed files with 137 additions and 136 deletions
|
|
@ -10,7 +10,7 @@ services:
|
||||||
- ./lavalink/plugins/:/opt/Lavalink/plugins/
|
- ./lavalink/plugins/:/opt/Lavalink/plugins/
|
||||||
- lavalink-data:/opt/Lavalink/data
|
- lavalink-data:/opt/Lavalink/data
|
||||||
environment:
|
environment:
|
||||||
JAVA_OPTS: "-Xmx256M -Xms64M"
|
JAVA_OPTS: "-Xmx1G -Xms256M"
|
||||||
SPOTIFY_CLIENT_ID: ${SPOTIFY_CLIENT_ID}
|
SPOTIFY_CLIENT_ID: ${SPOTIFY_CLIENT_ID}
|
||||||
SPOTIFY_CLIENT_SECRET: ${SPOTIFY_CLIENT_SECRET}
|
SPOTIFY_CLIENT_SECRET: ${SPOTIFY_CLIENT_SECRET}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,12 @@ server:
|
||||||
address: 0.0.0.0
|
address: 0.0.0.0
|
||||||
|
|
||||||
plugins:
|
plugins:
|
||||||
|
youtube:
|
||||||
|
enabled: true
|
||||||
|
clients:
|
||||||
|
- MUSIC
|
||||||
|
- WEB
|
||||||
|
- ANDROID_VR
|
||||||
lavasrc:
|
lavasrc:
|
||||||
providers:
|
providers:
|
||||||
- "ytsearch:\"%ISRC%\""
|
- "ytsearch:\"%ISRC%\""
|
||||||
|
|
@ -23,12 +29,16 @@ plugins:
|
||||||
mediaType: "music"
|
mediaType: "music"
|
||||||
playlistLoadLimit: 6
|
playlistLoadLimit: 6
|
||||||
|
|
||||||
|
|
||||||
lavalink:
|
lavalink:
|
||||||
|
plugins:
|
||||||
|
- dependency: "dev.lavalink.youtube:youtube-plugin:1.18.1"
|
||||||
|
repository: "https://maven.lavalink.dev/releases"
|
||||||
|
- dependency: "com.github.topi314.lavasrc:lavasrc-plugin:4.8.1"
|
||||||
|
repository: "https://maven.topi.wtf/releases"
|
||||||
server:
|
server:
|
||||||
password: "Hch4HiVCxkKs9JPiSnrt4Q"
|
password: "Hch4HiVCxkKs9JPiSnrt4Q"
|
||||||
sources:
|
sources:
|
||||||
youtube: true
|
youtube: false
|
||||||
soundcloud: true
|
soundcloud: true
|
||||||
bandcamp: true
|
bandcamp: true
|
||||||
twitch: true
|
twitch: true
|
||||||
|
|
@ -37,7 +47,7 @@ lavalink:
|
||||||
bufferDurationMs: 400
|
bufferDurationMs: 400
|
||||||
youtubePlaylistLoadLimit: 6
|
youtubePlaylistLoadLimit: 6
|
||||||
playerUpdateInterval: 5
|
playerUpdateInterval: 5
|
||||||
youtubeSearchEnabled: true
|
youtubeSearchEnabled: false
|
||||||
soundcloudSearchEnabled: true
|
soundcloudSearchEnabled: true
|
||||||
gc-warnings: true
|
gc-warnings: true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ public class LavalinkManager {
|
||||||
.setName("main")
|
.setName("main")
|
||||||
.setServerUri(lavalinkConfig.getWsUri())
|
.setServerUri(lavalinkConfig.getWsUri())
|
||||||
.setPassword(lavalinkConfig.getPassword())
|
.setPassword(lavalinkConfig.getPassword())
|
||||||
.setHttpTimeout(10000L)
|
.setHttpTimeout(30000L)
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
registerListeners();
|
registerListeners();
|
||||||
|
|
|
||||||
|
|
@ -91,18 +91,10 @@ public class MusicBotPlayHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String resolveIdentifier(String query) {
|
private static String resolveIdentifier(String query) {
|
||||||
if (query.startsWith("https://www.youtube.com/")
|
|
||||||
|| query.startsWith("https://youtu.be/")
|
|
||||||
|| query.startsWith("https://music.youtube.com/")) {
|
|
||||||
return "ytmsearch:" + query;
|
|
||||||
}
|
|
||||||
if (query.startsWith("https://open.spotify.com/")) {
|
|
||||||
return "spsearch:" + query;
|
|
||||||
}
|
|
||||||
if (query.startsWith("http://") || query.startsWith("https://")) {
|
if (query.startsWith("http://") || query.startsWith("https://")) {
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
return "ytmsearch:" + query;
|
return "ytsearch:" + query;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void connect(Guild g, Member m, LavalinkManager lm) {
|
private void connect(Guild g, Member m, LavalinkManager lm) {
|
||||||
|
|
|
||||||
|
|
@ -109,30 +109,29 @@ public class PlayCommand implements Command {
|
||||||
sendErr(ctx, "Укажи запрос после источника.");
|
sendErr(ctx, "Укажи запрос после источника.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String id;
|
|
||||||
boolean isUrl = raw.startsWith("http://") || raw.startsWith("https://");
|
boolean isUrl = raw.startsWith("http://") || raw.startsWith("https://");
|
||||||
boolean isYt =
|
|
||||||
isUrl
|
|
||||||
&& (raw.contains("youtube.com/")
|
|
||||||
|| raw.contains("youtu.be/")
|
|
||||||
|| raw.contains("spotify.com/"));
|
|
||||||
boolean isList = !isUrl && first.equals("list");
|
boolean isList = !isUrl && first.equals("list");
|
||||||
if (isYt) {
|
|
||||||
id = "ytmsearch:" + raw;
|
String id;
|
||||||
} else if (isUrl) {
|
if (isUrl) {
|
||||||
id = raw;
|
id = raw;
|
||||||
} else if (isList) {
|
} else if (isList) {
|
||||||
id = "ytsearch:" + raw;
|
id = "ytsearch:" + raw;
|
||||||
|
} else if (raw.startsWith("ytmsearch:")
|
||||||
|
|| raw.startsWith("ytsearch:")
|
||||||
|
|| raw.startsWith("scsearch:")) {
|
||||||
|
id = raw;
|
||||||
} else {
|
} else {
|
||||||
id =
|
id = "ytsearch:" + raw;
|
||||||
raw.startsWith("ytmsearch:") || raw.startsWith("ytsearch:") || raw.startsWith("scsearch:")
|
|
||||||
? raw
|
|
||||||
: "ytmsearch:" + raw;
|
|
||||||
}
|
}
|
||||||
boolean autoPlay = isUrl || (!isList);
|
|
||||||
|
boolean autoPlay = isUrl;
|
||||||
|
|
||||||
String noMatch =
|
String noMatch =
|
||||||
isUrl ? "Ничего не найдено по ссылке." : "Ничего не найдено по запросу: " + raw;
|
isUrl ? "Ничего не найдено по ссылке." : "Ничего не найдено по запросу: " + raw;
|
||||||
String query = isList ? raw : (isUrl ? null : raw);
|
String query = isList ? raw : (isUrl ? null : raw);
|
||||||
|
|
||||||
lm.getOrCreateLink(guild.getIdLong())
|
lm.getOrCreateLink(guild.getIdLong())
|
||||||
.loadItem(id)
|
.loadItem(id)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ public class PlaySlashHandler {
|
||||||
|
|
||||||
private void search(
|
private void search(
|
||||||
SlashCommandInteractionEvent event, String query, GuildMusicManager m, Guild guild) {
|
SlashCommandInteractionEvent event, String query, GuildMusicManager m, Guild guild) {
|
||||||
trackLoader.loadItem(m.getGuildId(), "ytmsearch:" + query, loader(event, m, guild, query));
|
trackLoader.loadItem(m.getGuildId(), "ytsearch:" + query, loader(event, m, guild, query));
|
||||||
}
|
}
|
||||||
|
|
||||||
private TrackLoader.LoadCallbacks loader(
|
private TrackLoader.LoadCallbacks loader(
|
||||||
|
|
|
||||||
|
|
@ -3,37 +3,29 @@ package creator.loki.feature.radio;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import com.google.inject.Singleton;
|
import creator.loki.feature.music.core.LavalinkManager;
|
||||||
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
|
import creator.loki.feature.music.manage.GuildMusicManager;
|
||||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
|
|
||||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
|
|
||||||
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
|
|
||||||
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
|
|
||||||
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers;
|
|
||||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
|
|
||||||
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
|
|
||||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
|
||||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
|
|
||||||
import creator.loki.feature.voice.AudioPlayerSendHandler;
|
|
||||||
import creator.loki.util.LoggerDecorator;
|
import creator.loki.util.LoggerDecorator;
|
||||||
|
import dev.arbjerg.lavalink.client.AbstractAudioLoadResultHandler;
|
||||||
|
import dev.arbjerg.lavalink.client.Link;
|
||||||
|
import dev.arbjerg.lavalink.client.player.Track;
|
||||||
|
import dev.arbjerg.lavalink.client.player.TrackLoaded;
|
||||||
import net.dv8tion.jda.api.JDA;
|
import net.dv8tion.jda.api.JDA;
|
||||||
import net.dv8tion.jda.api.entities.Guild;
|
import net.dv8tion.jda.api.entities.Guild;
|
||||||
|
import net.dv8tion.jda.api.entities.Member;
|
||||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@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 final AudioPlayerManager playerManager;
|
private final Map<Long, GuildRadioState> radioStates = new ConcurrentHashMap<>();
|
||||||
private final Map<Long, GuildRadioState> radioStates;
|
private LavalinkManager lavalinkManager;
|
||||||
private JDA radioJda;
|
private JDA radioJda;
|
||||||
|
|
||||||
public RadioManager() {
|
public void setLavalinkManager(LavalinkManager lm) {
|
||||||
this.playerManager = new DefaultAudioPlayerManager();
|
this.lavalinkManager = lm;
|
||||||
AudioSourceManagers.registerRemoteSources(playerManager);
|
|
||||||
this.radioStates = new ConcurrentHashMap<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRadioJda(JDA radioJda) {
|
public void setRadioJda(JDA radioJda) {
|
||||||
|
|
@ -53,56 +45,44 @@ public class RadioManager {
|
||||||
public void startRadio(long guildId, long channelId) {
|
public void startRadio(long guildId, long channelId) {
|
||||||
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) return;
|
if (radioJda == null || lavalinkManager == null) return;
|
||||||
|
|
||||||
Guild guild = radioJda.getGuildById(guildId);
|
Guild guild = radioJda.getGuildById(guildId);
|
||||||
VoiceChannel voiceChannel = guild != null ? guild.getVoiceChannelById(channelId) : null;
|
if (guild == null) return;
|
||||||
if (guild == null || voiceChannel == null) return;
|
|
||||||
|
|
||||||
// Already playing in this channel — ignore duplicate start
|
guild.getJDA().getDirectAudioController().connect(
|
||||||
if (state.player != null && guild.getAudioManager().isConnected()) return;
|
guild.getVoiceChannelById(channelId));
|
||||||
|
|
||||||
if (state.player != null) {
|
var mm = new GuildMusicManager(guildId, lavalinkManager, null);
|
||||||
state.player.destroy();
|
state.musicManager = mm;
|
||||||
state.player = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioPlayer player = playerManager.createPlayer();
|
lavalinkManager
|
||||||
player.addListener(
|
.getOrCreateLink(guildId)
|
||||||
new AudioEventAdapter() {
|
.loadItem(state.streamUrl)
|
||||||
|
.subscribe(
|
||||||
|
new AbstractAudioLoadResultHandler() {
|
||||||
@Override
|
@Override
|
||||||
public void onTrackEnd(AudioPlayer p, AudioTrack track, AudioTrackEndReason reason) {
|
public void ontrackLoaded(TrackLoaded r) {
|
||||||
if (reason == AudioTrackEndReason.FINISHED || reason == AudioTrackEndReason.STOPPED) {
|
mm.getScheduler().enqueue(r.getTrack());
|
||||||
p.playTrack(track.makeClone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTrackException(AudioPlayer p, AudioTrack track, FriendlyException ex) {
|
|
||||||
LoggerDecorator.warn(logger, "Radio error, restarting: " + ex.getMessage());
|
|
||||||
p.playTrack(track.makeClone());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
state.player = player;
|
|
||||||
state.volume = Math.max(0, Math.min(100, state.volume));
|
|
||||||
player.setVolume(state.volume);
|
|
||||||
|
|
||||||
guild.getAudioManager().setSendingHandler(new AudioPlayerSendHandler(player));
|
|
||||||
guild.getAudioManager().openAudioConnection(voiceChannel);
|
|
||||||
|
|
||||||
playerManager.loadItem(
|
|
||||||
state.streamUrl,
|
|
||||||
new AudioLoadResultHandler() {
|
|
||||||
@Override
|
|
||||||
public void trackLoaded(AudioTrack track) {
|
|
||||||
player.playTrack(track);
|
|
||||||
LoggerDecorator.music(logger, "Radio started in guild " + guildId);
|
LoggerDecorator.music(logger, "Radio started in guild " + guildId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void playlistLoaded(AudioPlaylist playlist) {
|
public void onPlaylistLoaded(
|
||||||
AudioTrack track = playlist.getTracks().isEmpty() ? null : playlist.getTracks().get(0);
|
dev.arbjerg.lavalink.client.player.PlaylistLoaded r) {
|
||||||
if (track != null) player.playTrack(track);
|
if (!r.getTracks().isEmpty()) {
|
||||||
|
mm.getScheduler().enqueue(r.getTracks().get(0));
|
||||||
|
LoggerDecorator.music(logger, "Radio started in guild " + guildId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSearchResultLoaded(
|
||||||
|
dev.arbjerg.lavalink.client.player.SearchResult r) {
|
||||||
|
if (!r.getTracks().isEmpty()) {
|
||||||
|
mm.getScheduler().enqueue(r.getTracks().get(0));
|
||||||
|
LoggerDecorator.music(logger, "Radio started in guild " + guildId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -111,55 +91,35 @@ public class RadioManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void loadFailed(FriendlyException exception) {
|
public void loadFailed(
|
||||||
LoggerDecorator.error(logger, "Failed to load radio: " + state.streamUrl, exception);
|
dev.arbjerg.lavalink.client.player.LoadFailed e) {
|
||||||
|
LoggerDecorator.warn(logger, "Failed to load radio: "
|
||||||
|
+ (e.getException().getMessage() != null
|
||||||
|
? e.getException().getMessage() : "unknown"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stopRadio(long guildId) {
|
public void stopRadio(long guildId) {
|
||||||
GuildRadioState state = radioStates.get(guildId);
|
GuildRadioState state = radioStates.remove(guildId);
|
||||||
if (state == null) return;
|
if (state == null) return;
|
||||||
|
if (state.musicManager != null) {
|
||||||
if (state.player != null) {
|
state.musicManager.stop();
|
||||||
state.player.destroy();
|
state.musicManager.destroy();
|
||||||
state.player = null;
|
}
|
||||||
|
if (lavalinkManager != null) {
|
||||||
|
Link link = lavalinkManager.getClient().getLinkIfCached(guildId);
|
||||||
|
if (link != null) link.destroy().subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (radioJda != null) {
|
if (radioJda != null) {
|
||||||
Guild guild = radioJda.getGuildById(guildId);
|
Guild guild = radioJda.getGuildById(guildId);
|
||||||
if (guild != null) {
|
if (guild != null) guild.getAudioManager().closeAudioConnection();
|
||||||
guild.getAudioManager().closeAudioConnection();
|
|
||||||
guild.getAudioManager().setSendingHandler(null);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
LoggerDecorator.music(logger, "Radio stopped for guild " + guildId);
|
LoggerDecorator.music(logger, "Radio stopped for guild " + guildId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setVolume(long guildId, int volume) {
|
|
||||||
int vol = Math.max(0, Math.min(100, volume));
|
|
||||||
GuildRadioState state = radioStates.get(guildId);
|
|
||||||
if (state == null) {
|
|
||||||
GuildRadioState newState = new GuildRadioState();
|
|
||||||
newState.volume = vol;
|
|
||||||
radioStates.put(guildId, newState);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
state.volume = vol;
|
|
||||||
if (state.player != null) {
|
|
||||||
state.player.setVolume(vol);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getVolume(long guildId) {
|
|
||||||
GuildRadioState state = radioStates.get(guildId);
|
|
||||||
return state != null ? state.volume : 50;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRadioActive(long guildId) {
|
public boolean isRadioActive(long guildId) {
|
||||||
GuildRadioState state = radioStates.get(guildId);
|
return radioStates.containsKey(guildId);
|
||||||
return state != null && state.player != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasRadioBot() {
|
public boolean hasRadioBot() {
|
||||||
|
|
@ -172,7 +132,6 @@ public class RadioManager {
|
||||||
|
|
||||||
private static class GuildRadioState {
|
private static class GuildRadioState {
|
||||||
String streamUrl;
|
String streamUrl;
|
||||||
AudioPlayer player;
|
GuildMusicManager musicManager;
|
||||||
int volume = 50;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package dev.arbjerg.lavalink.libraries.jda;
|
||||||
|
|
||||||
|
import dev.arbjerg.lavalink.client.LavalinkClient;
|
||||||
|
import dev.arbjerg.lavalink.client.loadbalancing.VoiceRegion;
|
||||||
|
import dev.arbjerg.lavalink.protocol.v4.VoiceState;
|
||||||
|
import net.dv8tion.jda.api.entities.Guild;
|
||||||
|
import net.dv8tion.jda.api.entities.Member;
|
||||||
|
import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel;
|
||||||
|
import net.dv8tion.jda.api.hooks.VoiceDispatchInterceptor;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class JDAVoiceUpdateListener implements VoiceDispatchInterceptor {
|
||||||
|
private final LavalinkClient lavalink;
|
||||||
|
|
||||||
|
public JDAVoiceUpdateListener(LavalinkClient lavalink) {
|
||||||
|
this.lavalink = lavalink;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVoiceServerUpdate(@NotNull VoiceServerUpdate update) {
|
||||||
|
String token = update.getToken();
|
||||||
|
String endpoint = update.getEndpoint();
|
||||||
|
String sessionId = update.getSessionId();
|
||||||
|
Guild guild = update.getGuild();
|
||||||
|
|
||||||
|
Member selfMember = guild.getMember(guild.getJDA().getSelfUser());
|
||||||
|
if (selfMember == null) return;
|
||||||
|
|
||||||
|
AudioChannel channel = selfMember.getVoiceState().getChannel();
|
||||||
|
if (channel == null) return;
|
||||||
|
|
||||||
|
VoiceState state = new VoiceState(token, endpoint, sessionId, channel.getId());
|
||||||
|
VoiceRegion region = VoiceRegion.Companion.fromEndpoint(endpoint);
|
||||||
|
lavalink.getOrCreateLink(guild.getIdLong(), region).onVoiceServerUpdate(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onVoiceStateUpdate(@NotNull VoiceStateUpdate update) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue