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/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 + ) +}