Committed by
GitHub
Add RoomEvent.ParticipantAttributesChanged (#473)
* Add RoomEvent.ParticipantAttributesChanged * fix test
正在显示
7 个修改的文件
包含
165 行增加
和
8 行删除
| @@ -89,6 +89,22 @@ sealed class RoomEvent(val room: Room) : Event() { | @@ -89,6 +89,22 @@ sealed class RoomEvent(val room: Room) : Event() { | ||
| 89 | val prevMetadata: String?, | 89 | val prevMetadata: String?, |
| 90 | ) : RoomEvent(room) | 90 | ) : RoomEvent(room) |
| 91 | 91 | ||
| 92 | + /** | ||
| 93 | + * When a participant's attributes are changed, fired for all participants | ||
| 94 | + */ | ||
| 95 | + class ParticipantAttributesChanged( | ||
| 96 | + room: Room, | ||
| 97 | + participant: Participant, | ||
| 98 | + /** | ||
| 99 | + * The attributes that have changed and their new associated values. | ||
| 100 | + */ | ||
| 101 | + val changedAttributes: Map<String, String>, | ||
| 102 | + /** | ||
| 103 | + * The old attributes prior to change. | ||
| 104 | + */ | ||
| 105 | + val oldAttributes: Map<String, String>, | ||
| 106 | + ) : RoomEvent(room) | ||
| 107 | + | ||
| 92 | class ParticipantNameChanged( | 108 | class ParticipantNameChanged( |
| 93 | room: Room, | 109 | room: Room, |
| 94 | val participant: Participant, | 110 | val participant: Participant, |
| @@ -576,6 +576,17 @@ constructor( | @@ -576,6 +576,17 @@ constructor( | ||
| 576 | ) | 576 | ) |
| 577 | } | 577 | } |
| 578 | 578 | ||
| 579 | + is ParticipantEvent.AttributesChanged -> { | ||
| 580 | + emitWhenConnected( | ||
| 581 | + RoomEvent.ParticipantAttributesChanged( | ||
| 582 | + this@Room, | ||
| 583 | + it.participant, | ||
| 584 | + it.changedAttributes, | ||
| 585 | + it.oldAttributes, | ||
| 586 | + ), | ||
| 587 | + ) | ||
| 588 | + } | ||
| 589 | + | ||
| 579 | is ParticipantEvent.NameChanged -> { | 590 | is ParticipantEvent.NameChanged -> { |
| 580 | emitWhenConnected( | 591 | emitWhenConnected( |
| 581 | RoomEvent.ParticipantNameChanged( | 592 | RoomEvent.ParticipantNameChanged( |
| @@ -684,6 +695,17 @@ constructor( | @@ -684,6 +695,17 @@ constructor( | ||
| 684 | ) | 695 | ) |
| 685 | } | 696 | } |
| 686 | 697 | ||
| 698 | + is ParticipantEvent.AttributesChanged -> { | ||
| 699 | + emitWhenConnected( | ||
| 700 | + RoomEvent.ParticipantAttributesChanged( | ||
| 701 | + this@Room, | ||
| 702 | + it.participant, | ||
| 703 | + it.changedAttributes, | ||
| 704 | + it.oldAttributes, | ||
| 705 | + ), | ||
| 706 | + ) | ||
| 707 | + } | ||
| 708 | + | ||
| 687 | is ParticipantEvent.NameChanged -> { | 709 | is ParticipantEvent.NameChanged -> { |
| 688 | emitWhenConnected( | 710 | emitWhenConnected( |
| 689 | RoomEvent.ParticipantNameChanged( | 711 | RoomEvent.ParticipantNameChanged( |
livekit-android-test/src/test/java/io/livekit/android/room/RoomParticipantEventMockE2ETest.kt
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright 2023-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 | + | ||
| 17 | +package io.livekit.android.room | ||
| 18 | + | ||
| 19 | +import io.livekit.android.events.RoomEvent | ||
| 20 | +import io.livekit.android.test.MockE2ETest | ||
| 21 | +import io.livekit.android.test.assert.assertIsClass | ||
| 22 | +import io.livekit.android.test.events.EventCollector | ||
| 23 | +import io.livekit.android.test.mock.TestData | ||
| 24 | +import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
| 25 | +import livekit.LivekitRtc.ParticipantUpdate | ||
| 26 | +import livekit.LivekitRtc.SignalResponse | ||
| 27 | +import org.junit.Assert.assertEquals | ||
| 28 | +import org.junit.Test | ||
| 29 | +import org.junit.runner.RunWith | ||
| 30 | +import org.robolectric.RobolectricTestRunner | ||
| 31 | + | ||
| 32 | +@ExperimentalCoroutinesApi | ||
| 33 | +@RunWith(RobolectricTestRunner::class) | ||
| 34 | +class RoomParticipantEventMockE2ETest : MockE2ETest() { | ||
| 35 | + | ||
| 36 | + @Test | ||
| 37 | + fun localParticipantAttributesChangedEvent() = runTest { | ||
| 38 | + connect() | ||
| 39 | + wsFactory.ws.clearRequests() | ||
| 40 | + wsFactory.registerSignalRequestHandler { request -> | ||
| 41 | + if (request.hasUpdateMetadata()) { | ||
| 42 | + val newInfo = with(TestData.LOCAL_PARTICIPANT.toBuilder()) { | ||
| 43 | + putAllAttributes(request.updateMetadata.attributesMap) | ||
| 44 | + build() | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + val response = with(SignalResponse.newBuilder()) { | ||
| 48 | + update = with(ParticipantUpdate.newBuilder()) { | ||
| 49 | + addParticipants(newInfo) | ||
| 50 | + build() | ||
| 51 | + } | ||
| 52 | + build() | ||
| 53 | + } | ||
| 54 | + wsFactory.receiveMessage(response) | ||
| 55 | + return@registerSignalRequestHandler true | ||
| 56 | + } | ||
| 57 | + return@registerSignalRequestHandler false | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + val newAttributes = mapOf("attribute" to "changedValue") | ||
| 61 | + | ||
| 62 | + val collector = EventCollector(room.events, coroutineRule.scope) | ||
| 63 | + room.localParticipant.updateAttributes(newAttributes) | ||
| 64 | + | ||
| 65 | + val events = collector.stopCollecting() | ||
| 66 | + | ||
| 67 | + assertEquals(1, events.size) | ||
| 68 | + assertIsClass(RoomEvent.ParticipantAttributesChanged::class.java, events.first()) | ||
| 69 | + } | ||
| 70 | +} |
| @@ -245,6 +245,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() { | @@ -245,6 +245,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() { | ||
| 245 | listOf( | 245 | listOf( |
| 246 | RoomEvent.ParticipantMetadataChanged::class.java, | 246 | RoomEvent.ParticipantMetadataChanged::class.java, |
| 247 | RoomEvent.ParticipantNameChanged::class.java, | 247 | RoomEvent.ParticipantNameChanged::class.java, |
| 248 | + RoomEvent.ParticipantAttributesChanged::class.java, | ||
| 248 | ), | 249 | ), |
| 249 | roomEvents, | 250 | roomEvents, |
| 250 | ) | 251 | ) |
| @@ -397,6 +397,10 @@ class CallViewModel( | @@ -397,6 +397,10 @@ class CallViewModel( | ||
| 397 | room.sendSimulateScenario(Room.SimulateScenario.SERVER_LEAVE_FULL_RECONNECT) | 397 | room.sendSimulateScenario(Room.SimulateScenario.SERVER_LEAVE_FULL_RECONNECT) |
| 398 | } | 398 | } |
| 399 | 399 | ||
| 400 | + fun updateAttribute(key: String, value: String) { | ||
| 401 | + room.localParticipant.updateAttributes(mapOf(key to value)) | ||
| 402 | + } | ||
| 403 | + | ||
| 400 | fun reconnect() { | 404 | fun reconnect() { |
| 401 | Timber.e { "Reconnecting." } | 405 | Timber.e { "Reconnecting." } |
| 402 | mutablePrimarySpeaker.value = null | 406 | mutablePrimarySpeaker.value = null |
| @@ -26,11 +26,38 @@ import androidx.activity.compose.setContent | @@ -26,11 +26,38 @@ import androidx.activity.compose.setContent | ||
| 26 | import androidx.activity.result.contract.ActivityResultContracts | 26 | import androidx.activity.result.contract.ActivityResultContracts |
| 27 | import androidx.appcompat.app.AppCompatActivity | 27 | import androidx.appcompat.app.AppCompatActivity |
| 28 | import androidx.compose.foundation.background | 28 | import androidx.compose.foundation.background |
| 29 | -import androidx.compose.foundation.layout.* | 29 | +import androidx.compose.foundation.layout.Arrangement |
| 30 | +import androidx.compose.foundation.layout.Column | ||
| 31 | +import androidx.compose.foundation.layout.Row | ||
| 32 | +import androidx.compose.foundation.layout.Spacer | ||
| 33 | +import androidx.compose.foundation.layout.aspectRatio | ||
| 34 | +import androidx.compose.foundation.layout.fillMaxHeight | ||
| 35 | +import androidx.compose.foundation.layout.fillMaxSize | ||
| 36 | +import androidx.compose.foundation.layout.fillMaxWidth | ||
| 37 | +import androidx.compose.foundation.layout.height | ||
| 38 | +import androidx.compose.foundation.layout.padding | ||
| 39 | +import androidx.compose.foundation.layout.size | ||
| 40 | +import androidx.compose.foundation.layout.wrapContentSize | ||
| 30 | import androidx.compose.foundation.lazy.LazyRow | 41 | import androidx.compose.foundation.lazy.LazyRow |
| 31 | -import androidx.compose.material.* | ||
| 32 | -import androidx.compose.runtime.* | 42 | +import androidx.compose.material.AlertDialog |
| 43 | +import androidx.compose.material.Button | ||
| 44 | +import androidx.compose.material.ExperimentalMaterialApi | ||
| 45 | +import androidx.compose.material.ExtendedFloatingActionButton | ||
| 46 | +import androidx.compose.material.Icon | ||
| 47 | +import androidx.compose.material.MaterialTheme | ||
| 48 | +import androidx.compose.material.OutlinedTextField | ||
| 49 | +import androidx.compose.material.Scaffold | ||
| 50 | +import androidx.compose.material.Surface | ||
| 51 | +import androidx.compose.material.Text | ||
| 52 | +import androidx.compose.material.rememberScaffoldState | ||
| 53 | +import androidx.compose.runtime.Composable | ||
| 54 | +import androidx.compose.runtime.collectAsState | ||
| 55 | +import androidx.compose.runtime.getValue | ||
| 33 | import androidx.compose.runtime.livedata.observeAsState | 56 | import androidx.compose.runtime.livedata.observeAsState |
| 57 | +import androidx.compose.runtime.mutableStateOf | ||
| 58 | +import androidx.compose.runtime.remember | ||
| 59 | +import androidx.compose.runtime.rememberCoroutineScope | ||
| 60 | +import androidx.compose.runtime.setValue | ||
| 34 | import androidx.compose.ui.Alignment | 61 | import androidx.compose.ui.Alignment |
| 35 | import androidx.compose.ui.Modifier | 62 | import androidx.compose.ui.Modifier |
| 36 | import androidx.compose.ui.graphics.Color | 63 | import androidx.compose.ui.graphics.Color |
| @@ -111,6 +138,7 @@ class CallActivity : AppCompatActivity() { | @@ -111,6 +138,7 @@ class CallActivity : AppCompatActivity() { | ||
| 111 | onSimulateNodeFailure = { viewModel.simulateNodeFailure() }, | 138 | onSimulateNodeFailure = { viewModel.simulateNodeFailure() }, |
| 112 | onSimulateLeaveFullReconnect = { viewModel.simulateServerLeaveFullReconnect() }, | 139 | onSimulateLeaveFullReconnect = { viewModel.simulateServerLeaveFullReconnect() }, |
| 113 | fullReconnect = { viewModel.reconnect() }, | 140 | fullReconnect = { viewModel.reconnect() }, |
| 141 | + onUpdateAttribute = { k, v -> viewModel.updateAttribute(k, v) }, | ||
| 114 | ) | 142 | ) |
| 115 | } | 143 | } |
| 116 | } | 144 | } |
| @@ -165,7 +193,8 @@ class CallActivity : AppCompatActivity() { | @@ -165,7 +193,8 @@ class CallActivity : AppCompatActivity() { | ||
| 165 | onSimulateMigration: () -> Unit = {}, | 193 | onSimulateMigration: () -> Unit = {}, |
| 166 | onSimulateNodeFailure: () -> Unit = {}, | 194 | onSimulateNodeFailure: () -> Unit = {}, |
| 167 | fullReconnect: () -> Unit = {}, | 195 | fullReconnect: () -> Unit = {}, |
| 168 | - onSimulateLeaveFullReconnect: () -> Unit, | 196 | + onSimulateLeaveFullReconnect: () -> Unit = {}, |
| 197 | + onUpdateAttribute: (key: String, value: String) -> Unit = { _, _ -> }, | ||
| 169 | ) { | 198 | ) { |
| 170 | AppTheme(darkTheme = true) { | 199 | AppTheme(darkTheme = true) { |
| 171 | ConstraintLayout( | 200 | ConstraintLayout( |
| @@ -432,10 +461,11 @@ class CallActivity : AppCompatActivity() { | @@ -432,10 +461,11 @@ class CallActivity : AppCompatActivity() { | ||
| 432 | if (showDebugDialog) { | 461 | if (showDebugDialog) { |
| 433 | DebugMenuDialog( | 462 | DebugMenuDialog( |
| 434 | onDismissRequest = { showDebugDialog = false }, | 463 | onDismissRequest = { showDebugDialog = false }, |
| 435 | - simulateMigration = { onSimulateMigration() }, | ||
| 436 | - simulateNodeFailure = { onSimulateNodeFailure() }, | ||
| 437 | - simulateLeaveFullReconnect = { onSimulateLeaveFullReconnect() }, | ||
| 438 | - fullReconnect = { fullReconnect() }, | 464 | + simulateMigration = onSimulateMigration, |
| 465 | + simulateNodeFailure = onSimulateNodeFailure, | ||
| 466 | + simulateLeaveFullReconnect = onSimulateLeaveFullReconnect, | ||
| 467 | + fullReconnect = fullReconnect, | ||
| 468 | + onUpdateAttribute = onUpdateAttribute, | ||
| 439 | ) | 469 | ) |
| 440 | } | 470 | } |
| 441 | } | 471 | } |
| @@ -41,6 +41,7 @@ fun DebugMenuDialog( | @@ -41,6 +41,7 @@ fun DebugMenuDialog( | ||
| 41 | fullReconnect: () -> Unit = {}, | 41 | fullReconnect: () -> Unit = {}, |
| 42 | simulateNodeFailure: () -> Unit = {}, | 42 | simulateNodeFailure: () -> Unit = {}, |
| 43 | simulateLeaveFullReconnect: () -> Unit = {}, | 43 | simulateLeaveFullReconnect: () -> Unit = {}, |
| 44 | + onUpdateAttribute: (key: String, value: String) -> Unit = { _, _ -> }, | ||
| 44 | ) { | 45 | ) { |
| 45 | Dialog(onDismissRequest = onDismissRequest) { | 46 | Dialog(onDismissRequest = onDismissRequest) { |
| 46 | Column( | 47 | Column( |
| @@ -79,6 +80,19 @@ fun DebugMenuDialog( | @@ -79,6 +80,19 @@ fun DebugMenuDialog( | ||
| 79 | }) { | 80 | }) { |
| 80 | Text("Reconnect to room") | 81 | Text("Reconnect to room") |
| 81 | } | 82 | } |
| 83 | + | ||
| 84 | + Button( | ||
| 85 | + onClick = { | ||
| 86 | + attributeValue++ | ||
| 87 | + onUpdateAttribute(attributeKey, attributeValue.toString()) | ||
| 88 | + onDismissRequest() | ||
| 89 | + }, | ||
| 90 | + ) { | ||
| 91 | + Text("Update Attribute") | ||
| 92 | + } | ||
| 82 | } | 93 | } |
| 83 | } | 94 | } |
| 84 | } | 95 | } |
| 96 | + | ||
| 97 | +val attributeKey = "key" | ||
| 98 | +var attributeValue = 0 |
-
请 注册 或 登录 后发表评论