From 696a455a25e818d048bd4f27424c5aacca2aefea Mon Sep 17 00:00:00 2001 From: Repulser Date: Thu, 8 Feb 2018 17:29:36 +0200 Subject: [PATCH 01/26] Fix voice checks --- .../main/java/lavalink/client/io/Link.java | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index 27a1bb45d..9400a8735 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -106,15 +106,17 @@ void connect(VoiceChannel channel, boolean checkChannel) { if (checkChannel && channel.equals(channel.getGuild().getSelfMember().getVoiceState().getChannel())) return; - final int userLimit = channel.getUserLimit(); // userLimit is 0 if no limit is set! - if (!self.isOwner() && !self.hasPermission(Permission.ADMINISTRATOR)) { - final long perms = PermissionUtil.getExplicitPermission(channel, self); - final long voicePerm = Permission.VOICE_MOVE_OTHERS.getRawValue(); - if (userLimit > 0 // If there is a userlimit - && userLimit <= channel.getMembers().size() // if that userlimit is reached - && (perms & voicePerm) != voicePerm) // If we don't have voice move others permissions - throw new InsufficientPermissionException(Permission.VOICE_MOVE_OTHERS, // then throw exception! - "Unable to connect to VoiceChannel due to userlimit! Requires permission VOICE_MOVE_OTHERS to bypass"); + if (this.state == State.CONNECTED) { + final int userLimit = channel.getUserLimit(); // userLimit is 0 if no limit is set! + if (!self.isOwner() && !self.hasPermission(Permission.ADMINISTRATOR)) { + final long perms = PermissionUtil.getExplicitPermission(channel, self); + final long voicePerm = Permission.VOICE_MOVE_OTHERS.getRawValue(); + if (userLimit > 0 // If there is a userlimit + && userLimit <= channel.getMembers().size() // if that userlimit is reached + && (perms & voicePerm) != voicePerm) // If we don't have voice move others permissions + throw new InsufficientPermissionException(Permission.VOICE_MOVE_OTHERS, // then throw exception! + "Unable to connect to VoiceChannel due to userlimit! Requires permission VOICE_MOVE_OTHERS to bypass"); + } } setState(State.CONNECTING); From 87c1cb8e7e53a75893268754b0c3b3cf6e68de93 Mon Sep 17 00:00:00 2001 From: Repulser Date: Thu, 8 Feb 2018 17:30:16 +0200 Subject: [PATCH 02/26] Use this instead --- LavalinkClient/src/main/java/lavalink/client/io/Link.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index 9400a8735..b15f76710 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -106,7 +106,7 @@ void connect(VoiceChannel channel, boolean checkChannel) { if (checkChannel && channel.equals(channel.getGuild().getSelfMember().getVoiceState().getChannel())) return; - if (this.state == State.CONNECTED) { + if (channel.getGuild().getSelfMember().getVoiceState().inVoiceChannel()) { final int userLimit = channel.getUserLimit(); // userLimit is 0 if no limit is set! if (!self.isOwner() && !self.hasPermission(Permission.ADMINISTRATOR)) { final long perms = PermissionUtil.getExplicitPermission(channel, self); From fa6f0962869b19c62c850f2810a4b65f944b6672 Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Thu, 8 Feb 2018 18:28:05 +0100 Subject: [PATCH 03/26] Fix client NPE on voice server update --- LavalinkClient/src/main/java/lavalink/client/io/Link.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index b15f76710..9e3d32bd5 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -201,7 +201,7 @@ public LavalinkSocket getNode() { public LavalinkSocket getNode(boolean selectIfAbsent) { if (selectIfAbsent && node == null) { node = lavalink.loadBalancer.determineBestSocket(guild); - player.onNodeChange(); + if (player != null) player.onNodeChange(); } return node; } From ddb63fc0b91dffa72eb4f00f62baddecc7d1ab45 Mon Sep 17 00:00:00 2001 From: Luke Date: Thu, 8 Feb 2018 18:32:27 +0000 Subject: [PATCH 04/26] Fix already-created youtube source manager instance not being used (#66) * Use the already created instance * Logging --- LavalinkServer/src/main/java/lavalink/server/Launcher.java | 7 +++++++ .../src/main/java/lavalink/server/player/Player.java | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/Launcher.java b/LavalinkServer/src/main/java/lavalink/server/Launcher.java index 6dfa659b6..b0c7ecc74 100644 --- a/LavalinkServer/src/main/java/lavalink/server/Launcher.java +++ b/LavalinkServer/src/main/java/lavalink/server/Launcher.java @@ -132,6 +132,13 @@ public static void main(String[] args) { } else { log.info("Using default buffer"); } + + Integer customPlaylistLimit = config.getYoutubePlaylistLoadLimit(); + if (customPlaylistLimit != null) { + log.info("Setting playlist load limit to {}", customPlaylistLimit); + } else { + log.info("Using default playlist load limit"); + } } else { log.warn("This system and architecture appears to not support native audio sending! " + "GC pauses may cause your bot to stutter during playback."); diff --git a/LavalinkServer/src/main/java/lavalink/server/player/Player.java b/LavalinkServer/src/main/java/lavalink/server/player/Player.java index 6c46b41a9..c5b92a27b 100644 --- a/LavalinkServer/src/main/java/lavalink/server/player/Player.java +++ b/LavalinkServer/src/main/java/lavalink/server/player/Player.java @@ -65,7 +65,7 @@ public class Player extends AudioEventAdapter implements AudioSendHandler { Integer playlistLoadLimit = Launcher.config.getYoutubePlaylistLoadLimit(); if (playlistLoadLimit != null) youtube.setPlaylistPageCount(playlistLoadLimit); - PLAYER_MANAGER.registerSourceManager(new YoutubeAudioSourceManager()); + PLAYER_MANAGER.registerSourceManager(youtube); } if (sources.isBandcamp()) PLAYER_MANAGER.registerSourceManager(new BandcampAudioSourceManager()); if (sources.isSoundcloud()) PLAYER_MANAGER.registerSourceManager(new SoundCloudAudioSourceManager()); From a632e6b5904287e9d08b38bd4319777dd9a809d1 Mon Sep 17 00:00:00 2001 From: Repulser Date: Tue, 13 Feb 2018 17:59:07 +0200 Subject: [PATCH 05/26] Fix destroy check (#70) --- LavalinkClient/src/main/java/lavalink/client/io/Link.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Link.java b/LavalinkClient/src/main/java/lavalink/client/io/Link.java index 9e3d32bd5..7c248e710 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Link.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Link.java @@ -167,8 +167,9 @@ void onDisconnected() { */ @SuppressWarnings("unused") public void destroy() { + boolean shouldDisconnect = state != State.DISCONNECTING && state != State.NOT_CONNECTED; setState(State.DESTROYING); - if (state != State.DISCONNECTING && state != State.NOT_CONNECTED) { + if (shouldDisconnect) { Guild g = getJda().getGuildById(guild); if (g != null) getMainWs().queueAudioDisconnect(g); } From e5a63165d284173007a38b2eb2906bcc7bc21428 Mon Sep 17 00:00:00 2001 From: Repulser Date: Sat, 17 Feb 2018 13:31:11 +0200 Subject: [PATCH 06/26] Update lavaplayer (#73) --- LavalinkClient/build.gradle | 2 +- LavalinkServer/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LavalinkClient/build.gradle b/LavalinkClient/build.gradle index 98de1996d..f519fbbf8 100644 --- a/LavalinkClient/build.gradle +++ b/LavalinkClient/build.gradle @@ -4,7 +4,7 @@ ext { moduleName = 'Lavalink-Client' } dependencies { - compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.47' + compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.51' compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.7' compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' compile group: 'org.json', name: 'json', version: '20180130' diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index e56811464..0d41d7a8a 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -17,7 +17,7 @@ jar { publishToMavenLocal.dependsOn 'bootRepackage' dependencies { - compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.47' + compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.51' compile group: 'com.github.DV8FromTheWorld', name: 'JDA-Audio', version: '91438c36d7107cf838c2f2eb147b08f989d929db' compile group: 'com.github.FredBoat', name: 'jda-nas', version: '1.0.6.1-JDA-Audio' compile group: 'com.github.shredder121', name: 'jda-async-packetprovider', version: '1.1' From 08a34c99a47a18ade7bd14e6c55ab92348caaa88 Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Sat, 17 Feb 2018 12:40:30 +0100 Subject: [PATCH 07/26] Document destroy op. Fixes #71 --- IMPLEMENTATION.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md index 8d1bb8d9b..a496c1432 100644 --- a/IMPLEMENTATION.md +++ b/IMPLEMENTATION.md @@ -104,6 +104,16 @@ Set player volume. Volume may range from 0 to 150. 100 is default. } ``` +Tell the server to potentially disconnect from the voice server and potentially remove the player with all its data. +This is useful if you want to move to a new node for a voice connection. Calling this op does not affect voice state, +and you can send the same VOICE_SERVER_UPDATE to a new node. +```json +{ + "op": "destroy", + "guildId": "..." +} +``` + ### Incoming messages See [LavalinkSocket.java](https://github.com/Frederikam/Lavalink/blob/dev/LavalinkClient/src/main/java/lavalink/client/io/LavalinkSocket.java) From ab759620dfc2cb7198fbdbce86b87346e48d3439 Mon Sep 17 00:00:00 2001 From: Frederikam Date: Tue, 20 Feb 2018 13:09:56 +0100 Subject: [PATCH 08/26] Add nodes async instead of blocking forever --- .../src/main/java/lavalink/client/io/Lavalink.java | 4 +++- .../src/main/java/lavalink/client/io/LavalinkSocket.java | 5 ----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java index 11a0bf34e..a8057c54b 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/Lavalink.java @@ -108,7 +108,9 @@ public void addNode(@Nonnull String name, @Nonnull URI serverUri, @Nonnull Strin headers.put("Num-Shards", Integer.toString(numShards)); headers.put("User-Id", userId); - nodes.add(new LavalinkSocket(name, this, serverUri, new Draft_6455(), headers)); + LavalinkSocket socket = new LavalinkSocket(name, this, serverUri, new Draft_6455(), headers); + socket.connect(); + nodes.add(socket); } @SuppressWarnings("unused") diff --git a/LavalinkClient/src/main/java/lavalink/client/io/LavalinkSocket.java b/LavalinkClient/src/main/java/lavalink/client/io/LavalinkSocket.java index 03e17511e..3434e7852 100644 --- a/LavalinkClient/src/main/java/lavalink/client/io/LavalinkSocket.java +++ b/LavalinkClient/src/main/java/lavalink/client/io/LavalinkSocket.java @@ -66,11 +66,6 @@ public class LavalinkSocket extends ReusableWebSocket { this.name = name; this.lavalink = lavalink; this.remoteUri = serverUri; - try { - this.connectBlocking(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } } @Override From cfc13ae99b7663c0460bd8f09a3582c82bccd907 Mon Sep 17 00:00:00 2001 From: Luke Date: Mon, 26 Feb 2018 21:30:40 +0000 Subject: [PATCH 09/26] Update lavaplayer (#79) * Update lavaplayer * Update lavaplayer --- LavalinkClient/build.gradle | 2 +- LavalinkServer/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LavalinkClient/build.gradle b/LavalinkClient/build.gradle index f519fbbf8..a01453552 100644 --- a/LavalinkClient/build.gradle +++ b/LavalinkClient/build.gradle @@ -4,7 +4,7 @@ ext { moduleName = 'Lavalink-Client' } dependencies { - compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.51' + compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.54' compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.7' compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' compile group: 'org.json', name: 'json', version: '20180130' diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index 0d41d7a8a..c9e6499a5 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -17,7 +17,7 @@ jar { publishToMavenLocal.dependsOn 'bootRepackage' dependencies { - compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.51' + compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.54' compile group: 'com.github.DV8FromTheWorld', name: 'JDA-Audio', version: '91438c36d7107cf838c2f2eb147b08f989d929db' compile group: 'com.github.FredBoat', name: 'jda-nas', version: '1.0.6.1-JDA-Audio' compile group: 'com.github.shredder121', name: 'jda-async-packetprovider', version: '1.1' From 7d6c0cace6cd1adaabe056661d43ac81efe91ef0 Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Wed, 28 Feb 2018 20:39:29 +0100 Subject: [PATCH 10/26] Update lavaplayer --- LavalinkServer/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index c9e6499a5..a87294f78 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -17,7 +17,7 @@ jar { publishToMavenLocal.dependsOn 'bootRepackage' dependencies { - compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.54' + compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.56' compile group: 'com.github.DV8FromTheWorld', name: 'JDA-Audio', version: '91438c36d7107cf838c2f2eb147b08f989d929db' compile group: 'com.github.FredBoat', name: 'jda-nas', version: '1.0.6.1-JDA-Audio' compile group: 'com.github.shredder121', name: 'jda-async-packetprovider', version: '1.1' From 27887a6c939c45d7a6fdacb64503c700b9603b2c Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Wed, 28 Feb 2018 20:39:52 +0100 Subject: [PATCH 11/26] Update lavaplayer --- LavalinkClient/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LavalinkClient/build.gradle b/LavalinkClient/build.gradle index a01453552..6ace8943a 100644 --- a/LavalinkClient/build.gradle +++ b/LavalinkClient/build.gradle @@ -4,7 +4,7 @@ ext { moduleName = 'Lavalink-Client' } dependencies { - compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.54' + compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.56' compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.7' compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' compile group: 'org.json', name: 'json', version: '20180130' From 13aeb0462450e873e7ff4d395ae2ddddc82bdd2d Mon Sep 17 00:00:00 2001 From: Ruben Dijkstra Date: Sun, 4 Mar 2018 16:47:58 +0100 Subject: [PATCH 12/26] Bump jda-async-packetprovider to 1.2 (#81) --- LavalinkServer/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index a87294f78..69209a8a5 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -20,7 +20,7 @@ dependencies { compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.56' compile group: 'com.github.DV8FromTheWorld', name: 'JDA-Audio', version: '91438c36d7107cf838c2f2eb147b08f989d929db' compile group: 'com.github.FredBoat', name: 'jda-nas', version: '1.0.6.1-JDA-Audio' - compile group: 'com.github.shredder121', name: 'jda-async-packetprovider', version: '1.1' + compile group: 'com.github.shredder121', name: 'jda-async-packetprovider', version: '1.2' compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.7' compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' From 0580d6783335329e4cfa4103aa95f77192589904 Mon Sep 17 00:00:00 2001 From: Napster Date: Mon, 5 Mar 2018 18:17:03 +0100 Subject: [PATCH 13/26] Put all the dependency versioning into the parent file (#82) * Put all the dependency versioning into the parent file * Fix wonky git plugin --- LavalinkClient/build.gradle | 24 +++++++++++----------- LavalinkServer/build.gradle | 32 ++++++++++++++--------------- build.gradle | 40 +++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 29 deletions(-) diff --git a/LavalinkClient/build.gradle b/LavalinkClient/build.gradle index 6ace8943a..261fae2c7 100644 --- a/LavalinkClient/build.gradle +++ b/LavalinkClient/build.gradle @@ -4,16 +4,16 @@ ext { moduleName = 'Lavalink-Client' } dependencies { - compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.56' - compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.7' - compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' - compile group: 'org.json', name: 'json', version: '20180130' - compile group: 'net.dv8tion', name: 'JDA', version: '3.5.0_334' - compileOnly group: 'io.prometheus', name: 'simpleclient', version: '0.1.0' - testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.0.0-M4' - testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.0.0-M4' - testCompile group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.0.0-M4' - testCompile group: 'org.junit.platform', name: 'junit-platform-runner', version: '1.0.0-M4' - testCompile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' - testCompile group: 'com.mashape.unirest', name: 'unirest-java', version: '1.4.9' + compile group: 'com.sedmelluq', name: 'lavaplayer', version: lavaplayerVersion + compile group: 'org.java-websocket', name: 'Java-WebSocket', version: javaWebSocketVersion + compile group: 'org.slf4j', name: 'slf4j-api', version: slf4jVersion + compile group: 'org.json', name: 'json', version: jsonOrgVersion + compile group: 'net.dv8tion', name: 'JDA', version: jdaVersion + compileOnly group: 'io.prometheus', name: 'simpleclient', version: prometheusVersion + testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: junitJupiterVersion + testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: junitJupiterVersion + testCompile group: 'org.junit.platform', name: 'junit-platform-launcher', version: junitPlatformVersion + testCompile group: 'org.junit.platform', name: 'junit-platform-runner', version: junitPlatformVersion + testCompile group: 'ch.qos.logback', name: 'logback-classic', version: logbackVersion + testCompile group: 'com.mashape.unirest', name: 'unirest-java', version: unirestVersion } diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index 69209a8a5..7ece2b547 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -1,8 +1,6 @@ -plugins { - id 'application' - id 'org.springframework.boot' version '1.5.10.RELEASE' - id 'com.gorylenko.gradle-git-properties' version '1.4.20' -} +apply plugin: 'application' +apply plugin: 'org.springframework.boot' +apply plugin: 'com.gorylenko.gradle-git-properties' description = 'Play audio to discord voice channels' mainClassName = "lavalink.server.Launcher" @@ -17,16 +15,16 @@ jar { publishToMavenLocal.dependsOn 'bootRepackage' dependencies { - compile group: 'com.sedmelluq', name: 'lavaplayer', version: '1.2.56' - compile group: 'com.github.DV8FromTheWorld', name: 'JDA-Audio', version: '91438c36d7107cf838c2f2eb147b08f989d929db' - compile group: 'com.github.FredBoat', name: 'jda-nas', version: '1.0.6.1-JDA-Audio' - compile group: 'com.github.shredder121', name: 'jda-async-packetprovider', version: '1.2' - compile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.7' - compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' - compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' - compile group: 'io.sentry', name: 'sentry-logback', version: '1.6.4' - compile group: 'com.github.oshi', name: 'oshi-core', version: '3.4.4' - compile group: 'org.json', name: 'json', version: '20180130' - compile group: 'com.google.guava', name: 'guava', version: '23.0' - compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '1.5.10.RELEASE' + compile group: 'com.sedmelluq', name: 'lavaplayer', version: lavaplayerVersion + compile group: 'com.github.DV8FromTheWorld', name: 'JDA-Audio', version: jdaAudioVersion + compile group: 'com.github.FredBoat', name: 'jda-nas', version: jdaNasVersion + compile group: 'com.github.shredder121', name: 'jda-async-packetprovider', version: jappVersion + compile group: 'org.java-websocket', name: 'Java-WebSocket', version: javaWebSocketVersion + compile group: 'ch.qos.logback', name: 'logback-classic', version: logbackVersion + compile group: 'org.slf4j', name: 'slf4j-api', version: slf4jVersion + compile group: 'io.sentry', name: 'sentry-logback', version: sentryLogbackVersion + compile group: 'com.github.oshi', name: 'oshi-core', version: oshiVersion + compile group: 'org.json', name: 'json', version: jsonOrgVersion + compile group: 'com.google.guava', name: 'guava', version: guavaVersion + compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: springBootVersion } diff --git a/build.gradle b/build.gradle index c37f11784..f8e1138e6 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,20 @@ allprojects { } subprojects { + buildscript { + ext { + springBootVersion = '1.5.10.RELEASE' + gradleGitVersion = '1.4.21' + } + repositories { + maven { url "https://plugins.gradle.org/m2/" } + maven { url 'http://repo.spring.io/plugins-release' } + } + dependencies { + classpath "gradle.plugin.com.gorylenko.gradle-git-properties:gradle-git-properties:${gradleGitVersion}" + classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}" + } + } apply plugin: 'java' apply plugin: 'idea' apply plugin: 'maven-publish' @@ -43,6 +57,32 @@ subprojects { task sourceJar(type: Jar) { from sourceSets.main.allJava } + + ext { + //@formatter:off + + lavaplayerVersion = '1.2.56' + jdaAudioVersion = '91438c36d7107cf838c2f2eb147b08f989d929db' + jdaNasVersion = '1.0.6.1-JDA-Audio' + jappVersion = '1.2' + jdaVersion = '3.5.0_334' + + springBootVersion = "${springBootVersion}" + javaWebSocketVersion = '1.3.7' + logbackVersion = '1.2.3' + slf4jVersion = '1.7.25' + sentryLogbackVersion = '1.6.4' + oshiVersion = '3.4.4' + jsonOrgVersion = '20180130' + guavaVersion = '23.0' + prometheusVersion = '0.1.0' + + junitJupiterVersion = '5.0.0-M4' + junitPlatformVersion = '1.0.0-M4' + unirestVersion = '1.4.9' + + //@formatter:on + } } ext { From 79fd8d87c31c5671a31db28d27d06a4d0297f02a Mon Sep 17 00:00:00 2001 From: Napster Date: Mon, 5 Mar 2018 23:08:50 +0100 Subject: [PATCH 14/26] Fix compilation of tests (#87) --- .../test/java/lavalink/client/LavalinkTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/LavalinkClient/src/test/java/lavalink/client/LavalinkTest.java b/LavalinkClient/src/test/java/lavalink/client/LavalinkTest.java index 1c8c6203e..558e53b90 100644 --- a/LavalinkClient/src/test/java/lavalink/client/LavalinkTest.java +++ b/LavalinkClient/src/test/java/lavalink/client/LavalinkTest.java @@ -90,14 +90,14 @@ static void tearDown() { @Test void vcJoinTest() { VoiceChannel vc = jda.getVoiceChannelById(System.getenv("TEST_VOICE_CHANNEL")); - lavalink.openVoiceConnection(vc); + lavalink.getLink(vc.getGuild()).connect(vc); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } - lavalink.closeVoiceConnection(vc.getGuild()); + lavalink.getLink(vc.getGuild()).disconnect(); } private List loadAudioTracks(String identifier) { @@ -126,9 +126,9 @@ private List loadAudioTracks(String identifier) { private void connectAndPlay(AudioTrack track) throws InterruptedException { VoiceChannel vc = jda.getVoiceChannelById(System.getenv("TEST_VOICE_CHANNEL")); - lavalink.openVoiceConnection(vc); + lavalink.getLink(vc.getGuild()).connect(vc); - IPlayer player = lavalink.getPlayer(vc.getGuild().getId()); + IPlayer player = lavalink.getLink(vc.getGuild()).getPlayer(); CountDownLatch latch = new CountDownLatch(1); PlayerEventListenerAdapter listener = new PlayerEventListenerAdapter() { @Override @@ -141,7 +141,7 @@ public void onTrackStart(IPlayer player, AudioTrack track) { player.playTrack(track); latch.await(5, TimeUnit.SECONDS); - lavalink.closeVoiceConnection(vc.getGuild()); + lavalink.getLink(vc.getGuild()).disconnect(); player.removeListener(listener); player.stopTrack(); @@ -161,9 +161,9 @@ void vcStreamTest() throws InterruptedException { @Test void stopTest() throws InterruptedException { VoiceChannel vc = jda.getVoiceChannelById(System.getenv("TEST_VOICE_CHANNEL")); - lavalink.openVoiceConnection(vc); + lavalink.getLink(vc.getGuild()).connect(vc); - IPlayer player = lavalink.getPlayer(vc.getGuild().getId()); + IPlayer player = lavalink.getLink(vc.getGuild()).getPlayer(); CountDownLatch latch = new CountDownLatch(1); PlayerEventListenerAdapter listener = new PlayerEventListenerAdapter() { @@ -185,7 +185,7 @@ public void onTrackEnd(IPlayer player, AudioTrack track, AudioTrackEndReason end player.playTrack(loadAudioTracks("aGOFOP2BIhI").get(0)); latch.await(5, TimeUnit.SECONDS); - lavalink.closeVoiceConnection(vc.getGuild()); + lavalink.getLink(vc.getGuild()).disconnect(); player.removeListener(listener); player.stopTrack(); From c613929d7de2f4599a78813acb365e7d27cdb96a Mon Sep 17 00:00:00 2001 From: Napster Date: Mon, 5 Mar 2018 23:09:18 +0100 Subject: [PATCH 15/26] Add a way to pass jvm args to gradle and make it respect them (#86) --- LavalinkServer/build.gradle | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index 7ece2b547..a4ea95fae 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -14,6 +14,18 @@ jar { } publishToMavenLocal.dependsOn 'bootRepackage' +bootRun { + //compiling tests during bootRun increases the likelyhood of catching broken tests locally instead of on the CI + dependsOn compileTestJava + + //pass in custom jvm args + // source: https://stackoverflow.com/a/25079415 + // example: ./gradlew bootRun -PjvmArgs="--illegal-access=debug -Dwhatever=value" + if (project.hasProperty('jvmArgs')) { + jvmArgs project.jvmArgs.split('\\s+') + } +} + dependencies { compile group: 'com.sedmelluq', name: 'lavaplayer', version: lavaplayerVersion compile group: 'com.github.DV8FromTheWorld', name: 'JDA-Audio', version: jdaAudioVersion From 4f7428dbb15ab12fecf5f1ba49c714aaa98c83f6 Mon Sep 17 00:00:00 2001 From: Napster Date: Tue, 6 Mar 2018 11:12:45 +0100 Subject: [PATCH 16/26] Bump dependencies and gradle (#85) * Bump deps * Bump Spring Boot to v2 * Bump gradle to v4.6 * Move publishing to lavalink client only * Adjust archive name of task that builds the jar * Fix and pimp client tests --- LavalinkClient/build.gradle | 28 +++ .../java/lavalink/client/LavalinkTest.java | 163 +++++++++++++----- .../client/RequireSystemProperty.java | 19 ++ .../client/RequireSystemPropertyExists.java | 31 ++++ LavalinkServer/build.gradle | 3 +- .../main/java/lavalink/server/Launcher.java | 3 +- build.gradle | 35 +--- gradle/wrapper/gradle-wrapper.jar | Bin 54712 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 9 files changed, 214 insertions(+), 70 deletions(-) create mode 100644 LavalinkClient/src/test/java/lavalink/client/RequireSystemProperty.java create mode 100644 LavalinkClient/src/test/java/lavalink/client/RequireSystemPropertyExists.java diff --git a/LavalinkClient/build.gradle b/LavalinkClient/build.gradle index 261fae2c7..d25d5ce35 100644 --- a/LavalinkClient/build.gradle +++ b/LavalinkClient/build.gradle @@ -3,6 +3,34 @@ version System.getenv('dev') == 'true' ? '-SNAPSHOT' : '2.0' ext { moduleName = 'Lavalink-Client' } + +apply plugin: 'maven-publish' + +publishing { + publications { + mavenJava(MavenPublication) { + groupId rootProject.group + artifactId moduleName + + from components.java + + artifact sourceJar { + classifier "sources" + } + } + } +} + +task install(dependsOn: 'publishToMavenLocal') +publishToMavenLocal.dependsOn 'jar' + +test { + useJUnitPlatform() + + systemProperty("TEST_TOKEN", System.getProperty("TEST_TOKEN")) + systemProperty("TEST_VOICE_CHANNEL", System.getProperty("TEST_VOICE_CHANNEL")) +} + dependencies { compile group: 'com.sedmelluq', name: 'lavaplayer', version: lavaplayerVersion compile group: 'org.java-websocket', name: 'Java-WebSocket', version: javaWebSocketVersion diff --git a/LavalinkClient/src/test/java/lavalink/client/LavalinkTest.java b/LavalinkClient/src/test/java/lavalink/client/LavalinkTest.java index 558e53b90..df5ceb3cb 100644 --- a/LavalinkClient/src/test/java/lavalink/client/LavalinkTest.java +++ b/LavalinkClient/src/test/java/lavalink/client/LavalinkTest.java @@ -35,8 +35,8 @@ import net.dv8tion.jda.core.JDABuilder; import net.dv8tion.jda.core.entities.VoiceChannel; import org.json.JSONArray; +import org.json.JSONObject; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -50,10 +50,22 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@RequireSystemProperty({ + LavalinkTest.PROPERTY_TOKEN, + LavalinkTest.PROPERTY_CHANNEL, +}) class LavalinkTest { private static final Logger log = LoggerFactory.getLogger(LavalinkTest.class); + public static final String PROPERTY_TOKEN = "TEST_TOKEN"; + public static final String PROPERTY_CHANNEL = "TEST_VOICE_CHANNEL"; + private static JDA jda = null; private static Lavalink lavalink = null; private static final String[] BILL_WURTZ_JINGLES = { @@ -67,37 +79,40 @@ class LavalinkTest { }; @BeforeAll - static void setUp() { - try { - jda = new JDABuilder(AccountType.BOT) - .setToken(System.getenv("TEST_TOKEN")) - .addEventListener(lavalink) - .buildBlocking(); - - lavalink = new Lavalink("152691313123393536", 1, integer -> jda); - lavalink.addNode(new URI("ws://localhost"), "youshallnotpass"); - } catch (Exception e) { - throw new RuntimeException(e); - } + static void setUp() throws Exception { + JDABuilder jdaBuilder = new JDABuilder(AccountType.BOT) + .setToken(getSystemProperty(PROPERTY_TOKEN)); + + JDA selfId = jdaBuilder.buildAsync(); + lavalink = new Lavalink(selfId.asBot().getApplicationInfo().submit().get(30, TimeUnit.SECONDS).getId(), 1, integer -> jda); + selfId.shutdown(); + + lavalink.addNode(new URI("ws://localhost:5555"), "youshallnotpass"); + + jda = jdaBuilder + .addEventListener(lavalink) + .buildAsync(); + + Thread.sleep(2000); + assertTrue(lavalink.getNodes().get(0).isAvailable(), "Could not connect to lavalink server"); } @AfterAll static void tearDown() { - lavalink.shutdown(); - jda.shutdown(); + if (lavalink != null) { + lavalink.shutdown(); + } + if (jda != null) { + jda.shutdown(); + } } @Test void vcJoinTest() { - VoiceChannel vc = jda.getVoiceChannelById(System.getenv("TEST_VOICE_CHANNEL")); - lavalink.getLink(vc.getGuild()).connect(vc); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - lavalink.getLink(vc.getGuild()).disconnect(); + VoiceChannel vc = fetchVoiceChannel(jda, getTestVoiceChannelId()); + ensureConnected(lavalink, vc); + assertEquals(Link.State.CONNECTED, lavalink.getLink(vc.getGuild()).getState(), "Failed to connect to voice channel"); + ensureNotConnected(lavalink, vc); } private List loadAudioTracks(String identifier) { @@ -106,13 +121,12 @@ private List loadAudioTracks(String identifier) { .header("Authorization", "youshallnotpass") .asJson() .getBody() - .getObject() - .getJSONArray("tracks"); + .getArray(); ArrayList list = new ArrayList<>(); trackData.forEach(o -> { try { - list.add(LavalinkUtil.toAudioTrack((String) o)); + list.add(LavalinkUtil.toAudioTrack(((JSONObject) o).getString("track"))); } catch (IOException e) { throw new RuntimeException(e); } @@ -125,8 +139,8 @@ private List loadAudioTracks(String identifier) { } private void connectAndPlay(AudioTrack track) throws InterruptedException { - VoiceChannel vc = jda.getVoiceChannelById(System.getenv("TEST_VOICE_CHANNEL")); - lavalink.getLink(vc.getGuild()).connect(vc); + VoiceChannel vc = fetchVoiceChannel(jda, getTestVoiceChannelId()); + ensureConnected(lavalink, vc); IPlayer player = lavalink.getLink(vc.getGuild()).getPlayer(); CountDownLatch latch = new CountDownLatch(1); @@ -141,11 +155,11 @@ public void onTrackStart(IPlayer player, AudioTrack track) { player.playTrack(track); latch.await(5, TimeUnit.SECONDS); - lavalink.getLink(vc.getGuild()).disconnect(); + ensureNotConnected(lavalink, vc); player.removeListener(listener); player.stopTrack(); - Assertions.assertEquals(0, latch.getCount()); + assertEquals(0, latch.getCount()); } @Test @@ -160,8 +174,8 @@ void vcStreamTest() throws InterruptedException { @Test void stopTest() throws InterruptedException { - VoiceChannel vc = jda.getVoiceChannelById(System.getenv("TEST_VOICE_CHANNEL")); - lavalink.getLink(vc.getGuild()).connect(vc); + VoiceChannel vc = fetchVoiceChannel(jda, getTestVoiceChannelId()); + ensureConnected(lavalink, vc); IPlayer player = lavalink.getLink(vc.getGuild()).getPlayer(); CountDownLatch latch = new CountDownLatch(1); @@ -185,18 +199,18 @@ public void onTrackEnd(IPlayer player, AudioTrack track, AudioTrackEndReason end player.playTrack(loadAudioTracks("aGOFOP2BIhI").get(0)); latch.await(5, TimeUnit.SECONDS); - lavalink.getLink(vc.getGuild()).disconnect(); + ensureNotConnected(lavalink, vc); player.removeListener(listener); player.stopTrack(); - Assertions.assertEquals(0, latch.getCount()); + assertEquals(0, latch.getCount()); } @Test void testPlayback() throws InterruptedException { - VoiceChannel vc = jda.getVoiceChannelById(System.getenv("TEST_VOICE_CHANNEL")); + VoiceChannel vc = fetchVoiceChannel(jda, getTestVoiceChannelId()); Link link = lavalink.getLink(vc.getGuild()); - link.connect(vc); + ensureConnected(lavalink, vc); IPlayer player = link.getPlayer(); CountDownLatch latch = new CountDownLatch(1); @@ -204,6 +218,7 @@ void testPlayback() throws InterruptedException { PlayerEventListenerAdapter listener = new PlayerEventListenerAdapter() { @Override public void onTrackEnd(IPlayer player, AudioTrack track, AudioTrackEndReason endReason) { + log.info(endReason.name()); if (endReason == AudioTrackEndReason.FINISHED) { latch.countDown(); } @@ -217,12 +232,82 @@ public void onTrackEnd(IPlayer player, AudioTrack track, AudioTrackEndReason end player.playTrack(loadAudioTracks(jingle).get(0)); latch.await(20, TimeUnit.SECONDS); - link.disconnect(); + ensureNotConnected(lavalink, vc); player.removeListener(listener); player.stopTrack(); - Assertions.assertEquals(0, latch.getCount()); + assertEquals(0, latch.getCount()); + } + + private static String getSystemProperty(String key) { + String value = System.getProperty(key); + + assertNotNull(value, "Missing system property " + key); + assertFalse(value.isEmpty(), "System property " + key + " is empty"); + + return value; } + private static long getTestVoiceChannelId() { + return Long.parseUnsignedLong(getSystemProperty(PROPERTY_CHANNEL)); + } + + private static VoiceChannel fetchVoiceChannel(JDA jda, long voiceChannelId) { + long started = System.currentTimeMillis(); + while (jda.getStatus() != JDA.Status.CONNECTED + && System.currentTimeMillis() - started < 10000 //wait 10 sec max + && !Thread.currentThread().isInterrupted()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + assertEquals(JDA.Status.CONNECTED, jda.getStatus(), "Failed to connect to Discord in a reasonable amount of time"); + + VoiceChannel voiceChannel = jda.getVoiceChannelById(voiceChannelId); + assertNotNull(voiceChannel, "Configured VoiceChannel not found on the configured Discord bot account"); + + return voiceChannel; + } + + + private static void ensureConnected(Lavalink lavalink, VoiceChannel voiceChannel) { + + Link link = lavalink.getLink(voiceChannel.getGuild()); + link.connect(voiceChannel); + long started = System.currentTimeMillis(); + while (link.getState() != Link.State.CONNECTED + && System.currentTimeMillis() - started < 10000 //wait 10 sec max + && !Thread.currentThread().isInterrupted()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + link.connect(voiceChannel); + } + + assertEquals(Link.State.CONNECTED, link.getState(), "Failed to connect to voice channel in a reasonable amount of time"); + } + + private static void ensureNotConnected(Lavalink lavalink, VoiceChannel voiceChannel) { + Link link = lavalink.getLink(voiceChannel.getGuild()); + link.disconnect(); + long started = System.currentTimeMillis(); + while (link.getState() != Link.State.NOT_CONNECTED && link.getState() != Link.State.DISCONNECTING + && System.currentTimeMillis() - started < 10000 //wait 10 sec max + && !Thread.currentThread().isInterrupted()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + link.disconnect(); + } + + assertTrue(link.getState() == Link.State.NOT_CONNECTED + || link.getState() == Link.State.DISCONNECTING, "Failed to disconnect from voice channel in a reasonable amount of time"); + } } diff --git a/LavalinkClient/src/test/java/lavalink/client/RequireSystemProperty.java b/LavalinkClient/src/test/java/lavalink/client/RequireSystemProperty.java new file mode 100644 index 000000000..b3208c94c --- /dev/null +++ b/LavalinkClient/src/test/java/lavalink/client/RequireSystemProperty.java @@ -0,0 +1,19 @@ +package lavalink.client; + +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Created by napster on 06.03.18. + */ +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(RequireSystemPropertyExists.class) +public @interface RequireSystemProperty { + + /** + * @return an array of System property keys + */ + String[] value(); +} diff --git a/LavalinkClient/src/test/java/lavalink/client/RequireSystemPropertyExists.java b/LavalinkClient/src/test/java/lavalink/client/RequireSystemPropertyExists.java new file mode 100644 index 000000000..75aa44239 --- /dev/null +++ b/LavalinkClient/src/test/java/lavalink/client/RequireSystemPropertyExists.java @@ -0,0 +1,31 @@ +package lavalink.client; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.support.AnnotationSupport; + +import java.util.Optional; + +/** + * Created by napster on 06.03.18. + *

+ * Checks whether the required system properties have been set + */ +public class RequireSystemPropertyExists implements ExecutionCondition { + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + Optional annotation = AnnotationSupport.findAnnotation(context.getElement(), RequireSystemProperty.class); + if (annotation.isPresent()) { + for (String propertyKey : annotation.get().value()) { + String propertyValue = System.getProperty(propertyKey); + if (propertyValue == null || propertyValue.isEmpty()) { + return ConditionEvaluationResult.disabled(String.format("System property '%s' not set. Skipping test.", propertyKey)); + } + } + return ConditionEvaluationResult.enabled("All required system properties present. Continuing test."); + } + return ConditionEvaluationResult.enabled("No RequireSystemProperty annotation found. Continuing test."); + } +} diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index a4ea95fae..5d2aff9fc 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -9,10 +9,9 @@ ext { moduleName = 'Lavalink-Server' } -jar { +bootJar { archiveName = "Lavalink.jar" } -publishToMavenLocal.dependsOn 'bootRepackage' bootRun { //compiling tests during bootRun increases the likelyhood of catching broken tests locally instead of on the CI diff --git a/LavalinkServer/src/main/java/lavalink/server/Launcher.java b/LavalinkServer/src/main/java/lavalink/server/Launcher.java index b0c7ecc74..fd7a830c2 100644 --- a/LavalinkServer/src/main/java/lavalink/server/Launcher.java +++ b/LavalinkServer/src/main/java/lavalink/server/Launcher.java @@ -35,6 +35,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; +import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @@ -112,7 +113,7 @@ private void turnOffSentry() { public static void main(String[] args) { SpringApplication sa = new SpringApplication(Launcher.class); - sa.setWebEnvironment(true); + sa.setWebApplicationType(WebApplicationType.SERVLET); sa.run(args); String os = System.getProperty("os.name"); diff --git a/build.gradle b/build.gradle index f8e1138e6..1780e1858 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ allprojects { subprojects { buildscript { ext { - springBootVersion = '1.5.10.RELEASE' + springBootVersion = '2.0.0.RELEASE' gradleGitVersion = '1.4.21' } repositories { @@ -27,33 +27,14 @@ subprojects { } apply plugin: 'java' apply plugin: 'idea' - apply plugin: 'maven-publish' sourceCompatibility = 1.8 targetCompatibility = 1.8 - publishing { - publications { - mavenJava(MavenPublication) { - groupId rootProject.group - artifactId moduleName - - from components.java - - artifact sourceJar { - classifier "sources" - } - } - } - } - compileJava.dependsOn 'clean' compileJava.options.encoding = 'UTF-8' compileJava.options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" - task install(dependsOn: 'publishToMavenLocal') - publishToMavenLocal.dependsOn 'jar' - task sourceJar(type: Jar) { from sourceSets.main.allJava } @@ -65,20 +46,20 @@ subprojects { jdaAudioVersion = '91438c36d7107cf838c2f2eb147b08f989d929db' jdaNasVersion = '1.0.6.1-JDA-Audio' jappVersion = '1.2' - jdaVersion = '3.5.0_334' + jdaVersion = '3.5.1_345' springBootVersion = "${springBootVersion}" javaWebSocketVersion = '1.3.7' logbackVersion = '1.2.3' slf4jVersion = '1.7.25' - sentryLogbackVersion = '1.6.4' + sentryLogbackVersion = '1.7.0' oshiVersion = '3.4.4' jsonOrgVersion = '20180130' - guavaVersion = '23.0' - prometheusVersion = '0.1.0' + guavaVersion = '24.0-jre' + prometheusVersion = '0.3.0' - junitJupiterVersion = '5.0.0-M4' - junitPlatformVersion = '1.0.0-M4' + junitJupiterVersion = '5.1.0' + junitPlatformVersion = '1.1.0' unirestVersion = '1.4.9' //@formatter:on @@ -92,6 +73,6 @@ ext { import org.gradle.api.tasks.wrapper.Wrapper.DistributionType task wrapper(type: Wrapper) { - gradleVersion = '4.4.1' + gradleVersion = '4.6' distributionType = DistributionType.ALL } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ed88a042a287c140a32e1639edfc91b2a233da8c..f6b961fd5a86aa5fbfe90f707c3138408be7c718 100644 GIT binary patch delta 16093 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 delta 16582 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 Ogj=MCmKgkJ_WuEs-6Qw_ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 57c7d2d22..9a4163a4f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip From e13df48d42ca5b2f503b3cb87519a20e25c871e6 Mon Sep 17 00:00:00 2001 From: Napster Date: Tue, 6 Mar 2018 15:39:47 +0100 Subject: [PATCH 17/26] Less static abuse more spring stuff (#84) --- .../main/java/lavalink/server/Launcher.java | 79 +++------------- .../config/AudioPlayerConfiguration.java | 46 +++++++++ .../config/AudioSendFactoryConfiguration.java | 56 +++++++++++ .../server/config/AudioSourcesConfig.java | 85 +++++++++++++++++ .../{Config.java => config/ServerConfig.java} | 93 +------------------ .../server/config/WebsocketConfig.java | 31 +++++++ .../lavalink/server/io/SocketContext.java | 45 +++++---- .../java/lavalink/server/io/SocketServer.java | 39 ++++++-- .../java/lavalink/server/io/StatsTask.java | 6 +- .../lavalink/server/player/AudioLoader.java | 8 +- .../server/player/AudioLoaderRestHandler.java | 20 ++-- .../lavalink/server/player/EventEmitter.java | 11 ++- .../java/lavalink/server/player/Player.java | 40 +------- .../main/java/lavalink/server/util/Util.java | 10 +- 14 files changed, 331 insertions(+), 238 deletions(-) create mode 100644 LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.java create mode 100644 LavalinkServer/src/main/java/lavalink/server/config/AudioSendFactoryConfiguration.java create mode 100644 LavalinkServer/src/main/java/lavalink/server/config/AudioSourcesConfig.java rename LavalinkServer/src/main/java/lavalink/server/{Config.java => config/ServerConfig.java} (52%) create mode 100644 LavalinkServer/src/main/java/lavalink/server/config/WebsocketConfig.java diff --git a/LavalinkServer/src/main/java/lavalink/server/Launcher.java b/LavalinkServer/src/main/java/lavalink/server/Launcher.java index fd7a830c2..aa353ed55 100644 --- a/LavalinkServer/src/main/java/lavalink/server/Launcher.java +++ b/LavalinkServer/src/main/java/lavalink/server/Launcher.java @@ -26,41 +26,35 @@ import io.sentry.Sentry; import io.sentry.SentryClient; import io.sentry.logback.SentryAppender; -import lavalink.server.io.SocketContext; +import lavalink.server.config.ServerConfig; import lavalink.server.io.SocketServer; import lavalink.server.util.SimpleLogToSLF4JAdapter; import net.dv8tion.jda.utils.SimpleLog; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.WebApplicationType; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.context.annotation.Bean; +import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.stereotype.Controller; import java.io.IOException; -import java.net.InetSocketAddress; import java.util.Properties; -@Configuration +@SpringBootApplication @ComponentScan -@EnableAutoConfiguration -@Controller public class Launcher { private static final Logger log = LoggerFactory.getLogger(Launcher.class); public final static long startTime = System.currentTimeMillis(); - public static Config config; - @SuppressWarnings("FieldCanBeLocal") - private final SocketServer socketServer; - @Autowired - public Launcher(Config config, SocketServer socketServer) { + public static void main(String[] args) { + SpringApplication sa = new SpringApplication(Launcher.class); + sa.setWebApplicationType(WebApplicationType.SERVLET); + sa.run(args); + } + + public Launcher(ServerConfig serverConfig, SocketServer socketServer) { Runtime.getRuntime().addShutdownHook(new Thread(() -> { log.info("Shutdown hook triggered"); try { @@ -72,13 +66,11 @@ public Launcher(Config config, SocketServer socketServer) { SimpleLog.LEVEL = SimpleLog.Level.OFF; SimpleLog.addListener(new SimpleLogToSLF4JAdapter()); - Launcher.config = config; - initSentry(); - this.socketServer = socketServer; + initSentry(serverConfig); } - private void initSentry() { - String sentryDsn = config.getSentryDsn(); + private void initSentry(ServerConfig serverConfig) { + String sentryDsn = serverConfig.getSentryDsn(); if (sentryDsn == null || sentryDsn.isEmpty()) { log.info("No sentry dsn found, turning off sentry."); turnOffSentry(); @@ -110,49 +102,4 @@ private void turnOffSentry() { Sentry.close(); sentryAppender.stop(); } - - public static void main(String[] args) { - SpringApplication sa = new SpringApplication(Launcher.class); - sa.setWebApplicationType(WebApplicationType.SERVLET); - sa.run(args); - - String os = System.getProperty("os.name"); - - log.info("OS: " + System.getProperty("os.name") + ", Arch: " + System.getProperty("os.arch")); - - if ((os.contains("Windows") || os.contains("Linux")) - && !System.getProperty("os.arch").equalsIgnoreCase("arm") - && !System.getProperty("os.arch").equalsIgnoreCase("arm-linux") - ) { - SocketContext.nasSupported = true; - log.info("JDA-NAS supported system detected. Enabled native audio sending."); - - Integer customBuffer = config.getBufferDurationMs(); - if (customBuffer != null) { - log.info("Setting buffer to {}ms", customBuffer); - } else { - log.info("Using default buffer"); - } - - Integer customPlaylistLimit = config.getYoutubePlaylistLoadLimit(); - if (customPlaylistLimit != null) { - log.info("Setting playlist load limit to {}", customPlaylistLimit); - } else { - log.info("Using default playlist load limit"); - } - } else { - log.warn("This system and architecture appears to not support native audio sending! " - + "GC pauses may cause your bot to stutter during playback."); - } - } - - @Bean - static SocketServer socketServer(@Value("${lavalink.server.ws.port:8080}") Integer port, - @Value("${lavalink.server.ws.host:0.0.0.0}") String host, - @Value("${lavalink.server.password}") String password) { - SocketServer ss = new SocketServer(new InetSocketAddress(host, port), password); - ss.start(); - return ss; - } - } diff --git a/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.java b/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.java new file mode 100644 index 000000000..78dacb5dd --- /dev/null +++ b/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.java @@ -0,0 +1,46 @@ +package lavalink.server.config; + +import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; +import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; +import com.sedmelluq.discord.lavaplayer.source.bandcamp.BandcampAudioSourceManager; +import com.sedmelluq.discord.lavaplayer.source.beam.BeamAudioSourceManager; +import com.sedmelluq.discord.lavaplayer.source.http.HttpAudioSourceManager; +import com.sedmelluq.discord.lavaplayer.source.local.LocalAudioSourceManager; +import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioSourceManager; +import com.sedmelluq.discord.lavaplayer.source.twitch.TwitchStreamAudioSourceManager; +import com.sedmelluq.discord.lavaplayer.source.vimeo.VimeoAudioSourceManager; +import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioSourceManager; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +/** + * Created by napster on 05.03.18. + */ +@Component +public class AudioPlayerConfiguration { + + @Bean + public AudioPlayerManager audioPlayerManager(AudioSourcesConfig sources, ServerConfig serverConfig) { + + AudioPlayerManager audioPlayerManager = new DefaultAudioPlayerManager(); + audioPlayerManager.enableGcMonitoring(); + + if (sources.isYoutube()) { + YoutubeAudioSourceManager youtube = new YoutubeAudioSourceManager(); + Integer playlistLoadLimit = serverConfig.getYoutubePlaylistLoadLimit(); + + if (playlistLoadLimit != null) youtube.setPlaylistPageCount(playlistLoadLimit); + audioPlayerManager.registerSourceManager(youtube); + } + if (sources.isBandcamp()) audioPlayerManager.registerSourceManager(new BandcampAudioSourceManager()); + if (sources.isSoundcloud()) audioPlayerManager.registerSourceManager(new SoundCloudAudioSourceManager()); + if (sources.isTwitch()) audioPlayerManager.registerSourceManager(new TwitchStreamAudioSourceManager()); + if (sources.isVimeo()) audioPlayerManager.registerSourceManager(new VimeoAudioSourceManager()); + if (sources.isMixer()) audioPlayerManager.registerSourceManager(new BeamAudioSourceManager()); + if (sources.isHttp()) audioPlayerManager.registerSourceManager(new HttpAudioSourceManager()); + if (sources.isLocal()) audioPlayerManager.registerSourceManager(new LocalAudioSourceManager()); + + return audioPlayerManager; + } + +} diff --git a/LavalinkServer/src/main/java/lavalink/server/config/AudioSendFactoryConfiguration.java b/LavalinkServer/src/main/java/lavalink/server/config/AudioSendFactoryConfiguration.java new file mode 100644 index 000000000..31358ec89 --- /dev/null +++ b/LavalinkServer/src/main/java/lavalink/server/config/AudioSendFactoryConfiguration.java @@ -0,0 +1,56 @@ +package lavalink.server.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * Created by napster on 05.03.18. + */ +@Component +public class AudioSendFactoryConfiguration { + + private static final Logger log = LoggerFactory.getLogger(AudioSendFactoryConfiguration.class); + + private boolean nasSupported = false; + private final int audioSendFactoryCount = Runtime.getRuntime().availableProcessors() * 2; + + public AudioSendFactoryConfiguration(ServerConfig serverConfig) { + String os = System.getProperty("os.name"); + + log.info("OS: " + System.getProperty("os.name") + ", Arch: " + System.getProperty("os.arch")); + + if ((os.contains("Windows") || os.contains("Linux")) + && !System.getProperty("os.arch").equalsIgnoreCase("arm") + && !System.getProperty("os.arch").equalsIgnoreCase("arm-linux") + ) { + nasSupported = true; + log.info("JDA-NAS supported system detected. Enabled native audio sending."); + + Integer customBuffer = serverConfig.getBufferDurationMs(); + if (customBuffer != null) { + log.info("Setting buffer to {}ms", customBuffer); + } else { + log.info("Using default buffer"); + } + + Integer customPlaylistLimit = serverConfig.getYoutubePlaylistLoadLimit(); + if (customPlaylistLimit != null) { + log.info("Setting playlist load limit to {}", customPlaylistLimit); + } else { + log.info("Using default playlist load limit"); + } + } else { + log.warn("This system and architecture appears to not support native audio sending! " + + "GC pauses may cause your bot to stutter during playback."); + } + } + + public boolean isNasSupported() { + return nasSupported; + } + + public int getAudioSendFactoryCount() { + return audioSendFactoryCount; + } +} diff --git a/LavalinkServer/src/main/java/lavalink/server/config/AudioSourcesConfig.java b/LavalinkServer/src/main/java/lavalink/server/config/AudioSourcesConfig.java new file mode 100644 index 000000000..cb28c0761 --- /dev/null +++ b/LavalinkServer/src/main/java/lavalink/server/config/AudioSourcesConfig.java @@ -0,0 +1,85 @@ +package lavalink.server.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * Created by napster on 05.03.18. + */ +@ConfigurationProperties(prefix = "lavalink.server.sources") +@Component +public class AudioSourcesConfig { + + private boolean youtube = true; + private boolean bandcamp = true; + private boolean soundcloud = true; + private boolean twitch = true; + private boolean vimeo = true; + private boolean mixer = true; + private boolean http = true; + private boolean local = false; + + public boolean isYoutube() { + return youtube; + } + + public void setYoutube(boolean youtube) { + this.youtube = youtube; + } + + public boolean isBandcamp() { + return bandcamp; + } + + public void setBandcamp(boolean bandcamp) { + this.bandcamp = bandcamp; + } + + public boolean isSoundcloud() { + return soundcloud; + } + + public void setSoundcloud(boolean soundcloud) { + this.soundcloud = soundcloud; + } + + public boolean isTwitch() { + return twitch; + } + + public void setTwitch(boolean twitch) { + this.twitch = twitch; + } + + public boolean isVimeo() { + return vimeo; + } + + public void setVimeo(boolean vimeo) { + this.vimeo = vimeo; + } + + public boolean isMixer() { + return mixer; + } + + public void setMixer(boolean mixer) { + this.mixer = mixer; + } + + public boolean isHttp() { + return http; + } + + public void setHttp(boolean http) { + this.http = http; + } + + public boolean isLocal() { + return local; + } + + public void setLocal(boolean local) { + this.local = local; + } +} diff --git a/LavalinkServer/src/main/java/lavalink/server/Config.java b/LavalinkServer/src/main/java/lavalink/server/config/ServerConfig.java similarity index 52% rename from LavalinkServer/src/main/java/lavalink/server/Config.java rename to LavalinkServer/src/main/java/lavalink/server/config/ServerConfig.java index 4a1b8ea11..d3ec08e5c 100644 --- a/LavalinkServer/src/main/java/lavalink/server/Config.java +++ b/LavalinkServer/src/main/java/lavalink/server/config/ServerConfig.java @@ -20,7 +20,7 @@ * SOFTWARE. */ -package lavalink.server; +package lavalink.server.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @@ -29,13 +29,7 @@ @ConfigurationProperties(prefix = "lavalink.server") @Component -public class Config { - - private final Sources sources = new Sources(); - - public Sources getSources() { - return sources; - } +public class ServerConfig { private String password; @@ -47,19 +41,16 @@ public void setPassword(String password) { this.password = password; } - @Nullable - private String sentryDsn; + private String sentryDsn = ""; - @Nullable public String getSentryDsn() { return sentryDsn; } - public void setSentryDsn(@Nullable String sentryDsn) { + public void setSentryDsn(String sentryDsn) { this.sentryDsn = sentryDsn; } - @SuppressWarnings("WeakerAccess") @Nullable public Integer bufferDurationMs; @@ -83,80 +74,4 @@ public Integer getYoutubePlaylistLoadLimit() { public void setYoutubePlaylistLoadLimit(@Nullable Integer youtubePlaylistLoadLimit) { this.youtubePlaylistLoadLimit = youtubePlaylistLoadLimit; } - - public static class Sources { - - private boolean youtube = true; - private boolean bandcamp = true; - private boolean soundcloud = true; - private boolean twitch = true; - private boolean vimeo = true; - private boolean mixer = true; - private boolean http = true; - private boolean local = false; - - public boolean isYoutube() { - return youtube; - } - - public void setYoutube(boolean youtube) { - this.youtube = youtube; - } - - public boolean isBandcamp() { - return bandcamp; - } - - public void setBandcamp(boolean bandcamp) { - this.bandcamp = bandcamp; - } - - public boolean isSoundcloud() { - return soundcloud; - } - - public void setSoundcloud(boolean soundcloud) { - this.soundcloud = soundcloud; - } - - public boolean isTwitch() { - return twitch; - } - - public void setTwitch(boolean twitch) { - this.twitch = twitch; - } - - public boolean isVimeo() { - return vimeo; - } - - public void setVimeo(boolean vimeo) { - this.vimeo = vimeo; - } - - public boolean isMixer() { - return mixer; - } - - public void setMixer(boolean mixer) { - this.mixer = mixer; - } - - public boolean isHttp() { - return http; - } - - public void setHttp(boolean http) { - this.http = http; - } - - public boolean isLocal() { - return local; - } - - public void setLocal(boolean local) { - this.local = local; - } - } } diff --git a/LavalinkServer/src/main/java/lavalink/server/config/WebsocketConfig.java b/LavalinkServer/src/main/java/lavalink/server/config/WebsocketConfig.java new file mode 100644 index 000000000..838619006 --- /dev/null +++ b/LavalinkServer/src/main/java/lavalink/server/config/WebsocketConfig.java @@ -0,0 +1,31 @@ +package lavalink.server.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * Created by napster on 05.03.18. + */ +@ConfigurationProperties(prefix = "lavalink.server.ws") +@Component +public class WebsocketConfig { + + private int port = 80; + private String host = "0.0.0.0"; + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } +} diff --git a/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java b/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java index 17e0bd364..dea1078a0 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/SocketContext.java @@ -24,7 +24,9 @@ import com.github.shredder121.asyncaudio.jdaaudio.AsyncPacketProviderFactory; import com.sedmelluq.discord.lavaplayer.jdaudp.NativeAudioSendFactory; -import lavalink.server.Launcher; +import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; +import lavalink.server.config.AudioSendFactoryConfiguration; +import lavalink.server.config.ServerConfig; import lavalink.server.player.Player; import lavalink.server.util.Util; import net.dv8tion.jda.Core; @@ -48,24 +50,30 @@ public class SocketContext { private static final Logger log = LoggerFactory.getLogger(SocketContext.class); - public static boolean nasSupported = false; + private final AudioPlayerManager audioPlayerManager; + private final ServerConfig serverConfig; private final WebSocket socket; + private final AudioSendFactoryConfiguration audioSendFactoryConfiguration; private String userId; private int shardCount; private final Map cores = new HashMap<>(); private final Map players = new ConcurrentHashMap<>(); private ScheduledExecutorService statsExecutor; public final ScheduledExecutorService playerUpdateService; - private static final int audioSendFactoryCount = Runtime.getRuntime().availableProcessors() * 2; private final ConcurrentHashMap sendFactories = new ConcurrentHashMap<>(); - SocketContext(WebSocket socket, String userId, int shardCount) { + SocketContext(AudioPlayerManager audioPlayerManager, ServerConfig serverConfig, WebSocket socket, + AudioSendFactoryConfiguration audioSendFactoryConfiguration, SocketServer socketServer, + String userId, int shardCount) { + this.audioPlayerManager = audioPlayerManager; + this.serverConfig = serverConfig; this.socket = socket; + this.audioSendFactoryConfiguration = audioSendFactoryConfiguration; this.userId = userId; this.shardCount = shardCount; statsExecutor = Executors.newSingleThreadScheduledExecutor(); - statsExecutor.scheduleAtFixedRate(new StatsTask(this), 0, 1, TimeUnit.MINUTES); + statsExecutor.scheduleAtFixedRate(new StatsTask(this, socketServer), 0, 1, TimeUnit.MINUTES); playerUpdateService = Executors.newScheduledThreadPool(2, r -> { Thread thread = new Thread(r); @@ -78,7 +86,7 @@ public class SocketContext { Core getCore(int shardId) { return cores.computeIfAbsent(shardId, __ -> { - if (nasSupported) + if (audioSendFactoryConfiguration.isNasSupported()) return new Core(userId, new CoreClientImpl(), core -> new ConnectionManagerImpl(), getAudioSendFactory(shardId)); else return new Core(userId, new CoreClientImpl(), (ConnectionManagerBuilder) core -> new ConnectionManagerImpl()); @@ -88,7 +96,7 @@ Core getCore(int shardId) { Player getPlayer(String guildId) { return players.computeIfAbsent(guildId, - __ -> new Player(this, guildId) + __ -> new Player(this, guildId, audioPlayerManager) ); } @@ -130,17 +138,18 @@ void shutdown() { } private IAudioSendFactory getAudioSendFactory(int shardId) { - return sendFactories.computeIfAbsent(shardId % audioSendFactoryCount, integer -> { - Integer customBuffer = Launcher.config.getBufferDurationMs(); - NativeAudioSendFactory nativeAudioSendFactory; - if (customBuffer != null) { - nativeAudioSendFactory = new NativeAudioSendFactory(customBuffer); - } else { - nativeAudioSendFactory = new NativeAudioSendFactory(); - } - - return AsyncPacketProviderFactory.adapt(nativeAudioSendFactory); - }); + return sendFactories.computeIfAbsent(shardId % audioSendFactoryConfiguration.getAudioSendFactoryCount(), + integer -> { + Integer customBuffer = serverConfig.getBufferDurationMs(); + NativeAudioSendFactory nativeAudioSendFactory; + if (customBuffer != null) { + nativeAudioSendFactory = new NativeAudioSendFactory(customBuffer); + } else { + nativeAudioSendFactory = new NativeAudioSendFactory(); + } + + return AsyncPacketProviderFactory.adapt(nativeAudioSendFactory); + }); } } diff --git a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java index 28d32333c..ecd76f47b 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java @@ -22,20 +22,25 @@ package lavalink.server.io; +import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import com.sedmelluq.discord.lavaplayer.track.TrackMarker; +import lavalink.server.config.AudioSendFactoryConfiguration; +import lavalink.server.config.ServerConfig; +import lavalink.server.config.WebsocketConfig; import lavalink.server.player.Player; import lavalink.server.player.TrackEndMarkerHandler; import lavalink.server.util.Util; import net.dv8tion.jda.Core; -import net.dv8tion.jda.manager.AudioManager; import org.java_websocket.WebSocket; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.server.WebSocketServer; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import javax.annotation.PostConstruct; import java.io.IOException; import java.net.InetSocketAddress; import java.util.Collection; @@ -45,15 +50,28 @@ import static lavalink.server.io.WSCodes.AUTHORIZATION_REJECTED; import static lavalink.server.io.WSCodes.INTERNAL_ERROR; +@Component public class SocketServer extends WebSocketServer { private static final Logger log = LoggerFactory.getLogger(SocketServer.class); - private static final Map contextMap = new HashMap<>(); - private final String password; - public SocketServer(InetSocketAddress address, String password) { - super(address); - this.password = password; + private final Map contextMap = new HashMap<>(); + private final ServerConfig serverConfig; + private final AudioPlayerManager audioPlayerManager; + private final AudioSendFactoryConfiguration audioSendFactoryConfiguration; + + public SocketServer(WebsocketConfig websocketConfig, ServerConfig serverConfig, AudioPlayerManager audioPlayerManager, + AudioSendFactoryConfiguration audioSendFactoryConfiguration) { + super(new InetSocketAddress(websocketConfig.getHost(), websocketConfig.getPort())); + this.serverConfig = serverConfig; + this.audioPlayerManager = audioPlayerManager; + this.audioSendFactoryConfiguration = audioSendFactoryConfiguration; + } + + @Override + @PostConstruct + public void start() { + super.start(); } @Override @@ -62,9 +80,10 @@ public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) { int shardCount = Integer.parseInt(clientHandshake.getFieldValue("Num-Shards")); String userId = clientHandshake.getFieldValue("User-Id"); - if (clientHandshake.getFieldValue("Authorization").equals(password)) { + if (clientHandshake.getFieldValue("Authorization").equals(serverConfig.getPassword())) { log.info("Connection opened from " + webSocket.getRemoteSocketAddress() + " with protocol " + webSocket.getDraft()); - contextMap.put(webSocket, new SocketContext(webSocket, userId, shardCount)); + contextMap.put(webSocket, new SocketContext(audioPlayerManager, serverConfig, webSocket, + audioSendFactoryConfiguration, this, userId, shardCount)); } else { log.error("Authentication failed from " + webSocket.getRemoteSocketAddress() + " with protocol " + webSocket.getDraft()); webSocket.close(AUTHORIZATION_REJECTED, "Authorization rejected"); @@ -126,7 +145,7 @@ public void onMessage(WebSocket webSocket, String s) { case "play": try { Player player = contextMap.get(webSocket).getPlayer(json.getString("guildId")); - AudioTrack track = Util.toAudioTrack(json.getString("track")); + AudioTrack track = Util.toAudioTrack(audioPlayerManager, json.getString("track")); if (json.has("startTime")) { track.setPosition(json.getLong("startTime")); } @@ -203,7 +222,7 @@ private int getShardId(WebSocket webSocket, JSONObject json) { return Util.getShardFromSnowflake(json.getString("guildId"), contextMap.get(webSocket).getShardCount()); } - static Collection getConnections() { + Collection getConnections() { return contextMap.values(); } diff --git a/LavalinkServer/src/main/java/lavalink/server/io/StatsTask.java b/LavalinkServer/src/main/java/lavalink/server/io/StatsTask.java index 941a4a1c7..c065676c0 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/StatsTask.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/StatsTask.java @@ -38,11 +38,13 @@ public class StatsTask implements Runnable { private static final Logger log = LoggerFactory.getLogger(StatsTask.class); private SocketContext context; + private final SocketServer socketServer; private final SystemInfo si = new SystemInfo(); - StatsTask(SocketContext context) { + StatsTask(SocketContext context, SocketServer socketServer) { this.context = context; + this.socketServer = socketServer; } @Override @@ -60,7 +62,7 @@ public void sendStats() { final int[] playersTotal = {0}; final int[] playersPlaying = {0}; - SocketServer.getConnections().forEach(socketContext -> { + socketServer.getConnections().forEach(socketContext -> { playersTotal[0] += socketContext.getPlayers().size(); playersPlaying[0] += socketContext.getPlayingPlayers().size(); }); diff --git a/LavalinkServer/src/main/java/lavalink/server/player/AudioLoader.java b/LavalinkServer/src/main/java/lavalink/server/player/AudioLoader.java index be4c5481d..272252a3b 100644 --- a/LavalinkServer/src/main/java/lavalink/server/player/AudioLoader.java +++ b/LavalinkServer/src/main/java/lavalink/server/player/AudioLoader.java @@ -23,6 +23,7 @@ package lavalink.server.player; import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; +import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; @@ -35,17 +36,22 @@ public class AudioLoader implements AudioLoadResultHandler { private static final Logger log = LoggerFactory.getLogger(AudioLoader.class); + private final AudioPlayerManager audioPlayerManager; private List loadedItems; private boolean used = false; + public AudioLoader(AudioPlayerManager audioPlayerManager) { + this.audioPlayerManager = audioPlayerManager; + } + List loadSync(String identifier) throws InterruptedException { if(used) throw new IllegalStateException("This loader can only be used once per instance"); used = true; - Player.PLAYER_MANAGER.loadItem(identifier, this); + audioPlayerManager.loadItem(identifier, this); synchronized (this) { this.wait(); diff --git a/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.java b/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.java index 838fb3bb4..040df6188 100644 --- a/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.java +++ b/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.java @@ -22,9 +22,10 @@ package lavalink.server.player; +import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo; -import lavalink.server.Launcher; +import lavalink.server.config.ServerConfig; import lavalink.server.util.Util; import org.json.JSONArray; import org.json.JSONObject; @@ -46,6 +47,13 @@ public class AudioLoaderRestHandler { private static final Logger log = LoggerFactory.getLogger(AudioLoaderRestHandler.class); + private final AudioPlayerManager audioPlayerManager; + private final ServerConfig serverConfig; + + public AudioLoaderRestHandler(AudioPlayerManager audioPlayerManager, ServerConfig serverConfig) { + this.audioPlayerManager = audioPlayerManager; + this.serverConfig = serverConfig; + } private void log(HttpServletRequest request) { String path = request.getServletPath(); @@ -58,7 +66,7 @@ private boolean isAuthorized(HttpServletRequest request, HttpServletResponse res return false; } - if (!request.getHeader("Authorization").equals(Launcher.config.getPassword())) { + if (!request.getHeader("Authorization").equals(serverConfig.getPassword())) { log.warn("Authorization failed"); response.setStatus(403); return false; @@ -90,14 +98,14 @@ public String getLoadTracks(HttpServletRequest request, HttpServletResponse resp return ""; JSONArray tracks = new JSONArray(); - List list = new AudioLoader().loadSync(identifier); + List list = new AudioLoader(audioPlayerManager).loadSync(identifier); list.forEach(track -> { JSONObject object = new JSONObject(); object.put("info", trackToJSON(track)); try { - String encoded = Util.toMessage(track); + String encoded = Util.toMessage(audioPlayerManager, track); object.put("track", encoded); tracks.put(object); } catch (IOException e) { @@ -116,7 +124,7 @@ public String getDecodeTrack(HttpServletRequest request, HttpServletResponse res if (!isAuthorized(request, response)) return ""; - AudioTrack audioTrack = Util.toAudioTrack(track); + AudioTrack audioTrack = Util.toAudioTrack(audioPlayerManager, track); return trackToJSON(audioTrack).toString(); } @@ -134,7 +142,7 @@ public String postDecodeTracks(HttpServletRequest request, HttpServletResponse r for (int i = 0; i < requestJSON.length(); i++) { String track = requestJSON.getString(i); - AudioTrack audioTrack = Util.toAudioTrack(track); + AudioTrack audioTrack = Util.toAudioTrack(audioPlayerManager, track); JSONObject infoJSON = trackToJSON(audioTrack); JSONObject trackJSON = new JSONObject() diff --git a/LavalinkServer/src/main/java/lavalink/server/player/EventEmitter.java b/LavalinkServer/src/main/java/lavalink/server/player/EventEmitter.java index 3d3cc0b69..336bf59c0 100644 --- a/LavalinkServer/src/main/java/lavalink/server/player/EventEmitter.java +++ b/LavalinkServer/src/main/java/lavalink/server/player/EventEmitter.java @@ -23,6 +23,7 @@ package lavalink.server.player; import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; +import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter; import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; @@ -38,9 +39,11 @@ public class EventEmitter extends AudioEventAdapter { private static final Logger log = LoggerFactory.getLogger(EventEmitter.class); + private final AudioPlayerManager audioPlayerManager; private final Player linkPlayer; - EventEmitter(Player linkPlayer) { + EventEmitter(AudioPlayerManager audioPlayerManager, Player linkPlayer) { + this.audioPlayerManager = audioPlayerManager; this.linkPlayer = linkPlayer; } @@ -51,7 +54,7 @@ public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason out.put("type", "TrackEndEvent"); out.put("guildId", linkPlayer.getGuildId()); try { - out.put("track", Util.toMessage(track)); + out.put("track", Util.toMessage(audioPlayerManager, track)); } catch (IOException e) { out.put("track", JSONObject.NULL); } @@ -69,7 +72,7 @@ public void onTrackException(AudioPlayer player, AudioTrack track, FriendlyExcep out.put("type", "TrackExceptionEvent"); out.put("guildId", linkPlayer.getGuildId()); try { - out.put("track", Util.toMessage(track)); + out.put("track", Util.toMessage(audioPlayerManager, track)); } catch (IOException e) { out.put("track", JSONObject.NULL); } @@ -88,7 +91,7 @@ public void onTrackStuck(AudioPlayer player, AudioTrack track, long thresholdMs) out.put("type", "TrackStuckEvent"); out.put("guildId", linkPlayer.getGuildId()); try { - out.put("track", Util.toMessage(track)); + out.put("track", Util.toMessage(audioPlayerManager, track)); } catch (IOException e) { out.put("track", JSONObject.NULL); } diff --git a/LavalinkServer/src/main/java/lavalink/server/player/Player.java b/LavalinkServer/src/main/java/lavalink/server/player/Player.java index c5b92a27b..53f14a3f4 100644 --- a/LavalinkServer/src/main/java/lavalink/server/player/Player.java +++ b/LavalinkServer/src/main/java/lavalink/server/player/Player.java @@ -24,21 +24,10 @@ 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.bandcamp.BandcampAudioSourceManager; -import com.sedmelluq.discord.lavaplayer.source.beam.BeamAudioSourceManager; -import com.sedmelluq.discord.lavaplayer.source.http.HttpAudioSourceManager; -import com.sedmelluq.discord.lavaplayer.source.local.LocalAudioSourceManager; -import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioSourceManager; -import com.sedmelluq.discord.lavaplayer.source.twitch.TwitchStreamAudioSourceManager; -import com.sedmelluq.discord.lavaplayer.source.vimeo.VimeoAudioSourceManager; -import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioSourceManager; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason; import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame; -import lavalink.server.Config; -import lavalink.server.Launcher; import lavalink.server.io.SocketContext; import lavalink.server.io.SocketServer; import net.dv8tion.jda.audio.AudioSendHandler; @@ -53,29 +42,6 @@ public class Player extends AudioEventAdapter implements AudioSendHandler { private static final Logger log = LoggerFactory.getLogger(Player.class); - public static final AudioPlayerManager PLAYER_MANAGER; - - static { - PLAYER_MANAGER = new DefaultAudioPlayerManager(); - PLAYER_MANAGER.enableGcMonitoring(); - - Config.Sources sources = Launcher.config.getSources(); - if (sources.isYoutube()) { - YoutubeAudioSourceManager youtube = new YoutubeAudioSourceManager(); - Integer playlistLoadLimit = Launcher.config.getYoutubePlaylistLoadLimit(); - - if (playlistLoadLimit != null) youtube.setPlaylistPageCount(playlistLoadLimit); - PLAYER_MANAGER.registerSourceManager(youtube); - } - if (sources.isBandcamp()) PLAYER_MANAGER.registerSourceManager(new BandcampAudioSourceManager()); - if (sources.isSoundcloud()) PLAYER_MANAGER.registerSourceManager(new SoundCloudAudioSourceManager()); - if (sources.isTwitch()) PLAYER_MANAGER.registerSourceManager(new TwitchStreamAudioSourceManager()); - if (sources.isVimeo()) PLAYER_MANAGER.registerSourceManager(new VimeoAudioSourceManager()); - if (sources.isMixer()) PLAYER_MANAGER.registerSourceManager(new BeamAudioSourceManager()); - if (sources.isHttp()) PLAYER_MANAGER.registerSourceManager(new HttpAudioSourceManager()); - if (sources.isLocal()) PLAYER_MANAGER.registerSourceManager(new LocalAudioSourceManager()); - } - private SocketContext socketContext; private final String guildId; private final AudioPlayer player; @@ -83,12 +49,12 @@ public class Player extends AudioEventAdapter implements AudioSendHandler { private AudioFrame lastFrame = null; private ScheduledFuture myFuture = null; - public Player(SocketContext socketContext, String guildId) { + public Player(SocketContext socketContext, String guildId, AudioPlayerManager audioPlayerManager) { this.socketContext = socketContext; this.guildId = guildId; - this.player = PLAYER_MANAGER.createPlayer(); + this.player = audioPlayerManager.createPlayer(); this.player.addListener(this); - this.player.addListener(new EventEmitter(this)); + this.player.addListener(new EventEmitter(audioPlayerManager, this)); this.player.addListener(audioLossCounter); } diff --git a/LavalinkServer/src/main/java/lavalink/server/util/Util.java b/LavalinkServer/src/main/java/lavalink/server/util/Util.java index 836673027..67ff7eb63 100644 --- a/LavalinkServer/src/main/java/lavalink/server/util/Util.java +++ b/LavalinkServer/src/main/java/lavalink/server/util/Util.java @@ -22,10 +22,10 @@ package lavalink.server.util; +import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; import com.sedmelluq.discord.lavaplayer.tools.io.MessageInput; import com.sedmelluq.discord.lavaplayer.tools.io.MessageOutput; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; -import lavalink.server.player.Player; import org.apache.commons.codec.binary.Base64; import java.io.ByteArrayInputStream; @@ -38,15 +38,15 @@ public static int getShardFromSnowflake(String snowflake, int numShards) { return (int) ((Long.parseLong(snowflake) >> 22) % numShards); } - public static AudioTrack toAudioTrack(String message) throws IOException { + public static AudioTrack toAudioTrack(AudioPlayerManager audioPlayerManager, String message) throws IOException { byte[] b64 = Base64.decodeBase64(message); ByteArrayInputStream bais = new ByteArrayInputStream(b64); - return Player.PLAYER_MANAGER.decodeTrack(new MessageInput(bais)).decodedTrack; + return audioPlayerManager.decodeTrack(new MessageInput(bais)).decodedTrack; } - public static String toMessage(AudioTrack track) throws IOException { + public static String toMessage(AudioPlayerManager audioPlayerManager, AudioTrack track) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - Player.PLAYER_MANAGER.encodeTrack(new MessageOutput(baos), track); + audioPlayerManager.encodeTrack(new MessageOutput(baos), track); return Base64.encodeBase64String(baos.toByteArray()); } From 3c54c0566e46ee9e9ccf3a6324ebcd3a8b15c02b Mon Sep 17 00:00:00 2001 From: itslukej Date: Tue, 6 Mar 2018 14:41:45 +0000 Subject: [PATCH 18/26] Add LavalinkServer Dockerfile (#74) * Add LavalinkServer dockerfile * Raise Xmx value * Remove ENV Vars --- LavalinkServer/docker/Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 LavalinkServer/docker/Dockerfile diff --git a/LavalinkServer/docker/Dockerfile b/LavalinkServer/docker/Dockerfile new file mode 100644 index 000000000..9b7371137 --- /dev/null +++ b/LavalinkServer/docker/Dockerfile @@ -0,0 +1,7 @@ +FROM openjdk:9-jre-slim + +WORKDIR /opt/Lavalink + +COPY LavalinkServer/build/libs/Lavalink.jar Lavalink.jar + +ENTRYPOINT ["java", "-jar", "-Xmx4G", "Lavalink.jar"] From 5166a457653cf2795aa50b207710ee7a486532bb Mon Sep 17 00:00:00 2001 From: Napster Date: Mon, 12 Mar 2018 10:05:03 +0100 Subject: [PATCH 19/26] Fix missing NAS classes --- LavalinkServer/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index 5d2aff9fc..5df7892a3 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -11,6 +11,8 @@ ext { bootJar { archiveName = "Lavalink.jar" + + requiresUnpack '**/jda-nas*.jar' //otherwise we get missing classes exceptions } bootRun { From 0d5a24edc9560e1da6533a7665ffe0eaac57e7b3 Mon Sep 17 00:00:00 2001 From: Napster Date: Mon, 12 Mar 2018 12:29:54 +0100 Subject: [PATCH 20/26] Adjust for CI docker build --- LavalinkServer/.gitignore | 1 + LavalinkServer/build.gradle | 7 +++++++ LavalinkServer/docker/.dockerignore | 4 ++++ LavalinkServer/docker/Dockerfile | 4 ++-- 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 LavalinkServer/docker/.dockerignore diff --git a/LavalinkServer/.gitignore b/LavalinkServer/.gitignore index ffb8ea2ec..3d29ec6be 100644 --- a/LavalinkServer/.gitignore +++ b/LavalinkServer/.gitignore @@ -6,3 +6,4 @@ /debug_token build/* /out/ +VERSION.txt diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index 5df7892a3..3ed24d4d2 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -41,3 +41,10 @@ dependencies { compile group: 'com.google.guava', name: 'guava', version: guavaVersion compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: springBootVersion } + +//create a simple version file that we will be reading to create appropriate docker tags +void versionTxt() { + new File("$projectDir/VERSION.txt").text = "$rootProject.version\n" +} + +versionTxt() diff --git a/LavalinkServer/docker/.dockerignore b/LavalinkServer/docker/.dockerignore new file mode 100644 index 000000000..96800f5ce --- /dev/null +++ b/LavalinkServer/docker/.dockerignore @@ -0,0 +1,4 @@ +** +#ignore everything except the following files, they are used in the Dockerfile to build the fredboat image +!Dockerfile +!Lavalink.jar diff --git a/LavalinkServer/docker/Dockerfile b/LavalinkServer/docker/Dockerfile index 9b7371137..04e02e9bb 100644 --- a/LavalinkServer/docker/Dockerfile +++ b/LavalinkServer/docker/Dockerfile @@ -2,6 +2,6 @@ FROM openjdk:9-jre-slim WORKDIR /opt/Lavalink -COPY LavalinkServer/build/libs/Lavalink.jar Lavalink.jar +COPY Lavalink.jar Lavalink.jar -ENTRYPOINT ["java", "-jar", "-Xmx4G", "Lavalink.jar"] +ENTRYPOINT ["java", "-Xmx4G", "-jar", "Lavalink.jar"] From d648861a4b17334808e7aa7a3a6aaae9c7daa8f5 Mon Sep 17 00:00:00 2001 From: Napster Date: Mon, 12 Mar 2018 12:33:26 +0100 Subject: [PATCH 21/26] Use server module version instead of root project version --- LavalinkServer/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index 3ed24d4d2..788803666 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -44,7 +44,7 @@ dependencies { //create a simple version file that we will be reading to create appropriate docker tags void versionTxt() { - new File("$projectDir/VERSION.txt").text = "$rootProject.version\n" + new File("$projectDir/VERSION.txt").text = "$project.version\n" } versionTxt() From cd200267d90d83512148b9db88617de68fcec158 Mon Sep 17 00:00:00 2001 From: Napster Date: Sat, 24 Mar 2018 11:16:04 +0100 Subject: [PATCH 22/26] Fix AudioPlayer leaking (#89) --- .../src/main/java/lavalink/server/io/SocketServer.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java index ecd76f47b..066950baa 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java +++ b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.java @@ -32,6 +32,7 @@ import lavalink.server.player.TrackEndMarkerHandler; import lavalink.server.util.Util; import net.dv8tion.jda.Core; +import net.dv8tion.jda.manager.AudioManager; import org.java_websocket.WebSocket; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.server.WebSocketServer; @@ -187,10 +188,11 @@ public void onMessage(WebSocket webSocket, String s) { case "destroy": Player player5 = contextMap.get(webSocket).getPlayers().remove(json.getString("guildId")); if (player5 != null) player5.stop(); - contextMap.get(webSocket) + AudioManager audioManager = contextMap.get(webSocket) .getCore(getShardId(webSocket, json)) - .getAudioManager(json.getString("guildId")) - .closeAudioConnection(); + .getAudioManager(json.getString("guildId")); + audioManager.setSendingHandler(null); + audioManager.closeAudioConnection(); break; default: log.warn("Unexpected operation: " + json.getString("op")); From f19d1af864edf783b308e4f4a6982ec37fac7b2e Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Thu, 29 Mar 2018 23:21:29 +0200 Subject: [PATCH 23/26] Update lavaplayer to 1.2.58 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1780e1858..a7de18987 100644 --- a/build.gradle +++ b/build.gradle @@ -42,7 +42,7 @@ subprojects { ext { //@formatter:off - lavaplayerVersion = '1.2.56' + lavaplayerVersion = '1.2.58' jdaAudioVersion = '91438c36d7107cf838c2f2eb147b08f989d929db' jdaNasVersion = '1.0.6.1-JDA-Audio' jappVersion = '1.2' From eb36c7df6e8a43c92c2d5ca6a4b72d75aa18efaa Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Mon, 2 Apr 2018 21:47:22 +0200 Subject: [PATCH 24/26] Fix LLC creating new players when we aren't even associated with a node --- .../client/player/LavalinkPlayer.java | 59 ++++++++++++------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/player/LavalinkPlayer.java b/LavalinkClient/src/main/java/lavalink/client/player/LavalinkPlayer.java index d0c544320..8e495f17f 100644 --- a/LavalinkClient/src/main/java/lavalink/client/player/LavalinkPlayer.java +++ b/LavalinkClient/src/main/java/lavalink/client/player/LavalinkPlayer.java @@ -24,6 +24,7 @@ import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import lavalink.client.LavalinkUtil; +import lavalink.client.io.LavalinkSocket; import lavalink.client.io.Link; import lavalink.client.player.event.IPlayerEventListener; import lavalink.client.player.event.PlayerEvent; @@ -81,18 +82,22 @@ public void playTrack(AudioTrack track) { try { position = track.getPosition(); TrackData trackData = track.getUserData(TrackData.class); - - JSONObject json = new JSONObject(); - json.put("op", "play"); - json.put("guildId", link.getGuildId()); - json.put("track", LavalinkUtil.toMessage(track)); - json.put("startTime", position); - if (trackData != null) { - json.put("startTime", trackData.startPos); - json.put("endTime", trackData.endPos); + LavalinkSocket node = link.getNode(false); + + if (node != null) { + JSONObject json = new JSONObject(); + json.put("op", "play"); + json.put("guildId", link.getGuildId()); + json.put("track", LavalinkUtil.toMessage(track)); + json.put("startTime", position); + if (trackData != null) { + json.put("startTime", trackData.startPos); + json.put("endTime", trackData.endPos); + } + json.put("pause", paused); + node.send(json.toString()); } - json.put("pause", paused); - link.getNode(true).send(json.toString()); + updateTime = System.currentTimeMillis(); this.track = track; emitEvent(new TrackStartEvent(this, track)); @@ -103,22 +108,27 @@ public void playTrack(AudioTrack track) { @Override public void stopTrack() { + track = null; + + LavalinkSocket node = link.getNode(false); + if (node == null) return; JSONObject json = new JSONObject(); json.put("op", "stop"); json.put("guildId", link.getGuildId()); - link.getNode(true).send(json.toString()); - track = null; + node.send(json.toString()); } @Override public void setPaused(boolean pause) { if (pause == paused) return; - - JSONObject json = new JSONObject(); - json.put("op", "pause"); - json.put("guildId", link.getGuildId()); - json.put("pause", pause); - link.getNode(true).send(json.toString()); + LavalinkSocket node = link.getNode(false); + if (node != null) { + JSONObject json = new JSONObject(); + json.put("op", "pause"); + json.put("guildId", link.getGuildId()); + json.put("pause", pause); + node.send(json.toString()); + } paused = pause; if (pause) { @@ -151,24 +161,29 @@ public long getTrackPosition() { public void seekTo(long position) { if (getPlayingTrack() == null) throw new IllegalStateException("Not currently playing anything"); if (!getPlayingTrack().isSeekable()) throw new IllegalStateException("Track cannot be seeked"); + LavalinkSocket node = link.getNode(false); + if (node == null) return; JSONObject json = new JSONObject(); json.put("op", "seek"); json.put("guildId", link.getGuildId()); json.put("position", position); - link.getNode(true).send(json.toString()); + node.send(json.toString()); } @Override public void setVolume(int volume) { volume = Math.min(150, Math.max(0, volume)); // Lavaplayer bounds + this.volume = volume; + + LavalinkSocket node = link.getNode(false); + if (node == null) return; JSONObject json = new JSONObject(); json.put("op", "volume"); json.put("guildId", link.getGuildId()); json.put("volume", volume); - link.getNode(true).send(json.toString()); - this.volume = volume; + node.send(json.toString()); } @Override From 7f656c3ba664c26641bba61a2a40b22c67b4dff2 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Mon, 2 Apr 2018 22:12:22 +0200 Subject: [PATCH 25/26] Amend the last commit to not break things --- .../client/player/LavalinkPlayer.java | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/LavalinkClient/src/main/java/lavalink/client/player/LavalinkPlayer.java b/LavalinkClient/src/main/java/lavalink/client/player/LavalinkPlayer.java index 8e495f17f..c8db99c69 100644 --- a/LavalinkClient/src/main/java/lavalink/client/player/LavalinkPlayer.java +++ b/LavalinkClient/src/main/java/lavalink/client/player/LavalinkPlayer.java @@ -82,21 +82,19 @@ public void playTrack(AudioTrack track) { try { position = track.getPosition(); TrackData trackData = track.getUserData(TrackData.class); - LavalinkSocket node = link.getNode(false); - - if (node != null) { - JSONObject json = new JSONObject(); - json.put("op", "play"); - json.put("guildId", link.getGuildId()); - json.put("track", LavalinkUtil.toMessage(track)); - json.put("startTime", position); - if (trackData != null) { - json.put("startTime", trackData.startPos); - json.put("endTime", trackData.endPos); - } - json.put("pause", paused); - node.send(json.toString()); + + JSONObject json = new JSONObject(); + json.put("op", "play"); + json.put("guildId", link.getGuildId()); + json.put("track", LavalinkUtil.toMessage(track)); + json.put("startTime", position); + if (trackData != null) { + json.put("startTime", trackData.startPos); + json.put("endTime", trackData.endPos); } + json.put("pause", paused); + //noinspection ConstantConditions + link.getNode(true).send(json.toString()); updateTime = System.currentTimeMillis(); this.track = track; @@ -161,14 +159,13 @@ public long getTrackPosition() { public void seekTo(long position) { if (getPlayingTrack() == null) throw new IllegalStateException("Not currently playing anything"); if (!getPlayingTrack().isSeekable()) throw new IllegalStateException("Track cannot be seeked"); - LavalinkSocket node = link.getNode(false); - if (node == null) return; JSONObject json = new JSONObject(); json.put("op", "seek"); json.put("guildId", link.getGuildId()); json.put("position", position); - node.send(json.toString()); + //noinspection ConstantConditions + link.getNode(true).send(json.toString()); } @Override From 13629459784c1b1800f0ec6805e339bec563f243 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Tue, 3 Apr 2018 19:02:13 +0200 Subject: [PATCH 26/26] Release version 2.0.1 --- LavalinkClient/build.gradle | 2 +- LavalinkServer/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LavalinkClient/build.gradle b/LavalinkClient/build.gradle index d25d5ce35..7ef43e9ae 100644 --- a/LavalinkClient/build.gradle +++ b/LavalinkClient/build.gradle @@ -1,5 +1,5 @@ description = 'JDA based client for the Lavalink-Server' -version System.getenv('dev') == 'true' ? '-SNAPSHOT' : '2.0' +version System.getenv('dev') == 'true' ? '-SNAPSHOT' : '2.0.1' ext { moduleName = 'Lavalink-Client' } diff --git a/LavalinkServer/build.gradle b/LavalinkServer/build.gradle index 788803666..7563f847d 100644 --- a/LavalinkServer/build.gradle +++ b/LavalinkServer/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'com.gorylenko.gradle-git-properties' description = 'Play audio to discord voice channels' mainClassName = "lavalink.server.Launcher" -version '2.0' +version '2.0.1' ext { moduleName = 'Lavalink-Server' }