Skip to content

Commit 7a24ea5

Browse files
authored
feat: DAVE Support (#1190)
1 parent a003505 commit 7a24ea5

File tree

12 files changed

+108
-54
lines changed

12 files changed

+108
-54
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
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.2.0
7+
> [!IMPORTANT]
8+
> This is the first Lavalink release with [DAVE](https://daveprotocol.com/) (E2EE voice) support.
9+
> To use DAVE, you need to update your client library to a version that supports it.
10+
> The voice state has a new [`channelId`](docs/api/rest.md#voice-state) field which is required to connect to discords voice servers.
11+
12+
* Updated Koe to [`3.0.0-pre1`](https://github.com/KyokoBot/koe/releases/tag/3.0.0-pre1) with DAVE support in https://github.com/lavalink-devs/Lavalink/pull/1190.
13+
614
## v4.1.2
715
* Updated Lavaplayer to [`2.2.5`](https://github.com/lavalink-devs/lavaplayer/releases/tag/2.2.5)
816
* Updated Koe to [`2.2.0-rc2`](https://github.com/KyokoBot/koe/releases/tag/2.2.0-rc2)

LavalinkServer/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ dependencies {
6868
implementation(libs.koe.udpqueue) {
6969
exclude(module="udp-queue")
7070
}
71+
implementation(libs.bundles.libdave.natives)
7172
implementation(libs.bundles.udpqueue.natives) {
7273
exclude(group = "com.sedmelluq", module = "lava-common")
7374
}

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

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import com.sedmelluq.lava.common.natives.architecture.DefaultArchitectureTypes
44
import com.sedmelluq.lava.common.natives.architecture.DefaultOperatingSystemTypes
55
import com.sedmelluq.lava.common.natives.architecture.SystemType
66
import moe.kyokobot.koe.KoeOptions
7-
import moe.kyokobot.koe.codec.udpqueue.UdpQueueFramePollerFactory
8-
import moe.kyokobot.koe.gateway.GatewayVersion
7+
import moe.kyokobot.koe.poller.udpqueue.QueueManagerPool
8+
import moe.kyokobot.koe.poller.udpqueue.UdpQueueFramePollerFactory
99
import org.slf4j.Logger
1010
import org.slf4j.LoggerFactory
1111
import org.springframework.context.annotation.Bean
@@ -26,14 +26,14 @@ class KoeConfiguration(val serverConfig: ServerConfig) {
2626

2727
SystemType(DefaultArchitectureTypes.X86_64, DefaultOperatingSystemTypes.WINDOWS),
2828
SystemType(DefaultArchitectureTypes.X86_32, DefaultOperatingSystemTypes.WINDOWS),
29+
SystemType(DefaultArchitectureTypes.ARMv8_64, DefaultOperatingSystemTypes.WINDOWS),
2930

3031
SystemType(DefaultArchitectureTypes.X86_64, DefaultOperatingSystemTypes.DARWIN),
3132
SystemType(DefaultArchitectureTypes.ARMv8_64, DefaultOperatingSystemTypes.DARWIN)
3233
)
3334

3435
@Bean
3536
fun koeOptions(): KoeOptions = KoeOptions.builder().apply {
36-
setGatewayVersion(GatewayVersion.V8)
3737
setDeafened(true)
3838
setEnableWSSPortOverride(false)
3939

@@ -44,25 +44,27 @@ class KoeConfiguration(val serverConfig: ServerConfig) {
4444
}
4545
log.info("OS: ${systemType?.osType ?: "unknown"}, Arch: ${systemType?.architectureType ?: "unknown"}")
4646

47-
var bufferSize = serverConfig.bufferDurationMs ?: UdpQueueFramePollerFactory.DEFAULT_BUFFER_DURATION
47+
var bufferSize = serverConfig.bufferDurationMs ?: QueueManagerPool.DEFAULT_BUFFER_DURATION
4848
if (bufferSize <= 0) {
4949
log.info("JDA-NAS is disabled! GC pauses may cause your bot to stutter during playback.")
5050
return@apply
5151
}
5252

53-
val nasSupported = supportedSystems.any { it.osType == systemType?.osType && it.architectureType == systemType?.architectureType }
53+
val nasSupported = supportedSystems.any { it.osType == systemType?.osType && it.architectureType == systemType.architectureType }
5454

5555
if (nasSupported) {
5656
log.info("Enabling JDA-NAS")
5757
if (bufferSize < 40) {
58-
log.warn("Buffer size of ${bufferSize}ms is illegal. Defaulting to ${UdpQueueFramePollerFactory.DEFAULT_BUFFER_DURATION}ms")
59-
bufferSize = UdpQueueFramePollerFactory.DEFAULT_BUFFER_DURATION
58+
log.warn("Buffer size of ${bufferSize}ms is illegal. Defaulting to ${QueueManagerPool.DEFAULT_BUFFER_DURATION}ms")
59+
bufferSize = QueueManagerPool.DEFAULT_BUFFER_DURATION
6060
}
6161
try {
6262
setFramePollerFactory(
6363
UdpQueueFramePollerFactory(
64-
bufferSize,
65-
Runtime.getRuntime().availableProcessors()
64+
QueueManagerPool(
65+
Runtime.getRuntime().availableProcessors(),
66+
bufferSize
67+
)
6668
)
6769
)
6870
} catch (e: Throwable) {

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager
2727
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter
2828
import com.sedmelluq.discord.lavaplayer.track.AudioTrack
2929
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason
30-
import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame
3130
import com.sedmelluq.discord.lavaplayer.track.playback.MutableAudioFrame
3231
import dev.arbjerg.lavalink.api.AudioPluginInfoModifier
3332
import dev.arbjerg.lavalink.api.IPlayer
@@ -37,7 +36,8 @@ import lavalink.server.io.SocketContext
3736
import lavalink.server.io.SocketServer.Companion.sendPlayerUpdate
3837
import lavalink.server.player.filters.FilterChain
3938
import moe.kyokobot.koe.MediaConnection
40-
import moe.kyokobot.koe.media.OpusAudioFrameProvider
39+
import moe.kyokobot.koe.codec.CodecInstance
40+
import moe.kyokobot.koe.media.AudioFrameProvider
4141
import java.nio.ByteBuffer
4242
import java.util.concurrent.ScheduledFuture
4343
import java.util.concurrent.TimeUnit
@@ -79,7 +79,7 @@ class LavalinkPlayer(
7979
}
8080

8181
fun provideTo(connection: MediaConnection) {
82-
connection.audioSender = Provider(connection)
82+
connection.audioSender = Provider()
8383
}
8484

8585

@@ -124,16 +124,23 @@ class LavalinkPlayer(
124124
)
125125
}
126126

127-
private inner class Provider(connection: MediaConnection?) : OpusAudioFrameProvider(connection) {
127+
private inner class Provider : AudioFrameProvider {
128+
129+
override fun onCodecChanged(codec: CodecInstance) {
130+
}
131+
132+
override fun dispose() {}
133+
128134
override fun canProvide() = audioPlayer.provide(mutableFrame).also { provided ->
129135
if (!provided) {
130136
audioLossCounter.onLoss()
131137
}
132138
}
133139

134-
override fun retrieveOpusFrame(buf: ByteBuf) {
140+
override fun provideFrame(buf: ByteBuf): Boolean {
135141
audioLossCounter.onSuccess()
136142
buf.writeBytes(buffer.flip())
143+
return true
137144
}
138145
}
139146
}

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ class PlayerRestHandler(
9797

9898
playerUpdate.voice.ifPresent {
9999
// Discord sometimes sends a partial voice server update missing the endpoint, which can be ignored.
100-
if (it.endpoint.isEmpty() || it.token.isEmpty() || it.sessionId.isEmpty()) {
101-
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Partial Lavalink voice state: $it")
100+
if (it.token.isBlank() || it.endpoint.isBlank() || it.sessionId.isBlank() || it.channelId.isNullOrBlank()) {
101+
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "token, endpoint, sessionId and channelId must be provided in voice state")
102102
}
103103
}
104104

@@ -118,13 +118,21 @@ class PlayerRestHandler(
118118
oldConn.voiceServerInfo == null ||
119119
oldConn.voiceServerInfo?.endpoint != it.endpoint ||
120120
oldConn.voiceServerInfo?.token != it.token ||
121-
oldConn.voiceServerInfo?.sessionId != it.sessionId
121+
oldConn.voiceServerInfo?.sessionId != it.sessionId ||
122+
oldConn.voiceServerInfo?.channelId != it.channelId!!.toLong()
122123
) {
123124
//clear old connection
124125
context.koe.destroyConnection(guildId)
125126

126127
val conn = context.getMediaConnection(player)
127-
conn.connect(VoiceServerInfo(it.sessionId, it.endpoint, it.token)).exceptionally {
128+
conn.connect(
129+
VoiceServerInfo.builder()
130+
.setSessionId(it.sessionId)
131+
.setEndpoint(it.endpoint)
132+
.setToken(it.token)
133+
.setChannelId(it.channelId!!.toLong())
134+
.build()
135+
).exceptionally {
128136
throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to connect to voice server")
129137
}.toCompletableFuture().join()
130138
player.provideTo(conn)

LavalinkServer/src/main/java/lavalink/server/util/util.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ fun LavalinkPlayer.toPlayer(context: SocketContext, pluginInfoModifiers: List<Au
106106
VoiceState(
107107
voiceServerInfo?.token ?: "",
108108
voiceServerInfo?.endpoint ?: "",
109-
voiceServerInfo?.sessionId ?: ""
109+
voiceServerInfo?.sessionId ?: "",
110+
voiceServerInfo?.channelId?.toString()
110111
),
111112
filters.toFilters(),
112113
)

README.md

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ A [basic example bot](https://github.com/lavalink-devs/lavalink-client/tree/main
1515
> Lavalink v4 is now **out** of beta! See [the changelog](CHANGELOG.md) for more information.
1616
1717
## Getting started
18-
* Pick one of the [up-to-date clients](https://lavalink.dev/clients). Advanced users can create their own using the [API documentation
19-
](https://lavalink.dev/api/)
18+
* Pick one of the [up-to-date clients](https://lavalink.dev/clients). Advanced users can create their own using the [API documentation](https://lavalink.dev/api/)
2019
* See the [server configuration documentation](https://lavalink.dev/configuration/) for configuring your Lavalink server
2120
* Explore [available plugins](https://lavalink.dev/plugins) for extra features
2221
* See also our [FAQ](https://lavalink.dev/getting-started/faq)
@@ -32,6 +31,7 @@ A [basic example bot](https://github.com/lavalink-devs/lavalink-client/tree/main
3231
</details>
3332

3433
## Features
34+
* Support for [DAVE](https://daveprotocol.com)
3535
* Powered by Lavaplayer
3636
* Minimal CPU/memory footprint
3737
* Twitch/YouTube (via [this](https://github.com/lavalink-devs/youtube-source#plugin) plugin) stream support
@@ -57,20 +57,26 @@ Support for other JVMs is also best-effort. Periodic CPU utilization stats are p
5757
Lavalink also runs on other hardware, but support is best-effort.
5858
Here is a list of known working hardware:
5959

60-
| Operating System | Architecture | Lavaplayer | JDA-NAS | Timescale | AVX2 |
61-
|------------------|--------------|------------|---------|-----------|------|
62-
| linux | x86-64 |||||
63-
| linux | x86 |||||
64-
| linux | arm |||||
65-
| linux | armhf |||||
66-
| linux | aarch32 |||||
67-
| linux | aarch64 |||||
68-
| linux-musl | x86-64 |||||
69-
| linux-musl | aarch64 |||||
70-
| windows | x86-64 |||||
71-
| Windows | x86 |||||
72-
| darwin | x86-64 |||||
73-
| darwin | aarch64e |||||
60+
| Operating System | Architecture | DAVE | Lavaplayer | JDA-NAS | Timescale | AVX2 |
61+
|------------------|--------------|------|------------|---------|-----------|-------|
62+
| linux | x86-64 ||||||
63+
| linux | x86 ||||||
64+
| linux | arm ||||||
65+
| linux | armhf ||||||
66+
| linux | aarch32 ||||||
67+
| linux | aarch64 ||||||
68+
| linux-musl | x86-64 ||||||
69+
| linux-musl | aarch64 ||||||
70+
| windows | x86-64 ||||||
71+
| Windows | x86 ||||||
72+
| Windows | aarch64 ||[^1] ||[^1] |[^1] |
73+
| darwin | x86-64 ||||||
74+
| darwin | aarch64e ||||||
75+
76+
[^1]: Windows on ARM is not natively supported, but seems to work fine with x86-64 JVMs & emulation.
77+
78+
> [!NOTE]
79+
> The minium supported version for glibc in DAVE is 2.35 (Ubuntu 22.04).
7480
7581
## Changelog
7682

docs/api/rest.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -344,15 +344,19 @@ Array of [Track](#track) objects
344344

345345
#### Voice State
346346

347-
| Field | Type | Description |
348-
|-----------|--------|---------------------------------------------------|
349-
| token | string | The Discord voice token to authenticate with |
350-
| endpoint | string | The Discord voice endpoint to connect to |
351-
| sessionId | string | The Discord voice session id to authenticate with |
352-
353-
`token`, `endpoint`, and `sessionId` are the 3 required values for connecting to one of Discord's voice servers.
354-
`sessionId` is provided by the Voice State Update event sent by Discord, whereas the `endpoint` and `token` are provided
355-
with the Voice Server Update. Please refer to https://discord.com/developers/docs/topics/gateway-events#voice
347+
| Field | Type | Description |
348+
|-----------|-------------|-------------------------------------------------------|
349+
| token | string | The Discord voice token to authenticate with |
350+
| endpoint | string | The Discord voice endpoint to connect to |
351+
| sessionId | string | The Discord voice session id to authenticate with |
352+
| channelId | ?string[^1] | The Discord voice channel id the bot is connecting to |
353+
354+
[^1]: `channelId` is not nullable when updating the player.
355+
356+
`token`, `endpoint`, `sessionId` and `channelId` are the 4 required values for connecting to one of Discord's voice servers.
357+
`sessionId` & `channelId` are provided by the Voice State Update event sent by Discord, whereas the `endpoint` and `token` are provided
358+
with the Voice Server Update.
359+
Please refer to https://docs.discord.com/developers/events/gateway-events#voice-state-update & https://docs.discord.com/developers/events/gateway-events#voice-server-update
356360

357361
#### Filters
358362

docs/changelog/v4.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## v4.2.0
2+
> [!IMPORTANT]
3+
> This is the first Lavalink release with [DAVE](https://daveprotocol.com/) (E2EE voice) support.
4+
> To use DAVE, you need to update your client library to a version that supports it.
5+
> The voice state has a new [`channelId`](../api/rest.md#voice-state) field which is required to connect to discords voice servers.
6+
7+
* Updated Koe to [`3.0.0-pre1`](https://github.com/KyokoBot/koe/releases/tag/3.0.0-pre1) with DAVE support in https://github.com/lavalink-devs/Lavalink/pull/1190.
8+
19
## v4.1.2
210
* Updated Lavaplayer to [`2.2.5`](https://github.com/lavalink-devs/lavaplayer/releases/tag/2.2.5)
311
* Updated Koe to [`2.2.0-rc2`](https://github.com/KyokoBot/koe/releases/tag/2.2.0-rc2)

protocol/src/commonMain/kotlin/dev/arbjerg/lavalink/protocol/v4/player.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package dev.arbjerg.lavalink.protocol.v4
22

33
import kotlinx.serialization.*
4-
import kotlinx.serialization.json.JsonNames
54
import kotlinx.serialization.json.JsonObject
65
import kotlin.jvm.JvmInline
76

@@ -97,7 +96,8 @@ data class TrackInfo(
9796
data class VoiceState(
9897
val token: String,
9998
val endpoint: String,
100-
val sessionId: String
99+
val sessionId: String,
100+
val channelId: String?
101101
)
102102

103103
@Serializable

0 commit comments

Comments
 (0)