Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ android {
isMinifyEnabled = false
}
}
testOptions {
unitTests.isReturnDefaultValues = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
Expand Down
21 changes: 8 additions & 13 deletions common/src/main/java/com/pedro/common/Extensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import java.io.UnsupportedEncodingException
import java.math.BigInteger
import java.nio.ByteBuffer
import java.security.MessageDigest
import java.net.InetAddress
import java.security.NoSuchAlgorithmException
import java.security.SecureRandom
import java.util.concurrent.BlockingQueue
Expand Down Expand Up @@ -143,13 +144,6 @@ fun newSingleThreadExecutor(queue: LinkedBlockingQueue<Runnable>): ExecutorServi
return ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, queue)
}

fun getSuspendContext(): Continuation<Unit> {
return object : Continuation<Unit> {
override val context = Dispatchers.IO
override fun resumeWith(result: Result<Unit>) {}
}
}

@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun <T> CameraCharacteristics.secureGet(key: CameraCharacteristics.Key<T>): T? {
return try { get(key) } catch (e: IllegalArgumentException) { null }
Expand All @@ -160,12 +154,6 @@ fun <T> CaptureRequest.Builder.secureGet(key: CaptureRequest.Key<T>): T? {
return try { get(key) } catch (e: IllegalArgumentException) { null }
}

fun String.getIndexes(char: Char): Array<Int> {
val indexes = mutableListOf<Int>()
forEachIndexed { index, c -> if (c == char) indexes.add(index) }
return indexes.toTypedArray()
}

fun Throwable.validMessage(): String {
return (message ?: "").ifEmpty { javaClass.simpleName }
}
Expand Down Expand Up @@ -342,4 +330,11 @@ fun ByteBuffer.put(buffer: ByteBuffer, offset: Int, length: Int) {
buffer.limit(offset + length)
this.put(buffer)
buffer.limit(limit)
}

/**
* Numeric IP as text, without reverse DNS lookup and without the IPv6 scope id (fe80::1%wlan0 -> fe80::1)
*/
fun InetAddress.addressToString(): String {
return (hostAddress ?: hostName).substringBefore("%")
}
11 changes: 8 additions & 3 deletions common/src/main/java/com/pedro/common/UrlParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class UrlParser private constructor(
init {
val url = uri.toString()
scheme = uri.scheme
host = uri.host
host = uri.host.removeSurrounding("[", "]")
port = if (uri.port < 0) null else uri.port
path = uri.path.removePrefix("/")
if (uri.query != null) {
Expand Down Expand Up @@ -79,17 +79,22 @@ class UrlParser private constructor(

fun getStreamName(): String = getFullPath().removePrefix(getAppName()).removePrefix("/").removePrefix("?")

/**
* Host ready to be concatenated in a url. IPv6 hosts need to be enclosed in brackets
*/
fun getHostForUrl(): String = if (host.contains(":")) "[$host]" else host

fun getTcUrl(): String {
val port = if (port != null) ":$port" else ""
val appName = if (getAppName().isNotEmpty()) "/${getAppName()}" else ""
return "$scheme://$host${port}${appName}"
return "$scheme://${getHostForUrl()}${port}${appName}"
}

fun getFullPath(): String {
val fullPath = "$path${if (query == null) "" else "?$query"}".removePrefix("?")
if (fullPath.isEmpty()) {
val port = if (port != null) ":$port" else ""
return url.removePrefix("$scheme://$host$port").removePrefix("/")
return url.removePrefix("$scheme://${getHostForUrl()}$port").removePrefix("/")
}
return fullPath
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.pedro.common.socket.java

import com.pedro.common.addressToString
import com.pedro.common.socket.base.UdpPacket
import com.pedro.common.socket.base.UdpStreamSocket
import com.pedro.common.socket.base.UdpType
Expand Down Expand Up @@ -98,7 +99,7 @@ class UdpStreamSocketJava(
val buffer = ByteArray(packetSize)
val udpPacket = DatagramPacket(buffer, buffer.size)
socket?.receive(udpPacket)
return UdpPacket(udpPacket.data, udpPacket.length, udpPacket.address.hostName, udpPacket.port)
return UdpPacket(udpPacket.data, udpPacket.length, udpPacket.address.addressToString(), udpPacket.port)
}

override suspend fun setRemoteAddress(host: String, port: Int) {
Expand All @@ -107,7 +108,7 @@ class UdpStreamSocketJava(
}

override suspend fun getLocalHost(): String {
return sourceHost ?: "0.0.0.0"
return socket?.localAddress?.addressToString() ?: sourceHost ?: "::"
}

override suspend fun getLocalPort(): Int {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class UdpStreamSocketKtor(
override suspend fun connect() {
val builder = aSocket(selectorManager).udp()
val localAddress = if (sourcePort == null) null else {
val localAddress = InetSocketAddress(sourceHost ?: "0.0.0.0", sourcePort)
val localAddress = InetSocketAddress(sourceHost ?: "::", sourcePort)
this.localAddress = localAddress
localAddress
}
Expand Down Expand Up @@ -96,15 +96,15 @@ class UdpStreamSocketKtor(
val length = datagram.packet.remaining.toInt()
val data = datagram.packet.readByteArray()
val address = datagram.address as? InetSocketAddress
return UdpPacket(data, length, address?.hostname, address?.port)
return UdpPacket(data, length, address?.hostname?.substringBefore("%"), address?.port)
}

override suspend fun setRemoteAddress(host: String, port: Int) {
address = InetSocketAddress(host, port)
}

override suspend fun getLocalHost(): String {
return localAddress?.hostname ?: "0.0.0.0"
return localAddress?.hostname ?: "::"
}

override suspend fun getLocalPort(): Int {
Expand Down
58 changes: 36 additions & 22 deletions common/src/test/java/com/pedro/common/BitrateManagerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,22 @@

package com.pedro.common

import com.pedro.common.util.MainDispatcherRule
import com.pedro.common.util.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Assert.assertTrue
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.MockedStatic
import org.mockito.Mockito
import org.mockito.junit.MockitoJUnitRunner
import org.mockito.kotlin.argumentCaptor
Expand All @@ -39,41 +45,49 @@ import org.mockito.kotlin.verify
@RunWith(MockitoJUnitRunner::class)
class BitrateManagerTest {

@OptIn(ExperimentalCoroutinesApi::class)
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
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 val timeUtilsMocked = Mockito.mockStatic(TimeUtils::class.java)
private lateinit var timeUtilsMocked: MockedStatic<TimeUtils>
private var fakeTime = 7502849023L

@Before
fun setup() {
timeUtilsMocked = Mockito.mockStatic(TimeUtils::class.java)
timeUtilsMocked.`when`<Long>(TimeUtils::getCurrentTimeMillis).then { fakeTime }
}

@After
fun teardown() {
fakeTime = 7502849023L
timeUtilsMocked.close()
}

@Test
fun `WHEN set multiple values THEN return total of values each second`() = runTest {
Utils.useStatics(listOf(timeUtilsMocked)) {
val bitrateManager = BitrateManager(connectChecker)
val fakeValues = arrayOf(100L, 200L, 300L, 400L, 500L)
var expectedResult = 0L
fakeValues.forEach {
bitrateManager.calculateBitrate(it)
expectedResult += it
}
fakeTime += 1000
val value = 100L
bitrateManager.calculateBitrate(value)
expectedResult += value
val resultValue = argumentCaptor<Long>()
verify(connectChecker, times(1)).onNewBitrate(resultValue.capture())
val marginError = 20
assertTrue(expectedResult - marginError <= resultValue.firstValue && resultValue.firstValue <= expectedResult + marginError)
val bitrateManager = BitrateManager(connectChecker)
val fakeValues = arrayOf(100L, 200L, 300L, 400L, 500L)
var expectedResult = 0L
fakeValues.forEach {
bitrateManager.calculateBitrate(it)
expectedResult += it
}
fakeTime += 1000
val value = 100L
bitrateManager.calculateBitrate(value)
expectedResult += value
val resultValue = argumentCaptor<Long>()
verify(connectChecker, times(1)).onNewBitrate(resultValue.capture())
val marginError = 20
assertTrue(expectedResult - marginError <= resultValue.firstValue && resultValue.firstValue <= expectedResult + marginError)
}
}
}
44 changes: 44 additions & 0 deletions common/src/test/java/com/pedro/common/UrlParserTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,50 @@ class UrlParserTest {
assertEquals(1935, urlParser0.port)
}

@Test
fun testIpv6Urls() {
val url = "rtmp://[::1]:1935/live/stream"
val urlParser = UrlParser.parse(url, arrayOf("rtmp"))
assertEquals("rtmp", urlParser.scheme)
assertEquals("::1", urlParser.host)
assertEquals(1935, urlParser.port)
assertEquals("live", urlParser.getAppName())
assertEquals("stream", urlParser.getStreamName())
assertEquals("rtmp://[::1]:1935/live", urlParser.getTcUrl())

val url1 = "rtsp://[2001:db8::1]/live/test"
val urlParser1 = UrlParser.parse(url1, arrayOf("rtsp"))
assertEquals("rtsp", urlParser1.scheme)
assertEquals("2001:db8::1", urlParser1.host)
assertEquals(null, urlParser1.port)
assertEquals("live/test", urlParser1.getFullPath())

val url2 = "srt://[2001:db8::aa:1]:9000?streamid=test/fake"
val urlParser2 = UrlParser.parse(url2, arrayOf("srt"))
assertEquals("srt", urlParser2.scheme)
assertEquals("2001:db8::aa:1", urlParser2.host)
assertEquals(9000, urlParser2.port)
assertEquals("test/fake", urlParser2.getQuery("streamid"))

val url3 = "rtmp://[::1]:1234?live"
val urlParser3 = UrlParser.parse(url3, arrayOf("rtmp"))
assertEquals("::1", urlParser3.host)
assertEquals(1234, urlParser3.port)
assertEquals("live", urlParser3.getAppName())
assertEquals("rtmp://[::1]:1234/live", urlParser3.getTcUrl())

val url4 = "udp://[ff0e::1]:5000"
val urlParser4 = UrlParser.parse(url4, arrayOf("udp"))
assertEquals("ff0e::1", urlParser4.host)
assertEquals(5000, urlParser4.port)

val url5 = "http://[::1]:8889/publish/stream"
val urlParser5 = UrlParser.parse(url5, arrayOf("http"))
assertEquals("::1", urlParser5.host)
assertEquals(8889, urlParser5.port)
assertEquals("publish/stream", urlParser5.getFullPath())
}

@Test
fun testUrl2() {
val urlParser = UrlParser.parse(
Expand Down
39 changes: 0 additions & 39 deletions common/src/test/java/com/pedro/common/util/MainDispatcherRule.kt

This file was deleted.

38 changes: 0 additions & 38 deletions common/src/test/java/com/pedro/common/util/Utils.kt

This file was deleted.

3 changes: 3 additions & 0 deletions encoder/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ android {
buildFeatures {
buildConfig = true
}
testOptions {
unitTests.isReturnDefaultValues = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
Expand Down
3 changes: 3 additions & 0 deletions extra-sources/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ android {
isMinifyEnabled = false
}
}
testOptions {
unitTests.isReturnDefaultValues = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
Expand Down
Loading
Loading