Skip to content

Commit 2946608

Browse files
authored
Merge pull request #1106 from lavalink-devs/dev
2 parents 475830f + 278bc91 commit 2946608

17 files changed

+138
-336
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
Each release usually includes various fixes and improvements.
44
The most noteworthy of these, as well as any features and breaking changes, are listed here.
55

6+
## v4.0.8
7+
* 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
8+
* 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
9+
* Allow usage of non-allocating frame buffers in https://github.com/lavalink-devs/Lavalink/pull/1095
10+
* Added shutdown handling to close sessions cleanly in https://github.com/lavalink-devs/Lavalink/pull/1102
11+
612
## v4.0.7
713
* Updated Lavaplayer to [`2.2.1`](https://github.com/lavalink-devs/lavaplayer/releases/tag/2.2.1)
814
* Updated spring-boot to `3.3.0` & spring-websocket to `6.1.9`

LavalinkServer/application.yml.example

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ lavalink:
3838
rotation: true
3939
channelMix: true
4040
lowPass: true
41+
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)
4142
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.
4243
frameBufferDurationMs: 5000 # How many milliseconds of audio to keep buffered
4344
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.

LavalinkServer/src/main/java/lavalink/server/config/AudioPlayerConfiguration.kt

+6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import com.sedmelluq.discord.lavaplayer.source.soundcloud.*
1414
import com.sedmelluq.discord.lavaplayer.source.twitch.TwitchStreamAudioSourceManager
1515
import com.sedmelluq.discord.lavaplayer.source.vimeo.VimeoAudioSourceManager
1616
import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioSourceManager
17+
import com.sedmelluq.discord.lavaplayer.track.playback.NonAllocatingAudioFrameBuffer
1718
import com.sedmelluq.lava.extensions.youtuberotator.YoutubeIpRotatorSetup
1819
import com.sedmelluq.lava.extensions.youtuberotator.planner.*
1920
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv4Block
@@ -55,6 +56,11 @@ class AudioPlayerConfiguration {
5556
audioPlayerManager.enableGcMonitoring()
5657
}
5758

