davidliu
Committed by GitHub

Implement LocalTrackSubscribed event (#489)

* Update protocol to 1.20.0

* Implement LocalTrackSubscribed event
  1 +---
  2 +"client-sdk-android": minor
  3 +---
  4 +
  5 +Implement LocalTrackSubscribed event
@@ -75,6 +75,13 @@ sealed class ParticipantEvent(open val participant: Participant) : Event() { @@ -75,6 +75,13 @@ sealed class ParticipantEvent(open val participant: Participant) : Event() {
75 class TrackUnmuted(participant: Participant, val publication: TrackPublication) : ParticipantEvent(participant) 75 class TrackUnmuted(participant: Participant, val publication: TrackPublication) : ParticipantEvent(participant)
76 76
77 // local participants 77 // local participants
  78 +
  79 + /**
  80 + * Fired when the first remote participant has subscribed to the localParticipant's track
  81 + */
  82 + class LocalTrackSubscribed(override val participant: LocalParticipant, val publication: LocalTrackPublication) :
  83 + ParticipantEvent(participant)
  84 +
78 /** 85 /**
79 * When a new track is published by the local participant. 86 * When a new track is published by the local participant.
80 */ 87 */
@@ -128,6 +128,11 @@ sealed class RoomEvent(val room: Room) : Event() { @@ -128,6 +128,11 @@ sealed class RoomEvent(val room: Room) : Event() {
128 class TrackUnmuted(room: Room, val publication: TrackPublication, val participant: Participant) : RoomEvent(room) 128 class TrackUnmuted(room: Room, val publication: TrackPublication, val participant: Participant) : RoomEvent(room)
129 129
130 /** 130 /**
  131 + * Fired when the first remote participant has subscribed to the localParticipant's track
  132 + */
  133 + class LocalTrackSubscribed(room: Room, val publication: LocalTrackPublication, val participant: LocalParticipant) : RoomEvent(room)
  134 +
  135 + /**
131 * When a new track is published to room after the local participant has joined. It will 136 * When a new track is published to room after the local participant has joined. It will
132 * not fire for tracks that are already published 137 * not fire for tracks that are already published
133 */ 138 */
@@ -267,6 +272,7 @@ enum class DisconnectReason { @@ -267,6 +272,7 @@ enum class DisconnectReason {
267 JOIN_FAILURE, 272 JOIN_FAILURE,
268 MIGRATION, 273 MIGRATION,
269 SIGNAL_CLOSE, 274 SIGNAL_CLOSE,
  275 + ROOM_CLOSED,
270 } 276 }
271 277
272 /** 278 /**
@@ -283,6 +289,7 @@ fun LivekitModels.DisconnectReason?.convert(): DisconnectReason { @@ -283,6 +289,7 @@ fun LivekitModels.DisconnectReason?.convert(): DisconnectReason {
283 LivekitModels.DisconnectReason.JOIN_FAILURE -> DisconnectReason.JOIN_FAILURE 289 LivekitModels.DisconnectReason.JOIN_FAILURE -> DisconnectReason.JOIN_FAILURE
284 LivekitModels.DisconnectReason.MIGRATION -> DisconnectReason.MIGRATION 290 LivekitModels.DisconnectReason.MIGRATION -> DisconnectReason.MIGRATION
285 LivekitModels.DisconnectReason.SIGNAL_CLOSE -> DisconnectReason.SIGNAL_CLOSE 291 LivekitModels.DisconnectReason.SIGNAL_CLOSE -> DisconnectReason.SIGNAL_CLOSE
  292 + LivekitModels.DisconnectReason.ROOM_CLOSED -> DisconnectReason.ROOM_CLOSED
286 LivekitModels.DisconnectReason.UNKNOWN_REASON, 293 LivekitModels.DisconnectReason.UNKNOWN_REASON,
287 LivekitModels.DisconnectReason.UNRECOGNIZED, 294 LivekitModels.DisconnectReason.UNRECOGNIZED,
288 null, 295 null,
@@ -776,6 +776,7 @@ internal constructor( @@ -776,6 +776,7 @@ internal constructor(
776 suspend fun onPostReconnect(isFullReconnect: Boolean) 776 suspend fun onPostReconnect(isFullReconnect: Boolean)
777 fun onLocalTrackUnpublished(trackUnpublished: LivekitRtc.TrackUnpublishedResponse) 777 fun onLocalTrackUnpublished(trackUnpublished: LivekitRtc.TrackUnpublishedResponse)
778 fun onTranscriptionReceived(transcription: LivekitModels.Transcription) 778 fun onTranscriptionReceived(transcription: LivekitModels.Transcription)
  779 + fun onLocalTrackSubscribed(trackSubscribed: LivekitRtc.TrackSubscribed)
779 } 780 }
780 781
781 companion object { 782 companion object {
@@ -909,6 +910,10 @@ internal constructor( @@ -909,6 +910,10 @@ internal constructor(
909 cont.resume(response.track) 910 cont.resume(response.track)
910 } 911 }
911 912
  913 + override fun onLocalTrackSubscribed(trackSubscribed: LivekitRtc.TrackSubscribed) {
  914 + listener?.onLocalTrackSubscribed(trackSubscribed)
  915 + }
  916 +
912 override fun onParticipantUpdate(updates: List<LivekitModels.ParticipantInfo>) { 917 override fun onParticipantUpdate(updates: List<LivekitModels.ParticipantInfo>) {
913 listener?.onUpdateParticipants(updates) 918 listener?.onUpdateParticipants(updates)
914 } 919 }
@@ -1249,6 +1249,23 @@ constructor( @@ -1249,6 +1249,23 @@ constructor(
1249 /** 1249 /**
1250 * @suppress 1250 * @suppress
1251 */ 1251 */
  1252 + override fun onLocalTrackSubscribed(response: LivekitRtc.TrackSubscribed) {
  1253 + val publication = localParticipant.trackPublications[response.trackSid] as? LocalTrackPublication
  1254 +
  1255 + if (publication == null) {
  1256 + LKLog.w { "Could not find local track publication for subscribed event " }
  1257 + return
  1258 + }
  1259 +
  1260 + coroutineScope.launch {
  1261 + emitWhenConnected(RoomEvent.LocalTrackSubscribed(this@Room, publication, this@Room.localParticipant))
  1262 + }
  1263 + this.localParticipant.onLocalTrackSubscribed(publication)
  1264 + }
  1265 +
  1266 + /**
  1267 + * @suppress
  1268 + */
1252 override fun onLocalTrackUnpublished(trackUnpublished: LivekitRtc.TrackUnpublishedResponse) { 1269 override fun onLocalTrackUnpublished(trackUnpublished: LivekitRtc.TrackUnpublishedResponse) {
1253 localParticipant.handleLocalTrackUnpublished(trackUnpublished) 1270 localParticipant.handleLocalTrackUnpublished(trackUnpublished)
1254 } 1271 }
@@ -697,6 +697,10 @@ constructor( @@ -697,6 +697,10 @@ constructor(
697 listener?.onParticipantUpdate(response.update.participantsList) 697 listener?.onParticipantUpdate(response.update.participantsList)
698 } 698 }
699 699
  700 + LivekitRtc.SignalResponse.MessageCase.TRACK_SUBSCRIBED -> {
  701 + listener?.onLocalTrackSubscribed(response.trackSubscribed)
  702 + }
  703 +
700 LivekitRtc.SignalResponse.MessageCase.TRACK_PUBLISHED -> { 704 LivekitRtc.SignalResponse.MessageCase.TRACK_PUBLISHED -> {
701 listener?.onLocalTrackPublished(response.trackPublished) 705 listener?.onLocalTrackPublished(response.trackPublished)
702 } 706 }
@@ -766,7 +770,7 @@ constructor( @@ -766,7 +770,7 @@ constructor(
766 // TODO 770 // TODO
767 } 771 }
768 772
769 - LivekitRtc.SignalResponse.MessageCase.ERROR_RESPONSE -> { 773 + LivekitRtc.SignalResponse.MessageCase.REQUEST_RESPONSE -> {
770 // TODO 774 // TODO
771 } 775 }
772 776
@@ -857,6 +861,7 @@ constructor( @@ -857,6 +861,7 @@ constructor(
857 fun onSubscriptionPermissionUpdate(subscriptionPermissionUpdate: LivekitRtc.SubscriptionPermissionUpdate) 861 fun onSubscriptionPermissionUpdate(subscriptionPermissionUpdate: LivekitRtc.SubscriptionPermissionUpdate)
858 fun onRefreshToken(token: String) 862 fun onRefreshToken(token: String)
859 fun onLocalTrackUnpublished(trackUnpublished: LivekitRtc.TrackUnpublishedResponse) 863 fun onLocalTrackUnpublished(trackUnpublished: LivekitRtc.TrackUnpublishedResponse)
  864 + fun onLocalTrackSubscribed(trackSubscribed: LivekitRtc.TrackSubscribed)
860 } 865 }
861 866
862 companion object { 867 companion object {
@@ -884,6 +884,15 @@ internal constructor( @@ -884,6 +884,15 @@ internal constructor(
884 } 884 }
885 } 885 }
886 886
  887 + internal fun onLocalTrackSubscribed(publication: LocalTrackPublication) {
  888 + if (!trackPublications.containsKey(publication.sid)) {
  889 + LKLog.w { "Could not find local track publication for subscribed event " }
  890 + return
  891 + }
  892 +
  893 + eventBus.postEvent(ParticipantEvent.LocalTrackSubscribed(this, publication), scope)
  894 + }
  895 +
887 /** 896 /**
888 * @suppress 897 * @suppress
889 */ 898 */
@@ -16,12 +16,20 @@ @@ -16,12 +16,20 @@
16 16
17 package io.livekit.android.room 17 package io.livekit.android.room
18 18
  19 +import io.livekit.android.events.ParticipantEvent
19 import io.livekit.android.events.RoomEvent 20 import io.livekit.android.events.RoomEvent
  21 +import io.livekit.android.room.participant.AudioTrackPublishOptions
  22 +import io.livekit.android.room.track.LocalAudioTrack
  23 +import io.livekit.android.room.track.LocalAudioTrackOptions
  24 +import io.livekit.android.room.track.Track
20 import io.livekit.android.test.MockE2ETest 25 import io.livekit.android.test.MockE2ETest
21 import io.livekit.android.test.assert.assertIsClass 26 import io.livekit.android.test.assert.assertIsClass
22 import io.livekit.android.test.events.EventCollector 27 import io.livekit.android.test.events.EventCollector
  28 +import io.livekit.android.test.mock.MockAudioProcessingController
  29 +import io.livekit.android.test.mock.MockAudioStreamTrack
23 import io.livekit.android.test.mock.TestData 30 import io.livekit.android.test.mock.TestData
24 import kotlinx.coroutines.ExperimentalCoroutinesApi 31 import kotlinx.coroutines.ExperimentalCoroutinesApi
  32 +import livekit.LivekitRtc
25 import livekit.LivekitRtc.ParticipantUpdate 33 import livekit.LivekitRtc.ParticipantUpdate
26 import livekit.LivekitRtc.SignalResponse 34 import livekit.LivekitRtc.SignalResponse
27 import org.junit.Assert.assertEquals 35 import org.junit.Assert.assertEquals
@@ -67,4 +75,53 @@ class RoomParticipantEventMockE2ETest : MockE2ETest() { @@ -67,4 +75,53 @@ class RoomParticipantEventMockE2ETest : MockE2ETest() {
67 assertEquals(1, events.size) 75 assertEquals(1, events.size)
68 assertIsClass(RoomEvent.ParticipantAttributesChanged::class.java, events.first()) 76 assertIsClass(RoomEvent.ParticipantAttributesChanged::class.java, events.first())
69 } 77 }
  78 +
  79 + @Test
  80 + fun localTrackSubscribed() = runTest {
  81 + connect()
  82 + room.localParticipant.publishAudioTrack(
  83 + LocalAudioTrack(
  84 + name = "",
  85 + mediaTrack = MockAudioStreamTrack(id = TestData.LOCAL_TRACK_PUBLISHED.trackPublished.cid),
  86 + options = LocalAudioTrackOptions(),
  87 + audioProcessingController = MockAudioProcessingController(),
  88 + dispatcher = coroutineRule.dispatcher,
  89 + ),
  90 + options = AudioTrackPublishOptions(
  91 + source = Track.Source.MICROPHONE,
  92 + ),
  93 + )
  94 + val roomCollector = EventCollector(room.events, coroutineRule.scope)
  95 + val participantCollector = EventCollector(room.localParticipant.events, coroutineRule.scope)
  96 +
  97 + wsFactory.receiveMessage(
  98 + with(SignalResponse.newBuilder()) {
  99 + trackSubscribed = with(LivekitRtc.TrackSubscribed.newBuilder()) {
  100 + trackSid = TestData.LOCAL_AUDIO_TRACK.sid
  101 + build()
  102 + }
  103 + build()
  104 + },
  105 + )
  106 +
  107 + val roomEvents = roomCollector.stopCollecting()
  108 + val participantEvents = participantCollector.stopCollecting()
  109 +
  110 + // Verify room events
  111 + run {
  112 + assertEquals(1, roomEvents.size)
  113 + assertIsClass(RoomEvent.LocalTrackSubscribed::class.java, roomEvents[0])
  114 +
  115 + val event = roomEvents.first() as RoomEvent.LocalTrackSubscribed
  116 + assertEquals(room, event.room)
  117 + assertEquals(room.localParticipant, event.participant)
  118 + assertEquals(room.localParticipant.getTrackPublication(Track.Source.MICROPHONE), event.publication)
  119 + }
  120 +
  121 + // Verify participant events
  122 + run {
  123 + assertEquals(1, participantEvents.size)
  124 + assertIsClass(ParticipantEvent.LocalTrackSubscribed::class.java, participantEvents[0])
  125 + }
  126 + }
70 } 127 }
1 -Subproject commit e099d367dd0bd8dac2df416279684c22693970e0 1 +Subproject commit 5c7350d25904ed8fd8163e91ff47f0577ca6afad