davidliu
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
... ... @@ -17,6 +17,11 @@ sealed class ParticipantEvent(open val participant: Participant) : Event() {
class MetadataChanged(participant: Participant, val prevMetadata: String?) : ParticipantEvent(participant)
/**
* When a participant's display name is changed, fired for all participants
*/
class NameChanged(participant: Participant, val name: String?) : ParticipantEvent(participant)
/**
* Fired when the current participant's isSpeaking property changes. (including LocalParticipant)
*/
class SpeakingChanged(participant: Participant, val isSpeaking: Boolean) : ParticipantEvent(participant)
... ...
... ... @@ -60,6 +60,12 @@ sealed class RoomEvent(val room: Room) : Event() {
val prevMetadata: String?
) : RoomEvent(room)
class ParticipantNameChanged(
room: Room,
val participant: Participant,
val name: String?
) : RoomEvent(room)
/**
* The participant was muted.
*
... ...
... ... @@ -220,6 +220,15 @@ constructor(
)
)
}
is ParticipantEvent.NameChanged -> {
emitWhenConnected(
RoomEvent.ParticipantNameChanged(
this@Room,
it.participant,
it.name
)
)
}
else -> {
/* do nothing */
}
... ... @@ -371,6 +380,15 @@ constructor(
)
)
}
is ParticipantEvent.NameChanged -> {
emitWhenConnected(
RoomEvent.ParticipantNameChanged(
this@Room,
it.participant,
it.name,
)
)
}
is ParticipantEvent.ParticipantPermissionsChanged -> eventBus.postEvent(
RoomEvent.ParticipantPermissionsChanged(
room = this@Room,
... ...
... ... @@ -436,6 +436,18 @@ constructor(
sendRequest(request)
}
fun sendUpdateLocalMetadata(metadata: String?, name: String?) {
val update = LivekitRtc.UpdateParticipantMetadata.newBuilder()
.setMetadata(metadata ?: "")
.setName(name ?: "")
val request = LivekitRtc.SignalRequest.newBuilder()
.setUpdateMetadata(update)
.build()
sendRequest(request)
}
fun sendSyncState(syncState: LivekitRtc.SyncState) {
val request = LivekitRtc.SignalRequest.newBuilder()
.setSyncState(syncState)
... ...
... ... @@ -491,6 +491,26 @@ internal constructor(
}
}
/**
* Updates the metadata of the local participant. Changes will not be reflected until the
* server responds confirming the update.
* Note: this requires `CanUpdateOwnMetadata` permission encoded in the token.
* @param metadata
*/
fun updateMetadata(metadata: String) {
this.engine.client.sendUpdateLocalMetadata(metadata, name)
}
/**
* Updates the name of the local participant. Changes will not be reflected until the
* server responds confirming the update.
* Note: this requires `CanUpdateOwnMetadata` permission encoded in the token.
* @param name
*/
fun updateName(name: String) {
this.engine.client.sendUpdateLocalMetadata(metadata, name)
}
internal fun onRemoteMuteChanged(trackSid: String, muted: Boolean) {
val pub = tracks[trackSid]
pub?.muted = muted
... ...
... ... @@ -75,7 +75,12 @@ open class Participant(
@FlowObservable
@get:FlowObservable
var name by flowDelegate<String?>(null)
var name by flowDelegate<String?>(null) { newValue, oldValue ->
if (newValue != oldValue) {
eventBus.postEvent(ParticipantEvent.NameChanged(this, newValue), scope)
}
}
internal set
/**
* Changes can be observed by using [io.livekit.android.util.flow]
... ...
... ... @@ -187,26 +187,6 @@ class RoomMockE2ETest : MockE2ETest() {
}
@Test
fun participantMetadataChanged() = runTest {
connect()
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.PARTICIPANT_JOIN.toOkioByteString()
)
val eventCollector = EventCollector(room.events, coroutineRule.scope)
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.PARTICIPANT_METADATA_CHANGED.toOkioByteString()
)
val events = eventCollector.stopCollecting()
Assert.assertEquals(1, events.size)
Assert.assertEquals(true, events[0] is RoomEvent.ParticipantMetadataChanged)
}
@Test
fun trackStreamStateChanged() = runTest {
connect()
... ...
... ... @@ -435,10 +435,24 @@ class SignalClientTest : BaseTest() {
build()
}
val PARTICIPANT_METADATA_CHANGED = with(LivekitRtc.SignalResponse.newBuilder()) {
val LOCAL_PARTICIPANT_METADATA_CHANGED = with(LivekitRtc.SignalResponse.newBuilder()) {
update = with(LivekitRtc.ParticipantUpdate.newBuilder()) {
val participantMetadataChanged = TestData.LOCAL_PARTICIPANT.toBuilder()
.setMetadata("changed_metadata")
.setName("changed_name")
.build()
addParticipants(participantMetadataChanged)
build()
}
build()
}
val REMOTE_PARTICIPANT_METADATA_CHANGED = with(LivekitRtc.SignalResponse.newBuilder()) {
update = with(LivekitRtc.ParticipantUpdate.newBuilder()) {
val participantMetadataChanged = TestData.REMOTE_PARTICIPANT.toBuilder()
.setMetadata("changed_metadata")
.setName("changed_name")
.build()
addParticipants(participantMetadataChanged)
... ...
package io.livekit.android.room.participant
import io.livekit.android.MockE2ETest
import io.livekit.android.assert.assertIsClassList
import io.livekit.android.events.EventCollector
import io.livekit.android.events.ParticipantEvent
import io.livekit.android.events.RoomEvent
import io.livekit.android.mock.MockAudioStreamTrack
import io.livekit.android.room.SignalClientTest
import io.livekit.android.room.track.LocalAudioTrack
import io.livekit.android.util.toOkioByteString
import io.livekit.android.util.toPBByteString
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
import livekit.LivekitRtc
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
... ... @@ -20,7 +24,6 @@ class LocalParticipantMockE2ETest : MockE2ETest() {
@Test
fun disconnectCleansLocalParticipant() = runTest {
connect()
val publishJob = launch {
... ... @@ -49,4 +52,75 @@ class LocalParticipantMockE2ETest : MockE2ETest() {
assertEquals(0, room.localParticipant.audioTracks.size)
assertEquals(0, room.localParticipant.videoTracks.size)
}
@Test
fun updateName() = runTest {
connect()
val newName = "new_name"
wsFactory.ws.clearRequests()
room.localParticipant.updateName(newName)
val requestString = wsFactory.ws.sentRequests.first().toPBByteString()
val sentRequest = LivekitRtc.SignalRequest.newBuilder()
.mergeFrom(requestString)
.build()
assertTrue(sentRequest.hasUpdateMetadata())
assertEquals(newName, sentRequest.updateMetadata.name)
}
@Test
fun updateMetadata() = runTest {
connect()
val newMetadata = "new_metadata"
wsFactory.ws.clearRequests()
room.localParticipant.updateMetadata(newMetadata)
val requestString = wsFactory.ws.sentRequests.first().toPBByteString()
val sentRequest = LivekitRtc.SignalRequest.newBuilder()
.mergeFrom(requestString)
.build()
assertTrue(sentRequest.hasUpdateMetadata())
assertEquals(newMetadata, sentRequest.updateMetadata.metadata)
}
@Test
fun participantMetadataChanged() = runTest {
connect()
val roomEventsCollector = EventCollector(room.events, coroutineRule.scope)
val participantEventsCollector = EventCollector(room.localParticipant.events, coroutineRule.scope)
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.LOCAL_PARTICIPANT_METADATA_CHANGED.toOkioByteString()
)
val roomEvents = roomEventsCollector.stopCollecting()
val participantEvents = participantEventsCollector.stopCollecting()
val localParticipant = room.localParticipant
val updateData = SignalClientTest.REMOTE_PARTICIPANT_METADATA_CHANGED.update.getParticipants(0)
assertEquals(updateData.metadata, localParticipant.metadata)
assertEquals(updateData.name, localParticipant.name)
assertIsClassList(
listOf(
RoomEvent.ParticipantMetadataChanged::class.java,
RoomEvent.ParticipantNameChanged::class.java,
),
roomEvents
)
assertIsClassList(
listOf(
ParticipantEvent.MetadataChanged::class.java,
ParticipantEvent.NameChanged::class.java,
),
participantEvents
)
}
}
\ No newline at end of file
... ...
package io.livekit.android.room.participant
import io.livekit.android.MockE2ETest
import io.livekit.android.assert.assertIsClassList
import io.livekit.android.events.EventCollector
import io.livekit.android.events.ParticipantEvent
import io.livekit.android.events.RoomEvent
import io.livekit.android.mock.MockAudioStreamTrack
import io.livekit.android.room.SignalClientTest
import io.livekit.android.room.track.LocalAudioTrack
import io.livekit.android.util.toOkioByteString
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
... ... @@ -39,9 +42,9 @@ class ParticipantMockE2ETest : MockE2ETest() {
simulateMessageFromServer(SignalClientTest.LOCAL_TRACK_UNPUBLISHED)
val events = eventCollector.stopCollecting()
Assert.assertEquals(1, events.size)
Assert.assertEquals(true, events[0] is RoomEvent.TrackUnpublished)
Assert.assertEquals(0, room.localParticipant.tracks.size)
assertEquals(1, events.size)
assertEquals(true, events[0] is RoomEvent.TrackUnpublished)
assertEquals(0, room.localParticipant.tracks.size)
}
@Test
... ... @@ -52,7 +55,48 @@ class ParticipantMockE2ETest : MockE2ETest() {
simulateMessageFromServer(SignalClientTest.PERMISSION_CHANGE)
val events = eventCollector.stopCollecting()
Assert.assertEquals(1, events.size)
Assert.assertEquals(true, events[0] is RoomEvent.ParticipantPermissionsChanged)
assertEquals(1, events.size)
assertEquals(true, events[0] is RoomEvent.ParticipantPermissionsChanged)
}
@Test
fun participantMetadataChanged() = runTest {
connect()
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.PARTICIPANT_JOIN.toOkioByteString()
)
val remoteParticipant = room.remoteParticipants.values.first()
val roomEventsCollector = EventCollector(room.events, coroutineRule.scope)
val participantEventsCollector = EventCollector(remoteParticipant.events, coroutineRule.scope)
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.REMOTE_PARTICIPANT_METADATA_CHANGED.toOkioByteString()
)
val roomEvents = roomEventsCollector.stopCollecting()
val participantEvents = participantEventsCollector.stopCollecting()
val updateData = SignalClientTest.REMOTE_PARTICIPANT_METADATA_CHANGED.update.getParticipants(0)
assertEquals(updateData.metadata, remoteParticipant.metadata)
assertEquals(updateData.name, remoteParticipant.name)
assertIsClassList(
listOf(
RoomEvent.ParticipantMetadataChanged::class.java,
RoomEvent.ParticipantNameChanged::class.java,
),
roomEvents
)
assertIsClassList(
listOf(
ParticipantEvent.MetadataChanged::class.java,
ParticipantEvent.NameChanged::class.java,
),
participantEvents
)
}
}
\ No newline at end of file
... ...
Subproject commit a1819deeabe143b1af1bf375d84e52b152f784ac
Subproject commit e78c5b18c0f3a18bae5d5cec44d30bb6358b9bc4
... ...