diff --git a/common/src/main/java/com/pedro/common/Extensions.kt b/common/src/main/java/com/pedro/common/Extensions.kt index 8b8ed58ce..7ff3b12a1 100644 --- a/common/src/main/java/com/pedro/common/Extensions.kt +++ b/common/src/main/java/com/pedro/common/Extensions.kt @@ -129,4 +129,10 @@ fun CameraCharacteristics.secureGet(key: CameraCharacteristics.Key): T? { @RequiresApi(Build.VERSION_CODES.LOLLIPOP) fun CaptureRequest.Builder.secureGet(key: CaptureRequest.Key): T? { return try { get(key) } catch (e: IllegalArgumentException) { null } +} + +fun String.getIndexes(char: Char): Array { + val indexes = mutableListOf() + forEachIndexed { index, c -> if (c == char) indexes.add(index) } + return indexes.toTypedArray() } \ No newline at end of file diff --git a/common/src/main/java/com/pedro/common/UrlParser.kt b/common/src/main/java/com/pedro/common/UrlParser.kt new file mode 100644 index 000000000..8746940de --- /dev/null +++ b/common/src/main/java/com/pedro/common/UrlParser.kt @@ -0,0 +1,109 @@ +package com.pedro.common + +import java.net.URI +import java.net.URISyntaxException +import java.util.regex.Pattern + +/** + * Created by pedro on 2/9/24. + */ +class UrlParser private constructor( + uri: URI, + private val url: String +) { + + companion object { + @Throws(URISyntaxException::class) + fun parse(endpoint: String, requiredProtocol: Array): UrlParser { + val uri = URI(endpoint) + if (!requiredProtocol.contains(uri.scheme.trim())) { + throw URISyntaxException(endpoint, "Invalid protocol: ${uri.scheme}") + } + if (uri.userInfo != null && !uri.userInfo.contains(":")) { + throw URISyntaxException(endpoint, "Invalid auth. Auth must contain ':'") + } + return UrlParser(uri, endpoint) + } + } + + var scheme: String = "" + private set + var host: String = "" + private set + var port: Int? = null + private set + var path: String = "" + private set + var query: String? = null + private set + var auth: String? = null + private set + + init { + val url = uri.toString() + scheme = uri.scheme + host = uri.host + port = if (uri.port < 0) null else uri.port + path = uri.path.removePrefix("/") + if (uri.query != null) { + val i = url.indexOf(uri.query) + query = url.substring(if (i < 0) 0 else i) + } + auth = uri.userInfo + } + + fun getQuery(key: String): String? = getAllQueries()[key] + + fun getAuthUser(): String? { + val userInfo = auth?.split(":") ?: return null + return if (userInfo.size == 2) userInfo[0] else null + } + + fun getAuthPassword(): String? { + val userInfo = auth?.split(":") ?: return null + return if (userInfo.size == 2) userInfo[1] else null + } + + fun getAppName(): String { + val fullPath = getFullPath() + val indexes = fullPath.getIndexes('/') + return when (indexes.size) { + 0 -> fullPath + 1 -> fullPath.substring(0, indexes[0]) + else -> { + if (getAllQueries().isEmpty()) { + fullPath.substring(0, indexes[1]) + } else { + fullPath.substring(0, indexes[0]) + } + } + } + } + + fun getStreamName(): String = getFullPath().removePrefix(getAppName()).removePrefix("/") + + fun getTcUrl(): String { + val port = if (port != null) ":$port" else "" + val appName = if (getAppName().isNotEmpty()) "/${getAppName()}" else "" + return "$scheme://$host${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 fullPath + } + + private fun getAllQueries(): Map { + val queries = query?.split("&") ?: emptyList() + val map = HashMap() + queries.forEach { entry -> + val data = entry.split(Pattern.compile("="), 2) + if (data.size == 2) map[data[0]] = data[1] + } + return map + } +} \ No newline at end of file diff --git a/common/src/test/java/com/pedro/common/UrlParserTest.kt b/common/src/test/java/com/pedro/common/UrlParserTest.kt new file mode 100644 index 000000000..7054376a1 --- /dev/null +++ b/common/src/test/java/com/pedro/common/UrlParserTest.kt @@ -0,0 +1,297 @@ +package com.pedro.common + +import junit.framework.TestCase.assertEquals +import org.junit.Test +import java.net.URISyntaxException +import java.util.regex.Pattern + +/** + * Created by pedro on 2/9/24. + */ +class UrlParserTest { + + @Test + fun testRtmpUrls() { + try { + val url = "rtmp://localhost:1935/live?test/fake" + val urlParser = UrlParser.parse(url, arrayOf("rtmp")) + assertEquals("rtmp", urlParser.scheme) + assertEquals("localhost", urlParser.host) + assertEquals(1935, urlParser.port) + assertEquals("live?test", urlParser.getAppName()) + assertEquals("fake", urlParser.getStreamName()) + assertEquals("rtmp://localhost:1935/live?test", urlParser.getTcUrl()) + + val url0 = "rtmp://192.168.238.182:1935/live/100044?userId=100044&roomTitle=123123&roomCover=http://192.168.238.182/xxxx.png" + val urlParser0 = UrlParser.parse(url0, arrayOf("rtmp")) + assertEquals("rtmp", urlParser0.scheme) + assertEquals("192.168.238.182", urlParser0.host) + assertEquals(1935, urlParser0.port) + assertEquals("live", urlParser0.getAppName()) + assertEquals("100044?userId=100044&roomTitle=123123&roomCover=http://192.168.238.182/xxxx.png", urlParser0.getStreamName()) + assertEquals("rtmp://192.168.238.182:1935/live", urlParser0.getTcUrl()) + + val url1 = "rtmp://user:pass@localhost:1935/live?test/fake" + val urlParser1 = UrlParser.parse(url1, arrayOf("rtmp")) + assertEquals("rtmp", urlParser1.scheme) + assertEquals("localhost", urlParser1.host) + assertEquals(1935, urlParser1.port) + assertEquals("live?test", urlParser1.getAppName()) + assertEquals("fake", urlParser1.getStreamName()) + assertEquals("rtmp://localhost:1935/live?test", urlParser1.getTcUrl()) + assertEquals("user", urlParser1.getAuthUser()) + assertEquals("pass", urlParser1.getAuthPassword()) + + val url2 = "rtmps://192.168.0.1/live" + val urlParser2 = UrlParser.parse(url2, arrayOf("rtmps")) + assertEquals("rtmps", urlParser2.scheme) + assertEquals("192.168.0.1", urlParser2.host) + assertEquals(null, urlParser2.port) + assertEquals("live", urlParser2.getAppName()) + assertEquals("", urlParser2.getStreamName()) + assertEquals("rtmps://192.168.0.1/live", urlParser2.getTcUrl()) + + val url3 = "rtmpts://192.168.0.1:1234/live/test" + val urlParser3 = UrlParser.parse(url3, arrayOf("rtmpts")) + assertEquals("rtmpts", urlParser3.scheme) + assertEquals("192.168.0.1", urlParser3.host) + assertEquals(1234, urlParser3.port) + assertEquals("live", urlParser3.getAppName()) + assertEquals("test", urlParser3.getStreamName()) + assertEquals("rtmpts://192.168.0.1:1234/live", urlParser3.getTcUrl()) + + val url4 = "rtmp://192.168.0.1:1234/live/" + val urlParser4 = UrlParser.parse(url4, arrayOf("rtmp")) + assertEquals("rtmp", urlParser4.scheme) + assertEquals("192.168.0.1", urlParser4.host) + assertEquals(1234, urlParser4.port) + assertEquals("live", urlParser4.getAppName()) + assertEquals("", urlParser4.getStreamName()) + assertEquals("rtmp://192.168.0.1:1234/live", urlParser4.getTcUrl()) + + val url5 = "rtmp://192.168.0.1:1234?live" + val urlParser5 = UrlParser.parse(url5, arrayOf("rtmp")) + assertEquals("rtmp", urlParser5.scheme) + assertEquals("192.168.0.1", urlParser5.host) + assertEquals(1234, urlParser5.port) + assertEquals("live", urlParser5.getAppName()) + assertEquals("", urlParser5.getStreamName()) + assertEquals("rtmp://192.168.0.1:1234/live", urlParser5.getTcUrl()) + + val url6 = "rtmp://192.168.0.1:1234/live/test/fake" + val urlParser6 = UrlParser.parse(url6, arrayOf("rtmp")) + assertEquals("rtmp", urlParser6.scheme) + assertEquals("192.168.0.1", urlParser6.host) + assertEquals(1234, urlParser6.port) + assertEquals("live/test", urlParser6.getAppName()) + assertEquals("fake", urlParser6.getStreamName()) + assertEquals("rtmp://192.168.0.1:1234/live/test", urlParser6.getTcUrl()) + } catch (e: URISyntaxException) { + assert(false) + } + } + + @Test + fun testRtspUrls() { + try { + val url = "rtsp://localhost:1935/live?test/fake" + val urlParser = UrlParser.parse(url, arrayOf("rtsp")) + assertEquals("rtsp", urlParser.scheme) + assertEquals("localhost", urlParser.host) + assertEquals(1935, urlParser.port) + assertEquals("live?test/fake", urlParser.getFullPath()) + + val url1 = "rtsp://user:pass@localhost:1935/live?test/fake" + val urlParser1 = UrlParser.parse(url1, arrayOf("rtsp")) + assertEquals("rtsp", urlParser1.scheme) + assertEquals("localhost", urlParser1.host) + assertEquals(1935, urlParser1.port) + assertEquals("live?test/fake", urlParser1.getFullPath()) + assertEquals("user", urlParser1.getAuthUser()) + assertEquals("pass", urlParser1.getAuthPassword()) + + val url2 = "rtsps://192.168.0.1/live" + val urlParser2 = UrlParser.parse(url2, arrayOf("rtsps")) + assertEquals("rtsps", urlParser2.scheme) + assertEquals("192.168.0.1", urlParser2.host) + assertEquals(null, urlParser2.port) + assertEquals("live", urlParser2.getFullPath()) + + val url3 = "rtsp://192.168.0.1:1234/live/test" + val urlParser3 = UrlParser.parse(url3, arrayOf("rtsp")) + assertEquals("rtsp", urlParser3.scheme) + assertEquals("192.168.0.1", urlParser3.host) + assertEquals(1234, urlParser3.port) + assertEquals("live/test", urlParser3.getFullPath()) + + val url4 = "rtsp://192.168.0.1:1234/live/" + val urlParser4 = UrlParser.parse(url4, arrayOf("rtsp")) + assertEquals("rtsp", urlParser4.scheme) + assertEquals("192.168.0.1", urlParser4.host) + assertEquals(1234, urlParser4.port) + assertEquals("live/", urlParser4.getFullPath()) + + val url5 = "rtsp://192.168.0.1:1234?live" + val urlParser5 = UrlParser.parse(url5, arrayOf("rtsp")) + assertEquals("rtsp", urlParser5.scheme) + assertEquals("192.168.0.1", urlParser5.host) + assertEquals(1234, urlParser5.port) + assertEquals("live", urlParser5.getFullPath()) + + val url6 = "rtsp://192.168.0.1:1234/live/test/fake" + val urlParser6 = UrlParser.parse(url6, arrayOf("rtsp")) + assertEquals("rtsp", urlParser6.scheme) + assertEquals("192.168.0.1", urlParser6.host) + assertEquals(1234, urlParser6.port) + assertEquals("live/test/fake", urlParser6.getFullPath()) + } catch (e: URISyntaxException) { + assert(false) + } + } + + @Test + fun testSrtUrls() { + try { + val url = "srt://localhost:1935/live?test/fake" + val urlParser = UrlParser.parse(url, arrayOf("srt")) + assertEquals("srt", urlParser.scheme) + assertEquals("localhost", urlParser.host) + assertEquals(1935, urlParser.port) + assertEquals(null, urlParser.getQuery("streamid")) + assertEquals("live?test/fake", urlParser.getFullPath()) + + val url2 = "srt://192.168.0.1?streamid=test/fake" + val urlParser2 = UrlParser.parse(url2, arrayOf("srt")) + assertEquals("srt", urlParser2.scheme) + assertEquals("192.168.0.1", urlParser2.host) + assertEquals(null, urlParser2.port) + assertEquals("test/fake", urlParser2.getQuery("streamid")) + + val url3 = "srt://192.168.0.1:1234?live=test&streamid=fake" + val urlParser3 = UrlParser.parse(url3, arrayOf("srt")) + assertEquals("srt", urlParser3.scheme) + assertEquals("192.168.0.1", urlParser3.host) + assertEquals(1234, urlParser3.port) + assertEquals("fake", urlParser3.getQuery("streamid")) + + val url4 = "rtsp://192.168.0.1:1234/streamid-test" + val urlParser4 = UrlParser.parse(url4, arrayOf("rtsp")) + assertEquals("rtsp", urlParser4.scheme) + assertEquals("192.168.0.1", urlParser4.host) + assertEquals(1234, urlParser4.port) + assertEquals(null, urlParser4.getQuery("streamid")) + assertEquals("streamid-test", urlParser4.getFullPath()) + + val url5 = "srt://push.domain.com:1105?streamid=#!::h=push.domain.com,r=/live/stream,m=publish" + val urlParser5 = UrlParser.parse(url5, arrayOf("srt")) + assertEquals("srt", urlParser5.scheme) + assertEquals("push.domain.com", urlParser5.host) + assertEquals(1105, urlParser5.port) + assertEquals("#!::h=push.domain.com,r=/live/stream,m=publish", urlParser5.getQuery("streamid")) + + val url6 = "srt://push.domain.com:1105/#!::h=push.domain.com,r=/live/stream,m=publish" + val urlParser6 = UrlParser.parse(url6, arrayOf("srt")) + assertEquals("srt", urlParser6.scheme) + assertEquals("push.domain.com", urlParser6.host) + assertEquals(1105, urlParser6.port) + assertEquals(null, urlParser6.getQuery("streamid")) + assertEquals("#!::h=push.domain.com,r=/live/stream,m=publish", urlParser6.getFullPath()) + } catch (e: URISyntaxException) { + assert(false) + } + } + + @Test + fun testUdpUrls() { + try { + val url = "udp://localhost:1935" + val urlParser = UrlParser.parse(url, arrayOf("udp")) + assertEquals("udp", urlParser.scheme) + assertEquals("localhost", urlParser.host) + assertEquals(1935, urlParser.port) + + val url0 = "udp://localhost:1935/" + val urlParser0 = UrlParser.parse(url0, arrayOf("udp")) + assertEquals("udp", urlParser0.scheme) + assertEquals("localhost", urlParser0.host) + assertEquals(1935, urlParser0.port) + } catch (e: URISyntaxException) { + assert(false) + } + } + + @Test + fun testUrl2() { + try { + val urlParser = UrlParser.parse( + "srt://push.domain.com:1105?streamid=#!::h=push.domain.com,r=/live/stream,m=publish&live=asd", + arrayOf("srt") + ) + println(urlParser.toString()) + println(urlParser.getQuery("streamid")) + } catch (e: IllegalArgumentException) { + + } + } + + @Test + fun testUrl3() { + try { + val urlParser = UrlParser.parse( + "rtmp://localhost?live=adasdasd", + arrayOf("rtmp") + ) + println(urlParser.toString()) + } catch (e: IllegalArgumentException) { + + } + } + + private fun oldParser(url: String) { + val urlPattern: Pattern = Pattern.compile("^rtmpt?s?://([^/:]+)(?::(\\d+))*/([^/]+)/?([^*]*)$") + val tunneled: Boolean + val tlsEnabled: Boolean + val rtmpMatcher = urlPattern.matcher(url) + if (!rtmpMatcher.matches()) { + assert(false) + } + val schema = rtmpMatcher.group(0) ?: "" + tunneled = schema.startsWith("rtmpt") + tlsEnabled = schema.startsWith("rtmps") || schema.startsWith("rtmpts") + + val host = rtmpMatcher.group(1) ?: "" + val portStr = rtmpMatcher.group(2) + val defaultPort = if (tlsEnabled) 443 else if (tunneled) 80 else 1935 + val port = portStr?.toInt() ?: defaultPort + val appName = getAppName(rtmpMatcher.group(3) ?: "", rtmpMatcher.group(4) ?: "") + val streamName = getStreamName(rtmpMatcher.group(4) ?: "") + val tcUrl = getTcUrl((rtmpMatcher.group(0) + ?: "").substring(0, (rtmpMatcher.group(0) + ?: "").length - streamName.length)) + println("OldParser(scheme='$schema', host='$host', port=$port, appName='$appName', streamName='$streamName', tcUrl='$tcUrl'") + } + + private fun getAppName(app: String, name: String): String { + return if (!name.contains("/")) { + app + } else { + app + "/" + name.substring(0, name.indexOf("/")) + } + } + + private fun getStreamName(name: String): String { + return if (!name.contains("/")) { + name + } else { + name.substring(name.indexOf("/") + 1) + } + } + + private fun getTcUrl(url: String): String { + return if (url.endsWith("/")) { + url.substring(0, url.length - 1) + } else { + url + } + } +} \ No newline at end of file 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 faf5f2327..f6cca2c69 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt @@ -21,6 +21,7 @@ import android.util.Log import com.pedro.common.AudioCodec import com.pedro.common.ConnectChecker import com.pedro.common.TimeUtils +import com.pedro.common.UrlParser import com.pedro.common.VideoCodec import com.pedro.common.onMainThread import com.pedro.rtmp.amf.AmfVersion @@ -44,7 +45,6 @@ import kotlinx.coroutines.launch import java.io.* import java.net.* import java.nio.ByteBuffer -import java.util.regex.Pattern import javax.net.ssl.TrustManager /** @@ -54,7 +54,7 @@ class RtmpClient(private val connectChecker: ConnectChecker) { private val TAG = "RtmpClient" - private val urlPattern: Pattern = Pattern.compile("^rtmpt?s?://([^/:]+)(?::(\\d+))*/([^/]+)/?([^*]*)$") + private val validSchemes = arrayOf("rtmp", "rtmps", "rtmpt", "rtmpts") private var socket: RtmpSocket? = null private var scope = CoroutineScope(Dispatchers.IO) @@ -214,12 +214,11 @@ class RtmpClient(private val connectChecker: ConnectChecker) { onMainThread { connectChecker.onConnectionStarted(url) } - val rtmpMatcher = urlPattern.matcher(url) - if (rtmpMatcher.matches()) { - val schema = rtmpMatcher.group(0) ?: "" - tunneled = schema.startsWith("rtmpt") - tlsEnabled = schema.startsWith("rtmps") || schema.startsWith("rtmpts") - } else { + + val urlParser = try { + UrlParser.parse(url, validSchemes) + } catch (e: URISyntaxException) { + isStreaming = false onMainThread { connectChecker.onConnectionFailed( "Endpoint malformed, should be: rtmp://ip:port/appname/streamname") @@ -227,15 +226,26 @@ class RtmpClient(private val connectChecker: ConnectChecker) { return@launch } - commandsManager.host = rtmpMatcher.group(1) ?: "" - val portStr = rtmpMatcher.group(2) + tunneled = urlParser.scheme.startsWith("rtmpt") + tlsEnabled = urlParser.scheme.endsWith("s") + commandsManager.host = urlParser.host val defaultPort = if (tlsEnabled) 443 else if (tunneled) 80 else 1935 - commandsManager.port = portStr?.toInt() ?: defaultPort - commandsManager.appName = getAppName(rtmpMatcher.group(3) ?: "", rtmpMatcher.group(4) ?: "") - commandsManager.streamName = getStreamName(rtmpMatcher.group(4) ?: "") - commandsManager.tcUrl = getTcUrl((rtmpMatcher.group(0) - ?: "").substring(0, (rtmpMatcher.group(0) - ?: "").length - commandsManager.streamName.length)) + commandsManager.port = urlParser.port ?: defaultPort + commandsManager.appName = urlParser.getAppName() + commandsManager.streamName = urlParser.getStreamName() + commandsManager.tcUrl = urlParser.getTcUrl() + if (commandsManager.appName.isEmpty()) { + isStreaming = false + onMainThread { + connectChecker.onConnectionFailed( + "Endpoint malformed, should be: rtmp://ip:port/appname/streamname") + } + return@launch + } + + val user = urlParser.getAuthUser() + val password = urlParser.getAuthPassword() + if (user != null && password != null) setAuthorization(user, password) val error = runCatching { if (!establishConnection()) { @@ -296,30 +306,6 @@ class RtmpClient(private val connectChecker: ConnectChecker) { return if (connected && !reachable) false else connected } - private fun getAppName(app: String, name: String): String { - return if (!name.contains("/")) { - app - } else { - app + "/" + name.substring(0, name.indexOf("/")) - } - } - - private fun getStreamName(name: String): String { - return if (!name.contains("/")) { - name - } else { - name.substring(name.indexOf("/") + 1) - } - } - - private fun getTcUrl(url: String): String { - return if (url.endsWith("/")) { - url.substring(0, url.length - 1) - } else { - url - } - } - @Throws(IOException::class) private fun establishConnection(): Boolean { val socket = if (tunneled) { 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 b217e42a6..e5847765a 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt @@ -21,6 +21,7 @@ import android.util.Log import com.pedro.common.AudioCodec import com.pedro.common.ConnectChecker import com.pedro.common.TLSSocketFactory +import com.pedro.common.UrlParser import com.pedro.common.VideoCodec import com.pedro.common.onMainThread import com.pedro.rtsp.rtsp.commands.CommandsManager @@ -41,9 +42,9 @@ import java.net.InetSocketAddress import java.net.Socket import java.net.SocketAddress import java.net.SocketTimeoutException +import java.net.URISyntaxException import java.nio.ByteBuffer import java.security.GeneralSecurityException -import java.util.regex.Pattern import javax.net.ssl.TrustManager /** @@ -53,7 +54,7 @@ class RtspClient(private val connectChecker: ConnectChecker) { private val TAG = "RtspClient" - private val urlPattern: Pattern = Pattern.compile("^rtsps?://([^/:]+)(?::(\\d+))*/([^/]+)/?([^*]*)$") + private val validSchemes = arrayOf("rtsp", "rtsps") //sockets objects private var connectionSocket: Socket? = null @@ -192,23 +193,34 @@ class RtspClient(private val connectChecker: ConnectChecker) { onMainThread { connectChecker.onConnectionStarted(url) } - val rtspMatcher = urlPattern.matcher(url) - if (rtspMatcher.matches()) { - tlsEnabled = (rtspMatcher.group(0) ?: "").startsWith("rtsps") - } else { + + val urlParser = try { + UrlParser.parse(url, validSchemes) + } catch (e: URISyntaxException) { + isStreaming = false + onMainThread { + connectChecker.onConnectionFailed("Endpoint malformed, should be: rtsp://ip:port/appname/streamname") + } + return@launch + } + + tlsEnabled = urlParser.scheme.endsWith("s") + val host = urlParser.host + val port = urlParser.port ?: if (tlsEnabled) 443 else 554 + val path = urlParser.getFullPath() + if (path.isEmpty()) { isStreaming = false onMainThread { connectChecker.onConnectionFailed("Endpoint malformed, should be: rtsp://ip:port/appname/streamname") } return@launch } - val host = rtspMatcher.group(1) ?: "" - val port: Int = rtspMatcher.group(2)?.toInt() ?: if (tlsEnabled) 443 else 554 - val streamName = if (rtspMatcher.group(4).isNullOrEmpty()) "" else "/" + rtspMatcher.group(4) - val path = "/" + rtspMatcher.group(3) + streamName + val user = urlParser.getAuthUser() + val password = urlParser.getAuthPassword() + if (user != null && password != null) setAuthorization(user, password) val error = runCatching { - commandsManager.setUrl(host, port, path) + commandsManager.setUrl(host, port, "/$path") rtspSender.setSocketsInfo(commandsManager.protocol, commandsManager.videoClientPorts, commandsManager.audioClientPorts) 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 31099990a..8ecfc239e 100644 --- a/srt/src/main/java/com/pedro/srt/srt/SrtClient.kt +++ b/srt/src/main/java/com/pedro/srt/srt/SrtClient.kt @@ -20,6 +20,7 @@ import android.media.MediaCodec import android.util.Log import com.pedro.common.AudioCodec import com.pedro.common.ConnectChecker +import com.pedro.common.UrlParser import com.pedro.common.VideoCodec import com.pedro.common.onMainThread import com.pedro.srt.srt.packets.ControlPacket @@ -50,8 +51,8 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import java.io.IOException import java.net.SocketTimeoutException +import java.net.URISyntaxException import java.nio.ByteBuffer -import java.util.regex.Pattern /** * Created by pedro on 20/8/23. @@ -60,7 +61,7 @@ class SrtClient(private val connectChecker: ConnectChecker) { private val TAG = "SrtClient" - private val urlPattern: Pattern = Pattern.compile("^srt://([^/:]+)(?::(\\d+))*/([^/]+)/?([^*]*)$") + private val validSchemes = arrayOf("srt") private val commandsManager = CommandsManager() private val srtSender = SrtSender(connectChecker, commandsManager) @@ -176,19 +177,27 @@ class SrtClient(private val connectChecker: ConnectChecker) { onMainThread { connectChecker.onConnectionStarted(url) } - val srtMatcher = urlPattern.matcher(url) - if (!srtMatcher.matches()) { + + val urlParser = try { + UrlParser.parse(url, validSchemes) + } catch (e: URISyntaxException) { + isStreaming = false + onMainThread { + connectChecker.onConnectionFailed("Endpoint malformed, should be: srt://ip:port/streamid") + } + return@launch + } + + val host = urlParser.host + val port = urlParser.port ?: 8888 + val path = urlParser.getQuery("streamid") ?: urlParser.getFullPath() + if (path.isEmpty()) { isStreaming = false onMainThread { connectChecker.onConnectionFailed("Endpoint malformed, should be: srt://ip:port/streamid") } return@launch } - val host = srtMatcher.group(1) ?: "" - val port: Int = srtMatcher.group(2)?.toInt() ?: 8888 - val streamName = - if (srtMatcher.group(4).isNullOrEmpty()) "" else "/" + srtMatcher.group(4) - val path = "${srtMatcher.group(3)}$streamName".trim() commandsManager.host = host val error = runCatching { diff --git a/udp/src/main/java/com/pedro/udp/UdpClient.kt b/udp/src/main/java/com/pedro/udp/UdpClient.kt index 86395d5df..e58d49705 100644 --- a/udp/src/main/java/com/pedro/udp/UdpClient.kt +++ b/udp/src/main/java/com/pedro/udp/UdpClient.kt @@ -20,6 +20,7 @@ import android.media.MediaCodec import android.util.Log import com.pedro.common.AudioCodec import com.pedro.common.ConnectChecker +import com.pedro.common.UrlParser import com.pedro.common.VideoCodec import com.pedro.common.onMainThread import com.pedro.udp.utils.UdpSocket @@ -31,8 +32,8 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import java.net.URISyntaxException import java.nio.ByteBuffer -import java.util.regex.Pattern /** * Created by pedro on 6/3/24. @@ -41,7 +42,7 @@ class UdpClient(private val connectChecker: ConnectChecker) { private val TAG = "UdpClient" - private val urlPattern: Pattern = Pattern.compile("^udp://([^/:]+)(?::(\\d+))*/?") + private val validSchemes = arrayOf("udp") private val commandManager = CommandManager() private val udpSender = UdpSender(connectChecker, commandManager) @@ -133,16 +134,19 @@ class UdpClient(private val connectChecker: ConnectChecker) { onMainThread { connectChecker.onConnectionStarted(url) } - val srtMatcher = urlPattern.matcher(url) - if (!srtMatcher.matches()) { + + val urlParser = try { + UrlParser.parse(url, validSchemes) + } catch (e: URISyntaxException) { isStreaming = false onMainThread { connectChecker.onConnectionFailed("Endpoint malformed, should be: udp://ip:port") } return@launch } - val host = srtMatcher.group(1) ?: "" - val port: Int? = srtMatcher.group(2)?.toInt() + + val host = urlParser.host + val port = urlParser.port if (port == null) { onMainThread { connectChecker.onConnectionFailed("Endpoint malformed, port is required")