Skip to content

Commit

Permalink
Merge pull request #1106 from lavalink-devs/dev
Browse files Browse the repository at this point in the history
  • Loading branch information
topi314 committed Sep 20, 2024
2 parents 475830f + 278bc91 commit 2946608
Show file tree
Hide file tree
Showing 17 changed files with 138 additions and 336 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
1 change: 1 addition & 0 deletions LavalinkServer/application.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -70,4 +73,4 @@ class KoeConfiguration(val serverConfig: ServerConfig) {
)
}
}.create()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions LavalinkServer/src/main/java/lavalink/server/io/ShutdownHandler.kt
Original file line number Diff line number Diff line change
@@ -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) }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
95 changes: 0 additions & 95 deletions LavalinkServer/src/main/java/lavalink/server/player/AudioLoader.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -53,7 +54,43 @@ class AudioLoaderRestHandler(
@RequestParam identifier: String
): ResponseEntity<LoadResult> {
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")
Expand All @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -46,6 +49,9 @@ class LavalinkPlayer(
audioPlayerManager: AudioPlayerManager,
pluginInfoModifiers: List<AudioPluginInfoModifier>
) : 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()
Expand Down Expand Up @@ -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())
}
}
}
Loading

0 comments on commit 2946608

Please sign in to comment.