feat: proper lk latency probing
This commit is contained in:
parent
acf83771e4
commit
847fcaa23b
|
|
@ -8,6 +8,7 @@ import chat.stoat.R
|
||||||
import chat.stoat.StoatApplication
|
import chat.stoat.StoatApplication
|
||||||
import chat.stoat.api.StoatAPI
|
import chat.stoat.api.StoatAPI
|
||||||
import chat.stoat.api.internals.ChannelUtils
|
import chat.stoat.api.internals.ChannelUtils
|
||||||
|
import chat.stoat.api.routes.misc.LiveKitNode
|
||||||
import chat.stoat.api.routes.misc.Root
|
import chat.stoat.api.routes.misc.Root
|
||||||
import chat.stoat.api.routes.misc.getRootRoute
|
import chat.stoat.api.routes.misc.getRootRoute
|
||||||
import chat.stoat.api.routes.voice.JoinCallResponse
|
import chat.stoat.api.routes.voice.JoinCallResponse
|
||||||
|
|
@ -15,20 +16,30 @@ import chat.stoat.api.routes.voice.joinCall
|
||||||
import chat.stoat.composables.voice.VoiceSound
|
import chat.stoat.composables.voice.VoiceSound
|
||||||
import chat.stoat.composables.voice.VoiceSoundPlayer
|
import chat.stoat.composables.voice.VoiceSoundPlayer
|
||||||
import chat.stoat.services.OngoingCallService
|
import chat.stoat.services.OngoingCallService
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.engine.okhttp.OkHttp
|
||||||
|
import io.ktor.client.request.get
|
||||||
|
import io.ktor.http.isSuccess
|
||||||
import io.livekit.android.LiveKit
|
import io.livekit.android.LiveKit
|
||||||
import io.livekit.android.events.RoomEvent
|
import io.livekit.android.events.RoomEvent
|
||||||
import io.livekit.android.room.Room
|
import io.livekit.android.room.Room
|
||||||
import io.livekit.android.room.track.RemoteAudioTrack
|
import io.livekit.android.room.track.RemoteAudioTrack
|
||||||
import io.livekit.android.room.track.Track
|
import io.livekit.android.room.track.Track
|
||||||
import io.livekit.android.util.flow
|
import io.livekit.android.util.flow
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.cancelChildren
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.joinAll
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import logcat.asLog
|
import logcat.asLog
|
||||||
import logcat.logcat
|
import logcat.logcat
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Owns the lifecycle of the current voice call.
|
* Owns the lifecycle of the current voice call.
|
||||||
|
|
@ -60,6 +71,8 @@ object VoiceCallManager {
|
||||||
isSheetVisible = true
|
isSheetVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val probeClient by lazy { HttpClient(OkHttp) }
|
||||||
|
|
||||||
private var micWasOnBeforeDeafen = false
|
private var micWasOnBeforeDeafen = false
|
||||||
private var soundPlayer: VoiceSoundPlayer? = null
|
private var soundPlayer: VoiceSoundPlayer? = null
|
||||||
private var sessionJob: Job? = null
|
private var sessionJob: Job? = null
|
||||||
|
|
@ -241,6 +254,41 @@ object VoiceCallManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun fastestNode(nodes: List<LiveKitNode>): LiveKitNode? =
|
||||||
|
withTimeoutOrNull(5.seconds) {
|
||||||
|
try {
|
||||||
|
coroutineScope {
|
||||||
|
val winner = CompletableDeferred<LiveKitNode>()
|
||||||
|
val probes = nodes.map { node ->
|
||||||
|
launch {
|
||||||
|
try {
|
||||||
|
val probeUrl = node.publicUrl
|
||||||
|
.replaceFirst("wss://", "https://")
|
||||||
|
.replaceFirst("ws://", "http://")
|
||||||
|
if (probeClient.get(probeUrl).status.isSuccess()) {
|
||||||
|
winner.complete(node)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
probes.joinAll()
|
||||||
|
winner.completeExceptionally(
|
||||||
|
IllegalStateException("No LiveKit node reachable")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
winner.await()
|
||||||
|
} finally {
|
||||||
|
coroutineContext.cancelChildren()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun fetchVoiceToken(channelId: String): JoinCallResponse? {
|
private suspend fun fetchVoiceToken(channelId: String): JoinCallResponse? {
|
||||||
val root: Root
|
val root: Root
|
||||||
try {
|
try {
|
||||||
|
|
@ -267,7 +315,13 @@ object VoiceCallManager {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val node = lk.nodes.random()
|
val node = fastestNode(lk.nodes)
|
||||||
|
if (node == null) {
|
||||||
|
logcat(LogPriority.ERROR) { "No LiveKit node responded to probing!" }
|
||||||
|
errorResource = R.string.voice_error_no_nodes
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
joinCall(channelId, node.name)
|
joinCall(channelId, node.name)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue