Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Media Sink Wants in Voice #898

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 42 additions & 4 deletions voice/api/voice.api
Original file line number Diff line number Diff line change
Expand Up @@ -526,21 +526,24 @@ public final class dev/kord/voice/gateway/DefaultVoiceGatewayBuilder {
public final fun getReconnectRetry ()Ldev/kord/gateway/retry/Retry;
public final fun getSelfId ()Ldev/kord/common/entity/Snowflake;
public final fun getSessionId ()Ljava/lang/String;
public final fun isDeaf ()Z
public final fun setClient (Lio/ktor/client/HttpClient;)V
public final fun setDeaf (Z)V
public final fun setEventFlow (Lkotlinx/coroutines/flow/MutableSharedFlow;)V
public final fun setReconnectRetry (Ldev/kord/gateway/retry/Retry;)V
}

public final class dev/kord/voice/gateway/DefaultVoiceGatewayData {
public fun <init> (Ldev/kord/common/entity/Snowflake;Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Lio/ktor/client/HttpClient;Ldev/kord/gateway/retry/Retry;Lkotlinx/coroutines/flow/MutableSharedFlow;)V
public fun <init> (Ldev/kord/common/entity/Snowflake;Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Lio/ktor/client/HttpClient;Ldev/kord/gateway/retry/Retry;ZLkotlinx/coroutines/flow/MutableSharedFlow;)V
public final fun component1 ()Ldev/kord/common/entity/Snowflake;
public final fun component2 ()Ldev/kord/common/entity/Snowflake;
public final fun component3 ()Ljava/lang/String;
public final fun component4 ()Lio/ktor/client/HttpClient;
public final fun component5 ()Ldev/kord/gateway/retry/Retry;
public final fun component6 ()Lkotlinx/coroutines/flow/MutableSharedFlow;
public final fun copy (Ldev/kord/common/entity/Snowflake;Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Lio/ktor/client/HttpClient;Ldev/kord/gateway/retry/Retry;Lkotlinx/coroutines/flow/MutableSharedFlow;)Ldev/kord/voice/gateway/DefaultVoiceGatewayData;
public static synthetic fun copy$default (Ldev/kord/voice/gateway/DefaultVoiceGatewayData;Ldev/kord/common/entity/Snowflake;Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Lio/ktor/client/HttpClient;Ldev/kord/gateway/retry/Retry;Lkotlinx/coroutines/flow/MutableSharedFlow;ILjava/lang/Object;)Ldev/kord/voice/gateway/DefaultVoiceGatewayData;
public final fun component6 ()Z
public final fun component7 ()Lkotlinx/coroutines/flow/MutableSharedFlow;
public final fun copy (Ldev/kord/common/entity/Snowflake;Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Lio/ktor/client/HttpClient;Ldev/kord/gateway/retry/Retry;ZLkotlinx/coroutines/flow/MutableSharedFlow;)Ldev/kord/voice/gateway/DefaultVoiceGatewayData;
public static synthetic fun copy$default (Ldev/kord/voice/gateway/DefaultVoiceGatewayData;Ldev/kord/common/entity/Snowflake;Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Lio/ktor/client/HttpClient;Ldev/kord/gateway/retry/Retry;ZLkotlinx/coroutines/flow/MutableSharedFlow;ILjava/lang/Object;)Ldev/kord/voice/gateway/DefaultVoiceGatewayData;
public fun equals (Ljava/lang/Object;)Z
public final fun getClient ()Lio/ktor/client/HttpClient;
public final fun getEventFlow ()Lkotlinx/coroutines/flow/MutableSharedFlow;
Expand All @@ -549,6 +552,7 @@ public final class dev/kord/voice/gateway/DefaultVoiceGatewayData {
public final fun getSelfId ()Ldev/kord/common/entity/Snowflake;
public final fun getSessionId ()Ljava/lang/String;
public fun hashCode ()I
public final fun isDeaf ()Z
public fun toString ()Ljava/lang/String;
}

Expand Down Expand Up @@ -668,12 +672,46 @@ public final class dev/kord/voice/gateway/Identify$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public final class dev/kord/voice/gateway/MediaSinkWants : dev/kord/voice/gateway/Command {
public static final field Serializer Ldev/kord/voice/gateway/MediaSinkWants$Serializer;
public fun <init> (ZLjava/util/Map;)V
public synthetic fun <init> (ZLjava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Z
public final fun component2 ()Ljava/util/Map;
public final fun copy (ZLjava/util/Map;)Ldev/kord/voice/gateway/MediaSinkWants;
public static synthetic fun copy$default (Ldev/kord/voice/gateway/MediaSinkWants;ZLjava/util/Map;ILjava/lang/Object;)Ldev/kord/voice/gateway/MediaSinkWants;
public fun equals (Ljava/lang/Object;)Z
public final fun getSsrcs ()Ljava/util/Map;
public final fun getWants ()Z
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class dev/kord/voice/gateway/MediaSinkWants$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Ldev/kord/voice/gateway/MediaSinkWants$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/kord/voice/gateway/MediaSinkWants;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/kord/voice/gateway/MediaSinkWants;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}

public final class dev/kord/voice/gateway/MediaSinkWants$Serializer : kotlinx/serialization/SerializationStrategy {
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/kord/voice/gateway/MediaSinkWants;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public final class dev/kord/voice/gateway/OpCode : java/lang/Enum {
public static final field ClientDisconnect Ldev/kord/voice/gateway/OpCode;
public static final field Heartbeat Ldev/kord/voice/gateway/OpCode;
public static final field HeartbeatAck Ldev/kord/voice/gateway/OpCode;
public static final field Hello Ldev/kord/voice/gateway/OpCode;
public static final field Identify Ldev/kord/voice/gateway/OpCode;
public static final field MediaSinkWants Ldev/kord/voice/gateway/OpCode;
public static final field Ready Ldev/kord/voice/gateway/OpCode;
public static final field Resume Ldev/kord/voice/gateway/OpCode;
public static final field Resumed Ldev/kord/voice/gateway/OpCode;
Expand Down
35 changes: 35 additions & 0 deletions voice/src/main/kotlin/gateway/Command.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import dev.kord.common.annotation.KordVoice
import dev.kord.common.entity.Snowflake
import dev.kord.voice.EncryptionMode
import dev.kord.voice.SpeakingFlags
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import kotlinx.serialization.SerializationStrategy as KSerializationStrategy

public sealed class Command {
Expand All @@ -31,6 +34,10 @@ public sealed class Command {
composite.encodeSerializableElement(descriptor, 0, OpCode.Serializer, OpCode.Heartbeat)
composite.encodeLongElement(descriptor, 1, value.nonce)
}
is MediaSinkWants -> {
composite.encodeSerializableElement(descriptor, 0, OpCode.Serializer, OpCode.MediaSinkWants)
composite.encodeSerializableElement(descriptor, 1, MediaSinkWants.Serializer, value)
}
is SendSpeaking -> {
composite.encodeSerializableElement(descriptor, 0, OpCode.Serializer, OpCode.Speaking)
composite.encodeSerializableElement(descriptor, 1, SendSpeaking.serializer(), value)
Expand Down Expand Up @@ -72,6 +79,34 @@ public data class SendSpeaking(
val ssrc: UInt
) : Command()

@KordVoice
@Serializable
public data class MediaSinkWants(
/**
* Control whether the client wants to receive audio packets from **ANY** SSRC.
*/
val wants: Boolean,
/**
* Control whether the client wants to receive audio packets from a specific SSRC.
*/
val ssrcs: Map<UInt, Boolean> = emptyMap(),
) : Command() {
public companion object Serializer : KSerializationStrategy<MediaSinkWants> {
@OptIn(ExperimentalSerializationApi::class)
override val descriptor: SerialDescriptor =
SerialDescriptor("MediaSinkWants", JsonObject.serializer().descriptor)

override fun serialize(encoder: Encoder, value: MediaSinkWants) {
val obj = buildJsonObject {
put("any", if (value.wants) 100 else 0)
value.ssrcs.forEach { (ssrc, wants) -> put(ssrc.toString(), if (wants) 100 else 0) }
}

encoder.encodeSerializableValue(JsonObject.serializer(), obj)
}
}
}

@KordVoice
@Serializable
public data class SelectProtocol(
Expand Down
4 changes: 3 additions & 1 deletion voice/src/main/kotlin/gateway/DefaultVoiceGateway.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public data class DefaultVoiceGatewayData(
val sessionId: String,
val client: HttpClient,
val reconnectRetry: Retry,
val isDeaf: Boolean,
val eventFlow: MutableSharedFlow<VoiceEvent>
)

Expand Down Expand Up @@ -196,7 +197,8 @@ public class DefaultVoiceGateway(
val copy = command.copy(data = command.data.copy(address = "ip"))
"Voice Gateway >>> ${Json.encodeToString(Command.SerializationStrategy, copy)}"
}
is Heartbeat, is Resume, is SendSpeaking -> "Voice Gateway >>> $json"

is SendSpeaking, is Resume, is MediaSinkWants, is Heartbeat -> "Voice Gateway >>> $json"
}
}
socket.send(Frame.Text(json))
Expand Down
2 changes: 2 additions & 0 deletions voice/src/main/kotlin/gateway/DefaultVoiceGatewayBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class DefaultVoiceGatewayBuilder(
public var client: HttpClient? = null
public var reconnectRetry: Retry? = null
public var eventFlow: MutableSharedFlow<VoiceEvent> = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE)
public var isDeaf: Boolean = false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VoiceConnectionBuilder already has the selfDeaf option, can't this be used instead?

Copy link
Contributor Author

@viztea viztea Dec 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose. I think the VoiceConnectionBuilder could get cleaned up quite a bit though.

Like I state in the PR description, you can set selfDeaf and a streams implementation.

Since selfDeaf is just a UI update right now it's not that much of an issue but if it gets updated to actually prevent voice receive it might be a good idea to set it depending on the streams implementation so that there isn't any confusion.

And maybe add some more functionality to it as well:

interface Streams {
	suspend fun setDeafen(value: Boolean)
	
	suspend fun setMute(ssrc: UInt, value: Boolean)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a fair solution. Makes the deafening much behavior much more consistent, especially with this addition. Streams#setDeafen might not be needed, as it isn't provided for the UI deafen either. I could see the use for a VoiceConnection#(set)Deafen which would UI deafen and media sink deafen (if streams are open).


public fun build(): DefaultVoiceGateway {
val client = client ?: HttpClient(CIO) {
Expand All @@ -37,6 +38,7 @@ public class DefaultVoiceGatewayBuilder(
sessionId,
client,
retry,
isDeaf,
eventFlow
)

Expand Down
3 changes: 2 additions & 1 deletion voice/src/main/kotlin/gateway/OpCode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public enum class OpCode(public val code: Int) {
Resume(7),
Hello(8),
Resumed(9),
ClientDisconnect(13);
ClientDisconnect(13),
MediaSinkWants(15);

internal object Serializer : KSerializer<OpCode> {
override val descriptor: SerialDescriptor
Expand Down
1 change: 1 addition & 0 deletions voice/src/main/kotlin/gateway/handler/HandshakeHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ internal class HandshakeHandler(
on<Hello> {
data.reconnectRetry.reset()
send(identify)
send(MediaSinkWants(!data.isDeaf))
}
}
}