diff --git a/.changeset/new-shrimps-rest.md b/.changeset/new-shrimps-rest.md new file mode 100644 index 00000000..998def25 --- /dev/null +++ b/.changeset/new-shrimps-rest.md @@ -0,0 +1,5 @@ +--- +"client-sdk-android": patch +--- + +Properly update the server info after reconnect diff --git a/livekit-android-sdk/detekt-baseline-release.xml b/livekit-android-sdk/detekt-baseline-release.xml index 418e8ea5..eb7ed040 100644 --- a/livekit-android-sdk/detekt-baseline-release.xml +++ b/livekit-android-sdk/detekt-baseline-release.xml @@ -72,6 +72,7 @@ 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?) + NestedBlockDepth:SignalClient.kt$SignalClient$private fun handleSignalResponse(ws: WebSocket, response: LivekitRtc.SignalResponse) SwallowedException:FlowExt.kt$e: CancellationException SwallowedException:LocalVideoTrack.kt$LocalVideoTrack$e: Exception SwallowedException:TextureViewRenderer.kt$TextureViewRenderer$e: NotFoundException diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/room/RTCEngine.kt b/livekit-android-sdk/src/main/java/io/livekit/android/room/RTCEngine.kt index dee0adab..03727e77 100644 --- a/livekit-android-sdk/src/main/java/io/livekit/android/room/RTCEngine.kt +++ b/livekit-android-sdk/src/main/java/io/livekit/android/room/RTCEngine.kt @@ -619,6 +619,8 @@ internal constructor( subscriber?.updateRTCConfig(rtcConfig) publisher?.updateRTCConfig(rtcConfig) lastMessageSeq = reconnectResponse.lastMessageSeq + } else { + LKLog.w { "Did not receive reconnect response" } } client.onReadyForResponses() } catch (e: Exception) { 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 5951becc..3f243270 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 @@ -98,15 +98,8 @@ constructor( internal var lastOptions: ConnectOptions? = null private var lastRoomOptions: RoomOptions? = null - // join will always return a JoinResponse. - // reconnect will return a ReconnectResponse or a Unit if a different response was received. @Volatile - private var joinContinuation: CancellableContinuation< - Either< - JoinResponse, - Either, - >, - >? = null + private var joinContinuation: CancellableContinuation? = null private lateinit var coroutineScope: CloseableCoroutineScope /** @@ -141,8 +134,10 @@ constructor( options: ConnectOptions = ConnectOptions(), roomOptions: RoomOptions = RoomOptions(), ): JoinResponse { - val joinResponse = connect(url, token, options, roomOptions) - return (joinResponse as Either.Left).value + return when (val result = connect(url, token, options, roomOptions)) { + is ConnectResult.Join -> result.response + else -> throw IllegalStateException("Unexpected response during join: $result") + } } /** @@ -151,17 +146,22 @@ constructor( @Throws(Exception::class) @VisibleForTesting suspend fun reconnect(url: String, token: String, participantSid: String?): Either { - val reconnectResponse = connect( - url, - token, - (lastOptions ?: ConnectOptions()).copy() - .apply { - reconnect = true - this.participantSid = participantSid - }, - lastRoomOptions ?: RoomOptions(), - ) - return (reconnectResponse as Either.Right).value + return when ( + val result = connect( + url, + token, + (lastOptions ?: ConnectOptions()).copy() + .apply { + reconnect = true + this.participantSid = participantSid + }, + lastRoomOptions ?: RoomOptions(), + ) + ) { + is ConnectResult.Reconnect -> Either.Left(result.response) + is ConnectResult.OtherResponse -> Either.Right(Unit) + is ConnectResult.Join -> throw IllegalStateException("Unexpected join response during reconnect") + } } private suspend fun connect( @@ -169,7 +169,7 @@ constructor( token: String, options: ConnectOptions, roomOptions: RoomOptions, - ): Either> { + ): ConnectResult { // Clean up any pre-existing connection. close(reason = "Starting new connection", shouldClearQueuedRequests = false) @@ -691,7 +691,7 @@ constructor( edition = ServerInfo.Edition.fromProto(response.join.serverInfo.edition), version = serverVersion ) - joinContinuation?.resumeWith(Result.success(Either.Left(response.join))) + joinContinuation?.resumeWith(Result.success(ConnectResult.Join(response.join))) joinContinuation = null } else if (response.hasLeave()) { // Some reconnects may immediately send leave back without a join response first. @@ -711,10 +711,21 @@ constructor( startPingJob() if (response.hasReconnect()) { - joinContinuation?.resumeWith(Result.success(Either.Right(Either.Left(response.reconnect)))) + if (response.reconnect.hasServerInfo()) { + try { + serverVersion = Semver(response.reconnect.serverInfo.version) + } catch (t: Throwable) { + LKLog.w(t) { "Thrown while trying to parse server version from reconnect." } + } + serverInfo = ServerInfo( + edition = ServerInfo.Edition.fromProto(response.reconnect.serverInfo.edition), + version = serverVersion, + ) + } + joinContinuation?.resumeWith(Result.success(ConnectResult.Reconnect(response.reconnect))) joinContinuation = null } else { - joinContinuation?.resumeWith(Result.success(Either.Right(Either.Right(Unit)))) + joinContinuation?.resumeWith(Result.success(ConnectResult.OtherResponse)) joinContinuation = null // Non-reconnect response, handle normally shouldProcessMessage = true @@ -829,7 +840,8 @@ constructor( } LivekitRtc.SignalResponse.MessageCase.RECONNECT -> { - // TODO + // Handshake-only message; handled in handleSignalResponse() before connection. + LKLog.d { "ignoring reconnect response received after connected" } } LivekitRtc.SignalResponse.MessageCase.SUBSCRIPTION_RESPONSE -> { @@ -960,6 +972,16 @@ constructor( fun onLocalTrackSubscribed(trackSubscribed: LivekitRtc.TrackSubscribed) } + /** + * Result of waiting for the initial signal response after opening the WebSocket. + * Join always yields [Join]; reconnect yields [Reconnect] or [OtherResponse]. + */ + private sealed class ConnectResult { + data class Join(val response: JoinResponse) : ConnectResult() + data class Reconnect(val response: ReconnectResponse) : ConnectResult() + data object OtherResponse : ConnectResult() + } + companion object { const val CONNECT_QUERY_TOKEN = "access_token" const val CONNECT_QUERY_RECONNECT = "reconnect"