diff --git a/CHANGELOG.md b/CHANGELOG.md index 496decab8..3c7eab75f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ Each release usually includes various fixes and improvements. The most noteworthy of these, as well as any features and breaking changes, are listed here. +## v4.0.8 +* Updated koe to [`2.0.3-rc2`](https://github.com/KyokoBot/koe/releases/tag/2.0.3-rc2) & use voice gateway `v8` in https://github.com/lavalink-devs/Lavalink/pull/1097 +* Updated Lavaplayer to [`2.2.2`](https://github.com/lavalink-devs/lavaplayer/releases/tag/2.2.2) in https://github.com/lavalink-devs/Lavalink/pull/1105 +* Allow usage of non-allocating frame buffers in https://github.com/lavalink-devs/Lavalink/pull/1095 +* Added shutdown handling to close sessions cleanly in https://github.com/lavalink-devs/Lavalink/pull/1102 + ## v4.0.7 * Updated Lavaplayer to [`2.2.1`](https://github.com/lavalink-devs/lavaplayer/releases/tag/2.2.1) * Updated spring-boot to `3.3.0` & spring-websocket to `6.1.9` diff --git a/LavalinkServer/application.yml.example b/LavalinkServer/application.yml.example index 617b1bfde..3badfaf47 100644 --- a/LavalinkServer/application.yml.example +++ b/LavalinkServer/application.yml.example @@ -38,6 +38,7 @@ lavalink: rotation: true channelMix: true lowPass: true + nonAllocatingFrameBuffer: false # Setting to true reduces the number of allocations made by each player at the expense of frame rebuilding (e.g. non-instantaneous volume changes) bufferDurationMs: 400 # The duration of the NAS buffer. Higher values fare better against longer GC pauses. Duration <= 0 to disable JDA-NAS. Minimum of 40ms, lower values may introduce pauses. frameBufferDurationMs: 5000 # How many milliseconds of audio to keep buffered opusEncodingQuality: 10 # Opus encoder quality. Valid values range from 0 to 10, where 10 is best quality but is the most expensive on the CPU. diff --git a/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.kt b/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.kt index ca6fd99c1..96571e689 100644 --- a/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.kt +++ b/LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.kt @@ -14,6 +14,7 @@ import com.sedmelluq.discord.lavaplayer.source.soundcloud.* 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.playback.NonAllocatingAudioFrameBuffer import com.sedmelluq.lava.extensions.youtuberotator.YoutubeIpRotatorSetup import com.sedmelluq.lava.extensions.youtuberotator.planner.* import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv4Block @@ -55,6 +56,11 @@ class AudioPlayerConfiguration { audioPlayerManager.enableGcMonitoring() } + if (serverConfig.isNonAllocatingFrameBuffer) { + log.info("Using a non-allocating frame buffer") + audioPlayerManager.configuration.setFrameBufferFactory(::NonAllocatingAudioFrameBuffer) + } + val defaultFrameBufferDuration = audioPlayerManager.frameBufferDuration serverConfig.frameBufferDurationMs?.let { if (it < 200) { // At the time of writing, LP enforces a minimum of 200ms. diff --git a/LavalinkServer/src/main/java/lavalink/server/config/KoeConfiguration.kt b/LavalinkServer/src/main/java/lavalink/server/config/KoeConfiguration.kt index 41397f506..53a9a999b 100644 --- a/LavalinkServer/src/main/java/lavalink/server/config/KoeConfiguration.kt +++ b/LavalinkServer/src/main/java/lavalink/server/config/KoeConfiguration.kt @@ -5,6 +5,7 @@ import com.sedmelluq.lava.common.natives.architecture.DefaultOperatingSystemType import com.sedmelluq.lava.common.natives.architecture.SystemType import moe.kyokobot.koe.KoeOptions import moe.kyokobot.koe.codec.udpqueue.UdpQueueFramePollerFactory +import moe.kyokobot.koe.gateway.GatewayVersion import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.context.annotation.Bean @@ -32,6 +33,8 @@ class KoeConfiguration(val serverConfig: ServerConfig) { @Bean fun koeOptions(): KoeOptions = KoeOptions.builder().apply { + setGatewayVersion(GatewayVersion.V8) + val systemType: SystemType? = try { SystemType(DefaultArchitectureTypes.detect(), DefaultOperatingSystemTypes.detect()) } catch (e: IllegalArgumentException) { @@ -70,4 +73,4 @@ class KoeConfiguration(val serverConfig: ServerConfig) { ) } }.create() -} \ No newline at end of file +} diff --git a/LavalinkServer/src/main/java/lavalink/server/config/ServerConfig.kt b/LavalinkServer/src/main/java/lavalink/server/config/ServerConfig.kt index 4cd3a0b97..da31f3251 100644 --- a/LavalinkServer/src/main/java/lavalink/server/config/ServerConfig.kt +++ b/LavalinkServer/src/main/java/lavalink/server/config/ServerConfig.kt @@ -30,6 +30,7 @@ import org.springframework.stereotype.Component @Component class ServerConfig { var password: String? = null + var isNonAllocatingFrameBuffer = false var bufferDurationMs: Int? = null var frameBufferDurationMs: Int? = null var opusEncodingQuality: Int? = null diff --git a/LavalinkServer/src/main/java/lavalink/server/io/ShutdownHandler.kt b/LavalinkServer/src/main/java/lavalink/server/io/ShutdownHandler.kt new file mode 100644 index 000000000..a6ebaefb0 --- /dev/null +++ b/LavalinkServer/src/main/java/lavalink/server/io/ShutdownHandler.kt @@ -0,0 +1,16 @@ +package lavalink.server.io + +import org.springframework.web.socket.CloseStatus + +class ShutdownHandler(private val socketServer: SocketServer) : Thread("lavalink-shutdown-handler") { + init { + isDaemon = false // we want this thread to block shutdown until it has finished running + } + + override fun run() { + socketServer.contexts.forEach { + // don't care about exceptions here, the JVM's shutting down anyway. + it.runCatching { closeWebSocket(CloseStatus.GOING_AWAY.code) } + } + } +} diff --git a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.kt b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.kt index 6ffbdfc61..4f2a72743 100644 --- a/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.kt +++ b/LavalinkServer/src/main/java/lavalink/server/io/SocketServer.kt @@ -56,6 +56,10 @@ final class SocketServer( private val statsCollector = StatsCollector(this) private val charPool = ('a'..'z') + ('0'..'9') + init { + Runtime.getRuntime().addShutdownHook(ShutdownHandler(this)) + } + companion object { private val log = LoggerFactory.getLogger(SocketServer::class.java) diff --git a/LavalinkServer/src/main/java/lavalink/server/player/AudioLoader.kt b/LavalinkServer/src/main/java/lavalink/server/player/AudioLoader.kt deleted file mode 100644 index 5987d372a..000000000 --- a/LavalinkServer/src/main/java/lavalink/server/player/AudioLoader.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2021 Freya Arbjerg and contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -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 -import dev.arbjerg.lavalink.api.AudioPluginInfoModifier -import dev.arbjerg.lavalink.protocol.v4.LoadResult -import lavalink.server.util.loadFailed -import lavalink.server.util.toPlaylistInfo -import lavalink.server.util.toPluginInfo -import lavalink.server.util.toTrack -import org.slf4j.LoggerFactory -import java.util.concurrent.CompletableFuture -import java.util.concurrent.CompletionStage -import java.util.concurrent.atomic.AtomicBoolean - -class AudioLoader( - private val audioPlayerManager: AudioPlayerManager, - private val pluginInfoModifiers: List -) : AudioLoadResultHandler { - - companion object { - private val log = LoggerFactory.getLogger(AudioLoader::class.java) - } - - private val loadResult = CompletableFuture() - private val used = AtomicBoolean(false) - - fun load(identifier: String?): LoadResult { - val isUsed = used.getAndSet(true) - check(!isUsed) { "This loader can only be used once per instance" } - log.trace("Loading item with identifier $identifier") - audioPlayerManager.loadItemSync(identifier, this) - return loadResult.get() - } - - override fun trackLoaded(audioTrack: AudioTrack) { - log.info("Loaded track ${audioTrack.info.title}") - - val track = audioTrack.toTrack(audioPlayerManager, pluginInfoModifiers) - loadResult.complete(LoadResult.trackLoaded(track)) - } - - override fun playlistLoaded(audioPlaylist: AudioPlaylist) { - log.info("Loaded playlist ${audioPlaylist.name}") - - val tracks = audioPlaylist.tracks.map { it.toTrack(audioPlayerManager, pluginInfoModifiers) } - if (audioPlaylist.isSearchResult) { - loadResult.complete(LoadResult.searchResult(tracks)) - return - } - loadResult.complete( - LoadResult.playlistLoaded( - audioPlaylist.toPlaylistInfo(), - audioPlaylist.toPluginInfo(pluginInfoModifiers), - tracks - ) - ) - } - - override fun noMatches() { - log.info("No matches found") - loadResult.complete(LoadResult.NoMatches()) - } - - override fun loadFailed(e: FriendlyException) { - log.error("Load failed", e) - - loadResult.complete(LoadResult.loadFailed(e)) - } - -} diff --git a/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.kt b/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.kt index 83114fc7e..4fdadb43c 100644 --- a/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.kt +++ b/LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.kt @@ -22,20 +22,21 @@ package lavalink.server.player 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 import dev.arbjerg.lavalink.api.AudioPluginInfoModifier import dev.arbjerg.lavalink.protocol.v4.EncodedTracks import dev.arbjerg.lavalink.protocol.v4.LoadResult import dev.arbjerg.lavalink.protocol.v4.Track import dev.arbjerg.lavalink.protocol.v4.Tracks import jakarta.servlet.http.HttpServletRequest -import lavalink.server.util.decodeTrack -import lavalink.server.util.toTrack +import lavalink.server.util.* import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.server.ResponseStatusException -import java.util.concurrent.CompletionStage @RestController class AudioLoaderRestHandler( @@ -53,7 +54,43 @@ class AudioLoaderRestHandler( @RequestParam identifier: String ): ResponseEntity { log.info("Got request to load for identifier \"${identifier}\"") - return ResponseEntity.ok(AudioLoader(audioPlayerManager, pluginInfoModifiers).load(identifier)) + + val item = try { + loadAudioItem(audioPlayerManager, identifier) + } catch (ex: FriendlyException) { + log.error("Failed to load track", ex) + return ResponseEntity.ok(LoadResult.loadFailed(ex)) + } + + val result = when (item) { + null -> LoadResult.NoMatches() + + is AudioTrack -> { + log.info("Loaded track ${item.info.title}") + LoadResult.trackLoaded(item.toTrack(audioPlayerManager, pluginInfoModifiers)) + } + + is AudioPlaylist -> { + log.info("Loaded playlist ${item.name}") + + val tracks = item.tracks.map { it.toTrack(audioPlayerManager, pluginInfoModifiers) } + if (item.isSearchResult) { + LoadResult.searchResult(tracks) + } else { + LoadResult.playlistLoaded(item.toPlaylistInfo(), item.toPluginInfo(pluginInfoModifiers), tracks) + } + } + + else -> { + log.error("Unknown item type: ${item.javaClass}") + throw ResponseStatusException( + HttpStatus.INTERNAL_SERVER_ERROR, + "Identifier returned unknown audio item type: ${item.javaClass.canonicalName}" + ) + } + } + + return ResponseEntity.ok(result) } @GetMapping("/v4/decodetrack") @@ -62,7 +99,12 @@ class AudioLoaderRestHandler( HttpStatus.BAD_REQUEST, "No track to decode provided" ) - return ResponseEntity.ok(decodeTrack(audioPlayerManager, trackToDecode).toTrack(trackToDecode, pluginInfoModifiers)) + return ResponseEntity.ok( + decodeTrack(audioPlayerManager, trackToDecode).toTrack( + trackToDecode, + pluginInfoModifiers + ) + ) } @PostMapping("/v4/decodetracks") diff --git a/LavalinkServer/src/main/java/lavalink/server/player/LavalinkPlayer.kt b/LavalinkServer/src/main/java/lavalink/server/player/LavalinkPlayer.kt index aeaec5866..039a360c5 100644 --- a/LavalinkServer/src/main/java/lavalink/server/player/LavalinkPlayer.kt +++ b/LavalinkServer/src/main/java/lavalink/server/player/LavalinkPlayer.kt @@ -21,12 +21,14 @@ */ package lavalink.server.player +import com.sedmelluq.discord.lavaplayer.format.StandardAudioDataFormats 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.track.AudioTrack import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame +import com.sedmelluq.discord.lavaplayer.track.playback.MutableAudioFrame import dev.arbjerg.lavalink.api.AudioPluginInfoModifier import dev.arbjerg.lavalink.api.IPlayer import io.netty.buffer.ByteBuf @@ -36,6 +38,7 @@ import lavalink.server.io.SocketServer.Companion.sendPlayerUpdate import lavalink.server.player.filters.FilterChain import moe.kyokobot.koe.MediaConnection import moe.kyokobot.koe.media.OpusAudioFrameProvider +import java.nio.ByteBuffer import java.util.concurrent.ScheduledFuture import java.util.concurrent.TimeUnit @@ -46,6 +49,9 @@ class LavalinkPlayer( audioPlayerManager: AudioPlayerManager, pluginInfoModifiers: List ) : AudioEventAdapter(), IPlayer { + private val buffer = ByteBuffer.allocate(StandardAudioDataFormats.DISCORD_OPUS.maximumChunkSize()) + private val mutableFrame = MutableAudioFrame().apply { setBuffer(buffer) } + val audioLossCounter = AudioLossCounter() var endMarkerHit = false var filters: FilterChain = FilterChain() @@ -117,21 +123,15 @@ class LavalinkPlayer( } private inner class Provider(connection: MediaConnection?) : OpusAudioFrameProvider(connection) { - private var lastFrame: AudioFrame? = null - - override fun canProvide(): Boolean { - lastFrame = audioPlayer.provide() - return if (lastFrame == null) { + override fun canProvide() = audioPlayer.provide(mutableFrame).also { provided -> + if (!provided) { audioLossCounter.onLoss() - false - } else { - true } } override fun retrieveOpusFrame(buf: ByteBuf) { audioLossCounter.onSuccess() - buf.writeBytes(lastFrame!!.data) + buf.writeBytes(buffer.flip()) } } } diff --git a/LavalinkServer/src/main/java/lavalink/server/player/PlayerRestHandler.kt b/LavalinkServer/src/main/java/lavalink/server/player/PlayerRestHandler.kt index 4f4831078..801614dfe 100644 --- a/LavalinkServer/src/main/java/lavalink/server/player/PlayerRestHandler.kt +++ b/LavalinkServer/src/main/java/lavalink/server/player/PlayerRestHandler.kt @@ -1,8 +1,6 @@ package lavalink.server.player -import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler import com.sedmelluq.discord.lavaplayer.tools.FriendlyException -import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist import com.sedmelluq.discord.lavaplayer.track.AudioTrack import com.sedmelluq.discord.lavaplayer.track.TrackMarker import dev.arbjerg.lavalink.api.AudioFilterExtension @@ -18,7 +16,6 @@ import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.server.ResponseStatusException -import java.util.concurrent.CompletableFuture @RestController class PlayerRestHandler( @@ -60,7 +57,10 @@ class PlayerRestHandler( val context = socketContext(socketServer, sessionId) if (playerUpdate.track.isPresent() && (playerUpdate.encodedTrack is Omissible.Present || playerUpdate.identifier is Omissible.Present)) { - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot specify both track and encodedTrack/identifier") + throw ResponseStatusException( + HttpStatus.BAD_REQUEST, + "Cannot specify both track and encodedTrack/identifier" + ) } val track = if (playerUpdate.track.isPresent()) { @@ -180,44 +180,19 @@ class PlayerRestHandler( decodeTrack(context.audioPlayerManager, it) } } else { - val trackFuture = CompletableFuture() - context.audioPlayerManager.loadItemSync( - (identifier as Omissible.Present).value, - object : AudioLoadResultHandler { - override fun trackLoaded(track: AudioTrack) { - trackFuture.complete(track) - } - - override fun playlistLoaded(playlist: AudioPlaylist) { - trackFuture.completeExceptionally( - ResponseStatusException( - HttpStatus.BAD_REQUEST, - "Cannot play a playlist or search result" - ) - ) - } - - override fun noMatches() { - trackFuture.completeExceptionally( - ResponseStatusException( - HttpStatus.BAD_REQUEST, - "No matches found for identifier" - ) - ) - } - - override fun loadFailed(exception: FriendlyException) { - trackFuture.completeExceptionally( - ResponseStatusException( - HttpStatus.INTERNAL_SERVER_ERROR, - exception.message, - getRootCause(exception) - ) - ) - } - }) - - trackFuture.join() + val item = try { + loadAudioItem(context.audioPlayerManager, (identifier as Omissible.Present).value) + // Safety: loadAudioItem ONLY throws FriendlyException + } catch (ex: FriendlyException) { + throw ResponseStatusException(HttpStatus.BAD_REQUEST, ex.message, getRootCause(ex)) + } + + if (item !is AudioTrack) throw ResponseStatusException( + HttpStatus.BAD_REQUEST, + if (item == null) "No matches found for identifier" else "Cannot play a playlist or search result" + ) + + item } newTrack?.let { diff --git a/LavalinkServer/src/main/java/lavalink/server/util/loading.kt b/LavalinkServer/src/main/java/lavalink/server/util/loading.kt new file mode 100644 index 000000000..7c2c44201 --- /dev/null +++ b/LavalinkServer/src/main/java/lavalink/server/util/loading.kt @@ -0,0 +1,25 @@ +package lavalink.server.util + +import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager +import com.sedmelluq.discord.lavaplayer.tools.ExceptionTools +import com.sedmelluq.discord.lavaplayer.tools.FriendlyException +import com.sedmelluq.discord.lavaplayer.track.AudioItem + +/** + * Loads an audio item from the specified [identifier]. + * + * This method wraps any exceptions thrown by the [AudioPlayerManager.loadItem] method in a [FriendlyException]. + * This is meant to maintain the behavior from callback-style item loading. + */ +fun loadAudioItem(manager: AudioPlayerManager, identifier: String): AudioItem? = try { + manager.loadItemSync(identifier) +} catch (ex: Throwable) { + // re-throw any errors that are not exceptions + ExceptionTools.rethrowErrors(ex) + + throw FriendlyException( + "Something went wrong while looking up the track.", + FriendlyException.Severity.FAULT, + ex + ) +} diff --git a/Testbot/build.gradle.kts b/Testbot/build.gradle.kts deleted file mode 100644 index 9ae13f7f2..000000000 --- a/Testbot/build.gradle.kts +++ /dev/null @@ -1,26 +0,0 @@ -plugins { - application - kotlin("jvm") -} - -group = "dev.arbjerg.lavalink" -version = "1.0" - -application { - mainClass = "lavalink.testbot.TestbotKt" -} - -repositories { - mavenCentral() - jcenter() - maven("https://jitpack.io") -} - -dependencies { - compileOnly(libs.lavalink.client) - compileOnly(libs.logback) - compileOnly(libs.kotlin.stdlib.jdk8) - compileOnly(libs.jda) { - exclude(module = "opus-java") - } -} diff --git a/Testbot/readme.md b/Testbot/readme.md deleted file mode 100644 index ad87488b5..000000000 --- a/Testbot/readme.md +++ /dev/null @@ -1,35 +0,0 @@ -# Lavalink Testbot -This is a minimalistic example of a bot using Lavalink. This example is based upon Lavalink-Client for Java. - -https://github.com/freyacodes/lavalink-client - -This uses Lavaplayer to load tracks rather than initially loading tracks via Lavalink. Non-JVM (Java) bots will need -to query the `/loadtracks` endpoint. - - -## Running the test bot -This guide assumes the following: -* You have a Lavalink node already running -* You have a Discord bot and its token -* You have a shell in the same directory as this readme -* You have Java 11 or newer. Check with `java -version` - -Run this command. Gradle will download as needed, and will build and run the bot: -```bash -../gradlew run --args "YOUR_BOT_TOKEN ws://localhost:2333 youshallnotpass" -``` - -Replace token, host, and password as needed. - -Replace `../gradlew` with `../gradlew.bat` if on Windows. - - -## Using the test bot -Only one command is currently supported, and only the first or selected track of a playlist or search will be played. Example usages: -``` -;;play https://www.youtube.com/watch?v=cRh1-_pRDzo -;;play https://soundcloud.com/home-2001/resonance -;;play https://www.youtube.com/watch?v=t2D5HlKLh34&list=PLKUyqLlH6brkzzJgD6Gdriga4mdtCAMBJ -;;play ytsearch: John Coltrane Giant Steps -;;play https://www.youtube.com/watch?v=dQw4w9WgXcQ -``` \ No newline at end of file diff --git a/Testbot/src/main/kotlin/lavalink/testbot/testbot.kt b/Testbot/src/main/kotlin/lavalink/testbot/testbot.kt deleted file mode 100644 index 24144fbe4..000000000 --- a/Testbot/src/main/kotlin/lavalink/testbot/testbot.kt +++ /dev/null @@ -1,105 +0,0 @@ -package lavalink.testbot - -import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler -import com.sedmelluq.discord.lavaplayer.tools.FriendlyException -import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist -import com.sedmelluq.discord.lavaplayer.track.AudioTrack -import lavalink.client.io.jda.JdaLavalink -import net.dv8tion.jda.api.JDA -import net.dv8tion.jda.api.JDABuilder -import net.dv8tion.jda.api.entities.Member -import net.dv8tion.jda.api.entities.TextChannel -import net.dv8tion.jda.api.events.ReadyEvent -import net.dv8tion.jda.api.events.StatusChangeEvent -import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent -import net.dv8tion.jda.api.hooks.ListenerAdapter -import net.dv8tion.jda.api.requests.GatewayIntent -import net.dv8tion.jda.api.utils.cache.CacheFlag.* -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import java.net.URI - -private val log: Logger = LoggerFactory.getLogger("Testbot") -private lateinit var jda: JDA -private val lavalink = JdaLavalink(1) { _ -> jda } -lateinit var host: String -lateinit var password: String - -fun main(args: Array) { - if (args.size < 3) { - throw IllegalArgumentException("Expected 3 arguments. Please refer to the readme.") - } - - val token = args[0] - host = args[1] - password = args[2] - - jda = JDABuilder.createDefault(token, - GatewayIntent.GUILD_MESSAGES, - GatewayIntent.GUILD_VOICE_STATES) - .disableCache(CLIENT_STATUS, ACTIVITY, CLIENT_STATUS, EMOTE, MEMBER_OVERRIDES) - .addEventListeners(Listener, lavalink) - .setVoiceDispatchInterceptor(lavalink.voiceInterceptor) - .build() -} - -object Listener : ListenerAdapter() { - override fun onStatusChange(event: StatusChangeEvent) { - log.info("{} -> {}", event.oldStatus, event.newStatus) - } - - override fun onReady(event: ReadyEvent) { - lavalink.setUserId(jda.selfUser.id) - lavalink.addNode(URI(host), password) - } - - override fun onGuildMessageReceived(event: GuildMessageReceivedEvent) { - val member = event.member ?: return - if (member.user.isBot) return - - try { - play(event.channel, member, event.message.contentRaw) - } catch (e: Exception) { - event.channel.sendMessage(e.message ?: e.toString()).queue() - } - } -} - -fun play(channel: TextChannel, member: Member, message: String) { - if (message.startsWith(";;play ")) { - val vc = member.voiceState?.channel - if (vc == null) { - channel.sendMessage("You must be in a voice channel").queue() - return - } - - val link = lavalink.getLink(channel.guild) - link.connect(vc) - - val track = message.drop(";;play ".length).trim() - link.node?.restClient?.loadItem(track, object : AudioLoadResultHandler { - override fun loadFailed(e: FriendlyException) { - channel.sendMessage(e.message ?: e.toString()).queue() - } - - override fun trackLoaded(track: AudioTrack) { - channel.sendMessage("Playing `${track.info.title}`").queue() - link.player.playTrack(track) - } - - override fun noMatches() { - channel.sendMessage("No matches").queue() - } - - override fun playlistLoaded(playlist: AudioPlaylist) { - val loaded = playlist.selectedTrack ?: playlist.tracks.firstOrNull() - if (loaded == null) { - channel.sendMessage("Empty playlist").queue() - return - } - channel.sendMessage("Playing `${loaded.info.title}` from list `${playlist.name}`").queue() - link.player.playTrack(loaded) - } - }) - } -} \ No newline at end of file diff --git a/Testbot/src/main/resources/logback.xml b/Testbot/src/main/resources/logback.xml deleted file mode 100644 index 4bd2e3760..000000000 --- a/Testbot/src/main/resources/logback.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 0988222f2..14cbf4aa8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,9 +2,7 @@ rootProject.name = "Lavalink-Parent" include(":Lavalink-Server") include(":protocol") -include(":Testbot") include(":plugin-api") -include("plugin-api") project(":Lavalink-Server").projectDir = file("$rootDir/LavalinkServer") @@ -36,8 +34,8 @@ fun VersionCatalogBuilder.spring() { } fun VersionCatalogBuilder.voice() { - version("lavaplayer", "2.2.1") - version("koe", "2.0.2") + version("lavaplayer", "2.2.2") + version("koe", "2.0.3-rc2") library("lavaplayer", "dev.arbjerg", "lavaplayer").versionRef("lavaplayer") library("lavaplayer-ip-rotator", "dev.arbjerg", "lavaplayer-ext-youtube-rotator").versionRef("lavaplayer") @@ -81,9 +79,6 @@ fun VersionCatalogBuilder.common() { } fun VersionCatalogBuilder.other() { - library("jda", "net.dv8tion", "JDA").version("4.1.1_135") - library("lavalink-client", "com.github.FredBoat", "Lavalink-Client").version("8d9b660") - val mavenPublishPlugin = version("maven-publish-plugin", "0.28.0") plugin("maven-publish", "com.vanniktech.maven.publish").versionRef(mavenPublishPlugin)