Skip to content

Commit 990b5a5

Browse files
authored
Handle SUBSCRIPTION_RESPONSE message from server (#959)
1 parent 224319f commit 990b5a5

6 files changed

Lines changed: 103 additions & 8 deletions

File tree

.changeset/dirty-cherries-thank.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"client-sdk-android": patch
3+
---
4+
5+
Emit `TrackSubscriptionFailed` events through `Room` and `RemoteParticipant` when the server detects a subscription failure

livekit-android-sdk/src/main/java/io/livekit/android/room/RTCEngine.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,6 +1030,7 @@ internal constructor(
10301030
fun onStreamStateUpdate(streamStates: List<LivekitRtc.StreamStateInfo>)
10311031
fun onSubscribedQualityUpdate(subscribedQualityUpdate: LivekitRtc.SubscribedQualityUpdate)
10321032
fun onSubscriptionPermissionUpdate(subscriptionPermissionUpdate: LivekitRtc.SubscriptionPermissionUpdate)
1033+
fun onSubscriptionError(subscriptionResponse: LivekitRtc.SubscriptionResponse)
10331034
fun onSignalConnected(isResume: Boolean)
10341035
fun onFullReconnecting()
10351036
suspend fun onPostReconnect(isFullReconnect: Boolean)
@@ -1266,6 +1267,10 @@ internal constructor(
12661267
listener?.onSubscriptionPermissionUpdate(subscriptionPermissionUpdate)
12671268
}
12681269

1270+
override fun onSubscriptionError(subscriptionResponse: LivekitRtc.SubscriptionResponse) {
1271+
listener?.onSubscriptionError(subscriptionResponse)
1272+
}
1273+
12691274
override fun onRefreshToken(token: String) {
12701275
sessionToken = token
12711276
regionUrlProvider?.token = token

livekit-android-sdk/src/main/java/io/livekit/android/room/Room.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,6 +1430,16 @@ constructor(
14301430
participant.onSubscriptionPermissionUpdate(subscriptionPermissionUpdate)
14311431
}
14321432

1433+
/**
1434+
* @suppress
1435+
*/
1436+
override fun onSubscriptionError(subscriptionResponse: LivekitRtc.SubscriptionResponse) {
1437+
val participant = remoteParticipants.values
1438+
.firstOrNull { it.trackPublications.containsKey(subscriptionResponse.trackSid) } as? RemoteParticipant
1439+
?: return
1440+
participant.onSubscriptionError(subscriptionResponse)
1441+
}
1442+
14331443
/**
14341444
* @suppress
14351445
*/

livekit-android-sdk/src/main/java/io/livekit/android/room/SignalClient.kt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -833,19 +833,13 @@ constructor(
833833
}
834834

835835
LivekitRtc.SignalResponse.MessageCase.SUBSCRIPTION_RESPONSE -> {
836-
// TODO
836+
listener?.onSubscriptionError(response.subscriptionResponse)
837837
}
838838

839839
LivekitRtc.SignalResponse.MessageCase.REQUEST_RESPONSE -> {
840840
// TODO
841841
}
842842

843-
LivekitRtc.SignalResponse.MessageCase.MESSAGE_NOT_SET,
844-
null,
845-
-> {
846-
LKLog.v { "empty messageCase!" }
847-
}
848-
849843
LivekitRtc.SignalResponse.MessageCase.ROOM_MOVED -> {
850844
// TODO
851845
}
@@ -869,6 +863,12 @@ constructor(
869863
LivekitRtc.SignalResponse.MessageCase.DATA_TRACK_SUBSCRIBER_HANDLES -> {
870864
// TODO
871865
}
866+
867+
LivekitRtc.SignalResponse.MessageCase.MESSAGE_NOT_SET,
868+
null,
869+
-> {
870+
LKLog.v { "empty messageCase!" }
871+
}
872872
}
873873
}
874874

@@ -954,6 +954,7 @@ constructor(
954954
fun onStreamStateUpdate(streamStates: List<LivekitRtc.StreamStateInfo>)
955955
fun onSubscribedQualityUpdate(subscribedQualityUpdate: LivekitRtc.SubscribedQualityUpdate)
956956
fun onSubscriptionPermissionUpdate(subscriptionPermissionUpdate: LivekitRtc.SubscriptionPermissionUpdate)
957+
fun onSubscriptionError(subscriptionResponse: LivekitRtc.SubscriptionResponse)
957958
fun onRefreshToken(token: String)
958959
fun onLocalTrackUnpublished(trackUnpublished: LivekitRtc.TrackUnpublishedResponse)
959960
fun onLocalTrackSubscribed(trackSubscribed: LivekitRtc.TrackSubscribed)

livekit-android-sdk/src/main/java/io/livekit/android/room/participant/RemoteParticipant.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,27 @@ class RemoteParticipant(
235235
}
236236
}
237237

238+
internal fun onSubscriptionError(subscriptionResponse: LivekitRtc.SubscriptionResponse) {
239+
val trackSid = subscriptionResponse.trackSid
240+
if (trackPublications[trackSid] !is RemoteTrackPublication) {
241+
return
242+
}
243+
244+
val exception = subscriptionErrorException(subscriptionResponse.err)
245+
internalListener?.onTrackSubscriptionFailed(trackSid, exception, this)
246+
eventBus.postEvent(ParticipantEvent.TrackSubscriptionFailed(this, trackSid, exception), scope)
247+
}
248+
249+
private fun subscriptionErrorException(error: LivekitModels.SubscriptionError): TrackException {
250+
return when (error) {
251+
LivekitModels.SubscriptionError.SE_CODEC_UNSUPPORTED -> TrackException.MediaException("Codec not supported")
252+
LivekitModels.SubscriptionError.SE_TRACK_NOTFOUND -> TrackException.InvalidTrackStateException("Track not found")
253+
LivekitModels.SubscriptionError.SE_UNKNOWN,
254+
LivekitModels.SubscriptionError.UNRECOGNIZED,
255+
-> TrackException.InvalidTrackStateException("Subscription failed")
256+
}
257+
}
258+
238259
// Internal methods just for posting events.
239260
internal fun onDataReceived(event: RoomEvent.DataReceived) {
240261
eventBus.postEvent(ParticipantEvent.DataReceived(this, event.data, event.topic, event.encryptionType), scope)

livekit-android-test/src/test/java/io/livekit/android/room/RoomParticipantEventMockE2ETest.kt

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 LiveKit, Inc.
2+
* Copyright 2023-2026 LiveKit, Inc.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,13 +19,16 @@ package io.livekit.android.room
1919
import io.livekit.android.events.ParticipantEvent
2020
import io.livekit.android.events.RoomEvent
2121
import io.livekit.android.room.participant.AudioTrackPublishOptions
22+
import io.livekit.android.room.participant.RemoteParticipant
2223
import io.livekit.android.room.track.Track
24+
import io.livekit.android.room.track.TrackException
2325
import io.livekit.android.test.MockE2ETest
2426
import io.livekit.android.test.assert.assertIsClass
2527
import io.livekit.android.test.events.EventCollector
2628
import io.livekit.android.test.mock.TestData
2729
import io.livekit.android.test.mock.room.track.createMockLocalAudioTrack
2830
import kotlinx.coroutines.ExperimentalCoroutinesApi
31+
import livekit.LivekitModels
2932
import livekit.LivekitRtc
3033
import livekit.LivekitRtc.ParticipantUpdate
3134
import livekit.LivekitRtc.SignalResponse
@@ -115,4 +118,54 @@ class RoomParticipantEventMockE2ETest : MockE2ETest() {
115118
assertIsClass(ParticipantEvent.LocalTrackSubscribed::class.java, participantEvents[0])
116119
}
117120
}
121+
122+
@Test
123+
fun trackSubscriptionFailed() = runTest {
124+
connect()
125+
126+
wsFactory.receiveMessage(TestData.PARTICIPANT_JOIN)
127+
128+
val remoteParticipant = room.getParticipantBySid(TestData.REMOTE_PARTICIPANT.sid) as RemoteParticipant
129+
val roomCollector = EventCollector(room.events, coroutineRule.scope)
130+
val participantCollector = EventCollector(remoteParticipant.events, coroutineRule.scope)
131+
132+
wsFactory.receiveMessage(
133+
with(SignalResponse.newBuilder()) {
134+
subscriptionResponse = with(LivekitRtc.SubscriptionResponse.newBuilder()) {
135+
trackSid = TestData.REMOTE_AUDIO_TRACK.sid
136+
err = LivekitModels.SubscriptionError.SE_CODEC_UNSUPPORTED
137+
build()
138+
}
139+
build()
140+
},
141+
)
142+
143+
val roomEvents = roomCollector.stopCollecting()
144+
val participantEvents = participantCollector.stopCollecting()
145+
146+
// Verify room events
147+
run {
148+
assertEquals(1, roomEvents.size)
149+
assertIsClass(RoomEvent.TrackSubscriptionFailed::class.java, roomEvents[0])
150+
151+
val event = roomEvents.first() as RoomEvent.TrackSubscriptionFailed
152+
assertEquals(room, event.room)
153+
assertEquals(TestData.REMOTE_AUDIO_TRACK.sid, event.sid)
154+
assertEquals(remoteParticipant, event.participant)
155+
assertIsClass(TrackException.MediaException::class.java, event.exception)
156+
assertEquals("Codec not supported", event.exception.message)
157+
}
158+
159+
// Verify participant events
160+
run {
161+
assertEquals(1, participantEvents.size)
162+
assertIsClass(ParticipantEvent.TrackSubscriptionFailed::class.java, participantEvents[0])
163+
164+
val event = participantEvents.first() as ParticipantEvent.TrackSubscriptionFailed
165+
assertEquals(remoteParticipant, event.participant)
166+
assertEquals(TestData.REMOTE_AUDIO_TRACK.sid, event.sid)
167+
assertIsClass(TrackException.MediaException::class.java, event.exception)
168+
assertEquals("Codec not supported", event.exception.message)
169+
}
170+
}
118171
}

0 commit comments

Comments
 (0)