diff --git a/.changeset/fix-camera-events-dispatch-race.md b/.changeset/fix-camera-events-dispatch-race.md new file mode 100644 index 00000000..0b9fd9d0 --- /dev/null +++ b/.changeset/fix-camera-events-dispatch-race.md @@ -0,0 +1,5 @@ +--- +"client-sdk-android": patch +--- + +Fixed data race in `CameraEventsDispatchHandler` by using `CopyOnWriteArraySet` for thread-safe iteration. \ No newline at end of file diff --git a/.changeset/fix-signal-client-volatile.md b/.changeset/fix-signal-client-volatile.md new file mode 100644 index 00000000..983f3399 --- /dev/null +++ b/.changeset/fix-signal-client-volatile.md @@ -0,0 +1,5 @@ +--- +"client-sdk-android": patch +--- + +Fix thread visibility issues in SignalClient that could cause messages to be silently dropped. \ No newline at end of file diff --git a/.changeset/fix-track-bitrates-race-condition.md b/.changeset/fix-track-bitrates-race-condition.md new file mode 100644 index 00000000..a992d1bd --- /dev/null +++ b/.changeset/fix-track-bitrates-race-condition.md @@ -0,0 +1,5 @@ +--- +"client-sdk-android": patch +--- + +Fixed race condition in `PeerConnectionTransport.trackBitrates` by ensuring writes happen on the RTC thread. \ No newline at end of file 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 fb6f5a01..09408480 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 @@ -325,11 +325,15 @@ constructor( } fun registerTrackBitrateInfo(cid: String, trackBitrateInfo: TrackBitrateInfo) { - trackBitrates[TrackBitrateInfoKey.Cid(cid)] = trackBitrateInfo + executeRTCIfNotClosed { + trackBitrates[TrackBitrateInfoKey.Cid(cid)] = trackBitrateInfo + } } fun registerTrackBitrateInfo(transceiver: RtpTransceiver, trackBitrateInfo: TrackBitrateInfo) { - trackBitrates[TrackBitrateInfoKey.Transceiver(transceiver)] = trackBitrateInfo + executeRTCIfNotClosed { + trackBitrates[TrackBitrateInfoKey.Transceiver(transceiver)] = trackBitrateInfo + } } suspend fun isConnected(): Boolean { 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 d6d6e924..735fed9c 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 @@ -79,9 +79,14 @@ constructor( private val ioDispatcher: CoroutineDispatcher, private val networkInfo: NetworkInfo, ) : WebSocketListener() { + @Volatile var isConnected = false private set + + @Volatile private var currentWs: WebSocket? = null + + @Volatile private var isReconnecting: Boolean = false var listener: Listener? = null internal var serverVersion: Semver? = null diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/room/track/video/CameraEventsDispatchHandler.kt b/livekit-android-sdk/src/main/java/io/livekit/android/room/track/video/CameraEventsDispatchHandler.kt index b87287d7..d2f57f83 100644 --- a/livekit-android-sdk/src/main/java/io/livekit/android/room/track/video/CameraEventsDispatchHandler.kt +++ b/livekit-android-sdk/src/main/java/io/livekit/android/room/track/video/CameraEventsDispatchHandler.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 LiveKit, Inc. + * Copyright 2023-2025 LiveKit, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,61 +17,54 @@ package io.livekit.android.room.track.video import livekit.org.webrtc.CameraVideoCapturer.CameraEventsHandler +import java.util.concurrent.CopyOnWriteArraySet /** * Dispatches CameraEventsHandler callbacks to registered handlers. */ class CameraEventsDispatchHandler : CameraEventsHandler { - private val handlers = mutableSetOf() + private val handlers = CopyOnWriteArraySet() - @Synchronized fun registerHandler(handler: CameraEventsHandler) { handlers.add(handler) } - @Synchronized fun unregisterHandler(handler: CameraEventsHandler) { handlers.remove(handler) } override fun onCameraError(errorDescription: String) { - val handlersCopy = handlers.toMutableSet() - for (handler in handlersCopy) { + for (handler in handlers) { handler.onCameraError(errorDescription) } } override fun onCameraDisconnected() { - val handlersCopy = handlers.toMutableSet() - for (handler in handlersCopy) { + for (handler in handlers) { handler.onCameraDisconnected() } } override fun onCameraFreezed(errorDescription: String) { - val handlersCopy = handlers.toMutableSet() - for (handler in handlersCopy) { + for (handler in handlers) { handler.onCameraFreezed(errorDescription) } } override fun onCameraOpening(cameraName: String) { - val handlersCopy = handlers.toMutableSet() - for (handler in handlersCopy) { + for (handler in handlers) { handler.onCameraOpening(cameraName) } } override fun onFirstFrameAvailable() { - val handlersCopy = handlers.toMutableSet() - for (handler in handlersCopy) { + for (handler in handlers) { handler.onFirstFrameAvailable() } } override fun onCameraClosed() { - val handlersCopy = handlers.toMutableSet() - for (handler in handlersCopy) { + for (handler in handlers) { handler.onCameraClosed() } }