Skip to content

Commit

Permalink
Add voice support on native
Browse files Browse the repository at this point in the history
  • Loading branch information
schlaubi authored and schlaubi committed Jan 25, 2024
1 parent 55dd87c commit 83e991d
Show file tree
Hide file tree
Showing 63 changed files with 321 additions and 104 deletions.
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ plugins {

allprojects {
repositories {
// TODO: Remove wants https://github.com/ktorio/ktor/pull/3950 lands
mavenLocal()
mavenCentral()
maven("https://oss.sonatype.org/content/repositories/snapshots/")
}
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Compiler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ object Jvm {
}

fun KotlinCommonCompilerOptions.applyKordCompilerOptions() {
allWarningsAsErrors = true
// allWarningsAsErrors = true
progressiveMode = true
freeCompilerArgs.add("-Xexpect-actual-classes")
}
Expand Down
7 changes: 5 additions & 2 deletions buildSrc/src/main/kotlin/kord-multiplatform-module.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,11 @@ kotlin {
explicitApi()

jvm()
js {
nodejs()

if (name != "voice" && name != "core-voice") {
js {
nodejs()
}
}
jvmToolchain(Jvm.target)

Expand Down
7 changes: 4 additions & 3 deletions buildSrc/src/main/kotlin/kord-native-module.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ plugins {
kotlin {
// There are issues with compiling the linux variant on linux
// Please use WSL if you need to work on the linux port
if(!Os.isFamily(Os.FAMILY_WINDOWS)) {
if (!Os.isFamily(Os.FAMILY_WINDOWS)) {
linuxX64()
// Waiting for Ktor
// https://youtrack.jetbrains.com/issue/KTOR-6173
//linuxArm64()
}

mingwX64()
if (name != "voice" && name != "core-voice") {
mingwX64()
}

macosArm64()
macosX64()
Expand All @@ -23,7 +25,6 @@ kotlin {
iosX64()
iosSimulatorArm64()

watchosX64()
watchosArm64()
watchosSimulatorArm64()

Expand Down
15 changes: 11 additions & 4 deletions core-voice/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
plugins {
`kord-module`
`kord-native-module`
`kord-multiplatform-module`
`kord-publishing`
}

dependencies {
api(projects.core)
api(projects.voice)
kotlin {
sourceSets {
commonMain {
dependencies {
api(projects.core)
api(projects.voice)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import dev.kord.core.entity.channel.VoiceChannel
import dev.kord.core.exception.GatewayNotFoundException
import dev.kord.voice.VoiceConnection
import dev.kord.voice.VoiceConnectionBuilder
import kotlin.jvm.JvmName

/**
* Connect to this [VoiceChannel] and create a [VoiceConnection] for this voice session.
Expand Down
3 changes: 0 additions & 3 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ kotlin {
api(libs.kord.cache.map)

implementation(libs.kotlin.logging)

// TODO remove when kordLogger is removed
implementation(libs.kotlin.logging.old)
}
}
nonJvmMain {
Expand Down
3 changes: 2 additions & 1 deletion core/src/commonMain/kotlin/builder/kord/KordBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ public abstract class BaseKordBuilder internal constructor(public val token: Str
val rateLimiter = IdentifyRateLimiter(resources.maxConcurrency, defaultDispatcher)
shards.map {
DefaultGateway {
client = resources.httpClient
// Workaround for: https://github.com/ktorio/ktor/pull/3950#issuecomment-1909088751
// client = resources.httpClient
identifyRateLimiter = rateLimiter
}
}
Expand Down
5 changes: 0 additions & 5 deletions gateway/src/commonMain/kotlin/DefaultGatewayBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ import dev.kord.gateway.ratelimit.IdentifyRateLimiter
import dev.kord.gateway.retry.LinearRetry
import dev.kord.gateway.retry.Retry
import io.ktor.client.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.websocket.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
Expand All @@ -29,9 +27,6 @@ public class DefaultGatewayBuilder {
public fun build(): DefaultGateway {
val client = client ?: HttpClient(httpEngine()) {
install(WebSockets)
install(ContentNegotiation) {
json()
}
}
val retry = reconnectRetry ?: LinearRetry(2.seconds, 20.seconds, 10)
val sendRateLimiter = sendRateLimiter ?: IntervalRateLimiter(limit = 120, interval = 60.seconds)
Expand Down
6 changes: 4 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# api dependencies
kotlin = "1.9.21" # https://github.com/JetBrains/kotlin
ktor = "2.3.7" # https://github.com/ktorio/ktor
ktor = "3.0.0-beta-1" # https://github.com/ktorio/ktor
kotlinx-coroutines = "1.7.3" # https://github.com/Kotlin/kotlinx.coroutines
kotlinx-serialization = "1.6.2" # https://github.com/Kotlin/kotlinx.serialization
kotlinx-datetime = "0.5.0" # https://github.com/Kotlin/kotlinx-datetime
Expand All @@ -16,6 +16,7 @@ kotlin-node = "18.16.12-pre.619" # https://github.com/JetBrains/kotlin-wrappers
bignum = "0.3.8" # https://github.com/ionspin/kotlin-multiplatform-bignum
stately = "2.0.6" # https://github.com/touchlab/Stately
fastZlib = "2.0.1" # https://github.com/timotejroiko/fast-zlib
sodium = "0.9.0" # https://github.com/ionspin/kotlin-multiplatform-libsodium

# code generation
ksp = "1.9.21-1.0.16" # https://github.com/google/ksp
Expand All @@ -24,7 +25,7 @@ kotlinpoet = "1.15.3" # https://github.com/square/kotlinpoet
# tests
junit5 = "5.10.1" # https://github.com/junit-team/junit5
mockk = "1.13.8" # https://github.com/mockk/mockk
slf4j = "2.0.9" # https://www.slf4j.org
slf4j = "2.0.11" # https://www.slf4j.org
kotlinx-io = "0.3.0" # https://github.com/Kotlin/kotlinx-io/

# plugins
Expand Down Expand Up @@ -69,6 +70,7 @@ kotlin-node = { module = "org.jetbrains.kotlin-wrappers:kotlin-node", version.re
# JDK replacements
bignum = { module = "com.ionspin.kotlin:bignum", version.ref = "bignum" }
stately-collections = { module = "co.touchlab:stately-concurrent-collections", version.ref = "stately" }
libsodium = { module = "com.ionspin.kotlin:multiplatform-crypto-libsodium-bindings", version.ref = "sodium" }

# code generation
ksp-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
Expand Down
32 changes: 32 additions & 0 deletions samples/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,22 +1,54 @@
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget

plugins {
`kord-internal-multiplatform-module`
`kord-native-module`
}

@OptIn(ExperimentalKotlinGradlePluginApi::class)
kotlin {
applyDefaultHierarchyTemplate {
common {
group("voice") {
withLinux()
withMacos()
withJvm()
}
}
}
js {
binaries.executable()
}

targets.withType<KotlinNativeTarget> {
binaries.executable {
entryPoint = "dev.kord.voice.test.main"
}
}

sourceSets {
commonMain {
dependencies {
implementation(projects.core)
implementation(libs.kotlin.logging)
}
}
jvmMain {
dependencies {
runtimeOnly(libs.slf4j.simple)
}
}
linuxMain {
dependencies {
implementation(libs.ktor.client.curl)
}
}

named("voiceMain") {
dependencies {
implementation(projects.coreVoice)
}
}
}
}
1 change: 1 addition & 0 deletions samples/src/jvmMain/resources/simplelogger.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.slf4j.simpleLogger.defaultLogLevel=trace
52 changes: 52 additions & 0 deletions samples/src/voiceMain/kotlin/VoiceBot.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
@file:OptIn(KordVoice::class)

package dev.kord.voice.test

import dev.kord.common.annotation.KordVoice
import dev.kord.core.Kord
import dev.kord.core.behavior.channel.BaseVoiceChannelBehavior
import dev.kord.core.behavior.channel.connect
import dev.kord.core.behavior.interaction.respondPublic
import dev.kord.core.event.interaction.GuildChatInputCommandInteractionCreateEvent
import dev.kord.core.on
import dev.kord.voice.AudioFrame
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main(args: Array<String>) = runBlocking {
val kord = Kord(args.firstOrNull() ?: error("Missing token"))

kord.createGlobalApplicationCommands {
input("join", "Test command") {
dmPermission = false
}
}

kord.on<GuildChatInputCommandInteractionCreateEvent> {
val channel = interaction.user.asMember(interaction.guildId).getVoiceState().getChannelOrNull()
if (channel == null) {
interaction.respondPublic { content = "not in channel" }
return@on
}
interaction.respondPublic { content = "success" }
channel.connectEcho()
}

kord.login()
}

private suspend fun BaseVoiceChannelBehavior.connectEcho() {
val buffer = ArrayList(listOf(AudioFrame.SILENCE, AudioFrame.SILENCE, AudioFrame.SILENCE, AudioFrame.SILENCE))
val connection = connect {
receiveVoice = true
audioProvider {
buffer.removeLastOrNull() ?: AudioFrame.SILENCE
}
}
connection.scope.launch {
connection.streams.incomingAudioFrames.collect { (userId, frame) ->
println("Received frame from:${userId}")
buffer.add(frame)
}
}
}
34 changes: 22 additions & 12 deletions voice/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
plugins {
java // for TweetNaclFast
`kord-module`
`kord-sampled-module`
`kord-native-module`
`kord-multiplatform-module`
`kord-publishing`
}

dependencies {
api(projects.common)
api(projects.gateway)
kotlin {
jvm {
withJava()
}

implementation(libs.kotlin.logging)
implementation(libs.slf4j.api)
sourceSets {
commonMain {
dependencies {
api(projects.common)
api(projects.gateway)

// TODO remove when voiceGatewayOnLogger is removed
implementation(libs.kotlin.logging.old)
api(libs.ktor.network)
implementation(libs.kotlin.logging)

compileOnly(projects.kspAnnotations)
compileOnly(projects.kspAnnotations)
}
}

api(libs.ktor.network)
nonJvmMain {
dependencies {
implementation(libs.libsodium)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.kord.voice

import dev.kord.common.annotation.KordVoice
import kotlin.jvm.JvmInline

/**
* A frame of 20ms Opus-encoded 48k stereo audio data.
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import io.github.oshai.kotlinlogging.KotlinLogging
import io.ktor.client.*
import io.ktor.client.plugins.websocket.*
import io.ktor.client.request.*
import io.ktor.util.network.*
import io.ktor.websocket.*
import kotlinx.atomicfu.AtomicRef
import kotlinx.atomicfu.atomic
Expand Down Expand Up @@ -88,7 +89,7 @@ public class DefaultVoiceGateway(
if (exception is CancellationException) break

defaultVoiceGatewayLogger.error(exception) { "" }
if (exception is java.nio.channels.UnresolvedAddressException) {
if (exception is UnresolvedAddressException) {
data.eventFlow.emit(Close.Timeout)
}

Expand Down Expand Up @@ -139,7 +140,7 @@ public class DefaultVoiceGateway(
}

private suspend fun read(frame: Frame) {
val json = String(frame.data, Charsets.UTF_8)
val json = frame.data.decodeToString()

try {
val event = jsonParser.decodeFromString(VoiceEvent.DeserializationStrategy, json)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ package dev.kord.voice.gateway

import dev.kord.common.annotation.KordVoice
import dev.kord.common.entity.Snowflake
import dev.kord.common.http.httpEngine
import dev.kord.gateway.retry.LinearRetry
import dev.kord.gateway.retry.Retry
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.websocket.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlin.time.Duration.Companion.seconds

Expand All @@ -23,11 +21,8 @@ public class DefaultVoiceGatewayBuilder(
public var eventFlow: MutableSharedFlow<VoiceEvent> = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE)

public fun build(): DefaultVoiceGateway {
val client = client ?: HttpClient(CIO) {
val client = client ?: HttpClient(httpEngine()) {
install(WebSockets)
install(ContentNegotiation) {
json()
}
}
val retry = reconnectRetry ?: LinearRetry(2.seconds, 20.seconds, 10)

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,6 @@ public interface VoiceGateway {
}


@Suppress("unused")
@Deprecated("Binary compatibility, remove after deprecation cycle.", level = DeprecationLevel.ERROR)
@PublishedApi
internal val voiceGatewayOnLogger: mu.KLogger = mu.KotlinLogging.logger("Gateway.on")

/**
* Logger used to report [Throwable]s caught in [VoiceGateway.on].
*/
Expand Down
Loading

0 comments on commit 83e991d

Please sign in to comment.