Committed by
GitHub
Support local participant name and metadata update (#210)
* protocol update * support and handle name/metadata update * wait for server to return updates since we don't know if we have perms to update * change function names to better indicate that these are not direct setter functions * fix tests
正在显示
11 个修改的文件
包含
210 行增加
和
32 行删除
| @@ -17,6 +17,11 @@ sealed class ParticipantEvent(open val participant: Participant) : Event() { | @@ -17,6 +17,11 @@ sealed class ParticipantEvent(open val participant: Participant) : Event() { | ||
| 17 | class MetadataChanged(participant: Participant, val prevMetadata: String?) : ParticipantEvent(participant) | 17 | class MetadataChanged(participant: Participant, val prevMetadata: String?) : ParticipantEvent(participant) |
| 18 | 18 | ||
| 19 | /** | 19 | /** |
| 20 | + * When a participant's display name is changed, fired for all participants | ||
| 21 | + */ | ||
| 22 | + class NameChanged(participant: Participant, val name: String?) : ParticipantEvent(participant) | ||
| 23 | + | ||
| 24 | + /** | ||
| 20 | * Fired when the current participant's isSpeaking property changes. (including LocalParticipant) | 25 | * Fired when the current participant's isSpeaking property changes. (including LocalParticipant) |
| 21 | */ | 26 | */ |
| 22 | class SpeakingChanged(participant: Participant, val isSpeaking: Boolean) : ParticipantEvent(participant) | 27 | class SpeakingChanged(participant: Participant, val isSpeaking: Boolean) : ParticipantEvent(participant) |
| @@ -60,6 +60,12 @@ sealed class RoomEvent(val room: Room) : Event() { | @@ -60,6 +60,12 @@ sealed class RoomEvent(val room: Room) : Event() { | ||
| 60 | val prevMetadata: String? | 60 | val prevMetadata: String? |
| 61 | ) : RoomEvent(room) | 61 | ) : RoomEvent(room) |
| 62 | 62 | ||
| 63 | + class ParticipantNameChanged( | ||
| 64 | + room: Room, | ||
| 65 | + val participant: Participant, | ||
| 66 | + val name: String? | ||
| 67 | + ) : RoomEvent(room) | ||
| 68 | + | ||
| 63 | /** | 69 | /** |
| 64 | * The participant was muted. | 70 | * The participant was muted. |
| 65 | * | 71 | * |
| @@ -220,6 +220,15 @@ constructor( | @@ -220,6 +220,15 @@ constructor( | ||
| 220 | ) | 220 | ) |
| 221 | ) | 221 | ) |
| 222 | } | 222 | } |
| 223 | + is ParticipantEvent.NameChanged -> { | ||
| 224 | + emitWhenConnected( | ||
| 225 | + RoomEvent.ParticipantNameChanged( | ||
| 226 | + this@Room, | ||
| 227 | + it.participant, | ||
| 228 | + it.name | ||
| 229 | + ) | ||
| 230 | + ) | ||
| 231 | + } | ||
| 223 | else -> { | 232 | else -> { |
| 224 | /* do nothing */ | 233 | /* do nothing */ |
| 225 | } | 234 | } |
| @@ -371,6 +380,15 @@ constructor( | @@ -371,6 +380,15 @@ constructor( | ||
| 371 | ) | 380 | ) |
| 372 | ) | 381 | ) |
| 373 | } | 382 | } |
| 383 | + is ParticipantEvent.NameChanged -> { | ||
| 384 | + emitWhenConnected( | ||
| 385 | + RoomEvent.ParticipantNameChanged( | ||
| 386 | + this@Room, | ||
| 387 | + it.participant, | ||
| 388 | + it.name, | ||
| 389 | + ) | ||
| 390 | + ) | ||
| 391 | + } | ||
| 374 | is ParticipantEvent.ParticipantPermissionsChanged -> eventBus.postEvent( | 392 | is ParticipantEvent.ParticipantPermissionsChanged -> eventBus.postEvent( |
| 375 | RoomEvent.ParticipantPermissionsChanged( | 393 | RoomEvent.ParticipantPermissionsChanged( |
| 376 | room = this@Room, | 394 | room = this@Room, |
| @@ -436,6 +436,18 @@ constructor( | @@ -436,6 +436,18 @@ constructor( | ||
| 436 | sendRequest(request) | 436 | sendRequest(request) |
| 437 | } | 437 | } |
| 438 | 438 | ||
| 439 | + fun sendUpdateLocalMetadata(metadata: String?, name: String?) { | ||
| 440 | + val update = LivekitRtc.UpdateParticipantMetadata.newBuilder() | ||
| 441 | + .setMetadata(metadata ?: "") | ||
| 442 | + .setName(name ?: "") | ||
| 443 | + | ||
| 444 | + val request = LivekitRtc.SignalRequest.newBuilder() | ||
| 445 | + .setUpdateMetadata(update) | ||
| 446 | + .build() | ||
| 447 | + | ||
| 448 | + sendRequest(request) | ||
| 449 | + } | ||
| 450 | + | ||
| 439 | fun sendSyncState(syncState: LivekitRtc.SyncState) { | 451 | fun sendSyncState(syncState: LivekitRtc.SyncState) { |
| 440 | val request = LivekitRtc.SignalRequest.newBuilder() | 452 | val request = LivekitRtc.SignalRequest.newBuilder() |
| 441 | .setSyncState(syncState) | 453 | .setSyncState(syncState) |
| @@ -491,6 +491,26 @@ internal constructor( | @@ -491,6 +491,26 @@ internal constructor( | ||
| 491 | } | 491 | } |
| 492 | } | 492 | } |
| 493 | 493 | ||
| 494 | + /** | ||
| 495 | + * Updates the metadata of the local participant. Changes will not be reflected until the | ||
| 496 | + * server responds confirming the update. | ||
| 497 | + * Note: this requires `CanUpdateOwnMetadata` permission encoded in the token. | ||
| 498 | + * @param metadata | ||
| 499 | + */ | ||
| 500 | + fun updateMetadata(metadata: String) { | ||
| 501 | + this.engine.client.sendUpdateLocalMetadata(metadata, name) | ||
| 502 | + } | ||
| 503 | + | ||
| 504 | + /** | ||
| 505 | + * Updates the name of the local participant. Changes will not be reflected until the | ||
| 506 | + * server responds confirming the update. | ||
| 507 | + * Note: this requires `CanUpdateOwnMetadata` permission encoded in the token. | ||
| 508 | + * @param name | ||
| 509 | + */ | ||
| 510 | + fun updateName(name: String) { | ||
| 511 | + this.engine.client.sendUpdateLocalMetadata(metadata, name) | ||
| 512 | + } | ||
| 513 | + | ||
| 494 | internal fun onRemoteMuteChanged(trackSid: String, muted: Boolean) { | 514 | internal fun onRemoteMuteChanged(trackSid: String, muted: Boolean) { |
| 495 | val pub = tracks[trackSid] | 515 | val pub = tracks[trackSid] |
| 496 | pub?.muted = muted | 516 | pub?.muted = muted |
| @@ -75,7 +75,12 @@ open class Participant( | @@ -75,7 +75,12 @@ open class Participant( | ||
| 75 | 75 | ||
| 76 | @FlowObservable | 76 | @FlowObservable |
| 77 | @get:FlowObservable | 77 | @get:FlowObservable |
| 78 | - var name by flowDelegate<String?>(null) | 78 | + var name by flowDelegate<String?>(null) { newValue, oldValue -> |
| 79 | + if (newValue != oldValue) { | ||
| 80 | + eventBus.postEvent(ParticipantEvent.NameChanged(this, newValue), scope) | ||
| 81 | + } | ||
| 82 | + } | ||
| 83 | + internal set | ||
| 79 | 84 | ||
| 80 | /** | 85 | /** |
| 81 | * Changes can be observed by using [io.livekit.android.util.flow] | 86 | * Changes can be observed by using [io.livekit.android.util.flow] |
| @@ -187,26 +187,6 @@ class RoomMockE2ETest : MockE2ETest() { | @@ -187,26 +187,6 @@ class RoomMockE2ETest : MockE2ETest() { | ||
| 187 | } | 187 | } |
| 188 | 188 | ||
| 189 | @Test | 189 | @Test |
| 190 | - fun participantMetadataChanged() = runTest { | ||
| 191 | - connect() | ||
| 192 | - | ||
| 193 | - wsFactory.listener.onMessage( | ||
| 194 | - wsFactory.ws, | ||
| 195 | - SignalClientTest.PARTICIPANT_JOIN.toOkioByteString() | ||
| 196 | - ) | ||
| 197 | - | ||
| 198 | - val eventCollector = EventCollector(room.events, coroutineRule.scope) | ||
| 199 | - wsFactory.listener.onMessage( | ||
| 200 | - wsFactory.ws, | ||
| 201 | - SignalClientTest.PARTICIPANT_METADATA_CHANGED.toOkioByteString() | ||
| 202 | - ) | ||
| 203 | - val events = eventCollector.stopCollecting() | ||
| 204 | - | ||
| 205 | - Assert.assertEquals(1, events.size) | ||
| 206 | - Assert.assertEquals(true, events[0] is RoomEvent.ParticipantMetadataChanged) | ||
| 207 | - } | ||
| 208 | - | ||
| 209 | - @Test | ||
| 210 | fun trackStreamStateChanged() = runTest { | 190 | fun trackStreamStateChanged() = runTest { |
| 211 | connect() | 191 | connect() |
| 212 | 192 |
| @@ -435,10 +435,24 @@ class SignalClientTest : BaseTest() { | @@ -435,10 +435,24 @@ class SignalClientTest : BaseTest() { | ||
| 435 | build() | 435 | build() |
| 436 | } | 436 | } |
| 437 | 437 | ||
| 438 | - val PARTICIPANT_METADATA_CHANGED = with(LivekitRtc.SignalResponse.newBuilder()) { | 438 | + val LOCAL_PARTICIPANT_METADATA_CHANGED = with(LivekitRtc.SignalResponse.newBuilder()) { |
| 439 | + update = with(LivekitRtc.ParticipantUpdate.newBuilder()) { | ||
| 440 | + val participantMetadataChanged = TestData.LOCAL_PARTICIPANT.toBuilder() | ||
| 441 | + .setMetadata("changed_metadata") | ||
| 442 | + .setName("changed_name") | ||
| 443 | + .build() | ||
| 444 | + | ||
| 445 | + addParticipants(participantMetadataChanged) | ||
| 446 | + build() | ||
| 447 | + } | ||
| 448 | + build() | ||
| 449 | + } | ||
| 450 | + | ||
| 451 | + val REMOTE_PARTICIPANT_METADATA_CHANGED = with(LivekitRtc.SignalResponse.newBuilder()) { | ||
| 439 | update = with(LivekitRtc.ParticipantUpdate.newBuilder()) { | 452 | update = with(LivekitRtc.ParticipantUpdate.newBuilder()) { |
| 440 | val participantMetadataChanged = TestData.REMOTE_PARTICIPANT.toBuilder() | 453 | val participantMetadataChanged = TestData.REMOTE_PARTICIPANT.toBuilder() |
| 441 | .setMetadata("changed_metadata") | 454 | .setMetadata("changed_metadata") |
| 455 | + .setName("changed_name") | ||
| 442 | .build() | 456 | .build() |
| 443 | 457 | ||
| 444 | addParticipants(participantMetadataChanged) | 458 | addParticipants(participantMetadataChanged) |
livekit-android-sdk/src/test/java/io/livekit/android/room/participant/LocalParticipantMockE2ETest.kt
| 1 | package io.livekit.android.room.participant | 1 | package io.livekit.android.room.participant |
| 2 | 2 | ||
| 3 | import io.livekit.android.MockE2ETest | 3 | import io.livekit.android.MockE2ETest |
| 4 | +import io.livekit.android.assert.assertIsClassList | ||
| 5 | +import io.livekit.android.events.EventCollector | ||
| 6 | +import io.livekit.android.events.ParticipantEvent | ||
| 7 | +import io.livekit.android.events.RoomEvent | ||
| 4 | import io.livekit.android.mock.MockAudioStreamTrack | 8 | import io.livekit.android.mock.MockAudioStreamTrack |
| 5 | import io.livekit.android.room.SignalClientTest | 9 | import io.livekit.android.room.SignalClientTest |
| 6 | import io.livekit.android.room.track.LocalAudioTrack | 10 | import io.livekit.android.room.track.LocalAudioTrack |
| 7 | import io.livekit.android.util.toOkioByteString | 11 | import io.livekit.android.util.toOkioByteString |
| 12 | +import io.livekit.android.util.toPBByteString | ||
| 8 | import kotlinx.coroutines.ExperimentalCoroutinesApi | 13 | import kotlinx.coroutines.ExperimentalCoroutinesApi |
| 9 | import kotlinx.coroutines.launch | 14 | import kotlinx.coroutines.launch |
| 10 | -import kotlinx.coroutines.test.advanceUntilIdle | ||
| 11 | -import kotlinx.coroutines.test.runCurrent | 15 | +import livekit.LivekitRtc |
| 12 | import org.junit.Assert.* | 16 | import org.junit.Assert.* |
| 13 | import org.junit.Test | 17 | import org.junit.Test |
| 14 | import org.junit.runner.RunWith | 18 | import org.junit.runner.RunWith |
| @@ -20,7 +24,6 @@ class LocalParticipantMockE2ETest : MockE2ETest() { | @@ -20,7 +24,6 @@ class LocalParticipantMockE2ETest : MockE2ETest() { | ||
| 20 | 24 | ||
| 21 | @Test | 25 | @Test |
| 22 | fun disconnectCleansLocalParticipant() = runTest { | 26 | fun disconnectCleansLocalParticipant() = runTest { |
| 23 | - | ||
| 24 | connect() | 27 | connect() |
| 25 | 28 | ||
| 26 | val publishJob = launch { | 29 | val publishJob = launch { |
| @@ -49,4 +52,75 @@ class LocalParticipantMockE2ETest : MockE2ETest() { | @@ -49,4 +52,75 @@ class LocalParticipantMockE2ETest : MockE2ETest() { | ||
| 49 | assertEquals(0, room.localParticipant.audioTracks.size) | 52 | assertEquals(0, room.localParticipant.audioTracks.size) |
| 50 | assertEquals(0, room.localParticipant.videoTracks.size) | 53 | assertEquals(0, room.localParticipant.videoTracks.size) |
| 51 | } | 54 | } |
| 55 | + | ||
| 56 | + @Test | ||
| 57 | + fun updateName() = runTest { | ||
| 58 | + connect() | ||
| 59 | + val newName = "new_name" | ||
| 60 | + wsFactory.ws.clearRequests() | ||
| 61 | + | ||
| 62 | + room.localParticipant.updateName(newName) | ||
| 63 | + | ||
| 64 | + val requestString = wsFactory.ws.sentRequests.first().toPBByteString() | ||
| 65 | + val sentRequest = LivekitRtc.SignalRequest.newBuilder() | ||
| 66 | + .mergeFrom(requestString) | ||
| 67 | + .build() | ||
| 68 | + | ||
| 69 | + assertTrue(sentRequest.hasUpdateMetadata()) | ||
| 70 | + assertEquals(newName, sentRequest.updateMetadata.name) | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + @Test | ||
| 74 | + fun updateMetadata() = runTest { | ||
| 75 | + connect() | ||
| 76 | + val newMetadata = "new_metadata" | ||
| 77 | + wsFactory.ws.clearRequests() | ||
| 78 | + | ||
| 79 | + room.localParticipant.updateMetadata(newMetadata) | ||
| 80 | + | ||
| 81 | + val requestString = wsFactory.ws.sentRequests.first().toPBByteString() | ||
| 82 | + val sentRequest = LivekitRtc.SignalRequest.newBuilder() | ||
| 83 | + .mergeFrom(requestString) | ||
| 84 | + .build() | ||
| 85 | + | ||
| 86 | + assertTrue(sentRequest.hasUpdateMetadata()) | ||
| 87 | + assertEquals(newMetadata, sentRequest.updateMetadata.metadata) | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + @Test | ||
| 91 | + fun participantMetadataChanged() = runTest { | ||
| 92 | + connect() | ||
| 93 | + | ||
| 94 | + val roomEventsCollector = EventCollector(room.events, coroutineRule.scope) | ||
| 95 | + val participantEventsCollector = EventCollector(room.localParticipant.events, coroutineRule.scope) | ||
| 96 | + | ||
| 97 | + wsFactory.listener.onMessage( | ||
| 98 | + wsFactory.ws, | ||
| 99 | + SignalClientTest.LOCAL_PARTICIPANT_METADATA_CHANGED.toOkioByteString() | ||
| 100 | + ) | ||
| 101 | + | ||
| 102 | + val roomEvents = roomEventsCollector.stopCollecting() | ||
| 103 | + val participantEvents = participantEventsCollector.stopCollecting() | ||
| 104 | + | ||
| 105 | + val localParticipant = room.localParticipant | ||
| 106 | + val updateData = SignalClientTest.REMOTE_PARTICIPANT_METADATA_CHANGED.update.getParticipants(0) | ||
| 107 | + assertEquals(updateData.metadata, localParticipant.metadata) | ||
| 108 | + assertEquals(updateData.name, localParticipant.name) | ||
| 109 | + | ||
| 110 | + assertIsClassList( | ||
| 111 | + listOf( | ||
| 112 | + RoomEvent.ParticipantMetadataChanged::class.java, | ||
| 113 | + RoomEvent.ParticipantNameChanged::class.java, | ||
| 114 | + ), | ||
| 115 | + roomEvents | ||
| 116 | + ) | ||
| 117 | + | ||
| 118 | + assertIsClassList( | ||
| 119 | + listOf( | ||
| 120 | + ParticipantEvent.MetadataChanged::class.java, | ||
| 121 | + ParticipantEvent.NameChanged::class.java, | ||
| 122 | + ), | ||
| 123 | + participantEvents | ||
| 124 | + ) | ||
| 125 | + } | ||
| 52 | } | 126 | } |
| 1 | package io.livekit.android.room.participant | 1 | package io.livekit.android.room.participant |
| 2 | 2 | ||
| 3 | import io.livekit.android.MockE2ETest | 3 | import io.livekit.android.MockE2ETest |
| 4 | +import io.livekit.android.assert.assertIsClassList | ||
| 4 | import io.livekit.android.events.EventCollector | 5 | import io.livekit.android.events.EventCollector |
| 6 | +import io.livekit.android.events.ParticipantEvent | ||
| 5 | import io.livekit.android.events.RoomEvent | 7 | import io.livekit.android.events.RoomEvent |
| 6 | import io.livekit.android.mock.MockAudioStreamTrack | 8 | import io.livekit.android.mock.MockAudioStreamTrack |
| 7 | import io.livekit.android.room.SignalClientTest | 9 | import io.livekit.android.room.SignalClientTest |
| 8 | import io.livekit.android.room.track.LocalAudioTrack | 10 | import io.livekit.android.room.track.LocalAudioTrack |
| 11 | +import io.livekit.android.util.toOkioByteString | ||
| 9 | import kotlinx.coroutines.ExperimentalCoroutinesApi | 12 | import kotlinx.coroutines.ExperimentalCoroutinesApi |
| 10 | import kotlinx.coroutines.launch | 13 | import kotlinx.coroutines.launch |
| 11 | -import org.junit.Assert | 14 | +import org.junit.Assert.assertEquals |
| 12 | import org.junit.Test | 15 | import org.junit.Test |
| 13 | import org.junit.runner.RunWith | 16 | import org.junit.runner.RunWith |
| 14 | import org.robolectric.RobolectricTestRunner | 17 | import org.robolectric.RobolectricTestRunner |
| @@ -39,9 +42,9 @@ class ParticipantMockE2ETest : MockE2ETest() { | @@ -39,9 +42,9 @@ class ParticipantMockE2ETest : MockE2ETest() { | ||
| 39 | simulateMessageFromServer(SignalClientTest.LOCAL_TRACK_UNPUBLISHED) | 42 | simulateMessageFromServer(SignalClientTest.LOCAL_TRACK_UNPUBLISHED) |
| 40 | val events = eventCollector.stopCollecting() | 43 | val events = eventCollector.stopCollecting() |
| 41 | 44 | ||
| 42 | - Assert.assertEquals(1, events.size) | ||
| 43 | - Assert.assertEquals(true, events[0] is RoomEvent.TrackUnpublished) | ||
| 44 | - Assert.assertEquals(0, room.localParticipant.tracks.size) | 45 | + assertEquals(1, events.size) |
| 46 | + assertEquals(true, events[0] is RoomEvent.TrackUnpublished) | ||
| 47 | + assertEquals(0, room.localParticipant.tracks.size) | ||
| 45 | } | 48 | } |
| 46 | 49 | ||
| 47 | @Test | 50 | @Test |
| @@ -52,7 +55,48 @@ class ParticipantMockE2ETest : MockE2ETest() { | @@ -52,7 +55,48 @@ class ParticipantMockE2ETest : MockE2ETest() { | ||
| 52 | simulateMessageFromServer(SignalClientTest.PERMISSION_CHANGE) | 55 | simulateMessageFromServer(SignalClientTest.PERMISSION_CHANGE) |
| 53 | val events = eventCollector.stopCollecting() | 56 | val events = eventCollector.stopCollecting() |
| 54 | 57 | ||
| 55 | - Assert.assertEquals(1, events.size) | ||
| 56 | - Assert.assertEquals(true, events[0] is RoomEvent.ParticipantPermissionsChanged) | 58 | + assertEquals(1, events.size) |
| 59 | + assertEquals(true, events[0] is RoomEvent.ParticipantPermissionsChanged) | ||
| 57 | } | 60 | } |
| 61 | + | ||
| 62 | + @Test | ||
| 63 | + fun participantMetadataChanged() = runTest { | ||
| 64 | + connect() | ||
| 65 | + | ||
| 66 | + wsFactory.listener.onMessage( | ||
| 67 | + wsFactory.ws, | ||
| 68 | + SignalClientTest.PARTICIPANT_JOIN.toOkioByteString() | ||
| 69 | + ) | ||
| 70 | + | ||
| 71 | + val remoteParticipant = room.remoteParticipants.values.first() | ||
| 72 | + val roomEventsCollector = EventCollector(room.events, coroutineRule.scope) | ||
| 73 | + val participantEventsCollector = EventCollector(remoteParticipant.events, coroutineRule.scope) | ||
| 74 | + wsFactory.listener.onMessage( | ||
| 75 | + wsFactory.ws, | ||
| 76 | + SignalClientTest.REMOTE_PARTICIPANT_METADATA_CHANGED.toOkioByteString() | ||
| 77 | + ) | ||
| 78 | + val roomEvents = roomEventsCollector.stopCollecting() | ||
| 79 | + val participantEvents = participantEventsCollector.stopCollecting() | ||
| 80 | + | ||
| 81 | + val updateData = SignalClientTest.REMOTE_PARTICIPANT_METADATA_CHANGED.update.getParticipants(0) | ||
| 82 | + assertEquals(updateData.metadata, remoteParticipant.metadata) | ||
| 83 | + assertEquals(updateData.name, remoteParticipant.name) | ||
| 84 | + | ||
| 85 | + assertIsClassList( | ||
| 86 | + listOf( | ||
| 87 | + RoomEvent.ParticipantMetadataChanged::class.java, | ||
| 88 | + RoomEvent.ParticipantNameChanged::class.java, | ||
| 89 | + ), | ||
| 90 | + roomEvents | ||
| 91 | + ) | ||
| 92 | + | ||
| 93 | + assertIsClassList( | ||
| 94 | + listOf( | ||
| 95 | + ParticipantEvent.MetadataChanged::class.java, | ||
| 96 | + ParticipantEvent.NameChanged::class.java, | ||
| 97 | + ), | ||
| 98 | + participantEvents | ||
| 99 | + ) | ||
| 100 | + } | ||
| 101 | + | ||
| 58 | } | 102 | } |
-
请 注册 或 登录 后发表评论