59+
if (serverConfig.isNonAllocatingFrameBuffer) {
60+
log.info("Using a non-allocating frame buffer")
61+
audioPlayerManager.configuration.setFrameBufferFactory(::NonAllocatingAudioFrameBuffer)
62+
}
63+
5864
val defaultFrameBufferDuration = audioPlayerManager.frameBufferDuration
5965
serverConfig.frameBufferDurationMs?.let {
6066
if (it < 200) { // At the time of writing, LP enforces a minimum of 200ms.

LavalinkServer/src/main/java/lavalink/server/config/KoeConfiguration.kt

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.sedmelluq.lava.common.natives.architecture.DefaultOperatingSystemType
55
import com.sedmelluq.lava.common.natives.architecture.SystemType
66
import moe.kyokobot.koe.KoeOptions
77
import moe.kyokobot.koe.codec.udpqueue.UdpQueueFramePollerFactory
8+
import moe.kyokobot.koe.gateway.GatewayVersion
89
import org.slf4j.Logger
910
import org.slf4j.LoggerFactory
1011
import org.springframework.context.annotation.Bean
@@ -32,6 +33,8 @@ class KoeConfiguration(val serverConfig: ServerConfig) {
3233

3334
@Bean
3435
fun koeOptions(): KoeOptions = KoeOptions.builder().apply {
36+
setGatewayVersion(GatewayVersion.V8)
37+
3538
val systemType: SystemType? = try {
3639
SystemType(DefaultArchitectureTypes.detect(), DefaultOperatingSystemTypes.detect())
3740
} catch (e: IllegalArgumentException) {
@@ -70,4 +73,4 @@ class KoeConfiguration(val serverConfig: ServerConfig) {
7073
)
7174
}
7275
}.create()
73-
}
76+
}

LavalinkServer/src/main/java/lavalink/server/config/ServerConfig.kt

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import org.springframework.stereotype.Component
3030
@Component
3131
class ServerConfig {
3232
var password: String? = null
33+
var isNonAllocatingFrameBuffer = false
3334
var bufferDurationMs: Int? = null
3435
var frameBufferDurationMs: Int? = null
3536
var opusEncodingQuality: Int? = null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package lavalink.server.io
2+
3+
import org.springframework.web.socket.CloseStatus
4+
5+
class ShutdownHandler(private val socketServer: SocketServer) : Thread("lavalink-shutdown-handler") {
6+
init {
7+
isDaemon = false // we want this thread to block shutdown until it has finished running
8+
}
9+
10+
override fun run() {
11+
socketServer.contexts.forEach {
12+
// don't care about exceptions here, the JVM's shutting down anyway.
13+
it.runCatching { closeWebSocket(CloseStatus.GOING_AWAY.code) }
14+
}
15+
}
16+
}

LavalinkServer/src/main/java/lavalink/server/io/SocketServer.kt

+4
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ final class SocketServer(
5656
private val statsCollector = StatsCollector(this)
5757
private val charPool = ('a'..'z') + ('0'..'9')
5858

59+
init {
60+
Runtime.getRuntime().addShutdownHook(ShutdownHandler(this))
61+
}
62+
5963
companion object {
6064
private val log = LoggerFactory.getLogger(SocketServer::class.java)
6165

LavalinkServer/src/main/java/lavalink/server/player/AudioLoader.kt

-95
This file was deleted.

LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.kt

+47-5
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,21 @@
2222
package lavalink.server.player
2323

2424
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager
25+
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException
26+
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist
27+
import com.sedmelluq.discord.lavaplayer.track.AudioTrack
2528
import dev.arbjerg.lavalink.api.AudioPluginInfoModifier
2629
import dev.arbjerg.lavalink.protocol.v4.EncodedTracks
2730
import dev.arbjerg.lavalink.protocol.v4.LoadResult
2831
import dev.arbjerg.lavalink.protocol.v4.Track
2932
import dev.arbjerg.lavalink.protocol.v4.Tracks
3033
import jakarta.servlet.http.HttpServletRequest
31-
import lavalink.server.util.decodeTrack
32-
import lavalink.server.util.toTrack
34+
import lavalink.server.util.*
3335
import org.slf4j.LoggerFactory
3436
import org.springframework.http.HttpStatus
3537
import org.springframework.http.ResponseEntity
3638
import org.springframework.web.bind.annotation.*
3739
import org.springframework.web.server.ResponseStatusException
38-
import java.util.concurrent.CompletionStage
3940

4041
@RestController
4142
class AudioLoaderRestHandler(
@@ -53,7 +54,43 @@ class AudioLoaderRestHandler(
5354
@RequestParam identifier: String
5455
): ResponseEntity<LoadResult> {
5556
log.info("Got request to load for identifier \"${identifier}\"")
56-
return ResponseEntity.ok(AudioLoader(audioPlayerManager, pluginInfoModifiers).load(identifier))
57+
58+
val item = try {
59+
loadAudioItem(audioPlayerManager, identifier)
60+
} catch (ex: FriendlyException) {
61+
log.error("Failed to load track", ex)
62+
return ResponseEntity.ok(LoadResult.loadFailed(ex))
63+
}
64+
65+
val result = when (item) {
66+
null -> LoadResult.NoMatches()
67+
68+
is AudioTrack -> {
69+
log.info("Loaded track ${item.info.title}")
70+
LoadResult.trackLoaded(item.toTrack(audioPlayerManager, pluginInfoModifiers))
71+
}
72+
73+
is AudioPlaylist -> {
74+
log.info("Loaded playlist ${item.name}")
75+
76+
val tracks = item.tracks.map { it.toTrack(audioPlayerManager, pluginInfoModifiers) }
77+
if (item.isSearchResult) {
78+
LoadResult.searchResult(tracks)
79+
} else {
80+
LoadResult.playlistLoaded(item.toPlaylistInfo(), item.toPluginInfo(pluginInfoModifiers), tracks)
81+
}
82+
}
83+
84+
else -> {
85+
log.error("Unknown item type: ${item.javaClass}")
86+
throw ResponseStatusException(
87+
HttpStatus.INTERNAL_SERVER_ERROR,
88+
"Identifier returned unknown audio item type: ${item.javaClass.canonicalName}"
89+
)
90+
}
91+
}
92+
93+
return ResponseEntity.ok(result)
5794
}
5895

5996
@GetMapping("/v4/decodetrack")
@@ -62,7 +99,12 @@ class AudioLoaderRestHandler(
6299
HttpStatus.BAD_REQUEST,
63100
"No track to decode provided"
64101
)
65-
return ResponseEntity.ok(decodeTrack(audioPlayerManager, trackToDecode).toTrack(trackToDecode, pluginInfoModifiers))
102+
return ResponseEntity.ok(
103+
decodeTrack(audioPlayerManager, trackToDecode).toTrack(
104+
trackToDecode,
105+
pluginInfoModifiers
106+
)
107+
)
66108
}
67109

68110
@PostMapping("/v4/decodetracks")

LavalinkServer/src/main/java/lavalink/server/player/LavalinkPlayer.kt

+9-9
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121
*/
2222
package lavalink.server.player
2323

24+
import com.sedmelluq.discord.lavaplayer.format.StandardAudioDataFormats
2425
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer
2526
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager
2627
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter
2728
import com.sedmelluq.discord.lavaplayer.track.AudioTrack
2829
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason
2930
import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame
31+
import com.sedmelluq.discord.lavaplayer.track.playback.MutableAudioFrame
3032
import dev.arbjerg.lavalink.api.AudioPluginInfoModifier
3133
import dev.arbjerg.lavalink.api.IPlayer
3234
import io.netty.buffer.ByteBuf
@@ -36,6 +38,7 @@ import lavalink.server.io.SocketServer.Companion.sendPlayerUpdate
3638
import lavalink.server.player.filters.FilterChain
3739
import moe.kyokobot.koe.MediaConnection
3840
import moe.kyokobot.koe.media.OpusAudioFrameProvider
41+
import java.nio.ByteBuffer
3942
import java.util.concurrent.ScheduledFuture
4043
import java.util.concurrent.TimeUnit
4144

@@ -46,6 +49,9 @@ class LavalinkPlayer(
4649
audioPlayerManager: AudioPlayerManager,
4750
pluginInfoModifiers: List<AudioPluginInfoModifier>
4851
) : AudioEventAdapter(), IPlayer {
52+
private val buffer = ByteBuffer.allocate(StandardAudioDataFormats.DISCORD_OPUS.maximumChunkSize())
53+
private val mutableFrame = MutableAudioFrame().apply { setBuffer(buffer) }
54+
4955
val audioLossCounter = AudioLossCounter()
5056
var endMarkerHit = false
5157
var filters: FilterChain = FilterChain()
@@ -117,21 +123,15 @@ class LavalinkPlayer(
117123
}
118124

119125
private inner class Provider(connection: MediaConnection?) : OpusAudioFrameProvider(connection) {
120-
private var lastFrame: AudioFrame? = null
121-
122-
override fun canProvide(): Boolean {
123-
lastFrame = audioPlayer.provide()
124-
return if (lastFrame == null) {
126+
override fun canProvide() = audioPlayer.provide(mutableFrame).also { provided ->
127+
if (!provided) {
125128
audioLossCounter.onLoss()
126-
false
127-
} else {
128-
true
129129
}
130130
}
131131

132132
override fun retrieveOpusFrame(buf: ByteBuf) {
133133
audioLossCounter.onSuccess()
134-
buf.writeBytes(lastFrame!!.data)
134+
buf.writeBytes(buffer.flip())
135135
}
136136
}
137137
}

0 commit comments

Comments
 (0)