diff --git a/AGENTS.md b/AGENTS.md
index 51a50ffed..f4567e201 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -6,6 +6,8 @@ Supported platforms: Android (minimum API level 21)
- Assemble: `./gradlew assemble`
- Run tests: `./gradlew test`
+- Run detekt (static analysis): `./gradlew detektDebug`
+- Run detekt with baseline: `./gradlew detektRelease`
- Install example app: `./gradlew sample-app-compose:installDebug`
## Architecture
diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml
index 537579a1d..fd99b5135 100644
--- a/config/detekt/detekt.yml
+++ b/config/detekt/detekt.yml
@@ -4,6 +4,10 @@ config:
warningsAsErrors: true
excludes: ''
+empty-blocks:
+ EmptyFunctionBlock:
+ ignoreOverridden: true
+
style:
active: false
@@ -20,4 +24,4 @@ livekit-rules:
exceptions:
TooGenericExceptionCaught:
- active: false
\ No newline at end of file
+ active: false
diff --git a/livekit-android-sdk/build.gradle b/livekit-android-sdk/build.gradle
index f6ded5177..706ffa27d 100644
--- a/livekit-android-sdk/build.gradle
+++ b/livekit-android-sdk/build.gradle
@@ -96,7 +96,7 @@ protobuf {
}
jacoco {
- toolVersion = "0.8.10"
+ toolVersion = "0.8.14"
}
tasks.withType(Test) {
diff --git a/livekit-android-sdk/detekt-baseline-release.xml b/livekit-android-sdk/detekt-baseline-release.xml
index ba07c77f6..89e10ca0b 100644
--- a/livekit-android-sdk/detekt-baseline-release.xml
+++ b/livekit-android-sdk/detekt-baseline-release.xml
@@ -27,32 +27,9 @@
CyclomaticComplexMethod:Room.kt$Room$@Throws(Exception::class) suspend fun connect(url: String, token: String, options: ConnectOptions = ConnectOptions())
CyclomaticComplexMethod:RoomEvent.kt$fun LivekitModels.DisconnectReason?.convert(): DisconnectReason
CyclomaticComplexMethod:SignalClient.kt$SignalClient$private fun handleSignalResponseImpl(ws: WebSocket, response: LivekitRtc.SignalResponse)
- DoubleMutabilityForCollection:CoroutineSdpObserver.kt$CoroutineSdpObserver$private var pendingCreate = mutableListOf<Continuation<Either<SessionDescription, String?>>>()
- DoubleMutabilityForCollection:CoroutineSdpObserver.kt$CoroutineSdpObserver$private var pendingSets = mutableListOf<Continuation<Either<Unit, String?>>>()
- DoubleMutabilityForCollection:E2EEManager.kt$E2EEManager$private var frameCryptors = mutableMapOf<Pair<String, Participant.Identity>, FrameCryptor>()
- DoubleMutabilityForCollection:PeerConnectionTransport.kt$PeerConnectionTransport$private var trackBitrates = mutableMapOf<TrackBitrateInfoKey, TrackBitrateInfo>()
- DoubleMutabilityForCollection:RegionUrlProvider.kt$RegionUrlProvider$private var attemptedRegions = mutableSetOf<RegionInfo>()
- DoubleMutabilityForCollection:Room.kt$Room$private var sidToIdentity = mutableMapOf<Participant.Sid, Participant.Identity>()
- DoubleMutabilityForCollection:Room.kt$Room$private var transcriptionReceivedTimes = mutableMapOf<String, Long>()
- EmptyCatchBlock:EndpointTokenSource.kt$EndpointTokenSource.<no name provided>${ }
- EmptyDefaultConstructor:CachingTokenSource.kt$InMemoryTokenStore$()
- EmptyDefaultConstructor:LocalScreencastVideoTrack.kt$LocalScreencastVideoTrack.MediaProjectionCallback$()
- EmptyFunctionBlock:CommunicationWorkaround.kt$NoopCommunicationWorkaround${ }
- EmptyFunctionBlock:LocalVideoTrack.kt$LocalVideoTrack.<no name provided>.<no name provided>${ }
- EmptyFunctionBlock:NoAudioHandler.kt$NoAudioHandler${ }
- EmptyFunctionBlock:PublisherTransportObserver.kt$PublisherTransportObserver${ }
EmptyFunctionBlock:RTCEngine.kt$RTCEngine${ }
- EmptyFunctionBlock:Room.kt$Room${ }
- EmptyFunctionBlock:SubscriberTransportObserver.kt$SubscriberTransportObserver${ }
- EmptyFunctionBlock:TextureViewRenderer.kt$TextureViewRenderer${}
- EmptyFunctionBlock:VideoFrameCapturer.kt$VideoFrameCapturer${ }
HasPlatformType:DataChannelManager.kt$DataChannelManager$@get:FlowObservable var state by flowDelegate(dataChannel.state()) private set
- HasPlatformType:RTCModule.kt$RTCModule$@Provides fun sdpFactory()
IgnoredReturnValue:BaseStreamReceiver.kt$BaseStreamReceiver$catch { }
- InjectDispatcher:CoroutinesModule.kt$CoroutinesModule$Default
- InjectDispatcher:CoroutinesModule.kt$CoroutinesModule$IO
- InjectDispatcher:CoroutinesModule.kt$CoroutinesModule$Unconfined
- InjectDispatcher:NetworkMonitor.kt$NetworkMonitor$IO
InstanceOfCheckForException:LocalParticipant.kt$LocalParticipant$e is RpcError
LargeClass:LocalParticipant.kt$LocalParticipant : ParticipantOutgoingDataStreamManagerRpcManager
LargeClass:RTCEngine.kt$RTCEngine : Listener
@@ -106,14 +83,9 @@
NestedBlockDepth:RTCEngine.kt$RTCEngine$private fun makeRTCConfig( serverResponse: Either<JoinResponse, ReconnectResponse>, connectOptions: ConnectOptions, ): RTCConfiguration
NestedBlockDepth:Room.kt$Room$override suspend fun onPostReconnect(isFullReconnect: Boolean)
NestedBlockDepth:SignalClient.kt$SignalClient$override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?)
- SwallowedException:EndpointTokenSource.kt$EndpointTokenSource.<no name provided>$e: Exception
SwallowedException:FlowExt.kt$e: CancellationException
SwallowedException:LocalVideoTrack.kt$LocalVideoTrack$e: Exception
- SwallowedException:NetworkCallbackManager.kt$NetworkCallbackManagerImpl$e: IllegalArgumentException
- SwallowedException:RemoteParticipant.kt$RemoteParticipant$e: Exception
SwallowedException:TextureViewRenderer.kt$TextureViewRenderer$e: NotFoundException
- ThrowingExceptionsWithoutMessageOrCause:LKDebugTree.kt$LKDebugTree$Throwable()
- ThrowingExceptionsWithoutMessageOrCause:SignalClient.kt$SignalClient$Exception()
TooManyFunctions:CommunicationWorkaround.kt$CommunicationWorkaroundImpl : CommunicationWorkaround
TooManyFunctions:E2EEManager.kt$E2EEManager
TooManyFunctions:IncomingDataStreamManager.kt$IncomingDataStreamManagerImpl : IncomingDataStreamManager
@@ -135,17 +107,12 @@
TooManyFunctions:SimulcastVideoEncoderFactoryWrapper.kt$SimulcastVideoEncoderFactoryWrapper$StreamEncoderWrapper : VideoEncoder
TooManyFunctions:SubscriberTransportObserver.kt$SubscriberTransportObserver : ObserverPeerConnectionStateObservable
TooManyFunctions:TextureViewRenderer.kt$TextureViewRenderer : TextureViewCallbackSurfaceTextureListenerVideoSinkRendererEventsNotifier
- UnnecessaryNotNullOperator:E2EEManager.kt$E2EEManager$room!!
- UnnecessaryNotNullOperator:Room.kt$Room$e2eeManager!!
- UnnecessarySafeCall:E2EEManager.kt$E2EEManager$(publication.track!! as LocalAudioTrack)?.sender
- UnnecessarySafeCall:E2EEManager.kt$E2EEManager$(publication.track!! as LocalVideoTrack)?.sender
UnsafeCallOnNullableType:AudioSwitchHandler.kt$AudioSwitchHandler$thread!!
UnsafeCallOnNullableType:CustomAudioProcessingFactory.kt$CustomAudioProcessingFactory.AudioProcessingBridge$buffer!!
UnsafeCallOnNullableType:DataPacketBuffer.kt$DataPacketBuffer$item!!
UnsafeCallOnNullableType:E2EEManager.kt$E2EEManager$participant.identity!!
UnsafeCallOnNullableType:E2EEManager.kt$E2EEManager$publication.track!!
UnsafeCallOnNullableType:E2EEManager.kt$E2EEManager$rtpReceiver!!
- UnsafeCallOnNullableType:E2EEManager.kt$E2EEManager$rtpSender!!
UnsafeCallOnNullableType:E2EEManager.kt$E2EEManager$this.room!!
UnsafeCallOnNullableType:EncodingUtils.kt$EncodingUtils$encodings.first().scalabilityMode!!
UnsafeCallOnNullableType:LocalParticipant.kt$LocalParticipant$options.backupCodec!!
diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/dagger/CoroutinesModule.kt b/livekit-android-sdk/src/main/java/io/livekit/android/dagger/CoroutinesModule.kt
index e35ff539e..9c844c179 100644
--- a/livekit-android-sdk/src/main/java/io/livekit/android/dagger/CoroutinesModule.kt
+++ b/livekit-android-sdk/src/main/java/io/livekit/android/dagger/CoroutinesModule.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023-2024 LiveKit, Inc.
+ * Copyright 2023-2026 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ import javax.inject.Named
* @suppress
*/
@Module
+@Suppress("InjectDispatcher")
internal object CoroutinesModule {
@Provides
@Named(InjectionNames.DISPATCHER_DEFAULT)
diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/dagger/RTCModule.kt b/livekit-android-sdk/src/main/java/io/livekit/android/dagger/RTCModule.kt
index 1198b13d4..e8d78fa43 100644
--- a/livekit-android-sdk/src/main/java/io/livekit/android/dagger/RTCModule.kt
+++ b/livekit-android-sdk/src/main/java/io/livekit/android/dagger/RTCModule.kt
@@ -413,7 +413,7 @@ internal object RTCModule {
fun videoHwAccel() = true
@Provides
- fun sdpFactory() = SdpFactory.getInstance()
+ fun sdpFactory(): SdpFactory = SdpFactory.getInstance()
}
/**
diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/e2ee/E2EEManager.kt b/livekit-android-sdk/src/main/java/io/livekit/android/e2ee/E2EEManager.kt
index cbbeb86eb..bc3fc77b4 100644
--- a/livekit-android-sdk/src/main/java/io/livekit/android/e2ee/E2EEManager.kt
+++ b/livekit-android-sdk/src/main/java/io/livekit/android/e2ee/E2EEManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023-2025 LiveKit, Inc.
+ * Copyright 2023-2026 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,7 +48,7 @@ constructor(
dataPacketCryptorManagerFactory: DataPacketCryptorManager.Factory,
) {
private var room: Room? = null
- private var frameCryptors = mutableMapOf, FrameCryptor>()
+ private val frameCryptors = mutableMapOf, FrameCryptor>()
private var algorithm: FrameCryptorAlgorithm = FrameCryptorAlgorithm.AES_GCM
private lateinit var emitEvent: (roomEvent: RoomEvent) -> Unit?
@@ -116,7 +116,7 @@ constructor(
LKLog.i { "Receiver::onFrameCryptionStateChanged: $trackId, state: $state" }
emitEvent(
RoomEvent.TrackE2EEStateEvent(
- room!!,
+ room,
publication.track!!,
publication,
participant,
@@ -138,15 +138,15 @@ constructor(
}
fun addPublishedTrack(track: Track, publication: TrackPublication, participant: LocalParticipant, room: Room) {
- val rtpSender: RtpSender? = when (publication.track!!) {
- is LocalAudioTrack -> (publication.track!! as LocalAudioTrack)?.sender
- is LocalVideoTrack -> (publication.track!! as LocalVideoTrack)?.sender
+ val rtpSender: RtpSender = when (publication.track!!) {
+ is LocalAudioTrack -> (publication.track!! as LocalAudioTrack).sender
+ is LocalVideoTrack -> (publication.track!! as LocalVideoTrack).sender
else -> {
throw IllegalArgumentException("unsupported track type")
}
} ?: throw IllegalArgumentException("rtpSender is null")
- val frameCryptor = addRtpSender(rtpSender!!, participant.identity!!, publication.sid, publication.track!!.kind.name.lowercase())
+ val frameCryptor = addRtpSender(rtpSender, participant.identity!!, publication.sid, publication.track!!.kind.name.lowercase())
frameCryptor.setObserver { trackId, state ->
LKLog.i { "Sender::onFrameCryptionStateChanged: $trackId, state: $state" }
emitEvent(
diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/room/PeerConnectionTransport.kt b/livekit-android-sdk/src/main/java/io/livekit/android/room/PeerConnectionTransport.kt
index fb6f5a011..efae279a4 100644
--- a/livekit-android-sdk/src/main/java/io/livekit/android/room/PeerConnectionTransport.kt
+++ b/livekit-android-sdk/src/main/java/io/livekit/android/room/PeerConnectionTransport.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023-2025 LiveKit, Inc.
+ * Copyright 2023-2026 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -93,7 +93,7 @@ constructor(
private var renegotiate = false
- private var trackBitrates = mutableMapOf()
+ private val trackBitrates = mutableMapOf()
private var isClosed = AtomicBoolean(false)
private val latestOfferId = AtomicInteger(0)
diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/room/RegionUrlProvider.kt b/livekit-android-sdk/src/main/java/io/livekit/android/room/RegionUrlProvider.kt
index 27e269238..765d1e0d6 100644
--- a/livekit-android-sdk/src/main/java/io/livekit/android/room/RegionUrlProvider.kt
+++ b/livekit-android-sdk/src/main/java/io/livekit/android/room/RegionUrlProvider.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2024-2025 LiveKit, Inc.
+ * Copyright 2024-2026 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,7 +47,7 @@ constructor(
private var regionSettings: RegionSettings? = null
private var lastUpdateAt: Long = 0L
private var settingsCacheTimeMs = 30000
- private var attemptedRegions = mutableSetOf()
+ private val attemptedRegions = mutableSetOf()
fun isLKCloud() = serverUrl.isLKCloud()
diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/room/Room.kt b/livekit-android-sdk/src/main/java/io/livekit/android/room/Room.kt
index c6c28399d..cddf5eda4 100644
--- a/livekit-android-sdk/src/main/java/io/livekit/android/room/Room.kt
+++ b/livekit-android-sdk/src/main/java/io/livekit/android/room/Room.kt
@@ -346,7 +346,7 @@ constructor(
val serverInfo: ServerInfo?
get() = engine.serverInfo
- private var sidToIdentity = mutableMapOf()
+ private val sidToIdentity = mutableMapOf()
private var mutableActiveSpeakers by flowDelegate(emptyList())
@@ -364,7 +364,7 @@ constructor(
private var regionUrlProvider: RegionUrlProvider? = null
private var regionUrl: String? = null
- private var transcriptionReceivedTimes = mutableMapOf()
+ private val transcriptionReceivedTimes = mutableMapOf()
internal var isPrerecording by defaultsManager::isPrerecording
@@ -1521,9 +1521,7 @@ constructor(
* @suppress
*/
override fun onTrackUnpublished(publication: LocalTrackPublication, participant: LocalParticipant) {
- e2eeManager?.let { e2eeManager ->
- e2eeManager!!.removePublishedTrack(publication.track!!, publication, participant, this)
- }
+ e2eeManager?.removePublishedTrack(publication.track!!, publication, participant, this)
eventBus.postEvent(RoomEvent.TrackUnpublished(this, publication, participant), coroutineScope)
}
@@ -1556,9 +1554,7 @@ constructor(
publication: RemoteTrackPublication,
participant: RemoteParticipant,
) {
- e2eeManager?.let { e2eeManager ->
- e2eeManager!!.removeSubscribedTrack(track, publication, participant, this)
- }
+ e2eeManager?.removeSubscribedTrack(track, publication, participant, this)
eventBus.postEvent(RoomEvent.TrackUnsubscribed(this, track, publication, participant), coroutineScope)
}
diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/room/SignalClient.kt b/livekit-android-sdk/src/main/java/io/livekit/android/room/SignalClient.kt
index 7c8889b0a..3e91af25a 100644
--- a/livekit-android-sdk/src/main/java/io/livekit/android/room/SignalClient.kt
+++ b/livekit-android-sdk/src/main/java/io/livekit/android/room/SignalClient.kt
@@ -866,6 +866,7 @@ constructor(
* Can be reused afterwards.
*/
fun close(code: Int = CLOSE_REASON_NORMAL_CLOSURE, reason: String = "Normal Closure", shouldClearQueuedRequests: Boolean = true) {
+ @Suppress("ThrowingExceptionsWithoutMessageOrCause")
LKLog.v(Exception()) { "Closing SignalClient: code = $code, reason = $reason" }
isConnected = false
isReconnecting = false
diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/room/network/NetworkCallbackManager.kt b/livekit-android-sdk/src/main/java/io/livekit/android/room/network/NetworkCallbackManager.kt
index 241f907fb..08ef77bd6 100644
--- a/livekit-android-sdk/src/main/java/io/livekit/android/room/network/NetworkCallbackManager.kt
+++ b/livekit-android-sdk/src/main/java/io/livekit/android/room/network/NetworkCallbackManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 LiveKit, Inc.
+ * Copyright 2024-2026 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -88,6 +88,7 @@ class NetworkCallbackManagerImpl(
@Synchronized
override fun unregisterCallback() {
if (!isClosed.get() && isRegistered.compareAndSet(true, false)) {
+ @Suppress("SwallowedException")
try {
connectivityManager.unregisterNetworkCallback(networkCallback)
} catch (e: IllegalArgumentException) {
diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/room/participant/RemoteParticipant.kt b/livekit-android-sdk/src/main/java/io/livekit/android/room/participant/RemoteParticipant.kt
index 3824d8acd..c0ac0e6e3 100644
--- a/livekit-android-sdk/src/main/java/io/livekit/android/room/participant/RemoteParticipant.kt
+++ b/livekit-android-sdk/src/main/java/io/livekit/android/room/participant/RemoteParticipant.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023-2025 LiveKit, Inc.
+ * Copyright 2023-2026 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -206,6 +206,7 @@ class RemoteParticipant(
val track = publication.track
if (track != null) {
+ @Suppress("SwallowedException")
try {
track.stop()
} catch (e: Exception) {
diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/room/track/LocalScreencastVideoTrack.kt b/livekit-android-sdk/src/main/java/io/livekit/android/room/track/LocalScreencastVideoTrack.kt
index 9cf3b550e..b5dcaa573 100644
--- a/livekit-android-sdk/src/main/java/io/livekit/android/room/track/LocalScreencastVideoTrack.kt
+++ b/livekit-android-sdk/src/main/java/io/livekit/android/room/track/LocalScreencastVideoTrack.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023-2025 LiveKit, Inc.
+ * Copyright 2023-2026 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -210,7 +210,7 @@ constructor(
/**
* Needed to deal with circular dependency.
*/
- class MediaProjectionCallback() : MediaProjection.Callback() {
+ class MediaProjectionCallback : MediaProjection.Callback() {
var track: Track? = null
diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/room/util/CoroutineSdpObserver.kt b/livekit-android-sdk/src/main/java/io/livekit/android/room/util/CoroutineSdpObserver.kt
index 6f839f2f4..0c56f9778 100644
--- a/livekit-android-sdk/src/main/java/io/livekit/android/room/util/CoroutineSdpObserver.kt
+++ b/livekit-android-sdk/src/main/java/io/livekit/android/room/util/CoroutineSdpObserver.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023-2024 LiveKit, Inc.
+ * Copyright 2023-2026 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -53,7 +53,7 @@ internal open class CoroutineSdpObserver : SdpObserver {
}
}
- private var pendingCreate = mutableListOf>>()
+ private val pendingCreate = mutableListOf>>()
private var setOutcome: Either? = null
set(value) {
@@ -75,7 +75,7 @@ internal open class CoroutineSdpObserver : SdpObserver {
}
}
}
- private var pendingSets = mutableListOf>>()
+ private val pendingSets = mutableListOf>>()
override fun onCreateSuccess(sdp: SessionDescription?) {
createOutcome = if (sdp == null) {
diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/stats/NetworkMonitor.kt b/livekit-android-sdk/src/main/java/io/livekit/android/stats/NetworkMonitor.kt
deleted file mode 100644
index efaf05eea..000000000
--- a/livekit-android-sdk/src/main/java/io/livekit/android/stats/NetworkMonitor.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2023 LiveKit, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.livekit.android.stats
-
-import android.content.Context
-import android.net.TrafficStats
-import io.livekit.android.util.LKLog
-import kotlinx.coroutines.*
-import kotlin.coroutines.CoroutineContext
-
-internal class NetworkMonitor(private val context: Context) {
-
- private lateinit var coroutineContext: CoroutineContext
- private lateinit var scope: CoroutineScope
- fun start() {
- coroutineContext = SupervisorJob() + Dispatchers.IO
- scope = CoroutineScope(coroutineContext)
- scope.launch {
- val uid = context.packageManager.getApplicationInfo(context.packageName, 0).uid
-
- var prevTxBytes = TrafficStats.getUidTxBytes(uid)
- var emaTxBytes = 0L
- while (this.isActive) {
- val totalTxBytes = TrafficStats.getUidTxBytes(uid)
- val intervalTxBytes = totalTxBytes - prevTxBytes
- prevTxBytes = totalTxBytes
- emaTxBytes = emaTxBytes / 2 + intervalTxBytes / 2
-
- LKLog.v { "send rate: ${convertBytesToReadableString(emaTxBytes)}" }
-
- delay(1000)
- }
- }
- }
-
- private fun convertBytesToReadableString(bytes: Long): String {
- var num = bytes.toFloat()
- var level = 0
- while (num >= 1024 && level < 2) {
- num /= 1024
- level++
- }
-
- // MBps should be way more than enough.
- val suffix = when (level) {
- 0 -> "Bps"
- 1 -> "kBps"
- 2 -> "MBps"
- else -> throw IllegalStateException("this shouldn't happen. level = $level")
- }
-
- return "$num $suffix"
- }
-
- fun stop() {
- coroutineContext.cancel()
- }
-}
diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/token/CachingTokenSource.kt b/livekit-android-sdk/src/main/java/io/livekit/android/token/CachingTokenSource.kt
index fdedd0e73..32ba37b46 100644
--- a/livekit-android-sdk/src/main/java/io/livekit/android/token/CachingTokenSource.kt
+++ b/livekit-android-sdk/src/main/java/io/livekit/android/token/CachingTokenSource.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 LiveKit, Inc.
+ * Copyright 2025-2026 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -127,7 +127,7 @@ interface TokenStore {
)
}
-internal class InMemoryTokenStore() : TokenStore {
+internal class InMemoryTokenStore : TokenStore {
var item: TokenStore.Item? = null
override suspend fun retrieve(): TokenStore.Item? = item
diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/token/EndpointTokenSource.kt b/livekit-android-sdk/src/main/java/io/livekit/android/token/EndpointTokenSource.kt
index b68886c0b..6f3cdd30a 100644
--- a/livekit-android-sdk/src/main/java/io/livekit/android/token/EndpointTokenSource.kt
+++ b/livekit-android-sdk/src/main/java/io/livekit/android/token/EndpointTokenSource.kt
@@ -17,6 +17,7 @@
package io.livekit.android.token
import io.livekit.android.dagger.globalOkHttpClient
+import io.livekit.android.util.LKLog
import io.livekit.android.util.rethrowIfCancellationSignal
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.ExperimentalSerializationApi
@@ -110,6 +111,7 @@ internal interface EndpointTokenSource : ConfigurableTokenSource {
try {
tokenResponse = snakeCaseJson.decodeFromString(bodyStr)
} catch (e: Exception) {
+ LKLog.d(e) { "Unable to decode token source json with snake_case, trying camelCase." }
}
if (tokenResponse == null) {
diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/util/LKDebugTree.kt b/livekit-android-sdk/src/main/java/io/livekit/android/util/LKDebugTree.kt
index 7dcf1b87d..b5281bddb 100644
--- a/livekit-android-sdk/src/main/java/io/livekit/android/util/LKDebugTree.kt
+++ b/livekit-android-sdk/src/main/java/io/livekit/android/util/LKDebugTree.kt
@@ -41,6 +41,7 @@ open class LKDebugTree {
val tag: String?
get() =
+ @Suppress("ThrowingExceptionsWithoutMessageOrCause")
Throwable()
.stackTrace
.first { it.className !in fqcnIgnore }