diff --git a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt index 1b085cf82d..d284319668 100644 --- a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt +++ b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt @@ -30,6 +30,8 @@ import android.widget.TextView import androidx.annotation.RequiresApi import androidx.fragment.app.Fragment import com.pedro.common.ConnectChecker +import com.pedro.common.StreamingStatsReport +import com.pedro.common.Throughput import com.pedro.common.onMainThreadHandler import com.pedro.encoder.input.sources.video.Camera1Source import com.pedro.encoder.input.sources.video.Camera2Source @@ -219,10 +221,18 @@ class CameraFragment: Fragment(), ConnectChecker { } } - override fun onNewBitrate(bitrate: Long) { + override fun onStreamingStats(report: StreamingStatsReport) { onMainThreadHandler { - bitrateAdapter.adaptBitrate(bitrate, genericStream.getStreamClient().hasCongestion()) - txtBitrate.text = String.format(Locale.getDefault(), "%.1f mb/s", bitrate / 1000_000f) + bitrateAdapter.adaptBitrate(report.smoothedBitrate, genericStream.getStreamClient().hasCongestion()) + if (report.throughput != Throughput.UNKNOWN) { + txtBitrate.text = String.format( + Locale.getDefault(), + "%.1f mb/s [%s, queue %d KB]", + report.smoothedBitrate / 1000_000f, + report.throughput.name.lowercase(), + report.queueBytesOut / 1024, + ) + } } } diff --git a/common/src/main/java/com/pedro/common/BitrateChecker.java b/common/src/main/java/com/pedro/common/BitrateChecker.java index a330bc6fad..8070661110 100644 --- a/common/src/main/java/com/pedro/common/BitrateChecker.java +++ b/common/src/main/java/com/pedro/common/BitrateChecker.java @@ -21,4 +21,6 @@ */ public interface BitrateChecker { default void onNewBitrate(long bitrate) {} + + default void onStreamingStats(StreamingStatsReport report) {} } diff --git a/common/src/main/java/com/pedro/common/BitrateManager.kt b/common/src/main/java/com/pedro/common/BitrateManager.kt index 49a64a9c0f..0afd4d822b 100644 --- a/common/src/main/java/com/pedro/common/BitrateManager.kt +++ b/common/src/main/java/com/pedro/common/BitrateManager.kt @@ -37,12 +37,14 @@ open class BitrateManager(private val bitrateChecker: BitrateChecker) { val currentValue = (bitrate / (timeDiff / 1000f)).toLong() if (bitrateOld == 0L) { bitrateOld = currentValue } bitrateOld = (bitrateOld + exponentialFactor * (currentValue - bitrateOld)).toLong() - onMainThread { bitrateChecker.onNewBitrate(bitrateOld) } timeStamp = TimeUtils.getCurrentTimeMillis() bitrate = 0 + onMainThread { bitrateChecker.onNewBitrate(bitrateOld) } } } + fun getSmoothedBitrate(): Long = bitrateOld + fun reset() { bitrate = 0 bitrateOld = 0 diff --git a/common/src/main/java/com/pedro/common/StreamBlockingQueue.kt b/common/src/main/java/com/pedro/common/StreamBlockingQueue.kt index 00709eef39..10fa911bc2 100644 --- a/common/src/main/java/com/pedro/common/StreamBlockingQueue.kt +++ b/common/src/main/java/com/pedro/common/StreamBlockingQueue.kt @@ -72,4 +72,10 @@ class StreamBlockingQueue(var capacity: Int) { } fun getSize() = queue.size + + /** + * Sum of [MediaFrame.info.size] for all frames in the main send queue. + * Delay/cache queue bytes are excluded. + */ + fun getTotalSize(): Long = queue.sumOf { it.info.size.toLong() } } \ No newline at end of file diff --git a/common/src/main/java/com/pedro/common/StreamingStatsMonitor.kt b/common/src/main/java/com/pedro/common/StreamingStatsMonitor.kt new file mode 100644 index 0000000000..783857b020 --- /dev/null +++ b/common/src/main/java/com/pedro/common/StreamingStatsMonitor.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 pedroSG94. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.pedro.common + +/** + * Collects send-queue and throughput statistics each second and classifies bandwidth trends + * over a sliding window, mirroring HaishinKit NetworkMonitor behavior. + */ +class StreamingStatsMonitor(private val bitrateChecker: BitrateChecker) { + + private val measureInterval = 3 + private val congestionThreshold = 95f + private val previousQueueBytesOut: MutableList = mutableListOf() + + fun reset() { + previousQueueBytesOut.clear() + } + + suspend fun collect( + queueBytesOut: Long, + bytesOutPerSecond: Long, + totalBytesOut: Long, + smoothedBitrate: Long, + queueCongestionPercent: Float, + ) { + var throughput = Throughput.UNKNOWN + previousQueueBytesOut.add(queueBytesOut) + if (measureInterval <= previousQueueBytesOut.size) { + var countQueuedBytesGrowing = 0 + for (i in 0 until previousQueueBytesOut.size - 1) { + if (previousQueueBytesOut[i] < previousQueueBytesOut[i + 1]) { + countQueuedBytesGrowing++ + } + } + if (countQueuedBytesGrowing == measureInterval - 1) { + throughput = Throughput.INSUFFICIENT + } else if (countQueuedBytesGrowing == 0) { + throughput = Throughput.SUFFICIENT + } + previousQueueBytesOut.removeAt(0) + } + // This library caps the queue at a fixed capacity, + // so a saturated queue stops growing and will return Insufficient + if (queueCongestionPercent >= congestionThreshold) { + throughput = Throughput.INSUFFICIENT + } + + val report = StreamingStatsReport( + bytesOutPerSecond = bytesOutPerSecond, + queueBytesOut = queueBytesOut, + totalBytesOut = totalBytesOut, + queueCongestionPercent = queueCongestionPercent, + throughput = throughput, + bitrate = bytesOutPerSecond * 8, + smoothedBitrate = smoothedBitrate, + ) + onMainThread { bitrateChecker.onStreamingStats(report) } + } +} diff --git a/common/src/main/java/com/pedro/common/StreamingStatsReport.kt b/common/src/main/java/com/pedro/common/StreamingStatsReport.kt new file mode 100644 index 0000000000..989d80da62 --- /dev/null +++ b/common/src/main/java/com/pedro/common/StreamingStatsReport.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 pedroSG94. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.pedro.common + +/** + * Per-second streaming statistics report. + * + * Mirrors HaishinKit [NetworkMonitorReport](https://github.com/HaishinKit/HaishinKit.swift/blob/main/HaishinKit/Sources/Network/NetworkMonitorReport.swift). + */ +data class StreamingStatsReport( + val bytesOutPerSecond: Long, + val queueBytesOut: Long, + val totalBytesOut: Long, + /** Percent (0-100) of the send queue's fixed capacity currently in use. */ + val queueCongestionPercent: Float, + val throughput: Throughput, + val bitrate: Long, + val smoothedBitrate: Long, +) diff --git a/common/src/main/java/com/pedro/common/Throughput.kt b/common/src/main/java/com/pedro/common/Throughput.kt new file mode 100644 index 0000000000..b5a25ea2ff --- /dev/null +++ b/common/src/main/java/com/pedro/common/Throughput.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 pedroSG94. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.pedro.common + +enum class Throughput { + UNKNOWN, + SUFFICIENT, + INSUFFICIENT, +} diff --git a/common/src/main/java/com/pedro/common/base/BaseSender.kt b/common/src/main/java/com/pedro/common/base/BaseSender.kt index 2f2f64d18f..7aa44e7e2f 100644 --- a/common/src/main/java/com/pedro/common/base/BaseSender.kt +++ b/common/src/main/java/com/pedro/common/base/BaseSender.kt @@ -6,7 +6,9 @@ import com.pedro.common.BufferPool import com.pedro.common.ConnectChecker import com.pedro.common.StreamBlockingQueue import com.pedro.common.clone +import com.pedro.common.StreamingStatsMonitor import com.pedro.common.frame.MediaFrame +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -36,6 +38,7 @@ abstract class BaseSender( private val droppedVideoFrames = AtomicLong(0) private val bitrateManager: BitrateManager = BitrateManager(connectChecker) + private val streamingStatsMonitor: StreamingStatsMonitor = StreamingStatsMonitor(connectChecker) protected var isEnableLogs = true private var job: Job? = null protected val scope = CoroutineScope(Dispatchers.IO) @@ -81,16 +84,34 @@ abstract class BaseSender( } } - fun start() { + suspend fun start() { + running = false + job?.cancelAndJoin() bitrateManager.reset() queue.clear { bufferPool.release(it.data) } + streamingStatsMonitor.reset() running = true job = scope.launch { val bitrateTask = async { while (scope.isActive && running) { - //bytes to bits - bitrateManager.calculateBitrate(bytesSendPerSecond.get() * 8) - bytesSendPerSecond.set(0) + try { + val bytesThisSecond = bytesSendPerSecond.getAndSet(0) + //bytes to bits + bitrateManager.calculateBitrate(bytesThisSecond * 8) + streamingStatsMonitor.collect( + queueBytesOut = queue.getTotalSize(), + bytesOutPerSecond = bytesThisSecond, + totalBytesOut = bytesSend.get(), + smoothedBitrate = bitrateManager.getSmoothedBitrate(), + queueCongestionPercent = queueUsagePercent(), + ) + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + //never let a reporting failure (e.g. no Main dispatcher, checker callback + //throwing) take down the send loop + Log.e(TAG, "bitrate/stats reporting failed", e) + } delay(timeMillis = 1000) } } @@ -115,10 +136,14 @@ abstract class BaseSender( @Throws(IllegalArgumentException::class) fun hasCongestion(percentUsed: Float = 20f): Boolean { if (percentUsed !in 0.0..100.0) throw IllegalArgumentException("the value must be in range 0 to 100") + return queueUsagePercent() >= percentUsed + } + + private fun queueUsagePercent(): Float { val size = queue.getSize().toFloat() val remaining = queue.remainingCapacity().toFloat() val capacity = size + remaining - return size >= capacity * (percentUsed / 100f) + return if (capacity <= 0f) 0f else (size / capacity) * 100f } fun resizeCache(newSize: Int) { @@ -132,6 +157,8 @@ abstract class BaseSender( fun getItemsInCache(): Int = queue.getSize() + fun getQueueBytesOut(): Long = queue.getTotalSize() + fun clearCache() { queue.clear { bufferPool.release(it.data) } } diff --git a/common/src/test/java/com/pedro/common/StreamingStatsMonitorTest.kt b/common/src/test/java/com/pedro/common/StreamingStatsMonitorTest.kt new file mode 100644 index 0000000000..e6d71c0eb4 --- /dev/null +++ b/common/src/test/java/com/pedro/common/StreamingStatsMonitorTest.kt @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2024 pedroSG94. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.pedro.common + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestWatcher +import org.junit.runner.Description +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnitRunner +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.times +import org.mockito.kotlin.verify + +@RunWith(MockitoJUnitRunner::class) +class StreamingStatsMonitorTest { + + @OptIn(ExperimentalCoroutinesApi::class) + @get:Rule + val mainDispatcherRule = object : TestWatcher() { + override fun starting(description: Description) { + Dispatchers.setMain(UnconfinedTestDispatcher()) + } + + override fun finished(description: Description) { + Dispatchers.resetMain() + } + } + + @Mock + private lateinit var connectChecker: ConnectChecker + + private lateinit var monitor: StreamingStatsMonitor + + @Before + fun setup() { + monitor = StreamingStatsMonitor(connectChecker) + } + + @Test + fun `WHEN fewer than 3 samples THEN throughput is Unknown`() = runTest { + monitor.collect( + queueBytesOut = 100L, + bytesOutPerSecond = 1000L, + totalBytesOut = 1000L, + smoothedBitrate = 8000L, + queueCongestionPercent = 0f, + ) + monitor.collect( + queueBytesOut = 200L, + bytesOutPerSecond = 1000L, + totalBytesOut = 2000L, + smoothedBitrate = 8000L, + queueCongestionPercent = 0f, + ) + + val captor = argumentCaptor() + verify(connectChecker, times(2)).onStreamingStats(captor.capture()) + captor.allValues.forEach { assertEquals(Throughput.UNKNOWN, it.throughput) } + } + + @Test + fun `WHEN queue bytes grow for 3 intervals THEN throughput is Insufficient`() = runTest { + repeat(2) { + monitor.collect( + queueBytesOut = (it + 1) * 100L, + bytesOutPerSecond = 500L, + totalBytesOut = 500L, + smoothedBitrate = 4000L, + queueCongestionPercent = 0f, + ) + } + monitor.collect( + queueBytesOut = 300L, + bytesOutPerSecond = 500L, + totalBytesOut = 1500L, + smoothedBitrate = 4000L, + queueCongestionPercent = 0f, + ) + + val captor = argumentCaptor() + verify(connectChecker, times(3)).onStreamingStats(captor.capture()) + assertEquals(Throughput.INSUFFICIENT, captor.lastValue.throughput) + assertEquals(300L, captor.lastValue.queueBytesOut) + assertEquals(500L, captor.lastValue.bytesOutPerSecond) + assertEquals(4000L, captor.lastValue.bitrate) + } + + @Test + fun `WHEN queue bytes shrink for 3 intervals THEN throughput is Sufficient`() = runTest { + monitor.collect( + queueBytesOut = 300L, + bytesOutPerSecond = 800L, + totalBytesOut = 800L, + smoothedBitrate = 6400L, + queueCongestionPercent = 0f, + ) + monitor.collect( + queueBytesOut = 200L, + bytesOutPerSecond = 800L, + totalBytesOut = 1600L, + smoothedBitrate = 6400L, + queueCongestionPercent = 0f, + ) + monitor.collect( + queueBytesOut = 100L, + bytesOutPerSecond = 800L, + totalBytesOut = 2400L, + smoothedBitrate = 6400L, + queueCongestionPercent = 0f, + ) + + val captor = argumentCaptor() + verify(connectChecker, times(3)).onStreamingStats(captor.capture()) + assertEquals(Throughput.SUFFICIENT, captor.lastValue.throughput) + } + + @Test + fun `WHEN queue trend is mixed THEN throughput stays Unknown`() = runTest { + monitor.collect(queueBytesOut = 100L, bytesOutPerSecond = 100L, totalBytesOut = 100L, smoothedBitrate = 800L, queueCongestionPercent = 0f) + monitor.collect(queueBytesOut = 200L, bytesOutPerSecond = 100L, totalBytesOut = 200L, smoothedBitrate = 800L, queueCongestionPercent = 0f) + monitor.collect(queueBytesOut = 150L, bytesOutPerSecond = 100L, totalBytesOut = 300L, smoothedBitrate = 800L, queueCongestionPercent = 0f) + + val captor = argumentCaptor() + verify(connectChecker, times(3)).onStreamingStats(captor.capture()) + assertEquals(Throughput.UNKNOWN, captor.lastValue.throughput) + } + + @Test + fun `WHEN reset THEN trend window clears`() = runTest { + repeat(3) { + monitor.collect( + queueBytesOut = (it + 1) * 100L, + bytesOutPerSecond = 100L, + totalBytesOut = 100L, + smoothedBitrate = 800L, + queueCongestionPercent = 0f, + ) + } + monitor.reset() + monitor.collect( + queueBytesOut = 400L, + bytesOutPerSecond = 100L, + totalBytesOut = 100L, + smoothedBitrate = 800L, + queueCongestionPercent = 0f, + ) + + val captor = argumentCaptor() + verify(connectChecker, times(4)).onStreamingStats(captor.capture()) + assertEquals(Throughput.UNKNOWN, captor.lastValue.throughput) + } + + @Test + fun `WHEN queue is congested THEN throughput is Insufficient even with a flat queue trend`() = runTest { + //queueBytesOut stays flat because the queue is saturated at its fixed capacity, not + //because throughput is healthy, so the trend alone would misread this as Sufficient + repeat(3) { + monitor.collect( + queueBytesOut = 1000L, + bytesOutPerSecond = 100L, + totalBytesOut = 100L, + smoothedBitrate = 800L, + queueCongestionPercent = 100f, + ) + } + + val captor = argumentCaptor() + verify(connectChecker, times(3)).onStreamingStats(captor.capture()) + assertEquals(Throughput.INSUFFICIENT, captor.lastValue.throughput) + assertEquals(100f, captor.lastValue.queueCongestionPercent) + } +} diff --git a/library/src/main/java/com/pedro/library/util/streamclient/GenericStreamClient.kt b/library/src/main/java/com/pedro/library/util/streamclient/GenericStreamClient.kt index 399cc48827..1df58c19b3 100644 --- a/library/src/main/java/com/pedro/library/util/streamclient/GenericStreamClient.kt +++ b/library/src/main/java/com/pedro/library/util/streamclient/GenericStreamClient.kt @@ -172,6 +172,8 @@ class GenericStreamClient( override fun getItemsInCache(): Int = connectedStreamClient?.getItemsInCache() ?: 0 + override fun getQueueBytesOut(): Long = connectedStreamClient?.getQueueBytesOut() ?: 0L + override fun getSentAudioFrames(): Long = connectedStreamClient?.getSentAudioFrames() ?: 0 override fun getSentVideoFrames(): Long = connectedStreamClient?.getSentVideoFrames() ?: 0 diff --git a/library/src/main/java/com/pedro/library/util/streamclient/RtmpStreamClient.kt b/library/src/main/java/com/pedro/library/util/streamclient/RtmpStreamClient.kt index 77f72f432a..e36a8e3de2 100644 --- a/library/src/main/java/com/pedro/library/util/streamclient/RtmpStreamClient.kt +++ b/library/src/main/java/com/pedro/library/util/streamclient/RtmpStreamClient.kt @@ -137,6 +137,8 @@ class RtmpStreamClient( override fun getItemsInCache(): Int = rtmpClient.getItemsInCache() + override fun getQueueBytesOut(): Long = rtmpClient.getQueueBytesOut() + override fun getSentAudioFrames(): Long = rtmpClient.sentAudioFrames override fun getSentVideoFrames(): Long = rtmpClient.sentVideoFrames diff --git a/library/src/main/java/com/pedro/library/util/streamclient/RtspStreamClient.kt b/library/src/main/java/com/pedro/library/util/streamclient/RtspStreamClient.kt index c0d033a2b6..d9e59555c4 100644 --- a/library/src/main/java/com/pedro/library/util/streamclient/RtspStreamClient.kt +++ b/library/src/main/java/com/pedro/library/util/streamclient/RtspStreamClient.kt @@ -92,6 +92,8 @@ class RtspStreamClient( override fun getItemsInCache(): Int = rtspClient.getItemsInCache() + override fun getQueueBytesOut(): Long = rtspClient.getQueueBytesOut() + override fun getSentAudioFrames(): Long = rtspClient.sentAudioFrames override fun getSentVideoFrames(): Long = rtspClient.sentVideoFrames 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 77b53964c2..c672940e19 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 @@ -89,6 +89,8 @@ class SrtStreamClient( override fun getItemsInCache(): Int = srtClient.getItemsInCache() + override fun getQueueBytesOut(): Long = srtClient.getQueueBytesOut() + override fun getSentAudioFrames(): Long = srtClient.sentAudioFrames override fun getSentVideoFrames(): Long = srtClient.sentVideoFrames diff --git a/library/src/main/java/com/pedro/library/util/streamclient/StreamBaseClient.kt b/library/src/main/java/com/pedro/library/util/streamclient/StreamBaseClient.kt index 4870549800..6eb21dc77c 100644 --- a/library/src/main/java/com/pedro/library/util/streamclient/StreamBaseClient.kt +++ b/library/src/main/java/com/pedro/library/util/streamclient/StreamBaseClient.kt @@ -51,6 +51,7 @@ abstract class StreamBaseClient { abstract fun clearCache() abstract fun getCacheSize(): Int abstract fun getItemsInCache(): Int + abstract fun getQueueBytesOut(): Long abstract fun getSentAudioFrames(): Long abstract fun getSentVideoFrames(): Long abstract fun getBytesSend(): Long diff --git a/library/src/main/java/com/pedro/library/util/streamclient/UdpStreamClient.kt b/library/src/main/java/com/pedro/library/util/streamclient/UdpStreamClient.kt index 02ff7aa71a..cf9784c7fa 100644 --- a/library/src/main/java/com/pedro/library/util/streamclient/UdpStreamClient.kt +++ b/library/src/main/java/com/pedro/library/util/streamclient/UdpStreamClient.kt @@ -75,6 +75,8 @@ class UdpStreamClient( override fun getItemsInCache(): Int = udpClient.getItemsInCache() + override fun getQueueBytesOut(): Long = udpClient.getQueueBytesOut() + override fun getSentAudioFrames(): Long = udpClient.sentAudioFrames override fun getSentVideoFrames(): Long = udpClient.sentVideoFrames diff --git a/library/src/main/java/com/pedro/library/util/streamclient/WhipStreamClient.kt b/library/src/main/java/com/pedro/library/util/streamclient/WhipStreamClient.kt index db470ce35a..5652db0651 100644 --- a/library/src/main/java/com/pedro/library/util/streamclient/WhipStreamClient.kt +++ b/library/src/main/java/com/pedro/library/util/streamclient/WhipStreamClient.kt @@ -79,6 +79,8 @@ class WhipStreamClient( override fun getItemsInCache(): Int = whipClient.getItemsInCache() + override fun getQueueBytesOut(): Long = whipClient.getQueueBytesOut() + override fun getSentAudioFrames(): Long = whipClient.sentAudioFrames override fun getSentVideoFrames(): Long = whipClient.sentVideoFrames diff --git a/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt b/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt index 6dd90d5537..30d11b8798 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt @@ -649,6 +649,8 @@ class RtmpClient(private val connectChecker: ConnectChecker) { fun getItemsInCache(): Int = rtmpSender.getItemsInCache() + fun getQueueBytesOut(): Long = rtmpSender.getQueueBytesOut() + /** * @param factor values from 0.1f to 1f * Set an exponential factor to the bitrate calculation to avoid bitrate spikes diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt index d5c3faf293..67720bddbf 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt @@ -525,6 +525,8 @@ class RtspClient(private val connectChecker: ConnectChecker) { fun getItemsInCache(): Int = rtspSender.getItemsInCache() + fun getQueueBytesOut(): Long = rtspSender.getQueueBytesOut() + /** * @param factor values from 0.1f to 1f * Set an exponential factor to the bitrate calculation to avoid bitrate spikes 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 d888618020..7b91ebdfec 100644 --- a/srt/src/main/java/com/pedro/srt/srt/SrtClient.kt +++ b/srt/src/main/java/com/pedro/srt/srt/SrtClient.kt @@ -60,6 +60,7 @@ import kotlinx.coroutines.withTimeoutOrNull import java.io.IOException import java.net.URISyntaxException import java.nio.ByteBuffer +import kotlin.time.Duration.Companion.milliseconds /** * Created by pedro on 20/8/23. @@ -290,7 +291,7 @@ class SrtClient(private val connectChecker: ConnectChecker) { private suspend fun disconnect(clear: Boolean) { if (isStreaming) srtSender.stop(clear) runCatching { - withTimeoutOrNull(100) { + withTimeoutOrNull(100.milliseconds) { commandsManager.writeShutdown(socket) } } @@ -324,7 +325,7 @@ class SrtClient(private val connectChecker: ConnectChecker) { jobRetry = scopeRetry.launch { reTries-- disconnect(false) - delay(delay) + delay(delay.milliseconds) val reconnectUrl = backupUrl ?: url connect(reconnectUrl, true) } @@ -484,6 +485,8 @@ class SrtClient(private val connectChecker: ConnectChecker) { fun getItemsInCache(): Int = srtSender.getItemsInCache() + fun getQueueBytesOut(): Long = srtSender.getQueueBytesOut() + /** * @param factor values from 0.1f to 1f * Set an exponential factor to the bitrate calculation to avoid bitrate spikes diff --git a/udp/src/main/java/com/pedro/udp/UdpClient.kt b/udp/src/main/java/com/pedro/udp/UdpClient.kt index d9151f62b0..5996c2f89f 100644 --- a/udp/src/main/java/com/pedro/udp/UdpClient.kt +++ b/udp/src/main/java/com/pedro/udp/UdpClient.kt @@ -40,6 +40,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.net.URISyntaxException import java.nio.ByteBuffer +import kotlin.time.Duration.Companion.milliseconds /** * Created by pedro on 6/3/24. @@ -228,7 +229,7 @@ class UdpClient(private val connectChecker: ConnectChecker) { jobRetry = scopeRetry.launch { reTries-- disconnect(false) - delay(delay) + delay(delay.milliseconds) val reconnectUrl = backupUrl ?: url connect(reconnectUrl, true) } @@ -300,6 +301,8 @@ class UdpClient(private val connectChecker: ConnectChecker) { fun getItemsInCache(): Int = udpSender.getItemsInCache() + fun getQueueBytesOut(): Long = udpSender.getQueueBytesOut() + /** * @param factor values from 0.1f to 1f * Set an exponential factor to the bitrate calculation to avoid bitrate spikes diff --git a/whip/src/main/java/com/pedro/whip/WhipClient.kt b/whip/src/main/java/com/pedro/whip/WhipClient.kt index 9b0b97a5d0..3c2803e64c 100644 --- a/whip/src/main/java/com/pedro/whip/WhipClient.kt +++ b/whip/src/main/java/com/pedro/whip/WhipClient.kt @@ -506,6 +506,8 @@ class WhipClient(private val connectChecker: ConnectChecker) { fun getItemsInCache(): Int = whipSender.getItemsInCache() + fun getQueueBytesOut(): Long = whipSender.getQueueBytesOut() + /** * @param factor values from 0.1f to 1f * Set an exponential factor to the bitrate calculation to avoid bitrate spikes