正在显示
8 个修改的文件
包含
99 行增加
和
14 行删除
| @@ -52,7 +52,7 @@ data class ConnectOptions( | @@ -52,7 +52,7 @@ data class ConnectOptions( | ||
| 52 | /** | 52 | /** |
| 53 | * the protocol version to use with the server. | 53 | * the protocol version to use with the server. |
| 54 | */ | 54 | */ |
| 55 | - val protocolVersion: ProtocolVersion = ProtocolVersion.v12, | 55 | + val protocolVersion: ProtocolVersion = ProtocolVersion.v13, |
| 56 | ) { | 56 | ) { |
| 57 | internal var reconnect: Boolean = false | 57 | internal var reconnect: Boolean = false |
| 58 | internal var participantSid: String? = null | 58 | internal var participantSid: String? = null |
| @@ -58,6 +58,7 @@ import livekit.LivekitModels | @@ -58,6 +58,7 @@ import livekit.LivekitModels | ||
| 58 | import livekit.LivekitModels.AudioTrackFeature | 58 | import livekit.LivekitModels.AudioTrackFeature |
| 59 | import livekit.LivekitRtc | 59 | import livekit.LivekitRtc |
| 60 | import livekit.LivekitRtc.JoinResponse | 60 | import livekit.LivekitRtc.JoinResponse |
| 61 | +import livekit.LivekitRtc.LeaveRequest | ||
| 61 | import livekit.LivekitRtc.ReconnectResponse | 62 | import livekit.LivekitRtc.ReconnectResponse |
| 62 | import livekit.org.webrtc.DataChannel | 63 | import livekit.org.webrtc.DataChannel |
| 63 | import livekit.org.webrtc.IceCandidate | 64 | import livekit.org.webrtc.IceCandidate |
| @@ -939,14 +940,34 @@ internal constructor( | @@ -939,14 +940,34 @@ internal constructor( | ||
| 939 | listener?.onConnectionQuality(updates) | 940 | listener?.onConnectionQuality(updates) |
| 940 | } | 941 | } |
| 941 | 942 | ||
| 942 | - override fun onLeave(leave: LivekitRtc.LeaveRequest) { | ||
| 943 | - if (leave.canReconnect) { | ||
| 944 | - // reconnect will be triggered on close. | ||
| 945 | - fullReconnectOnNext = true | ||
| 946 | - } else { | ||
| 947 | - close() | ||
| 948 | - val disconnectReason = leave.reason.convert() | ||
| 949 | - listener?.onEngineDisconnected(disconnectReason) | 943 | + override fun onLeave(leave: LeaveRequest) { |
| 944 | + LKLog.d { "leave request received: reason = ${leave.reason.name}" } | ||
| 945 | + if (leave.hasRegions()) { | ||
| 946 | + regionUrlProvider?.let { | ||
| 947 | + it.setServerReportedRegions(RegionSettings.fromProto(leave.regions)) | ||
| 948 | + } | ||
| 949 | + } | ||
| 950 | + | ||
| 951 | + when { | ||
| 952 | + leave.action == LeaveRequest.Action.RESUME -> { | ||
| 953 | + // resume will be triggered on close. | ||
| 954 | + // TODO: trigger immediately. | ||
| 955 | + fullReconnectOnNext = false | ||
| 956 | + } | ||
| 957 | + | ||
| 958 | + leave.action == LeaveRequest.Action.RECONNECT || | ||
| 959 | + // canReconnect is deprecated protocol version >= 13 | ||
| 960 | + leave.canReconnect -> { | ||
| 961 | + // resume will be triggered on close. | ||
| 962 | + // TODO: trigger immediately. | ||
| 963 | + fullReconnectOnNext = true | ||
| 964 | + } | ||
| 965 | + | ||
| 966 | + else -> { | ||
| 967 | + close() | ||
| 968 | + val disconnectReason = leave.reason.convert() | ||
| 969 | + listener?.onEngineDisconnected(disconnectReason) | ||
| 970 | + } | ||
| 950 | } | 971 | } |
| 951 | } | 972 | } |
| 952 | 973 |
| @@ -28,6 +28,7 @@ import kotlinx.coroutines.coroutineScope | @@ -28,6 +28,7 @@ import kotlinx.coroutines.coroutineScope | ||
| 28 | import kotlinx.serialization.Serializable | 28 | import kotlinx.serialization.Serializable |
| 29 | import kotlinx.serialization.decodeFromString | 29 | import kotlinx.serialization.decodeFromString |
| 30 | import kotlinx.serialization.json.Json | 30 | import kotlinx.serialization.json.Json |
| 31 | +import livekit.LivekitRtc | ||
| 31 | import okhttp3.OkHttpClient | 32 | import okhttp3.OkHttpClient |
| 32 | import okhttp3.Request | 33 | import okhttp3.Request |
| 33 | import java.net.URI | 34 | import java.net.URI |
| @@ -100,6 +101,11 @@ constructor( | @@ -100,6 +101,11 @@ constructor( | ||
| 100 | attemptedRegions.clear() | 101 | attemptedRegions.clear() |
| 101 | } | 102 | } |
| 102 | 103 | ||
| 104 | + fun setServerReportedRegions(regionSettings: RegionSettings) { | ||
| 105 | + this.regionSettings = regionSettings | ||
| 106 | + this.lastUpdateAt = SystemClock.elapsedRealtime() | ||
| 107 | + } | ||
| 108 | + | ||
| 103 | @AssistedFactory | 109 | @AssistedFactory |
| 104 | interface Factory { | 110 | interface Factory { |
| 105 | fun create(serverUrl: URI, token: String): RegionUrlProvider | 111 | fun create(serverUrl: URI, token: String): RegionUrlProvider |
| @@ -136,7 +142,17 @@ fun setRegionUrlProviderTesting(enable: Boolean) { | @@ -136,7 +142,17 @@ fun setRegionUrlProviderTesting(enable: Boolean) { | ||
| 136 | * @suppress | 142 | * @suppress |
| 137 | */ | 143 | */ |
| 138 | @Serializable | 144 | @Serializable |
| 139 | -data class RegionSettings(val regions: List<RegionInfo>) | 145 | +data class RegionSettings(val regions: List<RegionInfo>) { |
| 146 | + companion object { | ||
| 147 | + fun fromProto(proto: LivekitRtc.RegionSettings): RegionSettings { | ||
| 148 | + return RegionSettings( | ||
| 149 | + proto.regionsList.map { region -> | ||
| 150 | + RegionInfo(region.region, region.url, region.distance) | ||
| 151 | + }, | ||
| 152 | + ) | ||
| 153 | + } | ||
| 154 | + } | ||
| 155 | +} | ||
| 140 | 156 | ||
| 141 | /** | 157 | /** |
| 142 | * @suppress | 158 | * @suppress |
| @@ -113,6 +113,7 @@ constructor( | @@ -113,6 +113,7 @@ constructor( | ||
| 113 | NODE_FAILURE, | 113 | NODE_FAILURE, |
| 114 | MIGRATION, | 114 | MIGRATION, |
| 115 | SERVER_LEAVE, | 115 | SERVER_LEAVE, |
| 116 | + SERVER_LEAVE_FULL_RECONNECT, | ||
| 116 | } | 117 | } |
| 117 | 118 | ||
| 118 | @Serializable | 119 | @Serializable |
| @@ -878,6 +879,7 @@ constructor( | @@ -878,6 +879,7 @@ constructor( | ||
| 878 | SimulateScenario.NODE_FAILURE -> builder.nodeFailure = true | 879 | SimulateScenario.NODE_FAILURE -> builder.nodeFailure = true |
| 879 | SimulateScenario.MIGRATION -> builder.migration = true | 880 | SimulateScenario.MIGRATION -> builder.migration = true |
| 880 | SimulateScenario.SERVER_LEAVE -> builder.serverLeave = true | 881 | SimulateScenario.SERVER_LEAVE -> builder.serverLeave = true |
| 882 | + SimulateScenario.SERVER_LEAVE_FULL_RECONNECT -> builder.leaveRequestFullReconnect = true | ||
| 881 | } | 883 | } |
| 882 | sendSimulateScenario(builder.build()) | 884 | sendSimulateScenario(builder.build()) |
| 883 | } | 885 | } |
| @@ -540,9 +540,16 @@ constructor( | @@ -540,9 +540,16 @@ constructor( | ||
| 540 | } | 540 | } |
| 541 | 541 | ||
| 542 | fun sendLeave() { | 542 | fun sendLeave() { |
| 543 | - val request = LivekitRtc.SignalRequest.newBuilder() | ||
| 544 | - .setLeave(LivekitRtc.LeaveRequest.newBuilder().build()) | ||
| 545 | - .build() | 543 | + val request = with(LivekitRtc.SignalRequest.newBuilder()) { |
| 544 | + leave = with(LivekitRtc.LeaveRequest.newBuilder()) { | ||
| 545 | + reason = LivekitModels.DisconnectReason.CLIENT_INITIATED | ||
| 546 | + // server doesn't process this field, keeping it here to indicate the intent of a full disconnect | ||
| 547 | + action = LivekitRtc.LeaveRequest.Action.DISCONNECT | ||
| 548 | + build() | ||
| 549 | + } | ||
| 550 | + build() | ||
| 551 | + } | ||
| 552 | + | ||
| 546 | sendRequest(request) | 553 | sendRequest(request) |
| 547 | } | 554 | } |
| 548 | 555 | ||
| @@ -906,4 +913,7 @@ enum class ProtocolVersion(val value: Int) { | @@ -906,4 +913,7 @@ enum class ProtocolVersion(val value: Int) { | ||
| 906 | v10(10), | 913 | v10(10), |
| 907 | v11(11), | 914 | v11(11), |
| 908 | v12(12), | 915 | v12(12), |
| 916 | + | ||
| 917 | + // new leave request handling | ||
| 918 | + v13(13), | ||
| 909 | } | 919 | } |
| @@ -393,6 +393,10 @@ class CallViewModel( | @@ -393,6 +393,10 @@ class CallViewModel( | ||
| 393 | room.sendSimulateScenario(Room.SimulateScenario.NODE_FAILURE) | 393 | room.sendSimulateScenario(Room.SimulateScenario.NODE_FAILURE) |
| 394 | } | 394 | } |
| 395 | 395 | ||
| 396 | + fun simulateServerLeaveFullReconnect() { | ||
| 397 | + room.sendSimulateScenario(Room.SimulateScenario.SERVER_LEAVE_FULL_RECONNECT) | ||
| 398 | + } | ||
| 399 | + | ||
| 396 | fun reconnect() { | 400 | fun reconnect() { |
| 397 | Timber.e { "Reconnecting." } | 401 | Timber.e { "Reconnecting." } |
| 398 | mutablePrimarySpeaker.value = null | 402 | mutablePrimarySpeaker.value = null |
| @@ -109,6 +109,7 @@ class CallActivity : AppCompatActivity() { | @@ -109,6 +109,7 @@ class CallActivity : AppCompatActivity() { | ||
| 109 | onSendMessage = { viewModel.sendData(it) }, | 109 | onSendMessage = { viewModel.sendData(it) }, |
| 110 | onSimulateMigration = { viewModel.simulateMigration() }, | 110 | onSimulateMigration = { viewModel.simulateMigration() }, |
| 111 | onSimulateNodeFailure = { viewModel.simulateNodeFailure() }, | 111 | onSimulateNodeFailure = { viewModel.simulateNodeFailure() }, |
| 112 | + onSimulateLeaveFullReconnect = { viewModel.simulateServerLeaveFullReconnect() }, | ||
| 112 | fullReconnect = { viewModel.reconnect() }, | 113 | fullReconnect = { viewModel.reconnect() }, |
| 113 | ) | 114 | ) |
| 114 | } | 115 | } |
| @@ -164,6 +165,7 @@ class CallActivity : AppCompatActivity() { | @@ -164,6 +165,7 @@ class CallActivity : AppCompatActivity() { | ||
| 164 | onSimulateMigration: () -> Unit = {}, | 165 | onSimulateMigration: () -> Unit = {}, |
| 165 | onSimulateNodeFailure: () -> Unit = {}, | 166 | onSimulateNodeFailure: () -> Unit = {}, |
| 166 | fullReconnect: () -> Unit = {}, | 167 | fullReconnect: () -> Unit = {}, |
| 168 | + onSimulateLeaveFullReconnect: () -> Unit, | ||
| 167 | ) { | 169 | ) { |
| 168 | AppTheme(darkTheme = true) { | 170 | AppTheme(darkTheme = true) { |
| 169 | ConstraintLayout( | 171 | ConstraintLayout( |
| @@ -432,6 +434,7 @@ class CallActivity : AppCompatActivity() { | @@ -432,6 +434,7 @@ class CallActivity : AppCompatActivity() { | ||
| 432 | onDismissRequest = { showDebugDialog = false }, | 434 | onDismissRequest = { showDebugDialog = false }, |
| 433 | simulateMigration = { onSimulateMigration() }, | 435 | simulateMigration = { onSimulateMigration() }, |
| 434 | simulateNodeFailure = { onSimulateNodeFailure() }, | 436 | simulateNodeFailure = { onSimulateNodeFailure() }, |
| 437 | + simulateLeaveFullReconnect = { onSimulateLeaveFullReconnect() }, | ||
| 435 | fullReconnect = { fullReconnect() }, | 438 | fullReconnect = { fullReconnect() }, |
| 436 | ) | 439 | ) |
| 437 | } | 440 | } |
| 1 | +/* | ||
| 2 | + * Copyright 2024 LiveKit, Inc. | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 1 | package io.livekit.android.composesample.ui | 17 | package io.livekit.android.composesample.ui |
| 2 | 18 | ||
| 3 | import androidx.compose.foundation.background | 19 | import androidx.compose.foundation.background |
| 4 | -import androidx.compose.foundation.layout.* | 20 | +import androidx.compose.foundation.layout.Column |
| 21 | +import androidx.compose.foundation.layout.Spacer | ||
| 22 | +import androidx.compose.foundation.layout.fillMaxWidth | ||
| 23 | +import androidx.compose.foundation.layout.height | ||
| 24 | +import androidx.compose.foundation.layout.padding | ||
| 5 | import androidx.compose.foundation.shape.RoundedCornerShape | 25 | import androidx.compose.foundation.shape.RoundedCornerShape |
| 6 | import androidx.compose.material.Button | 26 | import androidx.compose.material.Button |
| 7 | import androidx.compose.material.Text | 27 | import androidx.compose.material.Text |
| @@ -20,6 +40,7 @@ fun DebugMenuDialog( | @@ -20,6 +40,7 @@ fun DebugMenuDialog( | ||
| 20 | simulateMigration: () -> Unit = {}, | 40 | simulateMigration: () -> Unit = {}, |
| 21 | fullReconnect: () -> Unit = {}, | 41 | fullReconnect: () -> Unit = {}, |
| 22 | simulateNodeFailure: () -> Unit = {}, | 42 | simulateNodeFailure: () -> Unit = {}, |
| 43 | + simulateLeaveFullReconnect: () -> Unit = {}, | ||
| 23 | ) { | 44 | ) { |
| 24 | Dialog(onDismissRequest = onDismissRequest) { | 45 | Dialog(onDismissRequest = onDismissRequest) { |
| 25 | Column( | 46 | Column( |
| @@ -44,6 +65,14 @@ fun DebugMenuDialog( | @@ -44,6 +65,14 @@ fun DebugMenuDialog( | ||
| 44 | }) { | 65 | }) { |
| 45 | Text("Simulate Node Failure") | 66 | Text("Simulate Node Failure") |
| 46 | } | 67 | } |
| 68 | + Button( | ||
| 69 | + onClick = { | ||
| 70 | + simulateLeaveFullReconnect() | ||
| 71 | + onDismissRequest() | ||
| 72 | + }, | ||
| 73 | + ) { | ||
| 74 | + Text("Simulate Server Leave Full Reconnect") | ||
| 75 | + } | ||
| 47 | Button(onClick = { | 76 | Button(onClick = { |
| 48 | fullReconnect() | 77 | fullReconnect() |
| 49 | onDismissRequest() | 78 | onDismissRequest() |
-
请 注册 或 登录 后发表评论