davidliu
Committed by GitHub

Implement LocalTrackSubscribed event (#489)

* Update protocol to 1.20.0

* Implement LocalTrackSubscribed event
---
"client-sdk-android": minor
---
Implement LocalTrackSubscribed event
... ...
... ... @@ -75,6 +75,13 @@ sealed class ParticipantEvent(open val participant: Participant) : Event() {
class TrackUnmuted(participant: Participant, val publication: TrackPublication) : ParticipantEvent(participant)
// local participants
/**
* Fired when the first remote participant has subscribed to the localParticipant's track
*/
class LocalTrackSubscribed(override val participant: LocalParticipant, val publication: LocalTrackPublication) :
ParticipantEvent(participant)
/**
* When a new track is published by the local participant.
*/
... ...
... ... @@ -128,6 +128,11 @@ sealed class RoomEvent(val room: Room) : Event() {
class TrackUnmuted(room: Room, val publication: TrackPublication, val participant: Participant) : RoomEvent(room)
/**
* Fired when the first remote participant has subscribed to the localParticipant's track
*/
class LocalTrackSubscribed(room: Room, val publication: LocalTrackPublication, val participant: LocalParticipant) : RoomEvent(room)
/**
* When a new track is published to room after the local participant has joined. It will
* not fire for tracks that are already published
*/
... ... @@ -267,6 +272,7 @@ enum class DisconnectReason {
JOIN_FAILURE,
MIGRATION,
SIGNAL_CLOSE,
ROOM_CLOSED,
}
/**
... ... @@ -283,6 +289,7 @@ fun LivekitModels.DisconnectReason?.convert(): DisconnectReason {
LivekitModels.DisconnectReason.JOIN_FAILURE -> DisconnectReason.JOIN_FAILURE
LivekitModels.DisconnectReason.MIGRATION -> DisconnectReason.MIGRATION
LivekitModels.DisconnectReason.SIGNAL_CLOSE -> DisconnectReason.SIGNAL_CLOSE
LivekitModels.DisconnectReason.ROOM_CLOSED -> DisconnectReason.ROOM_CLOSED
LivekitModels.DisconnectReason.UNKNOWN_REASON,
LivekitModels.DisconnectReason.UNRECOGNIZED,
null,
... ...
... ... @@ -776,6 +776,7 @@ internal constructor(
suspend fun onPostReconnect(isFullReconnect: Boolean)
fun onLocalTrackUnpublished(trackUnpublished: LivekitRtc.TrackUnpublishedResponse)
fun onTranscriptionReceived(transcription: LivekitModels.Transcription)
fun onLocalTrackSubscribed(trackSubscribed: LivekitRtc.TrackSubscribed)
}
companion object {
... ... @@ -909,6 +910,10 @@ internal constructor(
cont.resume(response.track)
}
override fun onLocalTrackSubscribed(trackSubscribed: LivekitRtc.TrackSubscribed) {
listener?.onLocalTrackSubscribed(trackSubscribed)
}
override fun onParticipantUpdate(updates: List<LivekitModels.ParticipantInfo>) {
listener?.onUpdateParticipants(updates)
}
... ...
... ... @@ -1249,6 +1249,23 @@ constructor(
/**
* @suppress
*/
override fun onLocalTrackSubscribed(response: LivekitRtc.TrackSubscribed) {
val publication = localParticipant.trackPublications[response.trackSid] as? LocalTrackPublication
if (publication == null) {
LKLog.w { "Could not find local track publication for subscribed event " }
return
}
coroutineScope.launch {
emitWhenConnected(RoomEvent.LocalTrackSubscribed(this@Room, publication, this@Room.localParticipant))
}
this.localParticipant.onLocalTrackSubscribed(publication)
}
/**
* @suppress
*/
override fun onLocalTrackUnpublished(trackUnpublished: LivekitRtc.TrackUnpublishedResponse) {
localParticipant.handleLocalTrackUnpublished(trackUnpublished)
}
... ...
... ... @@ -697,6 +697,10 @@ constructor(
listener?.onParticipantUpdate(response.update.participantsList)
}
LivekitRtc.SignalResponse.MessageCase.TRACK_SUBSCRIBED -> {
listener?.onLocalTrackSubscribed(response.trackSubscribed)
}
LivekitRtc.SignalResponse.MessageCase.TRACK_PUBLISHED -> {
listener?.onLocalTrackPublished(response.trackPublished)
}
... ... @@ -766,7 +770,7 @@ constructor(
// TODO
}
LivekitRtc.SignalResponse.MessageCase.ERROR_RESPONSE -> {
LivekitRtc.SignalResponse.MessageCase.REQUEST_RESPONSE -> {
// TODO
}
... ... @@ -857,6 +861,7 @@ constructor(
fun onSubscriptionPermissionUpdate(subscriptionPermissionUpdate: LivekitRtc.SubscriptionPermissionUpdate)
fun onRefreshToken(token: String)
fun onLocalTrackUnpublished(trackUnpublished: LivekitRtc.TrackUnpublishedResponse)
fun onLocalTrackSubscribed(trackSubscribed: LivekitRtc.TrackSubscribed)
}
companion object {
... ...
... ... @@ -884,6 +884,15 @@ internal constructor(
}
}
internal fun onLocalTrackSubscribed(publication: LocalTrackPublication) {
if (!trackPublications.containsKey(publication.sid)) {
LKLog.w { "Could not find local track publication for subscribed event " }
return
}
eventBus.postEvent(ParticipantEvent.LocalTrackSubscribed(this, publication), scope)
}
/**
* @suppress
*/
... ...
... ... @@ -16,12 +16,20 @@
package io.livekit.android.room
import io.livekit.android.events.ParticipantEvent
import io.livekit.android.events.RoomEvent
import io.livekit.android.room.participant.AudioTrackPublishOptions
import io.livekit.android.room.track.LocalAudioTrack
import io.livekit.android.room.track.LocalAudioTrackOptions
import io.livekit.android.room.track.Track
import io.livekit.android.test.MockE2ETest
import io.livekit.android.test.assert.assertIsClass
import io.livekit.android.test.events.EventCollector
import io.livekit.android.test.mock.MockAudioProcessingController
import io.livekit.android.test.mock.MockAudioStreamTrack
import io.livekit.android.test.mock.TestData
import kotlinx.coroutines.ExperimentalCoroutinesApi
import livekit.LivekitRtc
import livekit.LivekitRtc.ParticipantUpdate
import livekit.LivekitRtc.SignalResponse
import org.junit.Assert.assertEquals
... ... @@ -67,4 +75,53 @@ class RoomParticipantEventMockE2ETest : MockE2ETest() {
assertEquals(1, events.size)
assertIsClass(RoomEvent.ParticipantAttributesChanged::class.java, events.first())
}
@Test
fun localTrackSubscribed() = runTest {
connect()
room.localParticipant.publishAudioTrack(
LocalAudioTrack(
name = "",
mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid),
options = LocalAudioTrackOptions(),
audioProcessingController = MockAudioProcessingController(),
dispatcher = coroutineRule.dispatcher,
),
options = AudioTrackPublishOptions(
source = Track.Source.MICROPHONE,
),
)
val roomCollector = EventCollector(room.events, coroutineRule.scope)
val participantCollector = EventCollector(room.localParticipant.events, coroutineRule.scope)
wsFactory.receiveMessage(
with(SignalResponse.newBuilder()) {
trackSubscribed = with(LivekitRtc.TrackSubscribed.newBuilder()) {
trackSid = TestData.LOCAL_AUDIO_TRACK.sid
build()
}
build()
},
)
val roomEvents = roomCollector.stopCollecting()
val participantEvents = participantCollector.stopCollecting()
// Verify room events
run {
assertEquals(1, roomEvents.size)
assertIsClass(RoomEvent.LocalTrackSubscribed::class.java, roomEvents[0])
val event = roomEvents.first() as RoomEvent.LocalTrackSubscribed
assertEquals(room, event.room)
assertEquals(room.localParticipant, event.participant)
assertEquals(room.localParticipant.getTrackPublication(Track.Source.MICROPHONE), event.publication)
}
// Verify participant events
run {
assertEquals(1, participantEvents.size)
assertIsClass(ParticipantEvent.LocalTrackSubscribed::class.java, participantEvents[0])
}
}
}
... ...
Subproject commit e099d367dd0bd8dac2df416279684c22693970e0
Subproject commit 5c7350d25904ed8fd8163e91ff47f0577ca6afad
... ...