Skip to content

Commit f5d829a

Browse files
authored
Refactor sendData to return Result instead of throwing exceptions (#703)
* Switch away from throwing exceptions internally where not needed to avoid crashes. * changeset
1 parent 24173a8 commit f5d829a

18 files changed

Lines changed: 324 additions & 135 deletions

File tree

.changeset/soft-pugs-fetch.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"client-sdk-android": minor
3+
---
4+
5+
Refactor some internal data message sending methods to use Result instead of throwing Exceptions to
6+
fix crashes.

livekit-android-sdk/src/main/java/io/livekit/android/audio/PreconnectAudioBuffer.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,10 @@ internal constructor(timeout: Duration) : AudioTrackSink {
148148
)
149149

150150
try {
151-
sender.write(audioData)
151+
val result = sender.write(audioData)
152+
if (result.isFailure) {
153+
result.exceptionOrNull()?.let { throw it }
154+
}
152155
sender.close()
153156
} catch (e: Exception) {
154157
sender.close(e.localizedMessage)

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

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package io.livekit.android.room
1818

1919
import android.os.SystemClock
20+
import androidx.annotation.CheckResult
2021
import androidx.annotation.VisibleForTesting
2122
import com.google.protobuf.ByteString
2223
import com.vdurmont.semver4j.Semver
@@ -82,7 +83,6 @@ import livekit.org.webrtc.RtpSender
8283
import livekit.org.webrtc.RtpTransceiver
8384
import livekit.org.webrtc.RtpTransceiver.RtpTransceiverInit
8485
import livekit.org.webrtc.SessionDescription
85-
import java.net.ConnectException
8686
import java.nio.ByteBuffer
8787
import javax.inject.Inject
8888
import javax.inject.Named
@@ -445,7 +445,7 @@ internal constructor(
445445
* reconnect Signal and PeerConnections
446446
*/
447447
@Synchronized
448-
@VisibleForTesting
448+
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
449449
fun reconnect() {
450450
if (reconnectingJob?.isActive == true) {
451451
LKLog.d { "Reconnection is already in progress" }
@@ -625,22 +625,32 @@ internal constructor(
625625
}
626626
}
627627

628-
internal suspend fun sendData(dataPacket: LivekitModels.DataPacket) {
629-
ensurePublisherConnected(dataPacket.kind)
628+
@CheckResult
629+
internal suspend fun sendData(dataPacket: LivekitModels.DataPacket): Result<Unit> {
630+
try {
631+
ensurePublisherConnected(dataPacket.kind)
630632

631-
val buf = DataChannel.Buffer(
632-
ByteBuffer.wrap(dataPacket.toByteArray()),
633-
true,
634-
)
633+
val buf = DataChannel.Buffer(
634+
ByteBuffer.wrap(dataPacket.toByteArray()),
635+
true,
636+
)
635637

636-
val channel = dataChannelForKind(dataPacket.kind)
637-
?: throw TrackException.PublishException("channel not established for ${dataPacket.kind.name}")
638+
val channel = dataChannelForKind(dataPacket.kind)
639+
?: throw RoomException.ConnectException("channel not established for ${dataPacket.kind.name}")
638640

639-
channel.send(buf)
641+
channel.send(buf)
642+
} catch (e: Exception) {
643+
return Result.failure(e)
644+
}
645+
return Result.success(Unit)
640646
}
641647

642648
internal suspend fun waitForBufferStatusLow(kind: LivekitModels.DataPacket.Kind) {
643-
ensurePublisherConnected(kind)
649+
try {
650+
ensurePublisherConnected(kind)
651+
} catch (e: Exception) {
652+
return
653+
}
644654
val manager = when (kind) {
645655
LivekitModels.DataPacket.Kind.RELIABLE -> reliableDataChannelManager
646656
LivekitModels.DataPacket.Kind.LOSSY -> lossyDataChannelManager
@@ -650,11 +660,12 @@ internal constructor(
650660
}
651661

652662
if (manager == null) {
653-
throw IllegalStateException("Not connected!")
663+
return
654664
}
655665
manager.waitForBufferedAmountLow(DATA_CHANNEL_LOW_THRESHOLD.toLong())
656666
}
657667

668+
@Throws(exceptionClasses = [RoomException.ConnectException::class])
658669
private suspend fun ensurePublisherConnected(kind: LivekitModels.DataPacket.Kind) {
659670
if (!isSubscriberPrimary) {
660671
return
@@ -671,7 +682,7 @@ internal constructor(
671682
this.negotiatePublisher()
672683
}
673684

674-
val targetChannel = dataChannelForKind(kind) ?: throw IllegalArgumentException("Unknown data packet kind!")
685+
val targetChannel = dataChannelForKind(kind) ?: throw RoomException.ConnectException("Publisher isn't setup yet! Is room not connected?!")
675686
if (targetChannel.state() == DataChannel.State.OPEN) {
676687
return
677688
}
@@ -685,14 +696,14 @@ internal constructor(
685696
delay(50)
686697
}
687698

688-
throw ConnectException("could not establish publisher connection")
699+
throw RoomException.ConnectException("could not establish publisher connection")
689700
}
690701

691702
private fun dataChannelForKind(kind: LivekitModels.DataPacket.Kind) =
692703
when (kind) {
693704
LivekitModels.DataPacket.Kind.RELIABLE -> reliableDataChannel
694705
LivekitModels.DataPacket.Kind.LOSSY -> lossyDataChannel
695-
else -> null
706+
LivekitModels.DataPacket.Kind.UNRECOGNIZED -> throw IllegalArgumentException("Unknown data packet kind!")
696707
}
697708

698709
private fun getPublisherOfferConstraints(): MediaConstraints {
@@ -1137,6 +1148,7 @@ internal constructor(
11371148

11381149
val dataChannelInfos = LivekitModels.DataPacket.Kind.values()
11391150
.toList()
1151+
.filterNot { it == LivekitModels.DataPacket.Kind.UNRECOGNIZED }
11401152
.mapNotNull { kind -> dataChannelForKind(kind) }
11411153
.map { dataChannel ->
11421154
LivekitRtc.DataChannelInfo.newBuilder()

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

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,48 @@
1717
package io.livekit.android.room.datastream
1818

1919
sealed class StreamException(message: String? = null) : Exception(message) {
20+
/**
21+
* Unable to open a stream with the same ID as an existing open stream.
22+
*/
2023
class AlreadyOpenedException : StreamException()
21-
class AbnormalEndException(message: String?) : StreamException(message)
24+
25+
/**
26+
* Stream closed abnormally by remote participant.
27+
*/
28+
class AbnormalEndException(message: String? = null) : StreamException(message)
29+
30+
/**
31+
* Incoming chunk data could not be decoded.
32+
*/
2233
class DecodeFailedException : StreamException()
34+
35+
/**
36+
* Length exceeded total length specified in stream header.
37+
*/
2338
class LengthExceededException : StreamException()
39+
40+
/**
41+
* Length is less than total length specified in stream header.
42+
*/
2443
class IncompleteException : StreamException()
25-
class TerminatedException : StreamException()
44+
45+
/**
46+
* Stream terminated before completion.
47+
*/
48+
class TerminatedException(message: String? = null) : StreamException(message)
49+
50+
/**
51+
* Cannot perform operations on an unknown stream.
52+
*/
2653
class UnknownStreamException : StreamException()
54+
55+
/**
56+
* Given destination URL is not a directory.
57+
*/
2758
class NotDirectoryException : StreamException()
59+
60+
/**
61+
* Unable to read information about the file to send.
62+
*/
2863
class FileInfoUnavailableException : StreamException()
2964
}

livekit-android-sdk/src/main/java/io/livekit/android/room/datastream/outgoing/BaseStreamSender.kt

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,31 @@
1616

1717
package io.livekit.android.room.datastream.outgoing
1818

19+
import androidx.annotation.CheckResult
1920
import io.livekit.android.room.datastream.StreamException
2021

2122
abstract class BaseStreamSender<T>(
2223
internal val destination: StreamDestination<T>,
2324
) {
2425

25-
suspend fun write(data: T) {
26+
val isOpen: Boolean
27+
get() = destination.isOpen
28+
29+
/**
30+
* Write to the stream.
31+
*/
32+
@CheckResult
33+
suspend fun write(data: T): Result<Unit> {
2634
if (!destination.isOpen) {
27-
throw StreamException.TerminatedException()
35+
return Result.failure(StreamException.TerminatedException())
2836
}
2937

30-
writeImpl(data)
38+
return writeImpl(data)
3139
}
3240

33-
internal abstract suspend fun writeImpl(data: T)
41+
@CheckResult
42+
internal abstract suspend fun writeImpl(data: T): Result<Unit>
43+
3444
suspend fun close(reason: String? = null) {
3545
destination.close(reason)
3646
}
@@ -41,7 +51,9 @@ abstract class BaseStreamSender<T>(
4151
*/
4252
interface StreamDestination<T> {
4353
val isOpen: Boolean
44-
suspend fun write(data: T, chunker: DataChunker<T>)
54+
55+
@CheckResult
56+
suspend fun write(data: T, chunker: DataChunker<T>): Result<Unit>
4557
suspend fun close(reason: String?)
4658
}
4759

livekit-android-sdk/src/main/java/io/livekit/android/room/datastream/outgoing/ByteStreamSender.kt

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ class ByteStreamSender(
3030
val info: ByteStreamInfo,
3131
destination: StreamDestination<ByteArray>,
3232
) : BaseStreamSender<ByteArray>(destination = destination) {
33-
override suspend fun writeImpl(data: ByteArray) {
34-
destination.write(data, byteDataChunker)
33+
34+
override suspend fun writeImpl(data: ByteArray): Result<Unit> {
35+
return destination.write(data, byteDataChunker)
3536
}
3637
}
3738

@@ -50,9 +51,7 @@ private val byteDataChunker: DataChunker<ByteArray> = { data: ByteArray, chunkSi
5051
}
5152

5253
/**
53-
* Reads the file and writes it to the data stream.
54-
*
55-
* @throws
54+
* Reads the file from [filePath] and writes it to the data stream.
5655
*/
5756
suspend fun ByteStreamSender.writeFile(filePath: String) {
5857
write(FileSystem.SYSTEM.source(filePath.toPath()))
@@ -68,14 +67,23 @@ suspend fun ByteStreamSender.write(input: InputStream) {
6867
/**
6968
* Reads the source and sends it to the data stream.
7069
*/
71-
suspend fun ByteStreamSender.write(source: Source) {
70+
suspend fun ByteStreamSender.write(source: Source): Result<Unit> {
7271
val buffer = Buffer()
7372
while (true) {
74-
val readLen = source.read(buffer, 4096)
75-
if (readLen == -1L) {
76-
break
77-
}
73+
try {
74+
val readLen = source.read(buffer, 4096)
75+
if (readLen == -1L) {
76+
break
77+
}
7878

79-
write(buffer.readByteArray())
79+
val result = write(buffer.readByteArray())
80+
if (result.isFailure) {
81+
return result
82+
}
83+
} catch (e: Exception) {
84+
return Result.failure(e)
85+
}
8086
}
87+
88+
return Result.success(Unit)
8189
}

0 commit comments

Comments
 (0)