diff --git a/library/src/main/java/com/pedro/library/util/streamclient/SrtStreamClient.kt b/library/src/main/java/com/pedro/library/util/streamclient/SrtStreamClient.kt index c672940e1..6d9944191 100644 --- a/library/src/main/java/com/pedro/library/util/streamclient/SrtStreamClient.kt +++ b/library/src/main/java/com/pedro/library/util/streamclient/SrtStreamClient.kt @@ -73,6 +73,14 @@ class SrtStreamClient( srtClient.setLogs(enabled) } + /** + * Report a connection failure if the server sends nothing for [millis] (0 = disabled). + * See [com.pedro.srt.srt.SrtClient.setServerSilenceTimeout]. + */ + fun setServerSilenceTimeout(millis: Long) { + srtClient.setServerSilenceTimeout(millis) + } + override fun setCheckServerAlive(enabled: Boolean) { srtClient.setCheckServerAlive(enabled) } diff --git a/srt/src/main/java/com/pedro/srt/srt/SrtClient.kt b/srt/src/main/java/com/pedro/srt/srt/SrtClient.kt index 1dee25a66..81ccf7d2c 100644 --- a/srt/src/main/java/com/pedro/srt/srt/SrtClient.kt +++ b/srt/src/main/java/com/pedro/srt/srt/SrtClient.kt @@ -24,6 +24,7 @@ import com.pedro.common.ConnectionFailed import com.pedro.common.UrlParser import com.pedro.common.VideoCodec import com.pedro.common.frame.MediaFrame +import com.pedro.common.TimeUtils import com.pedro.common.onMainThread import com.pedro.common.socket.base.SocketType import com.pedro.common.socket.base.StreamSocket @@ -80,6 +81,7 @@ class SrtClient(private val connectChecker: ConnectChecker) { private var jobRetry: Job? = null private var checkServerAlive = false + private var serverSilenceTimeoutMs = 0L @Volatile var isStreaming = false private set @@ -169,6 +171,19 @@ class SrtClient(private val connectChecker: ConnectChecker) { checkServerAlive = enabled } + /** + * Report onConnectionFailed("No response from server") if no packet is received from the + * server for [millis]. While data is being published, the server acknowledges it with ACK + * packets (typically every 10 ms), so a long silence means the path is dead even if sending + * does not fail (for example, packets silently dropped by the network). + * Unlike [setCheckServerAlive] it needs no ICMP/Echo, which firewalls often block. + * Servers only acknowledge received data, so use a value well above the longest pause in + * which no media is sent. 0 (default) disables the check. + */ + fun setServerSilenceTimeout(millis: Long) { + serverSilenceTimeoutMs = millis + } + fun setReTries(reTries: Int) { numRetry = reTries this.reTries = reTries @@ -333,11 +348,13 @@ class SrtClient(private val connectChecker: ConnectChecker) { @Throws(IOException::class) private suspend fun handleServerPackets() { + var lastServerPacketTs = TimeUtils.getCurrentTimeMillis() while (scope.isActive && isStreaming) { val error = runCatching { if (isAlive()) { //ignore packet after connect if tunneled to avoid spam idle handleMessages() + lastServerPacketTs = TimeUtils.getCurrentTimeMillis() } else { onMainThread { connectChecker.onConnectionFailed("No response from server") @@ -347,6 +364,12 @@ class SrtClient(private val connectChecker: ConnectChecker) { }.exceptionOrNull() if (error != null && ConnectionFailed.parse(error.validMessage()) != ConnectionFailed.TIMEOUT) { scope.cancel() + } else if (error != null && serverSilenceTimeoutMs > 0 && + TimeUtils.getCurrentTimeMillis() - lastServerPacketTs >= serverSilenceTimeoutMs) { + onMainThread { + connectChecker.onConnectionFailed("No response from server") + } + scope.cancel() } } }